fix(models): block unsupported Grok multi-agent routing
This commit is contained in:
parent
50f59fee1b
commit
eb0af5bbe5
@ -71,7 +71,7 @@ Add to your shell profile (`~/.zshrc`, `~/.bashrc`) for persistence.
|
||||
### Others
|
||||
- **Mistral**: `mistral-large-latest`, `mistral-medium-latest`, `mistral-medium-3-5`, `mistral-small-latest`, `open-mistral-nemo-2407`, `codestral-latest`
|
||||
- **Groq**: `openai/gpt-oss-120b`, `openai/gpt-oss-20b`, `llama-3.3-70b-versatile`, `llama-3.1-8b-instant`
|
||||
- **Grok**: `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-4.20-0309-non-reasoning`
|
||||
- **Grok**: `grok-4.3`, `grok-4.20-0309-reasoning`, `grok-4.20-0309-non-reasoning`
|
||||
- **Ollama** (local): `llama3.3`, `llava`, any installed model
|
||||
|
||||
### Model Shortcuts
|
||||
|
||||
@ -244,7 +244,6 @@ struct AICLI {
|
||||
|
||||
Grok (xAI):
|
||||
• grok-4.3
|
||||
• grok-4.20-multi-agent-0309
|
||||
• grok-4.20-0309-reasoning, grok-4.20-0309-non-reasoning
|
||||
|
||||
Ollama (Local):
|
||||
|
||||
@ -333,7 +333,6 @@ public final class ModelCapabilityRegistry: @unchecked Sendable {
|
||||
)
|
||||
|
||||
self.capabilities["grok:grok-4.3"] = grokCapabilities
|
||||
self.capabilities["grok:grok-4.20-multi-agent-0309"] = grokCapabilities
|
||||
self.capabilities["grok:grok-4.20-0309-reasoning"] = grokCapabilities
|
||||
self.capabilities["grok:grok-4.20-0309-non-reasoning"] = grokCapabilities
|
||||
}
|
||||
|
||||
@ -388,7 +388,6 @@ public enum LanguageModel: Sendable, CustomStringConvertible, Hashable {
|
||||
public static var allCases: [Grok] {
|
||||
[
|
||||
.grok43,
|
||||
.grok420MultiAgent,
|
||||
.grok420Reasoning,
|
||||
.grok420NonReasoning,
|
||||
]
|
||||
@ -415,7 +414,12 @@ public enum LanguageModel: Sendable, CustomStringConvertible, Hashable {
|
||||
}
|
||||
|
||||
public var supportsTools: Bool {
|
||||
true
|
||||
switch self {
|
||||
case .grok420MultiAgent:
|
||||
false
|
||||
default:
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
public var supportsAudioInput: Bool {
|
||||
@ -1415,16 +1419,15 @@ extension LanguageModel {
|
||||
normalized.hasPrefix("grok-4-fast") ||
|
||||
normalized.hasPrefix("grok-code-fast") ||
|
||||
normalized == "grok-4-0709" ||
|
||||
normalized.contains("grok-4.20-multi-agent") ||
|
||||
dotted.contains("grok-4-20-multi-agent") ||
|
||||
compact.contains("grok420multiagent") ||
|
||||
normalized.contains("grok-beta") ||
|
||||
normalized.contains("grok-vision-beta")
|
||||
if unsupportedGrok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if dotted.contains("grok-4-20-multi-agent") || compact.contains("grok420multiagent") {
|
||||
return .grok(.grok420MultiAgent)
|
||||
}
|
||||
|
||||
if dotted.contains("grok-4-20-0309-reasoning") || compact.contains("grok4200309reasoning") {
|
||||
return .grok(.grok420Reasoning)
|
||||
}
|
||||
@ -1587,6 +1590,9 @@ extension LanguageModel {
|
||||
normalized.hasPrefix("grok-4-fast") ||
|
||||
normalized.hasPrefix("grok-code-fast") ||
|
||||
normalized == "grok-4-0709" ||
|
||||
normalized.contains("grok-4.20-multi-agent") ||
|
||||
normalized.contains("grok-4-20-multi-agent") ||
|
||||
normalized.contains("grok420multiagent") ||
|
||||
normalized.contains("grok-beta") ||
|
||||
normalized.contains("grok-vision-beta")
|
||||
}
|
||||
|
||||
@ -44,10 +44,11 @@ public struct ModelSelector {
|
||||
return .grok(grokModel)
|
||||
}
|
||||
|
||||
let isProviderQualifiedGrokModel = normalized.contains("/") && normalized.contains("grok")
|
||||
if
|
||||
Self.isUnsupportedLegacyOpenAIModel(normalized) ||
|
||||
Self.isUnsupportedLegacyAnthropicModel(normalized) ||
|
||||
Self.isUnsupportedLegacyGrokModel(normalized)
|
||||
(Self.isUnsupportedLegacyGrokModel(normalized) && !isProviderQualifiedGrokModel)
|
||||
{
|
||||
throw ModelValidationError.unsupportedModel(modelString)
|
||||
}
|
||||
@ -230,8 +231,6 @@ public struct ModelSelector {
|
||||
// Direct matches for available models only
|
||||
case "grok-4.3", "grok-4-3", "grok43", "grok-4.3-latest", "grok-4-latest", "grok-4", "grok-latest":
|
||||
return .grok43
|
||||
case "grok-4.20-multi-agent-0309", "grok-4-20-multi-agent-0309":
|
||||
return .grok420MultiAgent
|
||||
case "grok-4.20-0309-reasoning", "grok-4-20-0309-reasoning":
|
||||
return .grok420Reasoning
|
||||
case "grok-4.20-0309-non-reasoning", "grok-4-20-0309-non-reasoning":
|
||||
@ -298,6 +297,9 @@ public struct ModelSelector {
|
||||
normalized.hasPrefix("grok-4-fast") ||
|
||||
normalized.hasPrefix("grok-code-fast") ||
|
||||
normalized == "grok-4-0709" ||
|
||||
normalized.contains("grok-4.20-multi-agent") ||
|
||||
normalized.contains("grok-4-20-multi-agent") ||
|
||||
normalized.contains("grok420multiagent") ||
|
||||
normalized.contains("grok-beta") ||
|
||||
normalized.contains("grok-vision-beta")
|
||||
}
|
||||
|
||||
@ -11,8 +11,15 @@ public final class GrokProvider: ModelProvider {
|
||||
private let model: LanguageModel.Grok
|
||||
|
||||
public init(model: LanguageModel.Grok, configuration: TachikomaConfiguration) throws {
|
||||
let modelId = model.modelId
|
||||
guard !Self.requiresResponsesAPIRouting(modelId) else {
|
||||
throw TachikomaError.unsupportedOperation(
|
||||
"\(modelId) requires xAI Responses API routing"
|
||||
)
|
||||
}
|
||||
|
||||
self.model = model
|
||||
self.modelId = model.modelId
|
||||
self.modelId = modelId
|
||||
self.baseURL = configuration.getBaseURL(for: .grok) ?? "https://api.x.ai/v1"
|
||||
|
||||
// Get API key from configuration system (environment or credentials)
|
||||
@ -31,6 +38,16 @@ public final class GrokProvider: ModelProvider {
|
||||
)
|
||||
}
|
||||
|
||||
private static func requiresResponsesAPIRouting(_ modelId: String) -> Bool {
|
||||
let normalized = modelId.lowercased()
|
||||
let compact = normalized
|
||||
.replacingOccurrences(of: "-", with: "")
|
||||
.replacingOccurrences(of: ".", with: "")
|
||||
return normalized.contains("grok-4.20-multi-agent") ||
|
||||
normalized.contains("grok-4-20-multi-agent") ||
|
||||
compact.contains("grok420multiagent")
|
||||
}
|
||||
|
||||
public func generateText(request: ProviderRequest) async throws -> ProviderResponse {
|
||||
// Grok uses OpenAI-compatible API format - delegate to shared implementation
|
||||
try await OpenAICompatibleHelper.generateText(
|
||||
|
||||
@ -337,8 +337,6 @@ public enum ProviderParser {
|
||||
switch modelString.lowercased() {
|
||||
case "grok-4.3", "grok-4-3", "grok43", "grok-4.3-latest", "grok-4-latest", "grok-4", "grok-latest":
|
||||
return .grok(.grok43)
|
||||
case "grok-4.20-multi-agent-0309", "grok-4-20-multi-agent-0309":
|
||||
return .grok(.grok420MultiAgent)
|
||||
case "grok-4.20-0309-reasoning", "grok-4-20-0309-reasoning":
|
||||
return .grok(.grok420Reasoning)
|
||||
case "grok-4.20-0309-non-reasoning", "grok-4-20-0309-non-reasoning":
|
||||
@ -358,6 +356,9 @@ public enum ProviderParser {
|
||||
normalized.hasPrefix("grok-4-fast") ||
|
||||
normalized.hasPrefix("grok-code-fast") ||
|
||||
normalized == "grok-4-0709" ||
|
||||
normalized.contains("grok-4.20-multi-agent") ||
|
||||
normalized.contains("grok-4-20-multi-agent") ||
|
||||
normalized.contains("grok420multiagent") ||
|
||||
normalized.contains("grok-beta") ||
|
||||
normalized.contains("grok-vision-beta")
|
||||
}
|
||||
|
||||
@ -5,7 +5,6 @@ import Testing
|
||||
struct GrokModelCatalogTests {
|
||||
private static let catalog: [Model.Grok] = [
|
||||
.grok43,
|
||||
.grok420MultiAgent,
|
||||
.grok420Reasoning,
|
||||
.grok420NonReasoning,
|
||||
]
|
||||
@ -49,16 +48,25 @@ struct GrokModelCatalogTests {
|
||||
func `Grok model vision support matches current xAI catalog`() {
|
||||
self.requireModernPlatforms {
|
||||
#expect(Model.grok(.grok43).supportsVision)
|
||||
#expect(Model.grok(.grok420MultiAgent).supportsVision == false)
|
||||
#expect(Model.grok(.grok420Reasoning).supportsVision)
|
||||
#expect(Model.grok(.grok420NonReasoning).supportsVision)
|
||||
#expect(Model.grok(.grok420MultiAgent).supportsVision == false)
|
||||
#expect(Model.grok(.grok420MultiAgent).supportsTools == false)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func `ModelSelector rejects retired Grok identifiers`() {
|
||||
func `ModelSelector rejects retired and unsupported Grok identifiers`() {
|
||||
self.requireModernPlatforms {
|
||||
for id in ["grok-4-0709", "grok-3", "grok-2-1212", "grok-4-fast", "grok-code-fast-1"] {
|
||||
for id in [
|
||||
"grok-4-0709",
|
||||
"grok-3",
|
||||
"grok-2-1212",
|
||||
"grok-4-fast",
|
||||
"grok-code-fast-1",
|
||||
"grok-4.20-multi-agent-0309",
|
||||
"grok420multiagent",
|
||||
] {
|
||||
#expect(throws: ModelValidationError.self) {
|
||||
_ = try ModelSelector.parseModel(id)
|
||||
}
|
||||
@ -70,8 +78,27 @@ struct GrokModelCatalogTests {
|
||||
func `ModelSelector preserves provider-qualified Grok slugs as OpenRouter IDs`() throws {
|
||||
try self.requireModernPlatforms {
|
||||
let parsed = try ModelSelector.parseModel("xai/grok-code-fast-1")
|
||||
let multiAgent = try ModelSelector.parseModel("x-ai/grok-4.20-multi-agent")
|
||||
|
||||
#expect(parsed == .openRouter(modelId: "xai/grok-code-fast-1"))
|
||||
#expect(multiAgent == .openRouter(modelId: "x-ai/grok-4.20-multi-agent"))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func `Grok provider rejects multi-agent until Responses routing exists`() throws {
|
||||
self.requireModernPlatforms {
|
||||
let config = TachikomaConfiguration(apiKeys: ["grok": "test-key"])
|
||||
|
||||
#expect(throws: TachikomaError.self) {
|
||||
_ = try ProviderFactory.createProvider(for: .grok(.grok420MultiAgent), configuration: config)
|
||||
}
|
||||
#expect(throws: TachikomaError.self) {
|
||||
_ = try GrokProvider(model: .grok420MultiAgent, configuration: config)
|
||||
}
|
||||
#expect(throws: TachikomaError.self) {
|
||||
_ = try GrokProvider(model: .custom("grok420multiagent"), configuration: config)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,7 +39,6 @@ Notes:
|
||||
## xAI Grok (`LanguageModel.Grok`)
|
||||
|
||||
- `grok-4.3`
|
||||
- `grok-4.20-multi-agent-0309`
|
||||
- `grok-4.20-0309-reasoning`, `grok-4.20-0309-non-reasoning`
|
||||
|
||||
## Mistral (`LanguageModel.Mistral`)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user