From cbe283c28ffb7e788e6b8b77a8c016878fa824dc Mon Sep 17 00:00:00 2001
From: octo-patch <266937838+octo-patch@users.noreply.github.com>
Date: Thu, 9 Jul 2026 08:28:51 +0000
Subject: [PATCH 1/2] Add MiniMax provider support
---
BotSharp.sln | 15 +++++++++++
.../BotSharp.Plugin.MiniMaxAI.csproj | 17 +++++++++++++
.../MiniMaxAiPlugin.cs | 18 +++++++++++++
.../Providers/Chat/ChatCompletionProvider.cs | 15 +++++++++++
.../Providers/Text/TextCompletionProvider.cs | 13 ++++++++++
.../BotSharp.Plugin.MiniMaxAI/Using.cs | 8 ++++++
src/WebStarter/WebStarter.csproj | 1 +
src/WebStarter/appsettings.json | 25 +++++++++++++++++++
8 files changed, 112 insertions(+)
create mode 100644 src/Plugins/BotSharp.Plugin.MiniMaxAI/BotSharp.Plugin.MiniMaxAI.csproj
create mode 100644 src/Plugins/BotSharp.Plugin.MiniMaxAI/MiniMaxAiPlugin.cs
create mode 100644 src/Plugins/BotSharp.Plugin.MiniMaxAI/Providers/Chat/ChatCompletionProvider.cs
create mode 100644 src/Plugins/BotSharp.Plugin.MiniMaxAI/Providers/Text/TextCompletionProvider.cs
create mode 100644 src/Plugins/BotSharp.Plugin.MiniMaxAI/Using.cs
diff --git a/BotSharp.sln b/BotSharp.sln
index 31ce5133e..78db00ed4 100644
--- a/BotSharp.sln
+++ b/BotSharp.sln
@@ -125,6 +125,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Core.Rules", "src\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.DeepSeekAI", "src\Plugins\BotSharp.Plugin.DeepSeekAI\BotSharp.Plugin.DeepSeekAI.csproj", "{AF329442-B48E-4B48-A18A-1C869D1BA6F5}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.MiniMaxAI", "src\Plugins\BotSharp.Plugin.MiniMaxAI\BotSharp.Plugin.MiniMaxAI.csproj", "{D92C1A49-3F64-4D0C-91C0-B14EA47D2503}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Core.Realtime", "src\Infrastructure\BotSharp.Core.Realtime\BotSharp.Core.Realtime.csproj", "{781F1465-365C-0F22-1775-25025DAFA4C7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Test.ComputerUse", "tests\BotSharp.Test.ComputerUse\BotSharp.Test.ComputerUse.csproj", "{C19D9AC1-97DD-8E65-E8DB-D295A095AA2D}"
@@ -737,6 +739,18 @@ Global
{AF329442-B48E-4B48-A18A-1C869D1BA6F5}.Release|x64.Build.0 = Release|Any CPU
{AF329442-B48E-4B48-A18A-1C869D1BA6F5}.Release|x86.ActiveCfg = Release|Any CPU
{AF329442-B48E-4B48-A18A-1C869D1BA6F5}.Release|x86.Build.0 = Release|Any CPU
+ {D92C1A49-3F64-4D0C-91C0-B14EA47D2503}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D92C1A49-3F64-4D0C-91C0-B14EA47D2503}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D92C1A49-3F64-4D0C-91C0-B14EA47D2503}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D92C1A49-3F64-4D0C-91C0-B14EA47D2503}.Debug|x64.Build.0 = Debug|Any CPU
+ {D92C1A49-3F64-4D0C-91C0-B14EA47D2503}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D92C1A49-3F64-4D0C-91C0-B14EA47D2503}.Debug|x86.Build.0 = Debug|Any CPU
+ {D92C1A49-3F64-4D0C-91C0-B14EA47D2503}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D92C1A49-3F64-4D0C-91C0-B14EA47D2503}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D92C1A49-3F64-4D0C-91C0-B14EA47D2503}.Release|x64.ActiveCfg = Release|Any CPU
+ {D92C1A49-3F64-4D0C-91C0-B14EA47D2503}.Release|x64.Build.0 = Release|Any CPU
+ {D92C1A49-3F64-4D0C-91C0-B14EA47D2503}.Release|x86.ActiveCfg = Release|Any CPU
+ {D92C1A49-3F64-4D0C-91C0-B14EA47D2503}.Release|x86.Build.0 = Release|Any CPU
{781F1465-365C-0F22-1775-25025DAFA4C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{781F1465-365C-0F22-1775-25025DAFA4C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{781F1465-365C-0F22-1775-25025DAFA4C7}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -1001,6 +1015,7 @@ Global
{F812BAAE-5A7D-4DF7-8E71-70696B51C61F} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749}
{AFD64412-4D6A-452E-82A2-79E5D8842E29} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749}
{AF329442-B48E-4B48-A18A-1C869D1BA6F5} = {D5293208-2BEF-42FC-A64C-5954F61720BA}
+ {D92C1A49-3F64-4D0C-91C0-B14EA47D2503} = {D5293208-2BEF-42FC-A64C-5954F61720BA}
{781F1465-365C-0F22-1775-25025DAFA4C7} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749}
{C19D9AC1-97DD-8E65-E8DB-D295A095AA2D} = {32FAFFFE-A4CB-4FEE-BF7C-84518BBC6DCC}
{B268E2F0-060F-8466-7D81-ABA4D735CA59} = {51AFE054-AE99-497D-A593-69BAEFB5106F}
diff --git a/src/Plugins/BotSharp.Plugin.MiniMaxAI/BotSharp.Plugin.MiniMaxAI.csproj b/src/Plugins/BotSharp.Plugin.MiniMaxAI/BotSharp.Plugin.MiniMaxAI.csproj
new file mode 100644
index 000000000..4f3e17b8a
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.MiniMaxAI/BotSharp.Plugin.MiniMaxAI.csproj
@@ -0,0 +1,17 @@
+
+
+
+ $(TargetFramework)
+ enable
+ $(LangVersion)
+ $(BotSharpVersion)
+ $(GeneratePackageOnBuild)
+ $(GenerateDocumentationFile)
+ $(SolutionDir)packages
+
+
+
+
+
+
+
diff --git a/src/Plugins/BotSharp.Plugin.MiniMaxAI/MiniMaxAiPlugin.cs b/src/Plugins/BotSharp.Plugin.MiniMaxAI/MiniMaxAiPlugin.cs
new file mode 100644
index 000000000..0cc18ceb3
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.MiniMaxAI/MiniMaxAiPlugin.cs
@@ -0,0 +1,18 @@
+using BotSharp.Plugin.MiniMaxAI.Providers.Chat;
+using BotSharp.Plugin.MiniMaxAI.Providers.Text;
+
+namespace BotSharp.Plugin.MiniMaxAI;
+
+public class MiniMaxAiPlugin : IBotSharpPlugin
+{
+ public string Id => "8a4ebd68-4d7d-4c5c-aed9-263946cc3a0d";
+ public string Name => "MiniMax";
+ public string Description => "MiniMax AI models exposed through the OpenAI-compatible API.";
+ public string IconUrl => "https://www.minimax.io/favicon.ico";
+
+ public void RegisterDI(IServiceCollection services, IConfiguration config)
+ {
+ services.AddScoped();
+ services.AddScoped();
+ }
+}
diff --git a/src/Plugins/BotSharp.Plugin.MiniMaxAI/Providers/Chat/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.MiniMaxAI/Providers/Chat/ChatCompletionProvider.cs
new file mode 100644
index 000000000..3dc603b77
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.MiniMaxAI/Providers/Chat/ChatCompletionProvider.cs
@@ -0,0 +1,15 @@
+namespace BotSharp.Plugin.MiniMaxAI.Providers.Chat;
+
+public class ChatCompletionProvider : global::BotSharp.Plugin.OpenAI.Providers.Chat.ChatCompletionProvider
+{
+ public override string Provider => "minimax";
+
+ public ChatCompletionProvider(
+ global::BotSharp.Plugin.OpenAI.Settings.OpenAiSettings settings,
+ ILogger logger,
+ IServiceProvider services,
+ IConversationStateService state,
+ IFileStorageService fileStorage) : base(settings, logger, services, state, fileStorage)
+ {
+ }
+}
diff --git a/src/Plugins/BotSharp.Plugin.MiniMaxAI/Providers/Text/TextCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.MiniMaxAI/Providers/Text/TextCompletionProvider.cs
new file mode 100644
index 000000000..1f94a1ba9
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.MiniMaxAI/Providers/Text/TextCompletionProvider.cs
@@ -0,0 +1,13 @@
+namespace BotSharp.Plugin.MiniMaxAI.Providers.Text;
+
+public class TextCompletionProvider : global::BotSharp.Plugin.OpenAI.Providers.Text.TextCompletionProvider
+{
+ public override string Provider => "minimax";
+
+ public TextCompletionProvider(
+ global::BotSharp.Plugin.OpenAI.Settings.OpenAiSettings settings,
+ ILogger logger,
+ IServiceProvider services) : base(settings, logger, services)
+ {
+ }
+}
diff --git a/src/Plugins/BotSharp.Plugin.MiniMaxAI/Using.cs b/src/Plugins/BotSharp.Plugin.MiniMaxAI/Using.cs
new file mode 100644
index 000000000..06ffb04fd
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.MiniMaxAI/Using.cs
@@ -0,0 +1,8 @@
+global using Microsoft.Extensions.Configuration;
+global using Microsoft.Extensions.DependencyInjection;
+global using Microsoft.Extensions.Logging;
+
+global using BotSharp.Abstraction.Conversations;
+global using BotSharp.Abstraction.Files;
+global using BotSharp.Abstraction.MLTasks;
+global using BotSharp.Abstraction.Plugins;
diff --git a/src/WebStarter/WebStarter.csproj b/src/WebStarter/WebStarter.csproj
index 11cba27bc..6f7129e45 100644
--- a/src/WebStarter/WebStarter.csproj
+++ b/src/WebStarter/WebStarter.csproj
@@ -62,6 +62,7 @@
+
diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json
index 7688d81ea..79bbe9521 100644
--- a/src/WebStarter/appsettings.json
+++ b/src/WebStarter/appsettings.json
@@ -594,6 +594,30 @@
}
]
},
+ {
+ "Provider": "minimax",
+ "Models": [
+ {
+ "Id": "MiniMax-M3",
+ "Name": "MiniMax-M3",
+ "Version": "MiniMax-M3",
+ "ApiKey": "",
+ "Endpoint": "https://api.minimax.io/v1",
+ "Type": "chat",
+ "Capabilities": [
+ "Chat"
+ ],
+ "Cost": {
+ "TextInputCost": 0.0006,
+ "CachedTextInputCost": 0.00012,
+ "AudioInputCost": 0,
+ "CachedAudioInputCost": 0,
+ "TextOutputCost": 0.0024,
+ "AudioOutputCost": 0
+ }
+ }
+ ]
+ },
{
"Provider": "google-ai",
"Models": [
@@ -1056,6 +1080,7 @@
"BotSharp.Plugin.GoogleAI",
"BotSharp.Plugin.MetaAI",
"BotSharp.Plugin.DeepSeekAI",
+ "BotSharp.Plugin.MiniMaxAI",
"BotSharp.Plugin.MetaMessenger",
"BotSharp.Plugin.HuggingFace",
"BotSharp.Plugin.KnowledgeBase",
From 895e058eaf98a7ff189ec7522a8ba7ad2e70f7f3 Mon Sep 17 00:00:00 2001
From: octo-patch <266937838+octo-patch@users.noreply.github.com>
Date: Mon, 13 Jul 2026 17:23:55 +0800
Subject: [PATCH 2/2] Complete MiniMax provider coverage
---
.../Conversations/Models/TokenStatsModel.cs | 5 +-
.../MLTasks/Settings/LlmModelSetting.cs | 30 +-
.../Conversations/Services/TokenStatistics.cs | 14 +-
.../Providers/ChatCompletionProvider.cs | 38 +-
.../Providers/ProviderHelper.cs | 4 +
.../BotSharp.Plugin.AnthropicAI/Using.cs | 1 +
.../BotSharp.Plugin.MiniMaxAI.csproj | 1 +
.../MiniMaxAiPlugin.cs | 13 +-
.../Chat/AnthropicChatCompletionProvider.cs | 13 +
.../Chat/AnthropicCnChatCompletionProvider.cs | 13 +
.../Providers/Chat/ChatCompletionProvider.cs | 36 +-
.../Chat/OpenAiCnChatCompletionProvider.cs | 14 +
.../Providers/Text/TextCompletionProvider.cs | 13 -
.../BotSharp.Plugin.MiniMaxAI/Using.cs | 6 +
.../Chat/ChatCompletionProvider.Chat.cs | 7 +
.../Providers/Chat/ChatCompletionProvider.cs | 17 +-
src/WebStarter/appsettings.json | 444 +++++++++++++++++-
.../BotSharp.LLM.Tests.csproj | 4 +
.../MiniMaxProviderTests.cs | 129 +++++
19 files changed, 763 insertions(+), 39 deletions(-)
create mode 100644 src/Plugins/BotSharp.Plugin.MiniMaxAI/Providers/Chat/AnthropicChatCompletionProvider.cs
create mode 100644 src/Plugins/BotSharp.Plugin.MiniMaxAI/Providers/Chat/AnthropicCnChatCompletionProvider.cs
create mode 100644 src/Plugins/BotSharp.Plugin.MiniMaxAI/Providers/Chat/OpenAiCnChatCompletionProvider.cs
delete mode 100644 src/Plugins/BotSharp.Plugin.MiniMaxAI/Providers/Text/TextCompletionProvider.cs
create mode 100644 tests/BotSharp.LLM.Tests/MiniMaxProviderTests.cs
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/TokenStatsModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/TokenStatsModel.cs
index c87564003..236c3b18c 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/TokenStatsModel.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/TokenStatsModel.cs
@@ -5,10 +5,12 @@ public class TokenStatsModel
public string Provider { get; set; }
public string Model { get; set; }
public string Prompt { get; set; }
+ public string? ServiceTier { get; set; }
#region Text token
public long TextInputTokens { get; set; }
public long CachedTextInputTokens { get; set; }
+ public long CachedTextInputWriteTokens { get; set; }
public long TextOutputTokens { get; set; }
#endregion
@@ -29,7 +31,8 @@ public class TokenStatsModel
public float ImageGenerationUnitCost { get; set; }
#endregion
- public long TotalInputTokens => TextInputTokens + CachedTextInputTokens
+ public long TotalTextInputTokens => TextInputTokens + CachedTextInputTokens + CachedTextInputWriteTokens;
+ public long TotalInputTokens => TotalTextInputTokens
+ AudioInputTokens + CachedAudioInputTokens
+ ImageInputTokens + CachedImageInputTokens;
public long TotalOutputTokens => TextOutputTokens + AudioOutputTokens + ImageOutputTokens;
diff --git a/src/Infrastructure/BotSharp.Abstraction/MLTasks/Settings/LlmModelSetting.cs b/src/Infrastructure/BotSharp.Abstraction/MLTasks/Settings/LlmModelSetting.cs
index 3915f9f6e..c843c0395 100644
--- a/src/Infrastructure/BotSharp.Abstraction/MLTasks/Settings/LlmModelSetting.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/MLTasks/Settings/LlmModelSetting.cs
@@ -30,8 +30,10 @@ public class LlmModelSetting
public string ApiKey { get; set; } = null!;
public string? Endpoint { get; set; }
+ public long? ContextWindow { get; set; }
public LlmModelType Type { get; set; } = LlmModelType.Chat;
public List Capabilities { get; set; } = [];
+ public List InputModalities { get; set; } = [];
///
/// If true, allow sending images/videos to this model
@@ -183,7 +185,10 @@ public class LlmCostSetting
#region Text token
public float TextInputCost { get; set; } = 0f;
public float CachedTextInputCost { get; set; } = 0f;
+ public float CachedTextInputWriteCost { get; set; } = 0f;
public float TextOutputCost { get; set; } = 0f;
+ public string DefaultServiceTier { get; set; } = "standard";
+ public IList? TextTokenCostTiers { get; set; }
#endregion
#region Audio token
@@ -201,6 +206,29 @@ public class LlmCostSetting
#region Image
public IList? ImageCosts { get; set; }
#endregion
+
+ public LlmTextTokenCostTier? GetTextTokenCostTier(long inputTokens, string? serviceTier = null)
+ {
+ var selectedServiceTier = string.IsNullOrWhiteSpace(serviceTier)
+ ? DefaultServiceTier
+ : serviceTier;
+
+ return TextTokenCostTiers?.FirstOrDefault(x =>
+ string.Equals(x.ServiceTier, selectedServiceTier, StringComparison.OrdinalIgnoreCase)
+ && (!x.InputTokensGreaterThan.HasValue || inputTokens > x.InputTokensGreaterThan.Value)
+ && (!x.InputTokensLessThanOrEqual.HasValue || inputTokens <= x.InputTokensLessThanOrEqual.Value));
+ }
+}
+
+public class LlmTextTokenCostTier
+{
+ public string ServiceTier { get; set; } = "standard";
+ public long? InputTokensGreaterThan { get; set; }
+ public long? InputTokensLessThanOrEqual { get; set; }
+ public float TextInputCost { get; set; }
+ public float CachedTextInputCost { get; set; }
+ public float CachedTextInputWriteCost { get; set; }
+ public float TextOutputCost { get; set; }
}
public class LlmImageCost
@@ -240,4 +268,4 @@ public enum LlmModelCapability
Realtime = 11,
WebSearch = 12,
PdfReading = 13
-}
\ No newline at end of file
+}
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/TokenStatistics.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/TokenStatistics.cs
index 542d6c008..7385ea773 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/TokenStatistics.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/TokenStatistics.cs
@@ -41,9 +41,15 @@ public async Task AddToken(TokenStatsModel stats, RoleDialogModel message)
var settings = settingsService.GetSetting(stats.Provider, _model);
#region Text tokens
- var deltaTextInputCost = GetDeltaCost(stats.TextInputTokens, settings?.Cost?.TextInputCost);
- var deltaCachedTextInputCost = GetDeltaCost(stats.CachedTextInputTokens, settings?.Cost?.CachedTextInputCost);
- var deltaTextOutputCost = GetDeltaCost(stats.TextOutputTokens, settings?.Cost?.TextOutputCost);
+ var textCostTier = settings?.Cost?.GetTextTokenCostTier(stats.TotalTextInputTokens, stats.ServiceTier);
+ var deltaTextInputCost = GetDeltaCost(stats.TextInputTokens,
+ textCostTier?.TextInputCost ?? settings?.Cost?.TextInputCost);
+ var deltaCachedTextInputCost = GetDeltaCost(stats.CachedTextInputTokens,
+ textCostTier?.CachedTextInputCost ?? settings?.Cost?.CachedTextInputCost);
+ var deltaCachedTextInputWriteCost = GetDeltaCost(stats.CachedTextInputWriteTokens,
+ textCostTier?.CachedTextInputWriteCost ?? settings?.Cost?.CachedTextInputWriteCost);
+ var deltaTextOutputCost = GetDeltaCost(stats.TextOutputTokens,
+ textCostTier?.TextOutputCost ?? settings?.Cost?.TextOutputCost);
#endregion
#region Audio tokens
@@ -63,7 +69,7 @@ public async Task AddToken(TokenStatsModel stats, RoleDialogModel message)
#endregion
- var deltaPromptCost = deltaTextInputCost + deltaCachedTextInputCost
+ var deltaPromptCost = deltaTextInputCost + deltaCachedTextInputCost + deltaCachedTextInputWriteCost
+ deltaAudioInputCost + deltaCachedAudioInputCost
+ deltaImageInputCost + deltaCachedImageInputCost;
var deltaCompletionCost = deltaTextOutputCost + deltaAudioOutputCost + deltaImageOutputCost;
diff --git a/src/Plugins/BotSharp.Plugin.AnthropicAI/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AnthropicAI/Providers/ChatCompletionProvider.cs
index 8306c71be..fcb4d4b07 100644
--- a/src/Plugins/BotSharp.Plugin.AnthropicAI/Providers/ChatCompletionProvider.cs
+++ b/src/Plugins/BotSharp.Plugin.AnthropicAI/Providers/ChatCompletionProvider.cs
@@ -8,7 +8,7 @@ namespace BotSharp.Plugin.AnthropicAI.Providers;
public class ChatCompletionProvider : IChatCompletion
{
- public string Provider => "anthropic";
+ public virtual string Provider => "anthropic";
public string Model => _model;
protected readonly AnthropicSettings _settings;
@@ -94,7 +94,10 @@ public async Task GetChatCompletions(Agent agent, List GetChatCompletionsAsync(Agent agent, List GetChatCompletionsStreamingAsync(Agent agent,
Prompt = prompt,
Provider = Provider,
Model = _model,
+ ServiceTier = tokenUsage?.ServiceTier.ToString(),
TextInputTokens = tokenUsage?.InputTokens ?? 0,
+ CachedTextInputTokens = tokenUsage?.CacheReadInputTokens ?? 0,
+ CachedTextInputWriteTokens = tokenUsage?.CacheCreationInputTokens ?? 0,
TextOutputTokens = tokenUsage?.OutputTokens ?? 0
});
}
@@ -465,8 +474,25 @@ public void SetModelName(string model)
return null;
}
- var thinking = new ThinkingParameters();
var param = settings.Reasoning.Parameters!;
+ var thinkingType = LlmUtility.GetModelParameter(
+ param,
+ "ThinkingType",
+ _state.GetState("thinking_type"));
+ if (thinkingType.IsEqualTo("disabled"))
+ {
+ return null;
+ }
+
+ var thinking = new ThinkingParameters();
+ if (thinkingType.IsEqualTo("adaptive"))
+ {
+ thinking.Type = ThinkingType.adaptive;
+ }
+ else if (thinkingType.IsEqualTo("enabled"))
+ {
+ thinking.Type = ThinkingType.enabled;
+ }
var bt = _state.GetState("budget_tokens");
if (int.TryParse(bt, out var budgetTokens)
@@ -484,7 +510,11 @@ public void SetModelName(string model)
thinking.UseInterleavedThinking = useInterleavedThinking;
}
- return thinking.BudgetTokens > 0 ? thinking : null;
+ return thinkingType.IsEqualTo("adaptive")
+ || thinkingType.IsEqualTo("enabled")
+ || thinking.BudgetTokens > 0
+ ? thinking
+ : null;
}
private string GetPrompt(MessageParameters parameters)
@@ -576,4 +606,4 @@ private void CollectMessageContentParts(List contentParts, List();
var settings = settingsService.GetSetting(provider, model);
var client = new AnthropicClient(new APIAuthentication(settings.ApiKey));
+ if (!string.IsNullOrWhiteSpace(settings.Endpoint))
+ {
+ client.ApiUrlFormat = $"{settings.Endpoint.TrimEnd('/')}/{{0}}/{{1}}";
+ }
return client;
}
}
diff --git a/src/Plugins/BotSharp.Plugin.AnthropicAI/Using.cs b/src/Plugins/BotSharp.Plugin.AnthropicAI/Using.cs
index 04f851eb0..7b87d4c24 100644
--- a/src/Plugins/BotSharp.Plugin.AnthropicAI/Using.cs
+++ b/src/Plugins/BotSharp.Plugin.AnthropicAI/Using.cs
@@ -26,6 +26,7 @@
global using BotSharp.Abstraction.Hooks;
global using BotSharp.Abstraction.MessageHub.Models;
global using BotSharp.Abstraction.MLTasks.Settings;
+global using BotSharp.Abstraction.MLTasks.Utilities;
global using BotSharp.Abstraction.Options;
global using BotSharp.Abstraction.Utilities;
diff --git a/src/Plugins/BotSharp.Plugin.MiniMaxAI/BotSharp.Plugin.MiniMaxAI.csproj b/src/Plugins/BotSharp.Plugin.MiniMaxAI/BotSharp.Plugin.MiniMaxAI.csproj
index 4f3e17b8a..1ddba5cab 100644
--- a/src/Plugins/BotSharp.Plugin.MiniMaxAI/BotSharp.Plugin.MiniMaxAI.csproj
+++ b/src/Plugins/BotSharp.Plugin.MiniMaxAI/BotSharp.Plugin.MiniMaxAI.csproj
@@ -11,6 +11,7 @@
+
diff --git a/src/Plugins/BotSharp.Plugin.MiniMaxAI/MiniMaxAiPlugin.cs b/src/Plugins/BotSharp.Plugin.MiniMaxAI/MiniMaxAiPlugin.cs
index 0cc18ceb3..14fae8215 100644
--- a/src/Plugins/BotSharp.Plugin.MiniMaxAI/MiniMaxAiPlugin.cs
+++ b/src/Plugins/BotSharp.Plugin.MiniMaxAI/MiniMaxAiPlugin.cs
@@ -1,8 +1,11 @@
-using BotSharp.Plugin.MiniMaxAI.Providers.Chat;
-using BotSharp.Plugin.MiniMaxAI.Providers.Text;
+using AnthropicChatCompletionProvider = BotSharp.Plugin.MiniMaxAI.Providers.Chat.AnthropicChatCompletionProvider;
+using AnthropicCnChatCompletionProvider = BotSharp.Plugin.MiniMaxAI.Providers.Chat.AnthropicCnChatCompletionProvider;
+using OpenAiChatCompletionProvider = BotSharp.Plugin.MiniMaxAI.Providers.Chat.ChatCompletionProvider;
+using OpenAiCnChatCompletionProvider = BotSharp.Plugin.MiniMaxAI.Providers.Chat.OpenAiCnChatCompletionProvider;
namespace BotSharp.Plugin.MiniMaxAI;
+[PluginDependency("BotSharp.Plugin.OpenAI", "BotSharp.Plugin.AnthropicAI")]
public class MiniMaxAiPlugin : IBotSharpPlugin
{
public string Id => "8a4ebd68-4d7d-4c5c-aed9-263946cc3a0d";
@@ -12,7 +15,9 @@ public class MiniMaxAiPlugin : IBotSharpPlugin
public void RegisterDI(IServiceCollection services, IConfiguration config)
{
- services.AddScoped();
- services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
}
}
diff --git a/src/Plugins/BotSharp.Plugin.MiniMaxAI/Providers/Chat/AnthropicChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.MiniMaxAI/Providers/Chat/AnthropicChatCompletionProvider.cs
new file mode 100644
index 000000000..9e86b0c57
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.MiniMaxAI/Providers/Chat/AnthropicChatCompletionProvider.cs
@@ -0,0 +1,13 @@
+namespace BotSharp.Plugin.MiniMaxAI.Providers.Chat;
+
+public class AnthropicChatCompletionProvider : global::BotSharp.Plugin.AnthropicAI.Providers.ChatCompletionProvider
+{
+ public override string Provider => "minimax-anthropic";
+
+ public AnthropicChatCompletionProvider(
+ IServiceProvider services,
+ ILogger logger,
+ IConversationStateService state) : base(new(), services, logger, state)
+ {
+ }
+}
diff --git a/src/Plugins/BotSharp.Plugin.MiniMaxAI/Providers/Chat/AnthropicCnChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.MiniMaxAI/Providers/Chat/AnthropicCnChatCompletionProvider.cs
new file mode 100644
index 000000000..df9dfda45
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.MiniMaxAI/Providers/Chat/AnthropicCnChatCompletionProvider.cs
@@ -0,0 +1,13 @@
+namespace BotSharp.Plugin.MiniMaxAI.Providers.Chat;
+
+public class AnthropicCnChatCompletionProvider : AnthropicChatCompletionProvider
+{
+ public override string Provider => "minimax-anthropic-cn";
+
+ public AnthropicCnChatCompletionProvider(
+ IServiceProvider services,
+ ILogger logger,
+ IConversationStateService state) : base(services, logger, state)
+ {
+ }
+}
diff --git a/src/Plugins/BotSharp.Plugin.MiniMaxAI/Providers/Chat/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.MiniMaxAI/Providers/Chat/ChatCompletionProvider.cs
index 3dc603b77..41bb743fc 100644
--- a/src/Plugins/BotSharp.Plugin.MiniMaxAI/Providers/Chat/ChatCompletionProvider.cs
+++ b/src/Plugins/BotSharp.Plugin.MiniMaxAI/Providers/Chat/ChatCompletionProvider.cs
@@ -1,15 +1,47 @@
+using OpenAI.Chat;
+
namespace BotSharp.Plugin.MiniMaxAI.Providers.Chat;
+#pragma warning disable SCME0001
+
public class ChatCompletionProvider : global::BotSharp.Plugin.OpenAI.Providers.Chat.ChatCompletionProvider
{
public override string Provider => "minimax";
+ protected override bool UseResponseApi => false;
public ChatCompletionProvider(
- global::BotSharp.Plugin.OpenAI.Settings.OpenAiSettings settings,
ILogger logger,
IServiceProvider services,
IConversationStateService state,
- IFileStorageService fileStorage) : base(settings, logger, services, state, fileStorage)
+ IFileStorageService fileStorage) : base(new(), logger, services, state, fileStorage)
+ {
+ }
+
+ protected override void ConfigureChatCompletionOptions(
+ ChatCompletionOptions options,
+ LlmModelSetting? settings)
{
+ var parameters = settings?.Reasoning?.Parameters;
+ var thinkingType = LlmUtility.GetModelParameter(
+ parameters,
+ "ThinkingType",
+ _state.GetState("thinking_type"));
+ if (thinkingType is "adaptive" or "disabled")
+ {
+ var thinking = JsonSerializer.SerializeToUtf8Bytes(new { type = thinkingType });
+ options.Patch.Set("$.thinking"u8, BinaryData.FromBytes(thinking));
+ }
+
+ var serviceTiers = settings?.Cost?.TextTokenCostTiers?
+ .Select(x => x.ServiceTier)
+ .Distinct(StringComparer.OrdinalIgnoreCase);
+ var serviceTier = LlmUtility.VerifyModelParameter(
+ _state.GetState("service_tier"),
+ settings?.Cost?.DefaultServiceTier,
+ serviceTiers);
+ if (!string.IsNullOrWhiteSpace(serviceTier))
+ {
+ options.Patch.Set("$.service_tier"u8, serviceTier);
+ }
}
}
diff --git a/src/Plugins/BotSharp.Plugin.MiniMaxAI/Providers/Chat/OpenAiCnChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.MiniMaxAI/Providers/Chat/OpenAiCnChatCompletionProvider.cs
new file mode 100644
index 000000000..ad49467af
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.MiniMaxAI/Providers/Chat/OpenAiCnChatCompletionProvider.cs
@@ -0,0 +1,14 @@
+namespace BotSharp.Plugin.MiniMaxAI.Providers.Chat;
+
+public class OpenAiCnChatCompletionProvider : ChatCompletionProvider
+{
+ public override string Provider => "minimax-cn";
+
+ public OpenAiCnChatCompletionProvider(
+ ILogger logger,
+ IServiceProvider services,
+ IConversationStateService state,
+ IFileStorageService fileStorage) : base(logger, services, state, fileStorage)
+ {
+ }
+}
diff --git a/src/Plugins/BotSharp.Plugin.MiniMaxAI/Providers/Text/TextCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.MiniMaxAI/Providers/Text/TextCompletionProvider.cs
deleted file mode 100644
index 1f94a1ba9..000000000
--- a/src/Plugins/BotSharp.Plugin.MiniMaxAI/Providers/Text/TextCompletionProvider.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-namespace BotSharp.Plugin.MiniMaxAI.Providers.Text;
-
-public class TextCompletionProvider : global::BotSharp.Plugin.OpenAI.Providers.Text.TextCompletionProvider
-{
- public override string Provider => "minimax";
-
- public TextCompletionProvider(
- global::BotSharp.Plugin.OpenAI.Settings.OpenAiSettings settings,
- ILogger logger,
- IServiceProvider services) : base(settings, logger, services)
- {
- }
-}
diff --git a/src/Plugins/BotSharp.Plugin.MiniMaxAI/Using.cs b/src/Plugins/BotSharp.Plugin.MiniMaxAI/Using.cs
index 06ffb04fd..793ccd9d0 100644
--- a/src/Plugins/BotSharp.Plugin.MiniMaxAI/Using.cs
+++ b/src/Plugins/BotSharp.Plugin.MiniMaxAI/Using.cs
@@ -1,3 +1,7 @@
+global using System;
+global using System.Linq;
+global using System.Text.Json;
+
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.Logging;
@@ -5,4 +9,6 @@
global using BotSharp.Abstraction.Conversations;
global using BotSharp.Abstraction.Files;
global using BotSharp.Abstraction.MLTasks;
+global using BotSharp.Abstraction.MLTasks.Settings;
+global using BotSharp.Abstraction.MLTasks.Utilities;
global using BotSharp.Abstraction.Plugins;
diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.Chat.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.Chat.cs
index 55dc8eeb6..85f804b7a 100644
--- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.Chat.cs
+++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.Chat.cs
@@ -88,6 +88,7 @@ private async Task InnerGetChatCompletions(Agent agent, List InnerGetChatCompletionsAsync(Agent agent,
Prompt = prompt,
Provider = Provider,
Model = _model,
+ ServiceTier = value.ServiceTier?.ToString(),
TextInputTokens = (tokenUsage?.InputTokenCount ?? 0) - (inputTokenDetails?.CachedTokenCount ?? 0),
CachedTextInputTokens = inputTokenDetails?.CachedTokenCount ?? 0,
TextOutputTokens = tokenUsage?.OutputTokenCount ?? 0
@@ -231,6 +233,7 @@ private async Task InnerGetChatCompletionsStreamingAsync(Agent
using var textStream = new RealtimeTextStream();
var toolCalls = new List();
ChatTokenUsage? tokenUsage = null;
+ string? serviceTier = null;
var responseMessage = new RoleDialogModel(AgentRole.Assistant, string.Empty)
{
@@ -246,6 +249,7 @@ private async Task InnerGetChatCompletionsStreamingAsync(Agent
await foreach (var choice in chatClient.CompleteChatStreamingAsync(messages, options, cancellationToken))
{
tokenUsage = choice.Usage;
+ serviceTier = choice.ServiceTier?.ToString() ?? serviceTier;
if (!choice.ToolCallUpdates.IsNullOrEmpty())
{
@@ -355,6 +359,7 @@ private async Task InnerGetChatCompletionsStreamingAsync(Agent
Prompt = prompt,
Provider = Provider,
Model = _model,
+ ServiceTier = serviceTier,
TextInputTokens = (tokenUsage?.InputTokenCount ?? 0) - (inputTokenDetails?.CachedTokenCount ?? 0),
CachedTextInputTokens = inputTokenDetails?.CachedTokenCount ?? 0,
TextOutputTokens = tokenUsage?.OutputTokenCount ?? 0
@@ -626,6 +631,8 @@ private ChatCompletionOptions InitChatCompletionOption(Agent agent)
WebSearchOptions = webSearchOptions
};
+ ConfigureChatCompletionOptions(options, settings);
+
if (webSearchOptions == null)
{
var format = _state.GetState("response_format").IfNullOrEmptyAs(agent.LlmConfig?.ResponseFormat);
diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs
index 98c89980b..1be73f23f 100644
--- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs
+++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs
@@ -1,3 +1,5 @@
+using OpenAI.Chat;
+
namespace BotSharp.Plugin.OpenAI.Providers.Chat;
public partial class ChatCompletionProvider : IChatCompletion
@@ -15,6 +17,7 @@ public partial class ChatCompletionProvider : IChatCompletion
public virtual string Provider => "openai";
public string Model => _model;
+ protected virtual bool UseResponseApi => _settings.UseResponseApi;
public ChatCompletionProvider(
OpenAiSettings settings,
@@ -33,7 +36,7 @@ public ChatCompletionProvider(
public async Task GetChatCompletions(Agent agent, List conversations)
{
- if (_settings.UseResponseApi)
+ if (UseResponseApi)
{
return await InnerCreateResponse(agent, conversations);
}
@@ -48,7 +51,7 @@ public async Task GetChatCompletionsAsync(Agent agent,
Func onMessageReceived,
Func onFunctionExecuting)
{
- if (_settings.UseResponseApi)
+ if (UseResponseApi)
{
return await InnerCreateResponseAsync(agent, conversations, onMessageReceived, onFunctionExecuting);
}
@@ -60,7 +63,7 @@ public async Task GetChatCompletionsAsync(Agent agent,
public async Task GetChatCompletionsStreamingAsync(Agent agent, List conversations)
{
- if (_settings.UseResponseApi)
+ if (UseResponseApi)
{
return await InnerCreateResponseStreamingAsync(agent, conversations);
}
@@ -81,9 +84,15 @@ public void SetApiKey(string apiKey)
_apiKey = apiKey;
}
+ protected virtual void ConfigureChatCompletionOptions(
+ ChatCompletionOptions options,
+ LlmModelSetting? settings)
+ {
+ }
+
private static bool IsImageContentType(string? contentType)
{
return !string.IsNullOrEmpty(contentType)
&& contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase);
}
-}
\ No newline at end of file
+}
diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json
index 79bbe9521..34dd1afb8 100644
--- a/src/WebStarter/appsettings.json
+++ b/src/WebStarter/appsettings.json
@@ -603,17 +603,449 @@
"Version": "MiniMax-M3",
"ApiKey": "",
"Endpoint": "https://api.minimax.io/v1",
+ "ContextWindow": 1000000,
+ "Type": "chat",
+ "MultiModal": true,
+ "Capabilities": [
+ "Chat",
+ "ImageReading"
+ ],
+ "InputModalities": [
+ "text",
+ "image",
+ "video"
+ ],
+ "Reasoning": {
+ "Parameters": {
+ "ThinkingType": {
+ "Default": "adaptive",
+ "Options": [ "adaptive", "disabled" ]
+ }
+ }
+ },
+ "Cost": {
+ "TextInputCost": 0.0003,
+ "CachedTextInputCost": 0.00006,
+ "TextOutputCost": 0.0012,
+ "DefaultServiceTier": "standard",
+ "TextTokenCostTiers": [
+ {
+ "ServiceTier": "standard",
+ "InputTokensLessThanOrEqual": 512000,
+ "TextInputCost": 0.0003,
+ "CachedTextInputCost": 0.00006,
+ "TextOutputCost": 0.0012
+ },
+ {
+ "ServiceTier": "standard",
+ "InputTokensGreaterThan": 512000,
+ "TextInputCost": 0.0006,
+ "CachedTextInputCost": 0.00012,
+ "TextOutputCost": 0.0024
+ },
+ {
+ "ServiceTier": "priority",
+ "InputTokensLessThanOrEqual": 512000,
+ "TextInputCost": 0.00045,
+ "CachedTextInputCost": 0.00009,
+ "TextOutputCost": 0.0018
+ },
+ {
+ "ServiceTier": "priority",
+ "InputTokensGreaterThan": 512000,
+ "TextInputCost": 0.0009,
+ "CachedTextInputCost": 0.00018,
+ "TextOutputCost": 0.0036
+ }
+ ]
+ }
+ },
+ {
+ "Id": "MiniMax-M2.7",
+ "Name": "MiniMax-M2.7",
+ "Version": "MiniMax-M2.7",
+ "ApiKey": "",
+ "Endpoint": "https://api.minimax.io/v1",
+ "ContextWindow": 204800,
"Type": "chat",
"Capabilities": [
"Chat"
],
+ "InputModalities": [
+ "text"
+ ],
+ "Reasoning": {
+ "Parameters": {
+ "ThinkingType": {
+ "Default": "always_on",
+ "Options": [ "always_on" ]
+ }
+ }
+ },
"Cost": {
- "TextInputCost": 0.0006,
- "CachedTextInputCost": 0.00012,
- "AudioInputCost": 0,
- "CachedAudioInputCost": 0,
- "TextOutputCost": 0.0024,
- "AudioOutputCost": 0
+ "TextInputCost": 0.0003,
+ "CachedTextInputCost": 0.00006,
+ "CachedTextInputWriteCost": 0.000375,
+ "TextOutputCost": 0.0012,
+ "DefaultServiceTier": "standard",
+ "TextTokenCostTiers": [
+ {
+ "ServiceTier": "standard",
+ "TextInputCost": 0.0003,
+ "CachedTextInputCost": 0.00006,
+ "CachedTextInputWriteCost": 0.000375,
+ "TextOutputCost": 0.0012
+ },
+ {
+ "ServiceTier": "priority",
+ "TextInputCost": 0.00045,
+ "CachedTextInputCost": 0.00009,
+ "CachedTextInputWriteCost": 0.0005625,
+ "TextOutputCost": 0.0018
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "Provider": "minimax-cn",
+ "Models": [
+ {
+ "Id": "MiniMax-M3",
+ "Name": "MiniMax-M3",
+ "Version": "MiniMax-M3",
+ "ApiKey": "",
+ "Endpoint": "https://api.minimaxi.com/v1",
+ "ContextWindow": 1000000,
+ "Type": "chat",
+ "MultiModal": true,
+ "Capabilities": [
+ "Chat",
+ "ImageReading"
+ ],
+ "InputModalities": [
+ "text",
+ "image",
+ "video"
+ ],
+ "Reasoning": {
+ "Parameters": {
+ "ThinkingType": {
+ "Default": "adaptive",
+ "Options": [ "adaptive", "disabled" ]
+ }
+ }
+ },
+ "Cost": {
+ "TextInputCost": 0.0003,
+ "CachedTextInputCost": 0.00006,
+ "TextOutputCost": 0.0012,
+ "DefaultServiceTier": "standard",
+ "TextTokenCostTiers": [
+ {
+ "ServiceTier": "standard",
+ "InputTokensLessThanOrEqual": 512000,
+ "TextInputCost": 0.0003,
+ "CachedTextInputCost": 0.00006,
+ "TextOutputCost": 0.0012
+ },
+ {
+ "ServiceTier": "standard",
+ "InputTokensGreaterThan": 512000,
+ "TextInputCost": 0.0006,
+ "CachedTextInputCost": 0.00012,
+ "TextOutputCost": 0.0024
+ },
+ {
+ "ServiceTier": "priority",
+ "InputTokensLessThanOrEqual": 512000,
+ "TextInputCost": 0.00045,
+ "CachedTextInputCost": 0.00009,
+ "TextOutputCost": 0.0018
+ },
+ {
+ "ServiceTier": "priority",
+ "InputTokensGreaterThan": 512000,
+ "TextInputCost": 0.0009,
+ "CachedTextInputCost": 0.00018,
+ "TextOutputCost": 0.0036
+ }
+ ]
+ }
+ },
+ {
+ "Id": "MiniMax-M2.7",
+ "Name": "MiniMax-M2.7",
+ "Version": "MiniMax-M2.7",
+ "ApiKey": "",
+ "Endpoint": "https://api.minimaxi.com/v1",
+ "ContextWindow": 204800,
+ "Type": "chat",
+ "Capabilities": [
+ "Chat"
+ ],
+ "InputModalities": [
+ "text"
+ ],
+ "Reasoning": {
+ "Parameters": {
+ "ThinkingType": {
+ "Default": "always_on",
+ "Options": [ "always_on" ]
+ }
+ }
+ },
+ "Cost": {
+ "TextInputCost": 0.0003,
+ "CachedTextInputCost": 0.00006,
+ "CachedTextInputWriteCost": 0.000375,
+ "TextOutputCost": 0.0012,
+ "DefaultServiceTier": "standard",
+ "TextTokenCostTiers": [
+ {
+ "ServiceTier": "standard",
+ "TextInputCost": 0.0003,
+ "CachedTextInputCost": 0.00006,
+ "CachedTextInputWriteCost": 0.000375,
+ "TextOutputCost": 0.0012
+ },
+ {
+ "ServiceTier": "priority",
+ "TextInputCost": 0.00045,
+ "CachedTextInputCost": 0.00009,
+ "CachedTextInputWriteCost": 0.0005625,
+ "TextOutputCost": 0.0018
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "Provider": "minimax-anthropic",
+ "Models": [
+ {
+ "Id": "MiniMax-M3",
+ "Name": "MiniMax-M3",
+ "Version": "MiniMax-M3",
+ "ApiKey": "",
+ "Endpoint": "https://api.minimax.io/anthropic",
+ "ContextWindow": 1000000,
+ "Type": "chat",
+ "MultiModal": true,
+ "Capabilities": [
+ "Chat",
+ "ImageReading"
+ ],
+ "InputModalities": [
+ "text",
+ "image",
+ "video"
+ ],
+ "Reasoning": {
+ "Parameters": {
+ "ThinkingType": {
+ "Default": "disabled",
+ "Options": [ "adaptive", "disabled" ]
+ }
+ }
+ },
+ "Cost": {
+ "TextInputCost": 0.0003,
+ "CachedTextInputCost": 0.00006,
+ "TextOutputCost": 0.0012,
+ "DefaultServiceTier": "standard",
+ "TextTokenCostTiers": [
+ {
+ "ServiceTier": "standard",
+ "InputTokensLessThanOrEqual": 512000,
+ "TextInputCost": 0.0003,
+ "CachedTextInputCost": 0.00006,
+ "TextOutputCost": 0.0012
+ },
+ {
+ "ServiceTier": "standard",
+ "InputTokensGreaterThan": 512000,
+ "TextInputCost": 0.0006,
+ "CachedTextInputCost": 0.00012,
+ "TextOutputCost": 0.0024
+ },
+ {
+ "ServiceTier": "priority",
+ "InputTokensLessThanOrEqual": 512000,
+ "TextInputCost": 0.00045,
+ "CachedTextInputCost": 0.00009,
+ "TextOutputCost": 0.0018
+ },
+ {
+ "ServiceTier": "priority",
+ "InputTokensGreaterThan": 512000,
+ "TextInputCost": 0.0009,
+ "CachedTextInputCost": 0.00018,
+ "TextOutputCost": 0.0036
+ }
+ ]
+ }
+ },
+ {
+ "Id": "MiniMax-M2.7",
+ "Name": "MiniMax-M2.7",
+ "Version": "MiniMax-M2.7",
+ "ApiKey": "",
+ "Endpoint": "https://api.minimax.io/anthropic",
+ "ContextWindow": 204800,
+ "Type": "chat",
+ "Capabilities": [
+ "Chat"
+ ],
+ "InputModalities": [
+ "text"
+ ],
+ "Reasoning": {
+ "Parameters": {
+ "ThinkingType": {
+ "Default": "always_on",
+ "Options": [ "always_on" ]
+ }
+ }
+ },
+ "Cost": {
+ "TextInputCost": 0.0003,
+ "CachedTextInputCost": 0.00006,
+ "CachedTextInputWriteCost": 0.000375,
+ "TextOutputCost": 0.0012,
+ "DefaultServiceTier": "standard",
+ "TextTokenCostTiers": [
+ {
+ "ServiceTier": "standard",
+ "TextInputCost": 0.0003,
+ "CachedTextInputCost": 0.00006,
+ "CachedTextInputWriteCost": 0.000375,
+ "TextOutputCost": 0.0012
+ },
+ {
+ "ServiceTier": "priority",
+ "TextInputCost": 0.00045,
+ "CachedTextInputCost": 0.00009,
+ "CachedTextInputWriteCost": 0.0005625,
+ "TextOutputCost": 0.0018
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "Provider": "minimax-anthropic-cn",
+ "Models": [
+ {
+ "Id": "MiniMax-M3",
+ "Name": "MiniMax-M3",
+ "Version": "MiniMax-M3",
+ "ApiKey": "",
+ "Endpoint": "https://api.minimaxi.com/anthropic",
+ "ContextWindow": 1000000,
+ "Type": "chat",
+ "MultiModal": true,
+ "Capabilities": [
+ "Chat",
+ "ImageReading"
+ ],
+ "InputModalities": [
+ "text",
+ "image",
+ "video"
+ ],
+ "Reasoning": {
+ "Parameters": {
+ "ThinkingType": {
+ "Default": "disabled",
+ "Options": [ "adaptive", "disabled" ]
+ }
+ }
+ },
+ "Cost": {
+ "TextInputCost": 0.0003,
+ "CachedTextInputCost": 0.00006,
+ "TextOutputCost": 0.0012,
+ "DefaultServiceTier": "standard",
+ "TextTokenCostTiers": [
+ {
+ "ServiceTier": "standard",
+ "InputTokensLessThanOrEqual": 512000,
+ "TextInputCost": 0.0003,
+ "CachedTextInputCost": 0.00006,
+ "TextOutputCost": 0.0012
+ },
+ {
+ "ServiceTier": "standard",
+ "InputTokensGreaterThan": 512000,
+ "TextInputCost": 0.0006,
+ "CachedTextInputCost": 0.00012,
+ "TextOutputCost": 0.0024
+ },
+ {
+ "ServiceTier": "priority",
+ "InputTokensLessThanOrEqual": 512000,
+ "TextInputCost": 0.00045,
+ "CachedTextInputCost": 0.00009,
+ "TextOutputCost": 0.0018
+ },
+ {
+ "ServiceTier": "priority",
+ "InputTokensGreaterThan": 512000,
+ "TextInputCost": 0.0009,
+ "CachedTextInputCost": 0.00018,
+ "TextOutputCost": 0.0036
+ }
+ ]
+ }
+ },
+ {
+ "Id": "MiniMax-M2.7",
+ "Name": "MiniMax-M2.7",
+ "Version": "MiniMax-M2.7",
+ "ApiKey": "",
+ "Endpoint": "https://api.minimaxi.com/anthropic",
+ "ContextWindow": 204800,
+ "Type": "chat",
+ "Capabilities": [
+ "Chat"
+ ],
+ "InputModalities": [
+ "text"
+ ],
+ "Reasoning": {
+ "Parameters": {
+ "ThinkingType": {
+ "Default": "always_on",
+ "Options": [ "always_on" ]
+ }
+ }
+ },
+ "Cost": {
+ "TextInputCost": 0.0003,
+ "CachedTextInputCost": 0.00006,
+ "CachedTextInputWriteCost": 0.000375,
+ "TextOutputCost": 0.0012,
+ "DefaultServiceTier": "standard",
+ "TextTokenCostTiers": [
+ {
+ "ServiceTier": "standard",
+ "TextInputCost": 0.0003,
+ "CachedTextInputCost": 0.00006,
+ "CachedTextInputWriteCost": 0.000375,
+ "TextOutputCost": 0.0012
+ },
+ {
+ "ServiceTier": "priority",
+ "TextInputCost": 0.00045,
+ "CachedTextInputCost": 0.00009,
+ "CachedTextInputWriteCost": 0.0005625,
+ "TextOutputCost": 0.0018
+ }
+ ]
}
}
]
diff --git a/tests/BotSharp.LLM.Tests/BotSharp.LLM.Tests.csproj b/tests/BotSharp.LLM.Tests/BotSharp.LLM.Tests.csproj
index c95d7cfa2..0223aac31 100644
--- a/tests/BotSharp.LLM.Tests/BotSharp.LLM.Tests.csproj
+++ b/tests/BotSharp.LLM.Tests/BotSharp.LLM.Tests.csproj
@@ -24,6 +24,7 @@
+
@@ -34,5 +35,8 @@
PreserveNewest
+
+ PreserveNewest
+
diff --git a/tests/BotSharp.LLM.Tests/MiniMaxProviderTests.cs b/tests/BotSharp.LLM.Tests/MiniMaxProviderTests.cs
new file mode 100644
index 000000000..716dfcb1c
--- /dev/null
+++ b/tests/BotSharp.LLM.Tests/MiniMaxProviderTests.cs
@@ -0,0 +1,129 @@
+using BotSharp.Abstraction.MLTasks;
+using BotSharp.Abstraction.MLTasks.Settings;
+using BotSharp.Core.Infrastructures;
+using BotSharp.Plugin.MiniMaxAI;
+using BotSharp.Plugin.MiniMaxAI.Providers.Chat;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Shouldly;
+using AnthropicProviderHelper = BotSharp.Plugin.AnthropicAI.Providers.ProviderHelper;
+
+namespace BotSharp.LLM.Tests;
+
+public class MiniMaxProviderTests
+{
+ private static readonly Dictionary ExpectedEndpoints = new()
+ {
+ ["minimax"] = "https://api.minimax.io/v1",
+ ["minimax-cn"] = "https://api.minimaxi.com/v1",
+ ["minimax-anthropic"] = "https://api.minimax.io/anthropic",
+ ["minimax-anthropic-cn"] = "https://api.minimaxi.com/anthropic"
+ };
+
+ [Fact]
+ public void RegistersEveryProtocolAndRegionAlias()
+ {
+ var services = new ServiceCollection();
+ new MiniMaxAiPlugin().RegisterDI(services, new ConfigurationBuilder().Build());
+
+ var providerTypes = services
+ .Where(x => x.ServiceType == typeof(IChatCompletion))
+ .Select(x => x.ImplementationType)
+ .ToArray();
+
+ providerTypes.ShouldBe([
+ typeof(ChatCompletionProvider),
+ typeof(OpenAiCnChatCompletionProvider),
+ typeof(AnthropicChatCompletionProvider),
+ typeof(AnthropicCnChatCompletionProvider)
+ ]);
+ }
+
+ [Fact]
+ public void WebStarterSettingsCoverModelsEndpointsAndCapabilities()
+ {
+ var configuration = new ConfigurationBuilder()
+ .SetBasePath(AppContext.BaseDirectory)
+ .AddJsonFile("WebStarter.appsettings.json")
+ .Build();
+ var providers = configuration.GetSection("LlmProviders").Get>();
+
+ providers.ShouldNotBeNull();
+ foreach (var expected in ExpectedEndpoints)
+ {
+ var provider = providers.Single(x => x.Provider == expected.Key);
+ provider.Models.Select(x => x.Id).ShouldBe(["MiniMax-M3", "MiniMax-M2.7"]);
+ provider.Models.ShouldAllBe(x => x.Endpoint == expected.Value);
+
+ var m3 = provider.Models.Single(x => x.Id == "MiniMax-M3");
+ m3.ContextWindow.ShouldBe(1_000_000);
+ m3.MultiModal.ShouldBeTrue();
+ m3.InputModalities.ShouldBe(["text", "image", "video"]);
+ m3.Reasoning!.Parameters!["ThinkingType"].Default.ShouldBe(
+ expected.Key.Contains("anthropic", StringComparison.Ordinal)
+ ? "disabled"
+ : "adaptive");
+ m3.Cost.TextTokenCostTiers!.Count.ShouldBe(4);
+
+ var m27 = provider.Models.Single(x => x.Id == "MiniMax-M2.7");
+ m27.ContextWindow.ShouldBe(204_800);
+ m27.MultiModal.ShouldBeFalse();
+ m27.InputModalities.ShouldBe(["text"]);
+ m27.Reasoning!.Parameters!["ThinkingType"].Default.ShouldBe("always_on");
+ m27.Cost.CachedTextInputWriteCost.ShouldBe(0.000375f);
+ }
+ }
+
+ [Fact]
+ public void AnthropicClientUsesConfiguredBaseUrl()
+ {
+ const string providerName = "minimax-anthropic";
+ const string modelName = "MiniMax-M3";
+ var services = new ServiceCollection();
+ services.AddLogging();
+ services.AddSingleton(new List
+ {
+ new()
+ {
+ Provider = providerName,
+ Models =
+ [
+ new()
+ {
+ Id = modelName,
+ Name = modelName,
+ ApiKey = "test-key",
+ Endpoint = "https://api.minimax.io/anthropic"
+ }
+ ]
+ }
+ });
+ services.AddSingleton();
+
+ using var serviceProvider = services.BuildServiceProvider();
+ using var client = AnthropicProviderHelper.GetAnthropicClient(providerName, modelName, serviceProvider);
+
+ client.ApiUrlFormat.ShouldBe("https://api.minimax.io/anthropic/{0}/{1}");
+ }
+
+ [Fact]
+ public void SelectsTextCostTierByServiceTierAndInputLength()
+ {
+ var cost = new LlmCostSetting
+ {
+ DefaultServiceTier = "standard",
+ TextTokenCostTiers =
+ [
+ new() { ServiceTier = "standard", InputTokensLessThanOrEqual = 512_000, TextInputCost = 0.0003f },
+ new() { ServiceTier = "standard", InputTokensGreaterThan = 512_000, TextInputCost = 0.0006f },
+ new() { ServiceTier = "priority", InputTokensLessThanOrEqual = 512_000, TextInputCost = 0.00045f },
+ new() { ServiceTier = "priority", InputTokensGreaterThan = 512_000, TextInputCost = 0.0009f }
+ ]
+ };
+
+ cost.GetTextTokenCostTier(512_000)!.TextInputCost.ShouldBe(0.0003f);
+ cost.GetTextTokenCostTier(512_001)!.TextInputCost.ShouldBe(0.0006f);
+ cost.GetTextTokenCostTier(512_000, "priority")!.TextInputCost.ShouldBe(0.00045f);
+ cost.GetTextTokenCostTier(512_001, "priority")!.TextInputCost.ShouldBe(0.0009f);
+ }
+}