Skip to content

Commit ed107cf

Browse files
author
octo-patch
committed
feat: add MiniMax provider support
Add MiniMax as an OpenAI-compatible LLM provider: - Models: MiniMax-M2.7 (default) and MiniMax-M2.7-highspeed - API: https://api.minimax.io/v1 (OpenAI-compatible) - Temperature default: 1.0 (MiniMax requires > 0.0) - Context window: 1,000,000 tokens - Keychain storage for MINIMAX_API_KEY - Full UI: API key field, model picker with refresh, settings section Depends on macOS26/AgentTools#1 for the APIProvider.miniMax enum case.
1 parent f1c0959 commit ed107cf

12 files changed

Lines changed: 128 additions & 1 deletion

File tree

Agent/AgentViewModel/Core/AgentViewModel.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,19 @@ final class AgentViewModel {
292292
var qwenModels: [OpenAIModelInfo] = []
293293
var isFetchingQwenModels = false
294294

295+
// MARK: - MiniMax
296+
297+
var miniMaxAPIKey: String = KeychainService.shared.getMiniMaxAPIKey() ?? "" {
298+
didSet { KeychainService.shared.setMiniMaxAPIKey(miniMaxAPIKey) }
299+
}
300+
301+
var miniMaxModel: String = UserDefaults.standard.string(forKey: "miniMaxModel") ?? "MiniMax-M2.7" {
302+
didSet { UserDefaults.standard.set(miniMaxModel, forKey: "miniMaxModel") }
303+
}
304+
305+
var miniMaxModels: [OpenAIModelInfo] = Self.defaultMiniMaxModels
306+
var isFetchingMiniMaxModels = false
307+
295308
// MARK: - Google Gemini
296309

297310
var geminiAPIKey: String = KeychainService.shared.getGeminiAPIKey() ?? "" {
@@ -420,6 +433,9 @@ final class AgentViewModel {
420433
var grokTemperature: Double = UserDefaults.standard.object(forKey: "grokTemperature") as? Double ?? 0.2 {
421434
didSet { UserDefaults.standard.set(grokTemperature, forKey: "grokTemperature") }
422435
}
436+
var miniMaxTemperature: Double = UserDefaults.standard.object(forKey: "miniMaxTemperature") as? Double ?? 1.0 {
437+
didSet { UserDefaults.standard.set(miniMaxTemperature, forKey: "miniMaxTemperature") }
438+
}
423439

424440
/// Max output tokens per provider. 0 = let provider decide (omit from request).
425441
/// Claude API requires max_tokens so 0 defaults to 16384 at the service level.

Agent/AgentViewModel/Core/Colors.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ extension AgentViewModel {
118118
case .lmStudio: return lmStudioTemperature
119119
case .zAI: return zAITemperature
120120
case .bigModel: return zAITemperature
121+
case .miniMax: return miniMaxTemperature
121122
case .qwen: return openAITemperature
122123
case .gemini: return geminiTemperature
123124
case .grok: return grokTemperature

Agent/AgentViewModel/Features/DefaultModels.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,13 @@ extension AgentViewModel {
120120
OpenAIModelInfo(id: "mistralai/Mistral-Small-24B-Instruct-2501", name: "Mistral Small 24B"),
121121
]
122122

123+
// MARK: - MiniMax
124+
125+
nonisolated static let defaultMiniMaxModels: [OpenAIModelInfo] = [
126+
OpenAIModelInfo(id: "MiniMax-M2.7", name: "MiniMax-M2.7"),
127+
OpenAIModelInfo(id: "MiniMax-M2.7-highspeed", name: "MiniMax-M2.7-highspeed"),
128+
]
129+
123130
// MARK: - Ollama (Cloud)
124131

125132
nonisolated static let defaultOllamaModels: [OllamaModelInfo] = [

Agent/AgentViewModel/Features/ModelFetching.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,31 @@ extension AgentViewModel {
182182
}
183183
}
184184

185+
func fetchMiniMaxModels() {
186+
guard !miniMaxAPIKey.isEmpty else {
187+
miniMaxModels = Self.defaultMiniMaxModels
188+
return
189+
}
190+
isFetchingMiniMaxModels = true
191+
Task {
192+
defer { isFetchingMiniMaxModels = false }
193+
do {
194+
let models = try await Self.fetchOpenAICompatibleModels(
195+
baseURL: "https://api.minimax.io/v1",
196+
apiKey: miniMaxAPIKey
197+
)
198+
miniMaxModels = models.isEmpty ? Self.defaultMiniMaxModels : models
199+
let ids = miniMaxModels.map(\.id)
200+
if miniMaxModel.isEmpty || (!ids.isEmpty && !ids.contains(miniMaxModel)) {
201+
miniMaxModel = ids.first ?? "MiniMax-M2.7"
202+
}
203+
} catch {
204+
appendLog("Failed to fetch MiniMax models: \(error.localizedDescription)")
205+
miniMaxModels = Self.defaultMiniMaxModels
206+
}
207+
}
208+
}
209+
185210
// MARK: - Static API Fetch Helpers
186211

187212
private nonisolated static func fetchOpenAIModelsFromAPI(apiKey: String) async throws -> [OpenAIModelInfo] {
@@ -775,6 +800,7 @@ extension AgentViewModel {
775800
case .mistral: if force || mistralModels.isEmpty { fetchMistralModels() }
776801
case .codestral: if force || codestralModels.isEmpty { fetchCodestralModels() }
777802
case .vibe: if force || vibeModels.isEmpty { fetchVibeModels() }
803+
case .miniMax: if force || miniMaxModels.isEmpty { fetchMiniMaxModels() }
778804
case .bigModel: break
779805
default: break
780806
}

Agent/AgentViewModel/Features/ScriptTabs.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ extension AgentViewModel {
7272
case .lmStudio: return lmStudioModel
7373
case .zAI: return zAIModel.replacingOccurrences(of: ":v", with: "")
7474
case .bigModel: return bigModelModel.replacingOccurrences(of: ":v", with: "")
75+
case .miniMax: return miniMaxModel
7576
case .qwen: return qwenModel
7677
case .gemini: return geminiModel
7778
case .grok: return grokModel
@@ -95,6 +96,7 @@ extension AgentViewModel {
9596
case .lmStudio: return lmStudioAPIKey
9697
case .zAI: return zAIAPIKey
9798
case .bigModel: return bigModelAPIKey
99+
case .miniMax: return miniMaxAPIKey
98100
case .qwen: return qwenAPIKey
99101
case .gemini: return geminiAPIKey
100102
case .grok: return grokAPIKey
@@ -141,6 +143,9 @@ extension AgentViewModel {
141143
?? Self.defaultZAIModels.first(where: { $0.id == modelId })?.name ?? modelId
142144
case .bigModel:
143145
return modelId
146+
case .miniMax:
147+
return miniMaxModels.first(where: { $0.id == modelId })?.name
148+
?? Self.defaultMiniMaxModels.first(where: { $0.id == modelId })?.name ?? modelId
144149
case .qwen:
145150
return modelId
146151
case .gemini:

Agent/AgentViewModel/TaskExecution/Setup.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ extension AgentViewModel {
5656
case .bigModel:
5757
isVision = bigModelModel.hasSuffix(":v")
5858
modelName = bigModelModel.replacingOccurrences(of: ":v", with: "")
59+
case .miniMax:
60+
modelName = miniMaxModel
61+
isVision = false
5962
case .qwen:
6063
modelName = qwenModel
6164
isVision = Self.isVisionModel(qwenModel)

Agent/Services/KeychainService.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ final class KeychainService: Sendable {
7070
func setQwenAPIKey(_ key: String) { set(key: Self.qwenAPIKeyId, value: key) }
7171
func getQwenAPIKey() -> String? { get(key: Self.qwenAPIKeyId) }
7272

73+
private static let miniMaxAPIKey = "com.agent.minimax-api-key"
74+
func setMiniMaxAPIKey(_ key: String) { set(key: Self.miniMaxAPIKey, value: key) }
75+
func getMiniMaxAPIKey() -> String? { get(key: Self.miniMaxAPIKey) }
76+
7377
private func set(key: String, value: String) {
7478
guard let data = value.data(using: .utf8) else { return }
7579
delete(key: key)

Agent/Services/LLMProviderSetup.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ enum LLMProviderSetup {
77

88
static func registerAllProviders() {
99
LLMRegistry.shared.registerAll([
10-
claude, openAI, gemini, grok, mistral, codestral, vibe, deepSeek, huggingFace, zAI, bigModel, qwen,
10+
claude, openAI, gemini, grok, mistral, codestral, vibe, deepSeek, huggingFace, miniMax, zAI, bigModel, qwen,
1111
ollama, localOllama, vLLM, lmStudio, appleIntelligence
1212
])
1313
}
@@ -57,6 +57,17 @@ enum LLMProviderSetup {
5757
capabilities: [.streaming, .tools, .systemPrompt]
5858
)
5959

60+
static let miniMax = LLMProviderConfig(
61+
id: "miniMax", displayName: "MiniMax",
62+
kind: .cloudAPI, apiProtocol: .openAI,
63+
endpoint: LLMEndpoint(
64+
chatURL: "https://api.minimax.io/v1/chat/completions",
65+
modelsURL: "https://api.minimax.io/v1/models"
66+
),
67+
capabilities: [.streaming, .tools, .systemPrompt],
68+
temperature: 1.0
69+
)
70+
6071
static let zAI = LLMProviderConfig(
6172
id: "zAI", displayName: "Z.ai",
6273
kind: .cloudAPI, apiProtocol: .openAI,

Agent/Views/Output/ThinkingIndicatorView.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ struct ThinkingIndicatorView: View {
131131
case .grok: return 2_000_000
132132
case .zAI: return 128_000
133133
case .bigModel: return 128_000
134+
case .miniMax: return 1_000_000
134135
case .qwen: return 131_072
135136
case .mistral: return 256_000
136137
case .codestral: return 256_000

Agent/Views/Settings/FallbackChainView.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ struct FallbackChainView: View {
248248
case .codestral: if !viewModel.codestralModel.isEmpty { return viewModel.codestralModel }
249249
case .vibe: if !viewModel.vibeModel.isEmpty { return viewModel.vibeModel }
250250
case .bigModel: if !viewModel.bigModelModel.isEmpty { return viewModel.bigModelModel }
251+
case .miniMax: if !viewModel.miniMaxModel.isEmpty { return viewModel.miniMaxModel }
251252
case .foundationModel: return "Apple Intelligence"
252253
}
253254
// Fall back to the first dynamically-fetched model for this provider

0 commit comments

Comments
 (0)