fix(providers): switch minimax from claude to openai format so images work

The Anthropic-compatible /anthropic/v1/messages endpoint rejects image
input with 403. MiniMax's OpenAI-compatible /v1/chat/completions endpoint
supports image_url natively for MiniMax-M3.

- minimax + minimax-cn: format claude→openai, baseUrl→/v1/chat/completions
- Remove Anthropic-Version header + ?beta=true suffix (not needed for openai)
- Remove minimax/minimax-cn from ?beta=true executor case
- Update cache-control tests (openai format uses different caching path)
- Fix reasoning-split test names (no longer claude format)

TDD: 2 registry tests assert format=openai (red→green).
Refs: Hermes Agent #15715, MiniMax OpenAI-compatible API docs.
This commit is contained in:
diegosouzapw
2026-08-04 18:25:58 -03:00
parent 8027c60726
commit 25cf9d9065
6 changed files with 34 additions and 18 deletions

View File

@@ -1,17 +1,14 @@
import type { RegistryEntry } from "../../../shared.ts";
import { getAnthropicCompatHeaders, ANTHROPIC_VERSION_HEADER } from "../../../shared.ts";
export const minimax_cnProvider: RegistryEntry = {
id: "minimax-cn",
alias: "minimax-cn", // unique alias (was colliding with minimax)
format: "claude",
format: "openai",
executor: "default",
baseUrl: "https://api.minimaxi.com/anthropic/v1/messages",
baseUrl: "https://api.minimaxi.com/v1/chat/completions",
modelsUrl: "https://api.minimaxi.com/v1/models",
urlSuffix: "?beta=true",
authType: "apikey",
authHeader: "bearer",
headers: getAnthropicCompatHeaders(),
models: [
// Keep parity with minimax to ensure model discovery works for minimax-cn connections.
// #3110: MiniMax M3 — frontier coding model with 1M context

View File

@@ -1,17 +1,14 @@
import type { RegistryEntry } from "../../shared.ts";
import { getAnthropicCompatHeaders, ANTHROPIC_VERSION_HEADER } from "../../shared.ts";
export const minimaxProvider: RegistryEntry = {
id: "minimax",
alias: "minimax",
format: "claude",
format: "openai",
executor: "default",
baseUrl: "https://api.minimax.io/anthropic/v1/messages",
baseUrl: "https://api.minimax.io/v1/chat/completions",
modelsUrl: "https://api.minimax.io/v1/models",
urlSuffix: "?beta=true",
authType: "apikey",
authHeader: "bearer",
headers: getAnthropicCompatHeaders(),
models: [
// T12/T28: MiniMax default upgraded from M2.5 to M2.7
// #3110: MiniMax M3 — frontier coding model with 1M context

View File

@@ -289,8 +289,6 @@ export class DefaultExecutor extends BaseExecutor {
case "glm":
case "glmt":
case "kimi-coding":
case "minimax":
case "minimax-cn":
return `${this.config.baseUrl}?beta=true`;
case "agentrouter":
return this.usesClaudeCodeProtocol(credentials)

View File

@@ -17,8 +17,10 @@ describe("Cache Control Policy - Claude Protocol Providers", () => {
// These should be detected via targetFormat
assert.equal(providerSupportsCaching("bailian-coding-plan", "claude"), true);
assert.equal(providerSupportsCaching("glm", "claude"), true);
assert.equal(providerSupportsCaching("minimax", "claude"), true);
assert.equal(providerSupportsCaching("minimax-cn", "claude"), true);
// minimax/minimax-cn use openai format (#3110 / image 403 fix);
// caching support for their OpenAI-compatible endpoint is TBD
assert.equal(providerSupportsCaching("minimax", "openai"), false);
assert.equal(providerSupportsCaching("minimax-cn", "openai"), false);
assert.equal(providerSupportsCaching("kimi-coding", "claude"), true);
// #3955 — OpenAI / Codex use automatic prefix caching (no cache_control needed).
@@ -66,15 +68,17 @@ describe("Cache Control Policy - Claude Protocol Providers", () => {
true
);
// minimax now uses openai format — caching behavior may differ;
// cache_control preservation depends on whether it joins CACHING_PROVIDERS
assert.equal(
shouldPreserveCacheControl({
userAgent: claudeCodeUA,
isCombo: false,
targetProvider: "minimax",
targetFormat: "claude",
targetFormat: "openai",
settings: { alwaysPreserveClientCache: "auto" },
}),
true
false
);
});

View File

@@ -73,6 +73,26 @@ describe("MiniMax M3 model registration (#3110)", () => {
assert.equal(m3.contextLength, 1_048_576);
});
it("minimax uses openai format (not claude) so images work via /v1/chat/completions", () => {
const entry = REGISTRY.minimax;
assert.ok(entry, "minimax registry entry must exist");
assert.equal(
entry.format,
"openai",
"minimax must use openai format — the Anthropic-compatible /anthropic/v1/messages endpoint rejects images with 403; images work on the OpenAI-compatible /v1/chat/completions endpoint. See Hermes Agent #15715."
);
});
it("minimax-cn uses openai format (not claude) so images work via /v1/chat/completions", () => {
const entry = REGISTRY["minimax-cn"];
assert.ok(entry, "minimax-cn registry entry must exist");
assert.equal(
entry.format,
"openai",
"minimax-cn must use openai format — parity with minimax; the Anthropic endpoint on api.minimaxi.com also rejects images."
);
});
it("nvidia provider does NOT list minimaxai/minimax-m3 (removed in #3329 — 404 upstream)", () => {
const entry = REGISTRY.nvidia;
assert.ok(entry, "nvidia registry entry must exist");

View File

@@ -137,12 +137,12 @@ describe("responseSanitizer/reasoning — MiniMax M3 textual reasoning-tag route
});
describe("responseSanitizer/reasoning — MiniMax M3 fix regression guards", () => {
it("direct minimax tier (claude format) stays unaffected", () => {
it("direct minimax tier (openai format) stays unaffected for textual reasoning tags", () => {
assert.equal(isTextualReasoningTagNativeRoute("minimax", "minimax-m3"), false);
assert.equal(shouldParseTextualReasoningTags("minimax", "MiniMax-M3"), false);
});
it("direct minimax-cn tier (claude format) stays unaffected", () => {
it("direct minimax-cn tier (openai format) stays unaffected for textual reasoning tags", () => {
assert.equal(isTextualReasoningTagNativeRoute("minimax-cn", "minimax-m3"), false);
assert.equal(shouldParseTextualReasoningTags("minimax-cn", "MiniMax-M3"), false);
});