From e6fe58eb371a178555c4abd55ac865e045631903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D1=82=D0=B0=D0=BB=D0=B8=D0=B9=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=B8=D1=82=D0=B5=D0=BD=D0=BA=D0=BE?= Date: Tue, 13 Oct 2026 15:43:25 +0700 Subject: [PATCH] feat(mimo): add ai-mimo launcher for Xiaomi MiMo V2.6 Pro ai-mimo runs Claude Code through Xiaomi's Anthropic-compatible API (Token Plan or pay-as-you-go). The key's endpoint is found by probing token-plan-sgp, token-plan-ams and api.xiaomimimo.com (a key only works in its own region) and is saved to mimo_base_url; MIMO_BASE_URL overrides. Unreachable endpoints are skipped, token-plan-cn is left out of the probe since it times out from our network, and a 402 on the pay-as-you-go endpoint explains that a tp- Token Plan key is needed. Always the smartest model: mimo-v2.6-pro in every slot and for subagents, --model opus and --effort max on each start, 128000 output tokens (Claude Code's ceiling), real 1M context window. Measured on 13.10: MiMo ignores output_config.effort (even "bogus" is accepted, no length trend from low to max) and treats thinking adaptive like enabled, so reasoning is always on; EFFORT_MAPPING.md records this. /logout-mimo is registered as a command and handled by the logout hook (removes mimo_key and mimo_base_url). setup.sh status, statusline brand color, README, QUICK_START and AGENTS.md list the new launcher. Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 2 +- EFFORT_MAPPING.md | 38 +++- QUICK_START.md | 2 +- README.md | 4 +- .../claude/hooks/logout-account-hook.sh | 13 +- home-configs/claude/statusline-command.sh | 1 + scripts/ai-setup.sh | 190 +++++++++++++++++- setup.sh | 6 +- tests/test_fixes.sh | 116 +++++++++++ 9 files changed, 357 insertions(+), 15 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index e26562f..4d5d94c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,4 +2,4 @@ Данные правила применяются ко всем агентам, работающим конкретно в этом репозитории. -1. **Полная перегенерация standalone-скриптов при запуске сетап-скрипта (Full regeneration of standalone scripts on setup run):** При каждом запуске `ai-setup.sh` все генерируемые скрипты в выбранном `BIN_DIR` (`~/bin`, если он уже есть в `PATH`, иначе `~/.local/bin`: `ai-gpt`, `ai-deepseek`, `ai-kimi`, `ai-glm`, `ai-eltex`, `ai-eltex-qwen`, `ai-openrouter`, `ai-gemini`, `ai-api-helpers.sh`, `ai-claude`, `claude-gpt-effort-proxy.py`) должны быть **полностью перезаписаны** актуальными версиями. Запрещено выполнять слияние (merge) старого и нового содержимого или дополнение (append). Скрипт обязан привести все генерируемые файлы к эталонному виду, однозначно определяемому текущей конфигурацией. +1. **Полная перегенерация standalone-скриптов при запуске сетап-скрипта (Full regeneration of standalone scripts on setup run):** При каждом запуске `ai-setup.sh` все генерируемые скрипты в выбранном `BIN_DIR` (`~/bin`, если он уже есть в `PATH`, иначе `~/.local/bin`: `ai-gpt`, `ai-deepseek`, `ai-kimi`, `ai-glm`, `ai-eltex`, `ai-eltex-qwen`, `ai-mimo`, `ai-openrouter`, `ai-gemini`, `ai-api-helpers.sh`, `ai-claude`, `claude-gpt-effort-proxy.py`) должны быть **полностью перезаписаны** актуальными версиями. Запрещено выполнять слияние (merge) старого и нового содержимого или дополнение (append). Скрипт обязан привести все генерируемые файлы к эталонному виду, однозначно определяемому текущей конфигурацией. diff --git a/EFFORT_MAPPING.md b/EFFORT_MAPPING.md index c9d591b..3f7a81c 100644 --- a/EFFORT_MAPPING.md +++ b/EFFORT_MAPPING.md @@ -3,7 +3,7 @@ ## Как это работает `/effort` и `AI_EFFORT` относятся к лаунчерам, которые запускают Claude Code: -`ai-claude`, `ai-deepseek`, `ai-kimi`, `ai-glm`, `ai-openrouter`, `ai-opencode`. +`ai-claude`, `ai-deepseek`, `ai-kimi`, `ai-glm`, `ai-mimo`, `ai-openrouter`, `ai-opencode`. Нативные лаунчеры `ai-gpt` (OpenAI Codex), `ai-gemini` (Antigravity `agy`) и `ai-cursor` (Cursor CLI `agent`) не используют эту схему. У них свои настройки reasoning внутри @@ -99,6 +99,35 @@ low -> high -> max Effort не преобразуется локально — thinking budget не имеет прямого эффекта на OpenCode Go бекенд. Дефолт `ai-opencode`: `high`. +### Xiaomi MiMo V2.6 + +```text +thinking on (единственный уровень) +``` + +- `ai-mimo` работает через Claude Code с Anthropic-эндпоинтом Xiaomi (Token Plan региона + подписки или `api.xiaomimimo.com`). +- Claude Code 2.1.294 отправляет `thinking: {type: "adaptive"}` и `output_config.effort` со всеми + 5 уровнями и для `mimo-v2.6-pro`, и для `mimo-v2.6-flash`, даже без + `CLAUDE_CODE_ALWAYS_ENABLE_EFFORT` (проверено перехватом запроса). +- MiMo effort игнорирует. В доке Anthropic API MiMo из параметров рассуждения есть только + `thinking.type` (`enabled` по умолчанию / `disabled` / `adaptive`), `effort` и `budget_tokens` нет. + Невалидные `output_config.effort=bogus` и `thinking.type=bogus` дают HTTP 200. +- Замер 2026-10-13, по 3 запроса на уровень, средний `output_tokens`. Pro, счётная задача: + без effort ~310, `low` ~340, `high` ~280, `max` ~315. Pro, задача на включения-исключения: + `low` ~970, `max` ~825. Flash на ней же: `low` ~460, `max` ~580. Разброс внутри уровня больше + разницы между уровнями, тренда нет. +- Реально переключается только thinking: `thinking.type=disabled` даёт 4 токена без размышлений + и неверные ответы (2 из 3). Claude Code его для MiMo не отправляет. +- `thinking.type=adaptive` (его шлёт Claude Code) MiMo отрабатывает так же, как `enabled`: + размышления есть даже на «скажи привет», на задаче на включения-исключения `adaptive` ~670, + `enabled` ~600 токенов при разбросе 356-1161. `CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING=1` + в Claude Code 2.1.294 запрос не меняет, да и не нужен. +- Глубину размышлений ограничивает только `max_tokens`: `ai-mimo` выставляет + `CLAUDE_CODE_MAX_OUTPUT_TOKENS=128000` (потолок Claude Code, MiMo принимает до 131072). +- Дефолт `ai-mimo`: `max`, задаётся через CLI `--effort` при каждом запуске (как у `ai-eltex-qwen`). + На размышления не влияет, но если Xiaomi начнёт учитывать effort, лаунчер сразу будет на максимуме. + `claude-gpt-effort-proxy.py` все еще генерируется как совместимый helper для старого `claude-code-proxy` backend-а. Если использовать его вручную, он маппит только `xhigh` -> `high`, потому что `claude-code-proxy` принимает `low`, `medium`, `high`, `max`. @@ -111,13 +140,14 @@ Effort не преобразуется локально — thinking budget не | DeepSeek V4 | DeepSeek API | На стороне сервера | | Kimi K2.7/K2.6 | Moonshot API | На стороне сервера | | GLM 5.3 | Z.ai API (`low,medium→low`, `high,xhigh→high`, `max`) | На стороне сервера | +| Xiaomi MiMo V2.6 | Не маппится: MiMo принимает `output_config.effort`, но игнорирует его | - | | OpenRouter/GPT-5.5 | Локального маппинга нет | `ai-openrouter` отправляет значение как есть | | OpenCode Go | Локального маппинга нет | `ai-opencode` отправляет значение через routatic-proxy | | Legacy GPT proxy | helper-маппинг `xhigh` -> `high` | `~/bin/claude-gpt-effort-proxy.py` или `~/.local/bin/claude-gpt-effort-proxy.py` | ## Persistence effort между сессиями -Каждый Claude Code лаунчер (`ai-claude`, `ai-deepseek`, `ai-kimi`, `ai-glm`, `ai-openrouter`, `ai-opencode`) +Каждый Claude Code лаунчер (`ai-claude`, `ai-deepseek`, `ai-kimi`, `ai-glm`, `ai-mimo`, `ai-openrouter`, `ai-opencode`) запоминает свой уровень effort отдельно в `settings.json` лаунчера. `/effort` внутри сессии работает для всех уровней. `CLAUDE_CODE_EFFORT_LEVEL` не используется. @@ -130,8 +160,8 @@ AI_EFFORT=high ai-deepseek # включить high ai-deepseek # без флага - использовать уровень из settings.json ``` -Дефолты: `max` для `ai-glm`, `ai-eltex` и `ai-eltex-qwen`, `xhigh` для `ai-claude`, `high` для -остальных Claude Code лаунчеров. `ai-glm`, `ai-eltex` и `ai-eltex-qwen` передают уровень через CLI +Дефолты: `max` для `ai-glm`, `ai-eltex`, `ai-eltex-qwen` и `ai-mimo`, `xhigh` для `ai-claude`, `high` для +остальных Claude Code лаунчеров. `ai-glm`, `ai-eltex`, `ai-eltex-qwen` и `ai-mimo` передают уровень через CLI `--effort`, поэтому каждый запуск стартует на `max` (или на `AI_EFFORT`), а `/effort` внутри сессии действует до её конца. diff --git a/QUICK_START.md b/QUICK_START.md index 6a1e23b..9da300c 100644 --- a/QUICK_START.md +++ b/QUICK_START.md @@ -7,7 +7,7 @@ bash setup.sh ``` Установит лаунчеры -`ai-claude`, `ai-gpt`, `ai-deepseek`, `ai-kimi`, `ai-glm`, `ai-eltex`, `ai-eltex-qwen`, `ai-openrouter`, `ai-opencode`, `ai-gemini`, `ai-cursor` +`ai-claude`, `ai-gpt`, `ai-deepseek`, `ai-kimi`, `ai-glm`, `ai-eltex`, `ai-eltex-qwen`, `ai-mimo`, `ai-openrouter`, `ai-opencode`, `ai-gemini`, `ai-cursor` в `~/bin`, если он уже есть в `PATH`, иначе в `~/.local/bin/`, и запишет API-ключи. После установки, если команды не видны: diff --git a/README.md b/README.md index 057e409..7c088a5 100644 --- a/README.md +++ b/README.md @@ -163,6 +163,7 @@ bash tests/test_network.sh # полные тесты - `ai-glm` - Claude Code через Zhipu AI GLM 5.3 API (`api.z.ai/api/anthropic`) - `ai-eltex` - Claude Code через корпоративный LLM-прокси Eltex (`http://llm.eltex.loc:4000`, LiteLLM, team-подписка GLM). Ключ выдают админы ([дока](https://intdocs.eltex.loc/pages/viewpage.action?pageId=262329878)), модель `anth-glm-5.3` (Anthropic-эндпоинт Z.ai: настоящая GLM 5.3 и картинки); если ключу она недоступна, лаунчер выходит с понятной ошибкой; переопределение: `ELTEX_MODEL`, `ELTEX_BASE_URL` - `ai-eltex-qwen` - то же через корпоративный LLM-прокси Eltex, но на моделях Qwen со своим ключом от админов: по умолчанию `Eltex-Coder-Senior` (Qwen3.8-27B), если она есть у ключа, иначе `Eltex-Qwen3_8-Flash-Next`; ключ можно задать в `AI_ELTEX_LLM_API_KEY`, как в онбординге Eltex AI; переопределение: `ELTEX_QWEN_MODEL`, `ELTEX_BASE_URL` +- `ai-mimo` - Claude Code через Xiaomi MiMo V2.6 (Anthropic-совместимый API, Token Plan или pay-as-you-go): всегда самая умная модель `mimo-v2.6-pro` (во всех слотах и сабагентах, `--model opus` на каждом запуске), effort `max`, вывод до 128000 токенов, окно 1M. Эндпоинт региона подписки (`token-plan-sgp`/`-ams`, для `sk-` ключа `api.xiaomimimo.com`) находится перебором при вводе ключа; переопределение: `MIMO_BASE_URL` (нужно для `token-plan-cn`: он в перебор не входит, с наших машин не отвечает) - `ai-openrouter` - Claude Code через OpenRouter (GPT-5.5, Grok, Qwen, GLM 5.2 и др.) - `ai-opencode` - Claude Code через OpenCode Go + routatic-proxy (DeepSeek, Kimi, Qwen, GLM, MiniMax, MiMo) - `ai-gemini` - нативный Antigravity CLI `agy` @@ -222,6 +223,7 @@ android init - `~/.config/ai-setup/glm_key` - Zhipu AI (GLM) API key (права 600) - `~/.config/ai-setup/eltex_key` - ключ корпоративного LLM-прокси Eltex от админов (права 600) - `~/.config/ai-setup/eltex_qwen_key` - ключ для моделей Qwen на том же прокси (права 600) +- `~/.config/ai-setup/mimo_key` - Xiaomi MiMo API key (права 600), `mimo_base_url` - найденный для него эндпоинт - `~/.config/ai-setup/openrouter_key` - OpenRouter API key (права 600) - `~/.config/ai-setup/opencode_key` - OpenCode Go API key (права 600) - `~/.config/ai-setup/global_rules.md` - глобальные правила агентов @@ -230,7 +232,7 @@ android init ## Права запуска агентов - `ai-gpt` использует `--dangerously-bypass-approvals-and-sandbox` -- `ai-claude`, `ai-deepseek`, `ai-kimi`, `ai-glm`, `ai-eltex`, `ai-eltex-qwen`, `ai-openrouter`, `ai-opencode`, `ai-gemini` используют `--dangerously-skip-permissions` +- `ai-claude`, `ai-deepseek`, `ai-kimi`, `ai-glm`, `ai-eltex`, `ai-eltex-qwen`, `ai-mimo`, `ai-openrouter`, `ai-opencode`, `ai-gemini` используют `--dangerously-skip-permissions` - `ai-cursor` использует `--force` (алиас `--yolo`) Удобно для локального coding workflow, но это не sandbox для недоверенного кода. diff --git a/home-configs/claude/hooks/logout-account-hook.sh b/home-configs/claude/hooks/logout-account-hook.sh index 42eea87..e7d2b42 100755 --- a/home-configs/claude/hooks/logout-account-hook.sh +++ b/home-configs/claude/hooks/logout-account-hook.sh @@ -1,10 +1,10 @@ #!/usr/bin/env bash # Copyright (c) 2026 Виталий Никитенко. MIT License. -# UserPromptSubmit hook: перехватывает /logout, /logout-glm, /logout-eltex, /logout-eltex-qwen, /logout-deepseek, /logout-kimi. +# UserPromptSubmit hook: перехватывает /logout, /logout-glm, /logout-eltex, /logout-eltex-qwen, /logout-mimo, /logout-deepseek, /logout-kimi. # Удаляет сохранённый API-ключ провайдера, чтобы при следующем запуске лаунчер # запросил новый. -# /logout-glm | /logout-eltex | /logout-eltex-qwen | /logout-deepseek | /logout-kimi — явно указанный провайдер. -# /logout (без суффикса) — провайдер по AI_LAUNCHER (glm/eltex/eltex-qwen/deepseek/kimi). +# /logout-glm | /logout-eltex | /logout-eltex-qwen | /logout-mimo | /logout-deepseek | /logout-kimi — явно указанный провайдер. +# /logout (без суффикса) — провайдер по AI_LAUNCHER (glm/eltex/eltex-qwen/mimo/deepseek/kimi). # Для ai-claude и прочих лаунчеров /logout отступает (exit 0) — пусть работает # нативный oauth-logout. exit 2 после удаления: LLM не нужна, и нативная # /logout не пытается сделать бессмысленный oauth-logout для api-key провайдеров. @@ -19,11 +19,12 @@ case "$cmd" in logout-glm) target=glm ;; logout-eltex) target=eltex ;; logout-eltex-qwen) target=eltex-qwen ;; + logout-mimo) target=mimo ;; logout-deepseek) target=deepseek ;; logout-kimi) target=kimi ;; logout) case "${AI_LAUNCHER:-claude}" in - glm|eltex|eltex-qwen|deepseek|kimi) target="$AI_LAUNCHER" ;; + glm|eltex|eltex-qwen|mimo|deepseek|kimi) target="$AI_LAUNCHER" ;; *) exit 0 ;; esac ;; @@ -45,6 +46,10 @@ case "$target" in rm -f "$CFG/eltex_qwen_key" echo "✓ Eltex Qwen ключ удалён. Перезапусти ai-eltex-qwen, чтобы ввести новый." >&2 ;; + mimo) + rm -f "$CFG/mimo_key" "$CFG/mimo_base_url" + echo "✓ MiMo ключ удалён. Перезапусти ai-mimo, чтобы ввести новый." >&2 + ;; deepseek) rm -f "$CFG/deepseek_key" echo "✓ DeepSeek ключ удалён. Перезапусти ai-deepseek, чтобы ввести новый." >&2 diff --git a/home-configs/claude/statusline-command.sh b/home-configs/claude/statusline-command.sh index f4963e5..f3d1f7a 100755 --- a/home-configs/claude/statusline-command.sh +++ b/home-configs/claude/statusline-command.sh @@ -39,6 +39,7 @@ _brand_color() { claude) printf '\033[38;5;173m' ;; # Anthropic оранжевый kimi) printf '\033[38;5;81m' ;; # Kimi голубой glm) printf '\033[38;5;223m' ;; # Z.ai кремовый (бренд-фон z.ai, #FFE4B5) + mimo) printf '\033[38;5;202m' ;; # Xiaomi оранжевый (#FF6900) openrouter) printf '\033[38;5;135m' ;; # OpenRouter фиолетовый *) printf '\033[38;5;223m' ;; # кремовый (fallback) esac diff --git a/scripts/ai-setup.sh b/scripts/ai-setup.sh index 0882181..a595116 100755 --- a/scripts/ai-setup.sh +++ b/scripts/ai-setup.sh @@ -1995,6 +1995,192 @@ claude --dangerously-skip-permissions --effort "${AI_EFFORT:-max}" --system-prom ELTEXQWENEOF chmod +x "$BIN_DIR/ai-eltex-qwen" +# === ai-mimo === +cat > "$BIN_DIR/ai-mimo" << 'MIMOEOF' +#!/usr/bin/env bash +# ai-mimo - запуск Claude Code через Xiaomi MiMo V2.6 (Anthropic-compatible API, Token Plan) +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/ai-api-helpers.sh" 2>/dev/null || true + +key_file="$HOME/.config/ai-setup/mimo_key" +url_file="$HOME/.config/ai-setup/mimo_base_url" +api_key="" +reauth=0 + +# Принудительный сброс сохранённого ключа через AI_RESET_KEY=1 +if [ "${AI_RESET_KEY:-0}" == "1" ]; then + rm -f "$key_file" "$url_file" +fi + +[ -f "$key_file" ] && api_key=$(cat "$key_file") + +# Ключ Token Plan (tp-...) работает только на эндпоинте региона подписки (sgp/ams/cn), +# ключ pay-as-you-go (sk-...) - на api.xiaomimimo.com. Эндпоинт находим перебором при +# проверке ключа и запоминаем в mimo_base_url; MIMO_BASE_URL перебивает. +mimo_base_url="${MIMO_BASE_URL:-}" +[ -z "$mimo_base_url" ] && [ -f "$url_file" ] && mimo_base_url=$(cat "$url_file") + +# _mimo_check_key: 1-токенный запрос с ключом $1. Без известного эндпоинта перебирает +# регионы: чужой регион отвечает 401/403, первый другой ответ задаёт mimo_base_url. +# Недоступный эндпоинт (код 000*: _claude_test_api при сетевой ошибке отдаёт 000000) +# пропускаем. token-plan-cn в переборе нет: с наших машин он не отвечает (таймаут 15 с), +# для него MIMO_BASE_URL=https://token-plan-cn.xiaomimimo.com/anthropic. +# Bearer, как у Claude Code с ANTHROPIC_AUTH_TOKEN. +_mimo_check_key() { + local url auth_code="" auth_body="" + if [ -n "$mimo_base_url" ]; then + _claude_test_api "$mimo_base_url/v1/messages" "Authorization: Bearer $1" "mimo-v2.6-flash" + return + fi + for url in https://token-plan-sgp.xiaomimimo.com/anthropic \ + https://token-plan-ams.xiaomimimo.com/anthropic \ + https://api.xiaomimimo.com/anthropic; do + _claude_test_api "$url/v1/messages" "Authorization: Bearer $1" "mimo-v2.6-flash" + case "$_CLAUDE_TEST_CODE" in + 401|403) auth_code="$_CLAUDE_TEST_CODE"; auth_body="$_CLAUDE_TEST_BODY" ;; + 000*) ;; + *) mimo_base_url="$url"; return ;; + esac + done + # Ни один эндпоинт не принял ключ. Отказ в авторизации разберёт _handle_api_response, + # а без сети эндпоинт не узнать и запускать Claude Code некуда. + if [ -n "$auth_code" ]; then + _CLAUDE_TEST_CODE="$auth_code" + _CLAUDE_TEST_BODY="$auth_body" + return + fi + echo "" + echo -e "\033[0;31m[СЕТЬ]\033[0m Эндпоинты MiMo недоступны, ключ не проверен." + echo " Задай эндпоинт явно: MIMO_BASE_URL=https://token-plan-sgp.xiaomimimo.com/anthropic ai-mimo" + exit 1 +} + +# _mimo_check_result: разбор ответа проверки. 402 на pay-as-you-go эндпоинте значит ключ +# sk-... с нулевым балансом: подписку Token Plan он не тратит, для неё свой ключ tp-... +_mimo_check_result() { + _handle_api_response "MiMo" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Лимит Token Plan: https://platform.xiaomimimo.com" + if [ "$_API_RET" = "402" ] && [ "$mimo_base_url" = "https://api.xiaomimimo.com/anthropic" ]; then + echo " Это ключ pay-as-you-go (sk-...), подписку Token Plan он не использует." + echo " Для Token Plan создай ключ на странице Token Plan: Dedicated API Key -> Create API Key (tp-...)." + fi +} + +if [ -n "$api_key" ]; then + echo -n "Проверка сохранённого MiMo ключа... " + _mimo_check_key "$api_key" + _mimo_check_result + ret=$_API_RET + if [ $ret -eq 401 ]; then + rm -f "$key_file" "$url_file" + api_key="" + mimo_base_url="${MIMO_BASE_URL:-}" + reauth=1 + elif [ $ret -eq 429 ]; then + echo -n "Продолжить всё равно? (запросы могут не проходить) [y/N] " + read -r _ans + case "${_ans:-N}" in + [Yy]*) ;; + *) + echo -n "Хотите ввести новый MiMo ключ? [Y/n] " + read -r _ans2 + case "${_ans2:-Y}" in + [Yy]*) + rm -f "$key_file" "$url_file" + api_key="" + mimo_base_url="${MIMO_BASE_URL:-}" + reauth=1 + ;; + *) exit 1 ;; + esac + ;; + esac + elif [ $ret -ne 0 ]; then + exit 1 + fi +fi + +if [ -z "$api_key" ] && [ "$reauth" -eq 1 ]; then + echo -n "Хотите ввести новый MiMo ключ? [Y/n] " + read -r _ans; case "${_ans:-Y}" in [Yy]*) ;; *) exit 1 ;; esac +fi + +if [ -z "$api_key" ]; then + echo "Получить ключ: https://platform.xiaomimimo.com (Token Plan - tp-..., pay-as-you-go - sk-...)" + read -r -p "Введите ваш Xiaomi MiMo API ключ: " api_key + [ -z "$api_key" ] && { echo "Выход."; exit 1; } + + echo -n "Проверяю ключ... " + _mimo_check_key "$api_key" + _mimo_check_result + ret=$_API_RET + if [ $ret -eq 0 ] || [ $ret -eq 429 ]; then + mkdir -p "$(dirname "$key_file")" + echo "$api_key" > "$key_file" + echo "$mimo_base_url" > "$url_file" + chmod 600 "$key_file" "$url_file" + echo "Ключ сохранён (эндпоинт: $mimo_base_url)." + if [ $ret -eq 429 ]; then + echo -n "Продолжить всё равно? (запросы могут не проходить) [y/N] " + read -r _ans; case "${_ans:-N}" in [Yy]*) ;; *) exit 1 ;; esac + fi + else + echo "Ключ НЕ сохранён." + exit 1 + fi +fi + +_PROMPT_FILE=$(mktemp /tmp/ai-sys-prompt.XXXXXX) +trap 'rm -f "$_PROMPT_FILE"' EXIT INT TERM +_build_ai_sys_prompt > "$_PROMPT_FILE" +export AI_LAUNCHER=mimo +export CLAUDE_CONFIG_DIR="$HOME/.config/ai-setup/cfg/mimo" +# Модель: всегда самая умная MiMo - mimo-v2.6-pro во всех трёх слотах и в сабагентах, как +# у ai-glm. Flash не используем: ai-mimo задуман как максимум качества, а не экономия +# кредитов. pro-ultraspeed - тот же Pro, только быстрее и в ~10 раз дороже. +# mimo-v2.5-pro/mimo-v2.5 сняты 21.10.2026 (с 14.10 их роутят на v2.6-pro/v2.6-flash): +# https://mimo.mi.com/docs/en-US/updates/deprecate +# availableModels НЕ задаём (он схлопывает пикер). --model opus при каждом запуске: выбор +# через /model живёт до конца сессии, следующий запуск снова на Pro. +# CLAUDE_CODE_MAX_CONTEXT_TOKENS: mimo-* нет в реестре моделей Claude Code, без env окно +# считалось бы = 200K (ctx: врал бы в ~5 раз, автокомпакт жал бы сессию). У Pro реальное +# окно 1M, поэтому CLAUDE_CODE_DISABLE_1M_CONTEXT не выставляем. +# CLAUDE_CODE_MAX_OUTPUT_TOKENS=128000: потолок Claude Code (131072 он режет до 128000), +# MiMo V2.6 отдаёт до 131072. По умолчанию незнакомой модели досталось бы 32000 на +# размышления + ответ. +# Размышления: MiMo думает всегда. Claude Code шлёт thinking adaptive, а MiMo отрабатывает +# его так же, как enabled (замер 13.10: даже на "скажи привет" есть thinking, длина та же). +# Effort MiMo игнорирует: Claude Code шлёт output_config.effort (и без +# CLAUDE_CODE_ALWAYS_ENABLE_EFFORT), MiMo принимает даже effort=bogus, а длина размышлений +# от low до max не меняется (см. EFFORT_MAPPING.md). Стартуем на max через CLI --effort, +# как ai-eltex-qwen: если Xiaomi начнёт его учитывать, будет максимум. +_setup_isolated_config mimo opus high '' +# /logout-mimo: без зарегистрированной команды Claude Code отвечает "Unknown command" и +# не зовёт UserPromptSubmit-хук (см. ai-eltex). +mkdir -p "$CLAUDE_CONFIG_DIR/commands" +cat > "$CLAUDE_CONFIG_DIR/commands/logout-mimo.md" <<'CMDEOF' +--- +description: Удалить сохранённый ключ ai-mimo (обрабатывает хук, без LLM) +--- +Хук не сработал: удали ~/.config/ai-setup/mimo_key вручную и перезапусти ai-mimo (или запусти AI_RESET_KEY=1 ai-mimo). +CMDEOF +ANTHROPIC_BASE_URL="$mimo_base_url" \ +ANTHROPIC_AUTH_TOKEN="$api_key" \ +ANTHROPIC_DEFAULT_OPUS_MODEL=mimo-v2.6-pro \ +ANTHROPIC_DEFAULT_OPUS_MODEL_NAME="MiMo V2.6 Pro" \ +ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION="Xiaomi MiMo V2.6 Pro - флагман для сложных задач" \ +ANTHROPIC_DEFAULT_SONNET_MODEL=mimo-v2.6-pro \ +ANTHROPIC_DEFAULT_SONNET_MODEL_NAME="MiMo V2.6 Pro" \ +ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION="Xiaomi MiMo V2.6 Pro - флагман для сложных задач" \ +ANTHROPIC_DEFAULT_HAIKU_MODEL=mimo-v2.6-pro \ +ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME="MiMo V2.6 Pro" \ +ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION="Xiaomi MiMo V2.6 Pro - флагман для сложных задач" \ +CLAUDE_CODE_SUBAGENT_MODEL=mimo-v2.6-pro \ +CLAUDE_CODE_MAX_CONTEXT_TOKENS=1000000 \ +CLAUDE_CODE_MAX_OUTPUT_TOKENS=128000 \ +CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \ +claude --dangerously-skip-permissions --model opus --effort "${AI_EFFORT:-max}" --system-prompt-file "$_PROMPT_FILE" "$@" +MIMOEOF +chmod +x "$BIN_DIR/ai-mimo" + # === ai-openrouter === cat > "$BIN_DIR/ai-openrouter" << 'OPENROUTEREOF' #!/usr/bin/env bash @@ -2298,6 +2484,7 @@ _setup_isolated_config kimi opus high '' _setup_isolated_config glm opus xhigh '' _setup_isolated_config eltex opus xhigh '' _setup_isolated_config eltex-qwen opus high '' +_setup_isolated_config mimo opus high '' _setup_isolated_config openrouter "openai/gpt-5.5" high '' _setup_isolated_config opencode deepseek-v4-pro high '' @@ -2308,7 +2495,7 @@ rm -f "$BIN_DIR/claude_gpt" "$BIN_DIR/claude_deepseek" "$BIN_DIR/claude_kimi" "$ if [ "$BIN_DIR" != "$HOME/.local/bin" ]; then warn "BIN_DIR=$BIN_DIR — удаляю старые скрипты из ~/.local/bin/ ..." rm -f "$HOME/.local/bin/ai-gpt" "$HOME/.local/bin/ai-deepseek" "$HOME/.local/bin/ai-kimi" \ - "$HOME/.local/bin/ai-glm" "$HOME/.local/bin/ai-eltex" "$HOME/.local/bin/ai-eltex-qwen" "$HOME/.local/bin/ai-openrouter" "$HOME/.local/bin/ai-gemini" "$HOME/.local/bin/ai-cursor" "$HOME/.local/bin/ai-claude" \ + "$HOME/.local/bin/ai-glm" "$HOME/.local/bin/ai-eltex" "$HOME/.local/bin/ai-eltex-qwen" "$HOME/.local/bin/ai-mimo" "$HOME/.local/bin/ai-openrouter" "$HOME/.local/bin/ai-gemini" "$HOME/.local/bin/ai-cursor" "$HOME/.local/bin/ai-claude" \ "$HOME/.local/bin/ai-opencode" \ "$HOME/.local/bin/ai-api-helpers.sh" "$HOME/.local/bin/claude-gpt-effort-proxy.py" fi @@ -2330,6 +2517,7 @@ echo -e " ${CYAN}ai-kimi${NC} - Kimi K2.7 Code (через Claude echo -e " ${CYAN}ai-glm${NC} - GLM 5.3 (через Claude Code, Zhipu AI API)" echo -e " ${CYAN}ai-eltex${NC} - Корпоративный LLM-прокси Eltex (через Claude Code, ключ от админов)" echo -e " ${CYAN}ai-eltex-qwen${NC} - Корпоративный LLM-прокси Eltex, модели Qwen (через Claude Code, свой ключ)" +echo -e " ${CYAN}ai-mimo${NC} - Xiaomi MiMo V2.6 Pro, максимум рассуждений (через Claude Code, Token Plan)" echo -e " ${CYAN}ai-openrouter${NC} - OpenRouter (через Claude Code: GPT-5.5, Grok, Qwen, GLM 5.2 и др.)" echo -e " ${CYAN}ai-opencode${NC} - OpenCode Go (через Claude Code + routatic-proxy: DeepSeek, Kimi, Qwen, GLM, MiniMax, MiMo)" echo "" diff --git a/setup.sh b/setup.sh index 88ebb40..9ba9e3b 100755 --- a/setup.sh +++ b/setup.sh @@ -230,7 +230,7 @@ case "$choice" in fi echo -e "${BLD}AI инструменты:${CLR}" - for cmd in ai-claude ai-gpt ai-deepseek ai-kimi ai-glm ai-eltex ai-eltex-qwen ai-openrouter ai-opencode ai-gemini android; do + for cmd in ai-claude ai-gpt ai-deepseek ai-kimi ai-glm ai-eltex ai-eltex-qwen ai-mimo ai-openrouter ai-opencode ai-gemini android; do if command -v "$cmd" &>/dev/null; then echo -e " ${GRN}✓${CLR} $cmd" else @@ -241,7 +241,7 @@ case "$choice" in echo "" echo -e "${BLD}API ключи:${CLR}" cfg="$HOME/.config/ai-setup" - for f in deepseek_key kimi_key glm_key eltex_key eltex_qwen_key openrouter_key opencode_key; do + for f in deepseek_key kimi_key glm_key eltex_key eltex_qwen_key mimo_key openrouter_key opencode_key; do name="${f/_key/}" if [ -s "$cfg/$f" ]; then echo -e " ${GRN}✓${CLR} $name" @@ -252,7 +252,7 @@ case "$choice" in echo "" echo -e "${BLD}API доступность:${CLR}" - for entry in "Anthropic:api.anthropic.com" "DeepSeek:api.deepseek.com" "OpenAI:api.openai.com" "Kimi:api.kimi.com" "GLM:api.z.ai" "OpenRouter:openrouter.ai" "OpenCode:opencode.ai"; do + for entry in "Anthropic:api.anthropic.com" "DeepSeek:api.deepseek.com" "OpenAI:api.openai.com" "Kimi:api.kimi.com" "GLM:api.z.ai" "MiMo:api.xiaomimimo.com" "OpenRouter:openrouter.ai" "OpenCode:opencode.ai"; do _name="${entry%%:*}" _host="${entry##*:}" _ms=$(curl -s -o /dev/null -w "%{time_connect}" --max-time 5 "https://$_host" 2>/dev/null) diff --git a/tests/test_fixes.sh b/tests/test_fixes.sh index b3e10db..4b22f50 100755 --- a/tests/test_fixes.sh +++ b/tests/test_fixes.sh @@ -19,6 +19,7 @@ KIMI_SECTION=$(awk '/^cat > "\$BIN_DIR\/ai-kimi"/,/^KIMIEOF/' "$SCRIPT") GLM_SECTION=$(awk '/^cat > "\$BIN_DIR\/ai-glm"/,/^GLMEOF/' "$SCRIPT") ELTEX_SECTION=$(awk '/^cat > "\$BIN_DIR\/ai-eltex"/,/^ELTEXEOF/' "$SCRIPT") ELTEX_QWEN_SECTION=$(awk '/^cat > "\$BIN_DIR\/ai-eltex-qwen"/,/^ELTEXQWENEOF/' "$SCRIPT") +MIMO_SECTION=$(awk '/^cat > "\$BIN_DIR\/ai-mimo"/,/^MIMOEOF/' "$SCRIPT") LOGOUT_HOOK="$(cd "$(dirname "$0")/.." && pwd)/home-configs/claude/hooks/logout-account-hook.sh" GEMINI_SECTION=$(awk '/^cat > "\$BIN_DIR\/ai-gemini"/,/^GEMINIEOF/' "$SCRIPT") OPENCODE_SECTION=$(awk '/^cat > "\$BIN_DIR\/ai-opencode"/,/^OPENCODEEOF/' "$SCRIPT") @@ -230,6 +231,117 @@ test_logout_eltex() { rm -rf "$tmp_home" } +# ── ai-mimo: Claude Code через Xiaomi MiMo V2.6 ─────────────────────────── +test_mimo_launcher() { + if echo "$MIMO_SECTION" | grep -q 'claude --dangerously-skip-permissions' \ + && echo "$MIMO_SECTION" | grep -q 'key_file="$HOME/.config/ai-setup/mimo_key"' \ + && echo "$MIMO_SECTION" | grep -q 'CLAUDE_CONFIG_DIR="$HOME/.config/ai-setup/cfg/mimo"' \ + && echo "$MIMO_SECTION" | grep -q '^ANTHROPIC_BASE_URL="$mimo_base_url"' \ + && echo "$MIMO_SECTION" | grep -q '^ANTHROPIC_DEFAULT_OPUS_MODEL=mimo-v2.6-pro' \ + && echo "$MIMO_SECTION" | grep -q '^ANTHROPIC_DEFAULT_SONNET_MODEL=mimo-v2.6-pro' \ + && echo "$MIMO_SECTION" | grep -q '^ANTHROPIC_DEFAULT_HAIKU_MODEL=mimo-v2.6-pro' \ + && echo "$MIMO_SECTION" | grep -q '^CLAUDE_CODE_SUBAGENT_MODEL=mimo-v2.6-pro' \ + && echo "$MIMO_SECTION" | grep -q -- 'claude --dangerously-skip-permissions --model opus ' \ + && echo "$MIMO_SECTION" | grep -q '^CLAUDE_CODE_MAX_OUTPUT_TOKENS=128000' \ + && echo "$MIMO_SECTION" | grep -q '^CLAUDE_CODE_MAX_CONTEXT_TOKENS=1000000' \ + && ! echo "$MIMO_SECTION" | grep -q '^CLAUDE_CODE_DISABLE_1M_CONTEXT=' \ + && echo "$MIMO_SECTION" | grep -q 'cat > "$CLAUDE_CONFIG_DIR/commands/logout-mimo.md"'; then + ok "ai-mimo: own key/config, Pro in every slot and subagents, --model opus, 128000 output, 1M window, /logout-mimo" + else + fail "ai-mimo: must use mimo_key, cfg/mimo, mimo-v2.6-pro everywhere, --model opus, 128000 output, 1M window and logout-mimo.md" + fi +} + +# ── ai-mimo: эндпоинт региона Token Plan находится перебором и запоминается ── +test_mimo_region_probe() { + local tmp ok_run=1 + tmp=$(mktemp -d) + mkdir -p "$tmp/bin" "$tmp/mock" "$tmp/home" + echo "$HELPERS_SECTION" | HELPERS_FILE="$tmp/bin/ai-api-helpers.sh" bash + echo "$MIMO_SECTION" | BIN_DIR="$tmp/bin" bash + # Ключ подходит только к региону ams, sgp недоступен по сети (как curl: -w печатает + # 000 и exit 28), остальные эндпоинты отвечают 401. MOCK_DOWN=1 - сети нет совсем. + cat > "$tmp/mock/curl" <> "$tmp/curl.log" ;; + esac + case "\$a" in + https://*) [ -n "\${MOCK_DOWN:-}" ] && { printf '\n000'; exit 28; } ;; + esac + case "\$a" in + https://api.xiaomimimo.com/*) [ -n "\${MOCK_SK:-}" ] && { printf '{"error":{"message":"Insufficient account balance"}}\n402'; exit 0; } ;; + https://token-plan-*) [ -n "\${MOCK_SK:-}" ] && { printf '{"error":{"message":"invalid api key"}}\n401'; exit 0; } ;; + esac + case "\$a" in + https://token-plan-sgp.xiaomimimo.com/*) printf '\n000'; exit 28 ;; + https://token-plan-ams.xiaomimimo.com/*) printf '{"content":[]}\n200'; exit 0 ;; + https://*) printf '{"error":{"message":"invalid api key"}}\n401'; exit 0 ;; + esac +done +CURLEOF + printf '#!/usr/bin/env bash\necho "$ANTHROPIC_BASE_URL" > "%s/claude.url"\n' "$tmp" > "$tmp/mock/claude" + chmod +x "$tmp/mock/curl" "$tmp/mock/claude" + _run_mimo() { + (cd "$tmp" && printf 'tp-test\n' | HOME="$tmp/home" PATH="$tmp/mock:$PATH" \ + MIMO_BASE_URL= AI_RESET_KEY= AI_EFFORT= bash "$tmp/bin/ai-mimo" >/dev/null 2>&1) + } + local ams=https://token-plan-ams.xiaomimimo.com/anthropic + _run_mimo || ok_run=0 + [ "$(cat "$tmp/claude.url" 2>/dev/null)" = "$ams" ] || ok_run=0 + [ "$(cat "$tmp/home/.config/ai-setup/mimo_base_url" 2>/dev/null)" = "$ams" ] || ok_run=0 + [ "$(cat "$tmp/home/.config/ai-setup/mimo_key" 2>/dev/null)" = "tp-test" ] || ok_run=0 + # Повторный запуск: сохранённый эндпоинт, без перебора регионов. + rm -f "$tmp/curl.log" "$tmp/claude.url" + _run_mimo || ok_run=0 + [ "$(cat "$tmp/curl.log" 2>/dev/null)" = "$ams/v1/messages" ] || ok_run=0 + [ "$(cat "$tmp/claude.url" 2>/dev/null)" = "$ams" ] || ok_run=0 + # Без сети: ключ не сохраняется и Claude Code не запускается с пустым эндпоинтом. + rm -rf "$tmp/home" "$tmp/claude.url" + MOCK_DOWN=1 _run_mimo && ok_run=0 + [ ! -e "$tmp/claude.url" ] && [ ! -e "$tmp/home/.config/ai-setup/mimo_key" ] || ok_run=0 + # Ключ pay-as-you-go без баланса: Token Plan отвечает 401, api.xiaomimimo.com - 402, + # нужна подсказка про ключ tp-... + rm -rf "$tmp/home" + local out + out=$(cd "$tmp" && printf 'sk-test\n' | HOME="$tmp/home" PATH="$tmp/mock:$PATH" MOCK_SK=1 \ + MIMO_BASE_URL= AI_RESET_KEY= AI_EFFORT= bash "$tmp/bin/ai-mimo" 2>&1) && ok_run=0 + echo "$out" | grep -q 'Dedicated API Key' || ok_run=0 + [ ! -e "$tmp/claude.url" ] && [ ! -e "$tmp/home/.config/ai-setup/mimo_key" ] || ok_run=0 + if [ "$ok_run" -eq 1 ]; then + ok "ai-mimo: finds the Token Plan region by probing (skips unreachable), saves and reuses it" + else + fail "ai-mimo: region probe must skip unreachable regions, pick the one that accepts the key and reuse it" + fi + rm -rf "$tmp" +} + +# ── ai-mimo: effort max по умолчанию через CLI (MiMo effort игнорирует) ──── +test_mimo_effort() { + if echo "$MIMO_SECTION" | grep -q -- '--effort "${AI_EFFORT:-max}"' \ + && ! echo "$MIMO_SECTION" | grep -q '^_apply_effort'; then + ok "ai-mimo: starts with --effort max by default, AI_EFFORT via CLI" + else + fail "ai-mimo: must start with --effort \"\${AI_EFFORT:-max}\", no _apply_effort" + fi +} + +test_logout_mimo() { + local tmp_home rc=0 + tmp_home=$(mktemp -d) + mkdir -p "$tmp_home/.config/ai-setup" + echo "tp-test" > "$tmp_home/.config/ai-setup/mimo_key" + echo "https://token-plan-ams.xiaomimimo.com/anthropic" > "$tmp_home/.config/ai-setup/mimo_base_url" + echo '{"prompt":"/logout-mimo"}' | HOME="$tmp_home" AI_LAUNCHER=claude bash "$LOGOUT_HOOK" 2>/dev/null || rc=$? + if [ "$rc" -eq 2 ] && [ ! -f "$tmp_home/.config/ai-setup/mimo_key" ] && [ ! -f "$tmp_home/.config/ai-setup/mimo_base_url" ]; then + ok "logout hook: /logout-mimo removes mimo_key and mimo_base_url" + else + fail "logout hook: /logout-mimo must remove mimo_key and mimo_base_url (rc=$rc)" + fi + rm -rf "$tmp_home" +} + # ── ai-glm: default effort is max (still overridable via AI_EFFORT) ─────── test_glm_default_effort() { if echo "$GLM_SECTION" | grep -q -- '--effort "${AI_EFFORT:-max}"' \ @@ -449,6 +561,10 @@ test_eltex_qwen_effort test_logout_eltex test_eltex_qwen_launcher test_logout_eltex_qwen +test_mimo_launcher +test_mimo_region_probe +test_mimo_effort +test_logout_mimo test_gemini_native_launcher test_cursor_native_launcher test_global_rules_source_installed