Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,11 @@ derives the npm package version from that tag. See [the release guide](docs/rele

### `mmx text`

`text chat` and `text repl` default to `MiniMax-M3.1-Flash-Preview`. An explicit `--model` or saved `default-text-model` takes precedence. This preview currently requires M Plan or MiniMax Code; with a pay-as-you-go key, select `MiniMax-M3` explicitly. See the [model guide](https://platform.minimax.io/docs/guides/text-generation#agent).

```bash
mmx text chat --message "Write a poem"
mmx text chat --model MiniMax-M3 --message "Hello" --stream
mmx text chat --model MiniMax-M3.1-Flash-Preview --message "Hello" --stream
mmx text chat --system "You are a coding assistant" --message "Fizzbuzz in Go"
mmx text chat --message "user:Hi" --message "assistant:Hey!" --message "How are you?"
cat messages.json | mmx text chat --messages-file - --output json
Expand Down Expand Up @@ -196,22 +198,28 @@ Credential priority: `--api-key` flag > OAuth (config) > `api_key` (config).
mmx quota
mmx config show
mmx config set --key region --value cn
mmx config set --key default-text-model --value MiniMax-M3
mmx config set --key default-text-model --value MiniMax-M3.1-Flash-Preview
mmx config export-schema | jq .
```

### `mmx agent setup`

Configure MiniMax for Claude Code, Codex, Grok Build, OpenCode, Hermes, or Pi while preserving unrelated settings. Run without options for the interactive wizard; supplying options makes the command non-interactive and suitable for scripts.
Configure MiniMax for Claude Code, Codex, Grok Build, OpenCode, Hermes, Pi, or [Oh my Pi](https://github.com/can1357/oh-my-pi) while preserving unrelated settings. Run without options for the interactive wizard; supplying options makes the command non-interactive and suitable for scripts.

```bash
mmx agent setup
mmx agent setup --agent codex --agent claude-code --api-key "$MINIMAX_API_KEY" --region global
mmx agent setup --all --api-key "$MINIMAX_API_KEY" --region cn --output json
mmx agent setup --agent oh-my-pi --api-key "$MINIMAX_API_KEY" --region global
# --agent omp is an alias for --agent oh-my-pi
```

`agent setup` defaults to `MiniMax-M3.1-Flash-Preview` for subscription keys and to `MiniMax-M3` for pay-as-you-go keys (`sk-api-...`). In non-interactive mode, `--model` overrides this selection.

The command verifies the key and selected region before writing. Existing files are backed up when changed; use `--dry-run` to preview without a live request. In the interactive wizard, compatible agents that are missing from `PATH` can be installed from a second multi-select list using their official packages or installer scripts. The Hermes install uses its official core CLI stages and skips optional system packages, Node workspace, browser, computer-use, setup, and gateway stages. After installation, mmx applies the MiniMax configuration while preserving installer settings. Agents that fail a platform or prerequisite check are explained and left configuration-only. Non-interactive invocations remain configuration-only, and the command never launches an agent.

Oh my Pi uses its native `~/.omp/agent/models.yml` and `config.yml` (existing `.yaml` files are updated in place). Configuration honors `OMP_PROFILE` / `PI_PROFILE`, `PI_CONFIG_DIR`, and the default-profile `PI_CODING_AGENT_DIR` override. Run `omp` once to migrate legacy JSON-only configuration before using setup. Interactive installation uses the official prebuilt-binary installer, so it does not require Bun or npm.

### `mmx update`

```bash
Expand Down
14 changes: 11 additions & 3 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,11 @@ mmx quota

### `mmx text`

`text chat` 和 `text repl` 默认使用 `MiniMax-M3.1-Flash-Preview`;显式指定的 `--model` 或已保存的 `default-text-model` 优先。该预览模型目前仅通过 M Plan 和 MiniMax Code 提供;使用按量付费 Key 时,请显式选择 `MiniMax-M3`。详见[模型调用文档](https://platform.minimax.cn/docs/guides/text-generation#agent)。

```bash
mmx text chat --message "写一首诗"
mmx text chat --model MiniMax-M3 --message "你好" --stream
mmx text chat --model MiniMax-M3.1-Flash-Preview --message "你好" --stream
mmx text chat --system "你是编程助手" --message "用 Go 写 Fizzbuzz"
mmx text chat --message "user:你好" --message "assistant:嗨!" --message "你叫什么名字?"
cat messages.json | mmx text chat --messages-file - --output json
Expand Down Expand Up @@ -170,22 +172,28 @@ Global 与中国两个 region,选用能通过的那个。
mmx quota
mmx config show
mmx config set --key region --value cn
mmx config set --key default-text-model --value MiniMax-M3
mmx config set --key default-text-model --value MiniMax-M3.1-Flash-Preview
mmx config export-schema | jq .
```

### `mmx agent setup`

一键为 Claude Code、Codex、Grok Build、OpenCode、Hermes 或 Pi 配置 MiniMax,并保留配置文件中的其他设置。不带选项时进入交互式向导;带选项时自动使用非交互模式,便于脚本调用。
一键为 Claude Code、Codex、Grok Build、OpenCode、Hermes、Pi 或 [Oh my Pi](https://github.com/can1357/oh-my-pi) 配置 MiniMax,并保留配置文件中的其他设置。不带选项时进入交互式向导;带选项时自动使用非交互模式,便于脚本调用。

```bash
mmx agent setup
mmx agent setup --agent codex --agent claude-code --api-key "$MINIMAX_API_KEY" --region global
mmx agent setup --all --api-key "$MINIMAX_API_KEY" --region cn --output json
mmx agent setup --agent oh-my-pi --api-key "$MINIMAX_API_KEY" --region cn
# --agent omp 是 --agent oh-my-pi 的别名
```

`agent setup` 对订阅 Key 默认使用 `MiniMax-M3.1-Flash-Preview`,对按量付费 Key(`sk-api-...`)默认使用 `MiniMax-M3`。非交互模式下可用 `--model` 覆盖该选择。

写入前会验证 Key 和所选区域;修改已有文件时会创建备份。可用 `--dry-run` 预览且不会发起联网请求。在交互式向导中,兼容且未在 `PATH` 中检测到的 Agent 会出现在第二个安装多选列表,并通过官方软件包或安装脚本安装。Hermes 仅执行官方核心 CLI 安装阶段,跳过可选系统软件包、Node 工作区、浏览器、计算机控制、初始化和网关阶段。安装完成后,mmx 会保留安装器生成的其他设置并写入 MiniMax 配置。平台或依赖检查不通过的 Agent 会说明原因并保持仅配置。非交互调用仍只写配置,且该命令不会启动 Agent。

Oh my Pi 使用原生 `~/.omp/agent/models.yml` 和 `config.yml`(已有 `.yaml` 文件会原地更新),遵循 `OMP_PROFILE` / `PI_PROFILE`、`PI_CONFIG_DIR` 和默认 profile 的 `PI_CODING_AGENT_DIR` 配置。仅有旧版 JSON 配置时,请先运行一次 `omp` 完成迁移。交互式安装使用官方预编译二进制安装器,无需 Bun 或 npm。

### `mmx update`

```bash
Expand Down
6 changes: 4 additions & 2 deletions SDK.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,18 @@ You can also omit `apiKey` if it's already configured via `mmx config set api-ke

### Text

When `model` is omitted, the SDK uses `MiniMax-M3.1-Flash-Preview`. This preview currently requires M Plan or MiniMax Code; with a pay-as-you-go key, pass `model: 'MiniMax-M3'` explicitly. Thinking is enabled by default with `max` effort; no extra request fields are required. See the [model guide](https://platform.minimax.io/docs/guides/text-generation#agent).

```typescript
const response = await sdk.text.chat({
model: 'MiniMax-M3',
model: 'MiniMax-M3.1-Flash-Preview',
messages: [{ role: 'user', content: 'Hello!' }],
max_tokens: 4096,
});

// Streaming
const stream = await sdk.text.chat({
model: 'MiniMax-M3',
model: 'MiniMax-M3.1-Flash-Preview',
messages: [{ role: 'user', content: 'Write a poem' }],
stream: true,
});
Expand Down
8 changes: 4 additions & 4 deletions skill/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Always use these flags in non-interactive (agent/CI) contexts:

### text chat

Chat completion. Default model: `MiniMax-M3`.
Chat completion. Default model: `MiniMax-M3.1-Flash-Preview`. An explicit `--model` or saved `default-text-model` takes precedence. This preview currently requires M Plan or MiniMax Code; with a pay-as-you-go key, use `--model MiniMax-M3`. Thinking is enabled by default with `max` effort. See the [model guide](https://platform.minimax.io/docs/guides/text-generation#agent).

```bash
mmx text chat --message <text> [flags]
Expand All @@ -57,7 +57,7 @@ mmx text chat --message <text> [flags]
| `--message <text>` | string, **required**, repeatable | Message text. Prefix with `role:` to set role (e.g. `"system:You are helpful"`, `"user:Hello"`) |
| `--messages-file <path>` | string | JSON file with messages array. Use `-` for stdin |
| `--system <text>` | string | System prompt |
| `--model <model>` | string | Model ID (default: `MiniMax-M3`) |
| `--model <model>` | string | Model ID (default: `MiniMax-M3.1-Flash-Preview`) |
| `--max-tokens <n>` | number | Max tokens (default: 4096) |
| `--temperature <n>` | number | Sampling temperature (0.0, 1.0] |
| `--top-p <n>` | number | Nucleus sampling threshold |
Expand Down Expand Up @@ -381,7 +381,7 @@ Set per-modality defaults so you don't need `--model` every time:

```bash
# Set defaults
mmx config set --key default-text-model --value MiniMax-M3
mmx config set --key default-text-model --value MiniMax-M3.1-Flash-Preview
mmx config set --key default-speech-model --value speech-2.8-hd
mmx config set --key default-video-model --value MiniMax-Hailuo-2.3

Expand All @@ -391,7 +391,7 @@ mmx speech synthesize --text "Hello" --out hello.mp3
mmx video generate --prompt "Ocean waves"

# --model still overrides per-call
mmx text chat --model MiniMax-M3 --message "Hello"
mmx text chat --model MiniMax-M3.1-Flash-Preview --message "Hello"
```

**Resolution priority**: `--model` flag > config default > hardcoded fallback.
1 change: 1 addition & 0 deletions src/agent/availability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const AGENT_EXECUTABLES: Record<AgentId, readonly string[]> = {
opencode: ['opencode'],
hermes: ['hermes'],
pi: ['pi'],
'oh-my-pi': ['omp'],
};

const AGENT_RUNTIME_MARKERS: Partial<Record<AgentId, readonly [string, string]>> = {
Expand Down
111 changes: 111 additions & 0 deletions src/agent/configurator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,45 @@ function pathFromOverride(value: string | undefined, fallback: string, home: str
return isAbsolute(value) ? value : resolve(home, value);
}

function ohMyPiConfigPaths(home: string, env: NodeJS.ProcessEnv): string[] {
// Match OMP's profile/env precedence, including an explicitly empty OMP_PROFILE.
const profile = (env.OMP_PROFILE ?? env.PI_PROFILE)?.trim();
const root = join(home, env.PI_CONFIG_DIR || '.omp');
const validProfile = (name: string) => /^[a-z0-9][a-z0-9._-]{0,63}$/.test(name)
&& !name.endsWith('.')
&& !/^(?:con|prn|aux|nul|com[0-9]|lpt[0-9])(?:\..*)?$/i.test(name);
let dir: string;
if (profile && profile !== 'default') {
if (!validProfile(profile)) {
throw new CLIError('Invalid Oh my Pi profile name.', ExitCode.USAGE);
}
dir = join(root, 'profiles', profile, 'agent');
} else {
// A parent OMP process propagates its named profile's directory. Selecting
// the default profile must discard that inherited value, as OMP itself does.
const inheritedProfile = env.PI_PROFILE?.trim();
const inheritedDir = inheritedProfile && inheritedProfile !== 'default' && validProfile(inheritedProfile)
? join(root, 'profiles', inheritedProfile, 'agent') : undefined;
const override = env.PI_CODING_AGENT_DIR === inheritedDir ? undefined : env.PI_CODING_AGENT_DIR;
dir = override ? resolve(override) : join(root, 'agent');
}
return ['models', 'config'].map(name => {
const yml = join(dir, `${name}.yml`);
const yaml = join(dir, `${name}.yaml`);
if (existsSync(yml)) return yml;
if (existsSync(yaml)) return yaml;
const legacy = join(dir, name === 'models' ? 'models.json' : 'settings.json');
if (existsSync(legacy)) {
throw new CLIError(
'Oh my Pi has legacy JSON configuration that needs migration.',
ExitCode.GENERAL,
'Run omp once with the same profile/environment to migrate it to YAML, then retry. No files were changed.',
);
}
return yml;
});
}

function configPathsForAgent(
agent: AgentId,
options: AgentSetupOptions,
Expand Down Expand Up @@ -115,6 +154,8 @@ function configPathsForAgent(
);
return [join(dir, 'models.json'), join(dir, 'settings.json')];
}
case 'oh-my-pi':
return ohMyPiConfigPaths(home, env);
}
}

Expand Down Expand Up @@ -938,6 +979,73 @@ function preparePi(options: AgentSetupOptions, paths: string[]): PreparedAgentFi
];
}

function prepareOhMyPi(options: AgentSetupOptions, paths: string[]): PreparedAgentFile[] {
const providerId = options.region === 'cn' ? 'minimax-cn' : 'minimax';
const baseUrl = endpointsForRegion(options.region).anthropic;
return paths.map((path, index) => {
const existing = readPrepared(path);
const label = `Oh my Pi ${basename(path)}`;
const document = parseDocument(existing.before ?? '{}\n');
if (document.errors.length > 0) {
throw new CLIError(`${label} is not valid YAML.`, ExitCode.GENERAL, INVALID_CONFIG_HINT);
}
if (document.contents === null) document.set(index === 0 ? 'providers' : 'modelRoles', document.createNode({}));
const root = document.toJS() as unknown;
assertObject(root, `${label} must contain an object at its root.`);
if (index === 0) {
if (root.providers !== undefined) assertObject(root.providers, `${label} providers must be an object.`);
const provider = root.providers?.[providerId];
if (provider !== undefined) assertObject(provider, `${label} provider must be an object.`);
const models = provider?.models;
if (models !== undefined && !Array.isArray(models)) {
throw new CLIError(`${label} models must be an array.`, ExitCode.GENERAL, INVALID_CONFIG_HINT);
}
const entries = (models ?? []) as unknown[];
for (const entry of entries) assertObject(entry, `${label} model entries must be objects.`);
const prefix = ['providers', providerId];
for (const [key, value] of Object.entries({ baseUrl, apiKey: options.apiKey, api: 'anthropic-messages', auth: 'apiKey' })) {
document.setIn([...prefix, key], value);
}
if (models === undefined) document.setIn([...prefix, 'models'], document.createNode([]));
let appendIndex = entries.length;
for (const model of MINIMAX_MODELS) {
const found = entries.findIndex(entry => (entry as Record<string, unknown>).id === model.id);
const modelPath = [...prefix, 'models', found >= 0 ? found : appendIndex++];
const definition = {
id: model.id,
name: model.id,
api: 'anthropic-messages',
baseUrl,
reasoning: true,
input: [...model.input],
contextWindow: model.contextWindow,
maxTokens: model.maxTokens,
...(model.id === 'MiniMax-M3.1-Flash-Preview' ? {
thinking: {
mode: 'anthropic-adaptive',
efforts: ['low', 'medium', 'high', 'xhigh', 'max'],
defaultLevel: 'max',
requiresEffort: true,
},
} : {}),
};
if (found < 0) {
document.setIn(modelPath, document.createNode(definition));
} else {
for (const [key, value] of Object.entries(definition)) {
document.setIn([...modelPath, key], document.createNode(value));
}
}
}
} else {
if (root.modelRoles !== undefined) assertObject(root.modelRoles, `${label} modelRoles must be an object.`);
const effort = options.model === 'MiniMax-M3.1-Flash-Preview' ? ':max' : '';
document.setIn(['modelRoles', 'default'], `${providerId}/${options.model}${effort}`);
}
return { agent: 'oh-my-pi', path, ...existing, after: document.toString() };
});
}

export function prepareAgentConfigurations(options: AgentSetupOptions): PreparedAgentFile[] {
const prepared: PreparedAgentFile[] = [];
for (const agent of options.agents) {
Expand All @@ -961,6 +1069,9 @@ export function prepareAgentConfigurations(options: AgentSetupOptions): Prepared
case 'pi':
prepared.push(...preparePi(options, paths));
break;
case 'oh-my-pi':
prepared.push(...prepareOhMyPi(options, paths));
break;
}
}
assertDistinctConfigurationTargets(prepared);
Expand Down
Loading