From 464ad86325157b1b2dc24becc6b10c4988ef42ba Mon Sep 17 00:00:00 2001 From: octo-patch <266937838+octo-patch@users.noreply.github.com> Date: Wed, 29 Jul 2026 12:34:59 +0000 Subject: [PATCH 1/2] feat(pi): add built-in MiniMax provider presets Register a built-in minimax provider for the Pi backend so minimax/MiniMax-M3 and minimax/MiniMax-M2.7 resolve by id, with global and CN endpoint presets selected via MINIMAX_REGION and thinking routed through the existing --thinking-level surface. --- docs/configuration.md | 3 + docs/models.md | 31 ++++++++ .../src/__tests__/pi-sdk-tools.test.ts | 67 ++++++++++++++++ packages/processor/src/agents/pi-sdk.ts | 76 +++++++++++++++++++ 4 files changed, 177 insertions(+) diff --git a/docs/configuration.md b/docs/configuration.md index 15e24e01..acb2fda3 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -117,6 +117,9 @@ backend you're using. |---|---|---| | `OPENAI_API_KEY` | `--agent codex`, `--agent pi --model openai/...` | Codex SDK token or Pi OpenAI-provider token. Unset is fine if `AI_GATEWAY_API_KEY` is set. | | `OPENAI_BASE_URL` | `--agent codex` | Default (when `AI_GATEWAY_API_KEY` is set): `https://ai-gateway.vercel.sh/v1`. | +| `MINIMAX_API_KEY` | `--agent pi --model minimax/...` | Auth for the built-in MiniMax provider preset. | +| `MINIMAX_REGION` | `--agent pi --model minimax/...` | Endpoint preset: `global` (default) or `cn`. See [models.md](models.md). | +| `MINIMAX_BASE_URL` | `--agent pi --model minimax/...` | Override the region preset with a custom base URL. | | `PI_CODING_AGENT_DIR` | `--agent pi` | Optional Pi config/auth directory. Defaults to `~/.pi/agent`; local non-sandbox runs can reuse `auth.json` there. | | `DEEPSEC_AGENT_DEBUG` | both backends | Set to `1` to enable verbose agent logging. | | `DEEPSEC_DATA_ROOT` | core | Override the data directory location. Equivalent to `dataDir` in config. | diff --git a/docs/models.md b/docs/models.md index 23b9a636..b7300d3f 100644 --- a/docs/models.md +++ b/docs/models.md @@ -132,6 +132,37 @@ Repeat `--ai-header name=value` for provider-specific headers. There is no Martian-specific first-class integration; these flags are the generic provider override path. +### MiniMax (`minimax/…`) presets + +MiniMax ships as a built-in Pi provider preset, so you can select its +models by id without spelling out the base URL. The provider registers +`minimax/MiniMax-M3` (1M-token context) and `minimax/MiniMax-M2.7` +(204,800-token context): + +```bash +MINIMAX_API_KEY=... +pnpm deepsec process --project-id my-app --agent pi --model minimax/MiniMax-M3 +``` + +Two regional endpoint presets are built in. The default is the global +endpoint (`https://api.minimax.io/v1`); set `MINIMAX_REGION=cn` to use +the CN endpoint (`https://api.minimaxi.com/v1`) instead: + +```bash +MINIMAX_API_KEY=... +MINIMAX_REGION=cn +pnpm deepsec process --project-id my-app --agent pi --model minimax/MiniMax-M2.7 +``` + +`MINIMAX_BASE_URL` (or `--ai-base-url`) overrides the region preset for a +custom endpoint. Auth comes from `MINIMAX_API_KEY`, or point +`--ai-api-key-env` at a different variable. + +`--thinking-level` flows through to the model: `MiniMax-M3`'s reasoning is +adaptive and can be dialed down for cheaper waves, while `MiniMax-M2.7` +always reasons. In sandbox mode, add the endpoint host to `allowedHosts` +so worker egress can reach it. + ### `claude-sonnet-4-6` for `triage` Triage buckets findings into P0/P1/P2/skip without re-reading the code diff --git a/packages/processor/src/__tests__/pi-sdk-tools.test.ts b/packages/processor/src/__tests__/pi-sdk-tools.test.ts index dd5a7d1a..d3cc8d8e 100644 --- a/packages/processor/src/__tests__/pi-sdk-tools.test.ts +++ b/packages/processor/src/__tests__/pi-sdk-tools.test.ts @@ -5,6 +5,7 @@ import { ModelRegistry, ModelRuntime } from "@earendil-works/pi-coding-agent"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { createPiReadOnlyToolDefinitions, + registerMiniMaxProvider, resolvePiModelWithDynamicGateway, } from "../agents/pi-sdk.js"; @@ -70,6 +71,9 @@ describe("Pi model resolution", () => { "ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_API_KEY", "ANTHROPIC_BASE_URL", + "MINIMAX_API_KEY", + "MINIMAX_BASE_URL", + "MINIMAX_REGION", ] as const; const originalEnv = Object.fromEntries(KEYS.map((key) => [key, process.env[key]])); const originalFetch = globalThis.fetch; @@ -168,4 +172,67 @@ describe("Pi model resolution", () => { ).rejects.toThrow(/Pi model not found: acme\/nonexistent-model-1/); expect(called).toBe(false); }); + + it("registers the built-in MiniMax presets and resolves them offline", async () => { + for (const key of KEYS) delete process.env[key]; + let called = false; + globalThis.fetch = async () => { + called = true; + return new Response(JSON.stringify({ data: [] }), { status: 200 }); + }; + + const registry = await freshRegistry(); + registerMiniMaxProvider(registry, {}); + + const m3 = await resolvePiModelWithDynamicGateway(registry, "minimax/MiniMax-M3", {}); + expect(m3.provider).toBe("minimax"); + expect(m3.id).toBe("MiniMax-M3"); + expect(m3.api).toBe("openai-completions"); + expect(m3.reasoning).toBe(true); + expect(m3.input).toEqual(["text", "image"]); + expect(m3.contextWindow).toBe(1_000_000); + expect(m3.cost.input).toBe(0.6); + + const m27 = await resolvePiModelWithDynamicGateway(registry, "minimax/MiniMax-M2.7", {}); + expect(m27.provider).toBe("minimax"); + expect(m27.id).toBe("MiniMax-M2.7"); + expect(m27.contextWindow).toBe(204_800); + expect(m27.cost.cacheWrite).toBe(0.375); + + expect(called).toBe(false); + }); + + it("selects the CN MiniMax endpoint preset via MINIMAX_REGION", async () => { + for (const key of KEYS) delete process.env[key]; + process.env.MINIMAX_REGION = "cn"; + + const registry = await freshRegistry(); + registerMiniMaxProvider(registry, {}); + + expect(registry.getRegisteredProviderConfig("minimax")?.baseUrl).toBe( + "https://api.minimaxi.com/v1", + ); + }); + + it("defaults MiniMax to the global endpoint preset", async () => { + for (const key of KEYS) delete process.env[key]; + + const registry = await freshRegistry(); + registerMiniMaxProvider(registry, {}); + + expect(registry.getRegisteredProviderConfig("minimax")?.baseUrl).toBe( + "https://api.minimax.io/v1", + ); + }); + + it("lets --ai-base-url override the MiniMax region preset", async () => { + for (const key of KEYS) delete process.env[key]; + + const registry = await freshRegistry(); + registerMiniMaxProvider(registry, { aiBaseUrl: "https://example.test/v1" }); + + expect(registry.getRegisteredProviderConfig("minimax")?.baseUrl).toBe( + "https://example.test/v1", + ); + }); }); diff --git a/packages/processor/src/agents/pi-sdk.ts b/packages/processor/src/agents/pi-sdk.ts index ec7c911c..b58427a9 100644 --- a/packages/processor/src/agents/pi-sdk.ts +++ b/packages/processor/src/agents/pi-sdk.ts @@ -85,6 +85,78 @@ type PiModel = ReturnType[number]; type PiProviderConfig = Parameters[1]; type PiProviderModelConfig = NonNullable[number]; +// MiniMax exposes an OpenAI-compatible endpoint on two regional hosts that +// share the same model catalog. We register the provider so `--model +// minimax/` resolves offline (deepsec pins PI_OFFLINE, so pi never +// fetches a remote catalog for it). The region is selected with +// MINIMAX_REGION, or overridden outright with MINIMAX_BASE_URL / +// --ai-base-url; the model ids stay stable across regions. +const MINIMAX_PROVIDER = "minimax"; +const MINIMAX_API = "openai-completions" as const; +const MINIMAX_REGION_BASE_URLS = { + global: "https://api.minimax.io/v1", + cn: "https://api.minimaxi.com/v1", +} as const; +type MiniMaxRegion = keyof typeof MINIMAX_REGION_BASE_URLS; +const MINIMAX_DEFAULT_REGION: MiniMaxRegion = "global"; +// Output cap applied on the deepsec side; matches the generic pass-through +// default. deepsec's investigation JSON fits well under this. +const MINIMAX_MAX_TOKENS = 32_000; + +// Built-in model presets. cost is USD per million tokens (pi divides usage +// by 1e6). `reasoning: true` routes deepsec's --thinking-level through to +// each model: MiniMax-M3's thinking is adaptive and can be turned down for +// cheaper waves, while MiniMax-M2.7 always reasons. pi's `input` union +// covers only text/image, so the video modality is not represented here. +const MINIMAX_MODELS: PiProviderModelConfig[] = [ + { + id: "MiniMax-M3", + name: "MiniMax-M3", + api: MINIMAX_API, + reasoning: true, + input: ["text", "image"], + cost: { input: 0.6, output: 2.4, cacheRead: 0.12, cacheWrite: 0 }, + contextWindow: 1_000_000, + maxTokens: MINIMAX_MAX_TOKENS, + }, + { + id: "MiniMax-M2.7", + name: "MiniMax-M2.7", + api: MINIMAX_API, + reasoning: true, + input: ["text"], + cost: { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0.375 }, + contextWindow: 204_800, + maxTokens: MINIMAX_MAX_TOKENS, + }, +]; + +function resolveMiniMaxBaseUrl(cfg: PiAgentConfig): string { + if (cfg.aiBaseUrl) return cfg.aiBaseUrl; + if (process.env.MINIMAX_BASE_URL) return process.env.MINIMAX_BASE_URL; + const region = process.env.MINIMAX_REGION as MiniMaxRegion | undefined; + if (region && region in MINIMAX_REGION_BASE_URLS) return MINIMAX_REGION_BASE_URLS[region]; + return MINIMAX_REGION_BASE_URLS[MINIMAX_DEFAULT_REGION]; +} + +/** + * Register the built-in MiniMax provider preset (regional base URL + model + * catalog). Auth is layered separately in configureRuntimeAuth, or via the + * generic --ai-api-key-env override; a missing key just leaves the provider + * unauthenticated. Registering an unused provider is cheap. + */ +export function registerMiniMaxProvider(registry: ModelRegistry, cfg: PiAgentConfig): void { + const override: PiProviderConfig = { + baseUrl: resolveMiniMaxBaseUrl(cfg), + api: MINIMAX_API, + models: MINIMAX_MODELS, + }; + const key = cfg.aiApiKeyEnv ? process.env[cfg.aiApiKeyEnv] : process.env.MINIMAX_API_KEY; + if (key) override.apiKey = key; + if (cfg.aiHeaders && Object.keys(cfg.aiHeaders).length > 0) override.headers = cfg.aiHeaders; + registry.registerProvider(MINIMAX_PROVIDER, override); +} + interface PiSessionSetup { session: AgentSession; modelLabel: string; @@ -335,6 +407,9 @@ async function configureRuntimeAuth(runtime: ModelRuntime, cfg: PiAgentConfig): const openaiKey = process.env.OPENAI_API_KEY; if (openaiKey) await runtime.setRuntimeApiKey("openai", openaiKey, offline); + const minimaxKey = process.env.MINIMAX_API_KEY; + if (minimaxKey) await runtime.setRuntimeApiKey(MINIMAX_PROVIDER, minimaxKey, offline); + const customProvider = cfg.aiProvider ?? modelProviderFromName(cfg.model); if (customProvider && cfg.aiApiKeyEnv) { const key = process.env[cfg.aiApiKeyEnv]; @@ -512,6 +587,7 @@ async function createPiSession(projectRoot: string, cfg: PiAgentConfig): Promise // their tests) on the same ModelRegistry API as before. const modelRegistry = new ModelRegistry(runtime); configureProviderOverrides(modelRegistry, cfg); + registerMiniMaxProvider(modelRegistry, cfg); const modelName = cfg.model ?? DEFAULT_MODEL; const model = await resolvePiModelWithDynamicGateway(modelRegistry, modelName, cfg); From 363e94591949ec7e785241a079bde12036eaa8f6 Mon Sep 17 00:00:00 2001 From: jianglisu Date: Wed, 21 Oct 2026 14:17:44 +0800 Subject: [PATCH 2/2] fix(pi): isolate MiniMax route overrides --- .../src/__tests__/pi-sdk-tools.test.ts | 20 +++++++++++++++++++ packages/processor/src/agents/pi-sdk.ts | 14 ++++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/packages/processor/src/__tests__/pi-sdk-tools.test.ts b/packages/processor/src/__tests__/pi-sdk-tools.test.ts index ab144ba0..51175624 100644 --- a/packages/processor/src/__tests__/pi-sdk-tools.test.ts +++ b/packages/processor/src/__tests__/pi-sdk-tools.test.ts @@ -308,6 +308,26 @@ describe("Pi model resolution", () => { }); }); + it("does not leak another provider's custom route or credential into MiniMax", async () => { + for (const key of KEYS) delete process.env[key]; + process.env.ACME_MODEL_KEY = "acme-key"; + process.env.MINIMAX_API_KEY = "minimax-key"; + + const registry = await freshRegistry(); + registerMiniMaxProvider(registry, { + aiProvider: "acme", + aiBaseUrl: "https://models.acme.test/v1", + aiApiKeyEnv: "ACME_MODEL_KEY", + aiHeaders: { "x-acme": "yes" }, + }); + + expect(registry.getRegisteredProviderConfig("minimax")).toMatchObject({ + baseUrl: "https://api.minimax.io/v1", + apiKey: "minimax-key", + }); + expect(registry.getRegisteredProviderConfig("minimax")?.headers).toBeUndefined(); + }); + it("keeps request discovery offline for MiniMax presets", async () => { for (const key of KEYS) delete process.env[key]; process.env.MINIMAX_API_KEY = "fixture-key"; diff --git a/packages/processor/src/agents/pi-sdk.ts b/packages/processor/src/agents/pi-sdk.ts index 8a8e626c..fe0d6fc9 100644 --- a/packages/processor/src/agents/pi-sdk.ts +++ b/packages/processor/src/agents/pi-sdk.ts @@ -131,8 +131,12 @@ const MINIMAX_MODELS: PiProviderModelConfig[] = [ }, ]; +function isMiniMaxRoute(cfg: PiAgentConfig): boolean { + return (cfg.aiProvider ?? modelProviderFromName(cfg.model)) === MINIMAX_PROVIDER; +} + function resolveMiniMaxBaseUrl(cfg: PiAgentConfig): string { - if (cfg.aiBaseUrl) return cfg.aiBaseUrl; + if (cfg.aiBaseUrl && isMiniMaxRoute(cfg)) return cfg.aiBaseUrl; if (process.env.MINIMAX_BASE_URL) return process.env.MINIMAX_BASE_URL; const region = process.env.MINIMAX_REGION as MiniMaxRegion | undefined; if (region && region in MINIMAX_REGION_BASE_URLS) return MINIMAX_REGION_BASE_URLS[region]; @@ -151,9 +155,13 @@ export function registerMiniMaxProvider(registry: ModelRegistry, cfg: PiAgentCon api: MINIMAX_API, models: MINIMAX_MODELS, }; - const key = cfg.aiApiKeyEnv ? process.env[cfg.aiApiKeyEnv] : process.env.MINIMAX_API_KEY; + const key = isMiniMaxRoute(cfg) && cfg.aiApiKeyEnv + ? process.env[cfg.aiApiKeyEnv] + : process.env.MINIMAX_API_KEY; if (key) override.apiKey = key; - if (cfg.aiHeaders && Object.keys(cfg.aiHeaders).length > 0) override.headers = cfg.aiHeaders; + if (isMiniMaxRoute(cfg) && cfg.aiHeaders && Object.keys(cfg.aiHeaders).length > 0) { + override.headers = cfg.aiHeaders; + } registry.registerProvider(MINIMAX_PROVIDER, override); }