From 1f5f1565455cb9ac6e822e850477ed6ac7bf403c Mon Sep 17 00:00:00 2001 From: octo-patch <266937838+octo-patch@users.noreply.github.com> Date: Thu, 9 Jul 2026 05:36:31 +0000 Subject: [PATCH 1/2] providers: add MiniMax preset Add MiniMax as a configurable provider so Murph runtime deployments can route MiniMax-M3 through the provider registry. Co-Authored-By: Claude Opus 4.7 --- config/phantom.yaml | 2 +- src/config/__tests__/loader-metadata.test.ts | 33 ++++++++++++++++++++ src/config/__tests__/loader.test.ts | 22 +++++++++++++ src/config/__tests__/providers.test.ts | 25 +++++++++++++-- src/config/providers.ts | 14 +++++++++ 5 files changed, 92 insertions(+), 4 deletions(-) diff --git a/config/phantom.yaml b/config/phantom.yaml index 6def3965..b6e5a51d 100644 --- a/config/phantom.yaml +++ b/config/phantom.yaml @@ -24,7 +24,7 @@ timeout_minutes: 240 # the chosen provider; authentication happens at the env var named below. # # provider: -# type: anthropic # anthropic | zai | openrouter | vllm | ollama | litellm | custom +# type: anthropic # anthropic | zai | minimax | openrouter | vllm | ollama | litellm | custom # # api_key_env: ANTHROPIC_API_KEY # # Example: GLM-5.1 via Z.AI's Anthropic-compatible API (15x cheaper than Opus) diff --git a/src/config/__tests__/loader-metadata.test.ts b/src/config/__tests__/loader-metadata.test.ts index 63e9a54a..649efa8b 100644 --- a/src/config/__tests__/loader-metadata.test.ts +++ b/src/config/__tests__/loader-metadata.test.ts @@ -24,6 +24,7 @@ describe("loadConfig secret_source", () => { "ANTHROPIC_AUTH_TOKEN", "OPENAI_API_KEY", "ZAI_API_KEY", + "MINIMAX_API_KEY", "OPENROUTER_API_KEY", "LITELLM_KEY", ] as const; @@ -32,6 +33,7 @@ describe("loadConfig secret_source", () => { ANTHROPIC_AUTH_TOKEN: undefined, OPENAI_API_KEY: undefined, ZAI_API_KEY: undefined, + MINIMAX_API_KEY: undefined, OPENROUTER_API_KEY: undefined, LITELLM_KEY: undefined, }; @@ -149,6 +151,37 @@ provider: expect(process.env.OPENAI_API_KEY).toBeUndefined(); }); + test("secret_source: metadata with Murph MiniMax populates only MINIMAX_API_KEY", async () => { + const stubBody = "minimax-provider-token"; + globalThis.fetch = mock((url: string | Request) => { + expect(String(url)).toBe("http://gateway.test/v1/secrets/provider_token"); + return Promise.resolve( + new Response(stubBody, { + status: 200, + headers: { "X-Phantom-Rotation-Id": "1" }, + }), + ); + }) as unknown as typeof fetch; + + const path = writeYaml( + "metadata-murph-minimax.yaml", + ` +name: metadata-minimax +agent_runtime: murph +secret_source: metadata +secret_source_url: http://gateway.test +provider: + type: minimax +`, + ); + await loadConfig(path); + expect(process.env.MINIMAX_API_KEY).toBe(stubBody); + expect(process.env.ANTHROPIC_API_KEY).toBeUndefined(); + expect(process.env.ANTHROPIC_AUTH_TOKEN).toBeUndefined(); + expect(process.env.OPENAI_API_KEY).toBeUndefined(); + expect(process.env.ZAI_API_KEY).toBeUndefined(); + }); + test("secret_source: metadata resolves whole-string ${secret:NAME} references in nested config", async () => { // Use peers.test_peer.token, an existing schema field that accepts an // arbitrary string and is nested. We assert the resolved value via a diff --git a/src/config/__tests__/loader.test.ts b/src/config/__tests__/loader.test.ts index 524e53a3..898f2515 100644 --- a/src/config/__tests__/loader.test.ts +++ b/src/config/__tests__/loader.test.ts @@ -460,6 +460,28 @@ provider: } }); + test("loads a MiniMax provider block", async () => { + const path = writeYaml( + "minimax-provider.yaml", + ` +name: test +provider: + type: minimax + api_key_env: MINIMAX_API_KEY + model_mappings: + sonnet: MiniMax-M3 +`, + ); + try { + const config = await loadConfig(path); + expect(config.provider.type).toBe("minimax"); + expect(config.provider.api_key_env).toBe("MINIMAX_API_KEY"); + expect(config.provider.model_mappings?.sonnet).toBe("MiniMax-M3"); + } finally { + cleanup(); + } + }); + test("loads provider.type: openai when Murph runtime is selected", async () => { const path = writeYaml( "openai-provider-murph.yaml", diff --git a/src/config/__tests__/providers.test.ts b/src/config/__tests__/providers.test.ts index d720c4b4..73569d5f 100644 --- a/src/config/__tests__/providers.test.ts +++ b/src/config/__tests__/providers.test.ts @@ -29,6 +29,7 @@ const WATCHED = [ "ANTHROPIC_BASE_URL", "OPENAI_API_KEY", "ZAI_API_KEY", + "MINIMAX_API_KEY", "OPENROUTER_API_KEY", "LITELLM_KEY", "MURPH_PROVIDER", @@ -69,7 +70,7 @@ describe("ProviderSchema", () => { }); test("accepts each valid provider type", () => { - for (const type of ["anthropic", "openai", "zai", "openrouter", "vllm", "ollama", "litellm", "custom"] as const) { + for (const type of ["anthropic", "openai", "zai", "minimax", "openrouter", "vllm", "ollama", "litellm", "custom"] as const) { const parsed = ProviderSchema.parse({ type }); expect(parsed.type).toBe(type); } @@ -316,6 +317,24 @@ describe("buildAgentRuntimeEnv: Murph runtime", () => { expect(env.ANTHROPIC_API_KEY).toBe(""); }); + test("emits MiniMax route env", () => { + process.env.MINIMAX_API_KEY = "minimax-secret"; + const config = makeConfig( + { type: "minimax", model_mappings: { sonnet: "MiniMax-M3" } }, + { agent_runtime: "murph", model: "sonnet" }, + ); + const model = resolveAgentRuntimeModel(config, config.model); + const env = buildAgentRuntimeEnv(config, model); + + expect(model).toBe("MiniMax-M3"); + expect(env.MURPH_PROVIDER).toBe("openai-compat"); + expect(env.MURPH_PROVIDER_CONFIG).toBe("minimax"); + expect(env.MURPH_MODEL).toBe("MiniMax-M3"); + expect(env.OPENAI_BASE_URL).toBe("https://api.minimax.io/v1"); + expect(env.MINIMAX_API_KEY).toBe("minimax-secret"); + expect(env.OPENAI_API_KEY).toBe(""); + }); + test("emits OpenRouter route env", () => { process.env.OPENROUTER_API_KEY = "openrouter-secret"; const config = makeConfig( @@ -378,7 +397,7 @@ describe("buildAgentRuntimeEnv: Murph runtime", () => { describe("PROVIDER_PRESETS", () => { test("contains every provider type declared in the schema", () => { - for (const type of ["anthropic", "openai", "zai", "openrouter", "vllm", "ollama", "litellm", "custom"] as const) { + for (const type of ["anthropic", "openai", "zai", "minimax", "openrouter", "vllm", "ollama", "litellm", "custom"] as const) { expect(PROVIDER_PRESETS[type]).toBeDefined(); } }); @@ -389,7 +408,7 @@ describe("PROVIDER_PRESETS", () => { }); test("every non-anthropic Anthropic-compatible preset disables betas by default", () => { - for (const type of ["zai", "openrouter", "vllm", "ollama", "litellm", "custom"] as const) { + for (const type of ["zai", "minimax", "openrouter", "vllm", "ollama", "litellm", "custom"] as const) { expect(PROVIDER_PRESETS[type].disable_betas).toBe(true); } }); diff --git a/src/config/providers.ts b/src/config/providers.ts index 66d72c71..384494c7 100644 --- a/src/config/providers.ts +++ b/src/config/providers.ts @@ -17,6 +17,7 @@ export const PROVIDER_TYPES = [ "anthropic", "openai", "zai", + "minimax", "openrouter", "vllm", "ollama", @@ -86,6 +87,11 @@ export const PROVIDER_PRESETS: Readonly> = api_key_env: "ZAI_API_KEY", disable_betas: true, }, + minimax: { + base_url: "https://api.minimax.io/anthropic/v1", + api_key_env: "MINIMAX_API_KEY", + disable_betas: true, + }, openrouter: { base_url: "https://openrouter.ai/api/v1", api_key_env: "OPENROUTER_API_KEY", @@ -136,6 +142,7 @@ const MURPH_ROUTE_ENV_KEYS = [ const MURPH_CREDENTIAL_ENV_KEYS = [ "OPENAI_API_KEY", "ZAI_API_KEY", + "MINIMAX_API_KEY", "ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN", "DASHSCOPE_API_KEY", @@ -247,6 +254,13 @@ export function buildMurphProviderEnv( addSelectedCredential(env, sourceEnvKey, "ZAI_API_KEY"); break; } + case "minimax": { + env.MURPH_PROVIDER = "openai-compat"; + env.MURPH_PROVIDER_CONFIG = "minimax"; + env.OPENAI_BASE_URL = provider.base_url ?? "https://api.minimax.io/v1"; + addSelectedCredential(env, sourceEnvKey, "MINIMAX_API_KEY"); + break; + } case "openrouter": { env.MURPH_PROVIDER = "openai-compat"; env.MURPH_PROVIDER_CONFIG = "openrouter"; From 9e3cf5a094f86021ae351977c1ac724c032ff537 Mon Sep 17 00:00:00 2001 From: octo-patch <266937838+octo-patch@users.noreply.github.com> Date: Sun, 12 Jul 2026 15:44:28 +0800 Subject: [PATCH 2/2] providers: fix MiniMax endpoint and docs --- config/phantom.yaml | 9 ++++ docs/providers.md | 58 ++++++++++++++++++++++++++ src/config/__tests__/providers.test.ts | 42 ++++++++++++++++++- src/config/providers.ts | 2 +- 4 files changed, 108 insertions(+), 3 deletions(-) diff --git a/config/phantom.yaml b/config/phantom.yaml index b6e5a51d..b66412b0 100644 --- a/config/phantom.yaml +++ b/config/phantom.yaml @@ -36,6 +36,15 @@ timeout_minutes: 240 # sonnet: glm-5.1 # haiku: glm-4.5-air # +# Example: MiniMax with the two current text models +# provider: +# type: minimax +# api_key_env: MINIMAX_API_KEY +# model_mappings: +# opus: MiniMax-M3 +# sonnet: MiniMax-M3 +# haiku: MiniMax-M2.7 +# # Example: Murph runtime with OpenAI. Requires @murph/anthropic-sdk-shim to be # installed or linked into this Phantom project. # agent_runtime: murph diff --git a/docs/providers.md b/docs/providers.md index bed71244..44b797d0 100644 --- a/docs/providers.md +++ b/docs/providers.md @@ -43,6 +43,7 @@ phantom start --agent-runtime murph | `anthropic` (default) | `https://api.anthropic.com` | `ANTHROPIC_API_KEY` | Claude Opus, Sonnet, Haiku | | `openai` | `https://api.openai.com` | `OPENAI_API_KEY` | Murph runtime only | | `zai` | `https://api.z.ai/api/anthropic` | `ZAI_API_KEY` | GLM-5.1 and GLM-4.5-Air, roughly 15x cheaper than Opus | +| `minimax` | `https://api.minimax.io/anthropic` | `MINIMAX_API_KEY` | MiniMax-M3 and MiniMax-M2.7 | | `openrouter` | `https://openrouter.ai/api/v1` | `OPENROUTER_API_KEY` | 100+ models through a single key | | `vllm` | `http://localhost:8000` | none | Self-hosted OpenAI-compatible inference | | `ollama` | `http://localhost:11434` | none | Local GGUF models, zero API cost | @@ -130,6 +131,63 @@ ZAI_API_KEY= Both the main agent and every evolution judge route through Z.AI. The `claude-sonnet-4-6` model name is translated to `glm-5.1` on the wire by the `model_mappings` block. +### MiniMax + +The `minimax` preset supports both current text models. The default Anthropic runtime uses the Anthropic-compatible endpoint, while Murph uses the OpenAI-compatible endpoint. + +| Model | Context window | Input modalities | Thinking | +|------|----------------|------------------|----------| +| `MiniMax-M3` | 1,000,000 tokens | text, image, video | adaptive or disabled | +| `MiniMax-M2.7` | 204,800 tokens | text | always on | + +```yaml +# phantom.yaml, default Anthropic runtime +model: claude-sonnet-4-6 +provider: + type: minimax + api_key_env: MINIMAX_API_KEY + model_mappings: + opus: MiniMax-M3 + sonnet: MiniMax-M3 + haiku: MiniMax-M2.7 +``` + +```bash +# .env +MINIMAX_API_KEY= +``` + +Choose the endpoint for the runtime protocol and region: + +| Runtime protocol | Global default | China override | +|------------------|----------------|----------------| +| Anthropic | `https://api.minimax.io/anthropic` | `https://api.minimaxi.com/anthropic` | +| Murph OpenAI-compatible | `https://api.minimax.io/v1` | `https://api.minimaxi.com/v1` | + +The Anthropic client appends `/v1/messages`, so its base URL must end at `/anthropic`; the resulting global request URL is `https://api.minimax.io/anthropic/v1/messages`. To use the China endpoint, set the matching value from the table as `provider.base_url`. + +For the OpenAI-compatible route, select Murph and use the concrete model ID: + +```yaml +# phantom.yaml, Murph runtime +agent_runtime: murph +model: MiniMax-M3 +provider: + type: minimax +``` + +Pay-as-you-go prices are in USD per million tokens. MiniMax-M3 pricing depends on both the service tier and input length, so each tier is listed separately. + +| Model / service tier | Input length | Input | Output | Cache read | Cache write | +|----------------------|--------------|-------|--------|------------|-------------| +| `MiniMax-M3` standard | up to 512k | $0.30 | $1.20 | $0.06 | n/a | +| `MiniMax-M3` standard | over 512k | $0.60 | $2.40 | $0.12 | n/a | +| `MiniMax-M3` priority | up to 512k | $0.45 | $1.80 | $0.09 | n/a | +| `MiniMax-M3` priority | over 512k | $0.90 | $3.60 | $0.18 | n/a | +| `MiniMax-M2.7` | all requests | $0.30 | $1.20 | $0.06 | $0.375 | + +See the [MiniMax Anthropic API documentation](https://platform.minimax.io/docs/api-reference/text-anthropic-api) and [current pricing](https://platform.minimax.io/docs/guides/pricing-paygo) for API details. + ### Ollama (local, free) Run any GGUF model on your own GPU. No API key needed. diff --git a/src/config/__tests__/providers.test.ts b/src/config/__tests__/providers.test.ts index 73569d5f..eafe0bee 100644 --- a/src/config/__tests__/providers.test.ts +++ b/src/config/__tests__/providers.test.ts @@ -70,7 +70,17 @@ describe("ProviderSchema", () => { }); test("accepts each valid provider type", () => { - for (const type of ["anthropic", "openai", "zai", "minimax", "openrouter", "vllm", "ollama", "litellm", "custom"] as const) { + for (const type of [ + "anthropic", + "openai", + "zai", + "minimax", + "openrouter", + "vllm", + "ollama", + "litellm", + "custom", + ] as const) { const parsed = ProviderSchema.parse({ type }); expect(parsed.type).toBe(type); } @@ -174,6 +184,24 @@ describe("buildProviderEnv: zai preset", () => { }); }); +describe("buildProviderEnv: MiniMax preset", () => { + test("sets the SDK base URL, credentials, model mappings, and disables betas", () => { + process.env.MINIMAX_API_KEY = "minimax-secret"; + const config = makeConfig({ + type: "minimax", + model_mappings: { sonnet: "MiniMax-M3", haiku: "MiniMax-M2.7" }, + }); + const env = buildProviderEnv(config); + + expect(env.ANTHROPIC_BASE_URL).toBe("https://api.minimax.io/anthropic"); + expect(env.ANTHROPIC_AUTH_TOKEN).toBe("minimax-secret"); + expect(env.ANTHROPIC_API_KEY).toBe("minimax-secret"); + expect(env.ANTHROPIC_DEFAULT_SONNET_MODEL).toBe("MiniMax-M3"); + expect(env.ANTHROPIC_DEFAULT_HAIKU_MODEL).toBe("MiniMax-M2.7"); + expect(env.CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS).toBe("1"); + }); +}); + describe("buildProviderEnv: ollama preset", () => { test("sets local base_url, no auth token, disables betas", () => { const config = makeConfig({ type: "ollama" }); @@ -397,7 +425,17 @@ describe("buildAgentRuntimeEnv: Murph runtime", () => { describe("PROVIDER_PRESETS", () => { test("contains every provider type declared in the schema", () => { - for (const type of ["anthropic", "openai", "zai", "minimax", "openrouter", "vllm", "ollama", "litellm", "custom"] as const) { + for (const type of [ + "anthropic", + "openai", + "zai", + "minimax", + "openrouter", + "vllm", + "ollama", + "litellm", + "custom", + ] as const) { expect(PROVIDER_PRESETS[type]).toBeDefined(); } }); diff --git a/src/config/providers.ts b/src/config/providers.ts index 384494c7..9ead2fb5 100644 --- a/src/config/providers.ts +++ b/src/config/providers.ts @@ -88,7 +88,7 @@ export const PROVIDER_PRESETS: Readonly> = disable_betas: true, }, minimax: { - base_url: "https://api.minimax.io/anthropic/v1", + base_url: "https://api.minimax.io/anthropic", api_key_env: "MINIMAX_API_KEY", disable_betas: true, },