diff --git a/BotSharp.sln b/BotSharp.sln
index fc6036727..20bfeb54e 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/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/LlmCostSetting.cs b/src/Infrastructure/BotSharp.Abstraction/MLTasks/Settings/LlmCostSetting.cs
new file mode 100644
index 000000000..825b20906
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/MLTasks/Settings/LlmCostSetting.cs
@@ -0,0 +1,64 @@
+namespace BotSharp.Abstraction.MLTasks.Settings;
+
+///
+/// Cost per 1K tokens
+///
+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
+ public float AudioInputCost { get; set; } = 0f;
+ public float CachedAudioInputCost { get; set; } = 0f;
+ public float AudioOutputCost { get; set; } = 0f;
+ #endregion
+
+ #region Image token
+ public float ImageInputCost { get; set; } = 0f;
+ public float CachedImageInputCost { get; set; } = 0f;
+ public float ImageOutputCost { get; set; } = 0f;
+ #endregion
+
+ #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
+{
+ ///
+ /// Attributes: e.g., [quality]: "medium", [size] = "1024x1024"
+ ///
+ public Dictionary Attributes { get; set; } = [];
+ public float Cost { get; set; } = 0f;
+}
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Abstraction/MLTasks/Settings/LlmModelSetting.cs b/src/Infrastructure/BotSharp.Abstraction/MLTasks/Settings/LlmModelSetting.cs
index 3915f9f6e..815b3b44a 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
@@ -174,44 +176,6 @@ public class ModelParamSetting
public IEnumerable? Options { get; set; }
}
-
-///
-/// Cost per 1K tokens
-///
-public class LlmCostSetting
-{
- #region Text token
- public float TextInputCost { get; set; } = 0f;
- public float CachedTextInputCost { get; set; } = 0f;
- public float TextOutputCost { get; set; } = 0f;
- #endregion
-
- #region Audio token
- public float AudioInputCost { get; set; } = 0f;
- public float CachedAudioInputCost { get; set; } = 0f;
- public float AudioOutputCost { get; set; } = 0f;
- #endregion
-
- #region Image token
- public float ImageInputCost { get; set; } = 0f;
- public float CachedImageInputCost { get; set; } = 0f;
- public float ImageOutputCost { get; set; } = 0f;
- #endregion
-
- #region Image
- public IList? ImageCosts { get; set; }
- #endregion
-}
-
-public class LlmImageCost
-{
- ///
- /// Attributes: e.g., [quality]: "medium", [size] = "1024x1024"
- ///
- public Dictionary Attributes { get; set; } = [];
- public float Cost { get; set; } = 0f;
-}
-
public enum LlmModelType
{
All = 0,
@@ -240,4 +204,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/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/reasoner.naive.liquid b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/reasoner.naive.liquid
index 1861ab36b..396a4449d 100644
--- a/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/reasoner.naive.liquid
+++ b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/reasoner.naive.liquid
@@ -4,7 +4,7 @@ Route to the last handling agent in priority.
Expected next action agent is {{ expected_next_action_agent }}.
{%- else -%}
Next action agent is determined from the latest user message in the context of the active user goal.
-If the user only provides requested information, keep the previous next action agent or fall back to the agent for the active user goal.
+If the user only provides requested information, keep the previous next action agent or route to the agent for the active user goal.
{%- endif %}
{% if expected_user_goal_agent != empty -%}
Expected user goal agent is {{ expected_user_goal_agent }}.
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
new file mode 100644
index 000000000..1ddba5cab
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.MiniMaxAI/BotSharp.Plugin.MiniMaxAI.csproj
@@ -0,0 +1,18 @@
+
+
+
+ $(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..14fae8215
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.MiniMaxAI/MiniMaxAiPlugin.cs
@@ -0,0 +1,23 @@
+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";
+ 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();
+ 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
new file mode 100644
index 000000000..41bb743fc
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.MiniMaxAI/Providers/Chat/ChatCompletionProvider.cs
@@ -0,0 +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(
+ ILogger logger,
+ IServiceProvider services,
+ IConversationStateService state,
+ 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/Using.cs b/src/Plugins/BotSharp.Plugin.MiniMaxAI/Using.cs
new file mode 100644
index 000000000..793ccd9d0
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.MiniMaxAI/Using.cs
@@ -0,0 +1,14 @@
+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;
+
+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/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..34dd1afb8 100644
--- a/src/WebStarter/appsettings.json
+++ b/src/WebStarter/appsettings.json
@@ -594,6 +594,462 @@
}
]
},
+ {
+ "Provider": "minimax",
+ "Models": [
+ {
+ "Id": "MiniMax-M3",
+ "Name": "MiniMax-M3",
+ "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.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
+ }
+ ]
+ }
+ }
+ ]
+ },
{
"Provider": "google-ai",
"Models": [
@@ -1056,6 +1512,7 @@
"BotSharp.Plugin.GoogleAI",
"BotSharp.Plugin.MetaAI",
"BotSharp.Plugin.DeepSeekAI",
+ "BotSharp.Plugin.MiniMaxAI",
"BotSharp.Plugin.MetaMessenger",
"BotSharp.Plugin.HuggingFace",
"BotSharp.Plugin.KnowledgeBase",
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);
+ }
+}