diff --git a/AGENTS.md b/AGENTS.md index 4d5d94c..2536c58 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-mimo`, `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-qwen`, `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 e21cecc..9a3ac7e 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-mimo`, `ai-openrouter`, `ai-opencode`. +`ai-claude`, `ai-deepseek`, `ai-kimi`, `ai-glm`, `ai-mimo`, `ai-qwen`, `ai-openrouter`, `ai-opencode`. Нативные лаунчеры `ai-gpt` (OpenAI Codex), `ai-gemini` (Antigravity `agy`) и `ai-cursor` (Cursor CLI `agent`) не используют эту схему. У них свои настройки reasoning внутри @@ -128,6 +128,22 @@ thinking on (единственный уровень) - Дефолт `ai-mimo`: `max`, задаётся через CLI `--effort` при каждом запуске (как у `ai-eltex-qwen`). На размышления не влияет, но если Xiaomi начнёт учитывать effort, лаунчер сразу будет на максимуме. +### Qwen3.8 (Alibaba Token Plan) + +```text +low -> medium -> high -> xhigh -> max (принимаются, у Max тренда нет) +``` + +- `ai-qwen` работает через Claude Code с Anthropic-эндпоинтом Alibaba Model Studio + (`token-plan.ap-southeast-1.maas.aliyuncs.com/apps/anthropic`). +- Qwen проверяет `output_config.effort`: `bogus` даёт HTTP 400 со списком + `low, medium, high, xhigh, max`. `thinking.type=adaptive` принимается, размышления есть всегда. +- Замер 2026-10-20, задача на включения-исключения, `output_tokens`. `qwen3.8-max`, по 3 запроса: + `low` 435/513/615, `high` 363/412/662, `max` 397/539/657 - разброс больше разницы, тренда нет. + `qwen3.8-flash`, по 2 запроса: `low` ~430, `max` ~790. +- Дефолт `ai-qwen`: `max` через CLI `--effort` при каждом запуске, `CLAUDE_CODE_ALWAYS_ENABLE_EFFORT=1` + как страховка. + `claude-gpt-effort-proxy.py` все еще генерируется как совместимый helper для старого `claude-code-proxy` backend-а. Если использовать его вручную, он маппит только `xhigh` -> `high`, потому что `claude-code-proxy` принимает `low`, `medium`, `high`, `max`. @@ -141,13 +157,14 @@ thinking on (единственный уровень) | 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`, но игнорирует его | - | +| Qwen3.8 | Alibaba API, значение уходит как есть | На стороне сервера | | 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-mimo`, `ai-openrouter`, `ai-opencode`) +Каждый Claude Code лаунчер (`ai-claude`, `ai-deepseek`, `ai-kimi`, `ai-glm`, `ai-mimo`, `ai-qwen`, `ai-openrouter`, `ai-opencode`) запоминает свой уровень effort отдельно в `settings.json` лаунчера. `/effort` внутри сессии работает для всех уровней. `CLAUDE_CODE_EFFORT_LEVEL` не используется. @@ -160,8 +177,8 @@ AI_EFFORT=high ai-deepseek # включить high ai-deepseek # без флага - использовать уровень из settings.json ``` -Дефолты: `xhigh` для `ai-claude` и `ai-glm`, `max` для `ai-eltex`, `ai-eltex-qwen` и `ai-mimo`, `high` для -остальных Claude Code лаунчеров. `ai-glm`, `ai-eltex`, `ai-eltex-qwen` и `ai-mimo` передают уровень через CLI +Дефолты: `xhigh` для `ai-claude` и `ai-glm`, `max` для `ai-eltex`, `ai-eltex-qwen`, `ai-mimo` и `ai-qwen`, `high` для +остальных Claude Code лаунчеров. `ai-glm`, `ai-eltex`, `ai-eltex-qwen`, `ai-mimo` и `ai-qwen` передают уровень через CLI `--effort`, поэтому каждый запуск стартует на дефолте (или на `AI_EFFORT`), а `/effort` внутри сессии действует до её конца. diff --git a/QUICK_START.md b/QUICK_START.md index 9da300c..4499f3a 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-mimo`, `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-qwen`, `ai-openrouter`, `ai-opencode`, `ai-gemini`, `ai-cursor` в `~/bin`, если он уже есть в `PATH`, иначе в `~/.local/bin/`, и запишет API-ключи. После установки, если команды не видны: diff --git a/README.md b/README.md index edc7f9c..6b72202 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,7 @@ bash tests/test_network.sh # полные тесты - `ai-eltex` - Claude Code через корпоративный LLM-прокси Eltex (`http://llm.eltex.loc:4000`, LiteLLM, team-подписка GLM). Ключ выдают админы ([дока](https://intdocs.eltex.loc/pages/viewpage.action?pageId=262329878)), три модели в `/model`: `anth-glm-5.3` по умолчанию (Anthropic-эндпоинт Z.ai: картинки, effort, окно 1M), `zai-glm-5.3` (OpenAI-эндпоинт: без картинок и effort, 200K) и `zai-glm-5.3-flash` (быстрая, картинки видит сама, 200K); если ключу недоступна `anth-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-qwen` - Claude Code через Alibaba Cloud Model Studio (Token Plan, Anthropic-совместимый API `token-plan.ap-southeast-1.maas.aliyuncs.com/apps/anthropic`): общий ключ вшит в скрипт, запускается сразу без ввода ключа. `qwen3.8-max` в opus/sonnet и сабагентах (`--model opus` на каждом запуске), `qwen3.8-flash` в haiku, effort `max`, окно 1M, картинки видят обе модели - `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` @@ -232,7 +233,7 @@ android init ## Права запуска агентов - `ai-gpt` использует `--dangerously-bypass-approvals-and-sandbox` -- `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-claude`, `ai-deepseek`, `ai-kimi`, `ai-glm`, `ai-eltex`, `ai-eltex-qwen`, `ai-mimo`, `ai-qwen`, `ai-openrouter`, `ai-opencode`, `ai-gemini` используют `--dangerously-skip-permissions` - `ai-cursor` использует `--force` (алиас `--yolo`) Удобно для локального coding workflow, но это не sandbox для недоверенного кода. diff --git a/home-configs/claude/statusline-command.sh b/home-configs/claude/statusline-command.sh index f3d1f7a..6d2aae5 100755 --- a/home-configs/claude/statusline-command.sh +++ b/home-configs/claude/statusline-command.sh @@ -40,6 +40,7 @@ _brand_color() { 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) + qwen) printf '\033[38;5;99m' ;; # Qwen фиолетовый (#615CED) 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 af6b2c5..c86b1b0 100755 --- a/scripts/ai-setup.sh +++ b/scripts/ai-setup.sh @@ -2219,6 +2219,54 @@ claude --dangerously-skip-permissions --model opus --effort "${AI_EFFORT:-max}" MIMOEOF chmod +x "$BIN_DIR/ai-mimo" +# === ai-qwen === +cat > "$BIN_DIR/ai-qwen" << 'QWENEOF' +#!/usr/bin/env bash +# ai-qwen - запуск Claude Code через Alibaba Cloud Model Studio (Qwen 3.8, Token Plan, +# Anthropic-compatible API). Ключ общий и вшит в скрипт: запускается сразу, без ввода ключа. +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/ai-api-helpers.sh" 2>/dev/null || true + +qwen_base_url="https://token-plan.ap-southeast-1.maas.aliyuncs.com/apps/anthropic" +api_key="sk-sp-H.LEDIL.UJfr.MEUCIBmN5Rtjyn9iKPnhw2sym_1auFL4YZ4tnJh54TurNMo8AiEA1SbkS2Gt872ncq-32xK2ZxlpZxS3ldNHnTgX6dNCIok" + +_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=qwen +export CLAUDE_CONFIG_DIR="$HOME/.config/ai-setup/cfg/qwen" +# Модели (список ключа: .../compatible-mode/v1/models): qwen3.8-max - самая умная, она в +# opus/sonnet и в сабагентах; haiku = qwen3.8-flash (быстрая, Claude Code берёт слот haiku +# и для фоновых задач вроде заголовков - так дешевле для лимита Token Plan). +# availableModels НЕ задаём (он схлопывает пикер). --model opus при каждом запуске, как в ai-mimo. +# Окно: у обеих моделей вход до 983616 токенов (замер 20.10: ответ "Range of input length +# should be [1, 983616]"). qwen3.8-* нет в реестре Claude Code, без +# CLAUDE_CODE_MAX_CONTEXT_TOKENS окно считалось бы = 200K. Автокомпакт с окном 1000000 +# срабатывает раньше предела API. Картинки обе модели видят сами. +# Effort Qwen проверяет (bogus -> 400, low/medium/high/xhigh/max принимаются), но у Max тренда +# нет, у Flash max размышляет почти вдвое дольше low (см. EFFORT_MAPPING.md). Стартуем на max +# через CLI --effort, как ai-mimo. CLAUDE_CODE_ALWAYS_ENABLE_EFFORT - страховка, как в ai-glm. +# CLAUDE_CODE_MAX_OUTPUT_TOKENS=64000 вместо дефолтных 32000, как у ai-glm. +_setup_isolated_config qwen opus high '' +ANTHROPIC_BASE_URL="$qwen_base_url" \ +ANTHROPIC_AUTH_TOKEN="$api_key" \ +ANTHROPIC_DEFAULT_OPUS_MODEL=qwen3.8-max \ +ANTHROPIC_DEFAULT_OPUS_MODEL_NAME="Qwen3.8 Max" \ +ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION="Qwen3.8 Max - флагман Alibaba для сложных задач" \ +ANTHROPIC_DEFAULT_SONNET_MODEL=qwen3.8-max \ +ANTHROPIC_DEFAULT_SONNET_MODEL_NAME="Qwen3.8 Max" \ +ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION="Qwen3.8 Max - флагман Alibaba для сложных задач" \ +ANTHROPIC_DEFAULT_HAIKU_MODEL=qwen3.8-flash \ +ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME="Qwen3.8 Flash" \ +ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION="Qwen3.8 Flash - быстрая" \ +CLAUDE_CODE_SUBAGENT_MODEL=qwen3.8-max \ +CLAUDE_CODE_MAX_CONTEXT_TOKENS=1000000 \ +CLAUDE_CODE_MAX_OUTPUT_TOKENS=64000 \ +CLAUDE_CODE_ALWAYS_ENABLE_EFFORT=1 \ +CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \ +claude --dangerously-skip-permissions --model opus --effort "${AI_EFFORT:-max}" --system-prompt-file "$_PROMPT_FILE" "$@" +QWENEOF +chmod +x "$BIN_DIR/ai-qwen" + # === ai-openrouter === cat > "$BIN_DIR/ai-openrouter" << 'OPENROUTEREOF' #!/usr/bin/env bash @@ -2523,6 +2571,7 @@ _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 qwen opus high '' _setup_isolated_config openrouter "openai/gpt-5.5" high '' _setup_isolated_config opencode deepseek-v4-pro high '' @@ -2533,7 +2582,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-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-glm" "$HOME/.local/bin/ai-eltex" "$HOME/.local/bin/ai-eltex-qwen" "$HOME/.local/bin/ai-mimo" "$HOME/.local/bin/ai-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-opencode" \ "$HOME/.local/bin/ai-api-helpers.sh" "$HOME/.local/bin/claude-gpt-effort-proxy.py" fi @@ -2556,6 +2605,7 @@ echo -e " ${CYAN}ai-glm${NC} - GLM 5.3 (через Claude Code, Z 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-qwen${NC} - Qwen3.8 Max (через Claude Code, Alibaba 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 9ba9e3b..dc4c62e 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-mimo 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-qwen ai-openrouter ai-opencode ai-gemini android; do if command -v "$cmd" &>/dev/null; then echo -e " ${GRN}✓${CLR} $cmd" else @@ -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" "MiMo:api.xiaomimimo.com" "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" "Qwen:token-plan.ap-southeast-1.maas.aliyuncs.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 f487df0..c96634a 100755 --- a/tests/test_fixes.sh +++ b/tests/test_fixes.sh @@ -20,6 +20,7 @@ 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") +QWEN_SECTION=$(awk '/^cat > "\$BIN_DIR\/ai-qwen"/,/^QWENEOF/' "$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") @@ -431,6 +432,43 @@ test_logout_mimo() { rm -rf "$tmp_home" } +# ── ai-qwen: Claude Code через Alibaba Token Plan, ключ вшит ─────────────── +test_qwen_launcher() { + if echo "$QWEN_SECTION" | grep -q 'CLAUDE_CONFIG_DIR="$HOME/.config/ai-setup/cfg/qwen"' \ + && echo "$QWEN_SECTION" | grep -q '^ANTHROPIC_BASE_URL="$qwen_base_url"' \ + && echo "$QWEN_SECTION" | grep -q '^ANTHROPIC_DEFAULT_OPUS_MODEL=qwen3.8-max' \ + && echo "$QWEN_SECTION" | grep -q '^ANTHROPIC_DEFAULT_SONNET_MODEL=qwen3.8-max' \ + && echo "$QWEN_SECTION" | grep -q '^ANTHROPIC_DEFAULT_HAIKU_MODEL=qwen3.8-flash' \ + && echo "$QWEN_SECTION" | grep -q '^CLAUDE_CODE_SUBAGENT_MODEL=qwen3.8-max' \ + && echo "$QWEN_SECTION" | grep -q '^CLAUDE_CODE_MAX_CONTEXT_TOKENS=1000000' \ + && echo "$QWEN_SECTION" | grep -q -- 'claude --dangerously-skip-permissions --model opus --effort "${AI_EFFORT:-max}"'; then + ok "ai-qwen: own config, qwen3.8-max in opus/sonnet/subagents, flash in haiku, 1M window, effort max" + else + fail "ai-qwen: must use cfg/qwen, qwen3.8-max/flash slots, 1M window, --model opus and --effort max" + fi +} + +# ── ai-qwen: стартует сразу со вшитым ключом, ничего не спрашивая ───────── +test_qwen_starts_without_prompt() { + local tmp url token 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 "$QWEN_SECTION" | BIN_DIR="$tmp/bin" bash + printf '#!/usr/bin/env bash\necho "$ANTHROPIC_BASE_URL $ANTHROPIC_AUTH_TOKEN" > "%s/claude.env"\n' "$tmp" > "$tmp/mock/claude" + chmod +x "$tmp/mock/claude" + (cd "$tmp" && HOME="$tmp/home" PATH="$tmp/mock:$PATH" AI_EFFORT= bash "$tmp/bin/ai-qwen" /dev/null 2>&1) || ok_run=0 + read -r url token < "$tmp/claude.env" 2>/dev/null || ok_run=0 + [ "${url:-}" = "https://token-plan.ap-southeast-1.maas.aliyuncs.com/apps/anthropic" ] || ok_run=0 + case "${token:-}" in sk-sp-?*) ;; *) ok_run=0 ;; esac + if [ "$ok_run" -eq 1 ]; then + ok "ai-qwen: starts Claude Code with the built-in endpoint and key, no prompt" + else + fail "ai-qwen: must start Claude Code right away with the built-in Token Plan endpoint and sk-sp- key" + fi + rm -rf "$tmp" +} + # ── ai-glm: default effort is xhigh (still overridable via AI_EFFORT) ────── test_glm_default_effort() { if echo "$GLM_SECTION" | grep -q -- '--effort "${AI_EFFORT:-xhigh}"' \ @@ -656,6 +694,8 @@ test_mimo_launcher test_mimo_region_probe test_mimo_effort test_logout_mimo +test_qwen_launcher +test_qwen_starts_without_prompt test_gemini_native_launcher test_cursor_native_launcher test_global_rules_source_installed