feat(eltex): glm-5.3 via the proxy's raw /anthropic path

Claude Code now talks to the raw /anthropic path of the Eltex proxy,
which forwards requests byte-for-byte to the Z.ai Anthropic endpoint,
the same one ai-glm uses directly:
- images work through Z.ai's server-side analyze_image (Read png ->
  server_tool_use analyze_image -> correct answer, same as ai-glm);
  the regular /v1/messages route converts to the OpenAI endpoint,
  where Z.ai rejects images with 400;
- effort reaches Z.ai: low has no thinking, then medium < high < max;
- prompt cache: ~99% of input is a cache read from the second turn,
  same as ai-glm.
The key check and model pick still use the proxy root /v1/models.

The model is glm-5.3 in every picker slot with [1m] for the 1M
window; the anth-glm-5.3 / zai-glm-5.3 / flash names are gone.
Effort stays max by default via CLI --effort.

Checked on 20.10 on llm-t2.eltex.loc:4000; admins are moving that
config to llm.eltex.loc:4000, which stays the default URL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Виталий Никитенко
2026-10-20 13:19:37 +07:00
parent 0502478081
commit 709e144a60
4 changed files with 75 additions and 84 deletions

View File

@@ -188,7 +188,6 @@ ai-deepseek # без флага - использовать ур
- `max` effort имеет реальный эффект у Anthropic, DeepSeek и GLM; у Kimi это всё тот же thinking on. - `max` effort имеет реальный эффект у Anthropic, DeepSeek и GLM; у Kimi это всё тот же thinking on.
- `low`/`medium` у Kimi фактически не снижают reasoning; у DeepSeek `low` и у GLM `low`/`medium` дают реальный уровень `low` и экономят бюджет. - `low`/`medium` у Kimi фактически не снижают reasoning; у DeepSeek `low` и у GLM `low`/`medium` дают реальный уровень `low` и экономят бюджет.
- У GLM осмысленно выбирать только `low`, `high` (или `xhigh`) и `max`. - У GLM осмысленно выбирать только `low`, `high` (или `xhigh`) и `max`.
- `ai-eltex`: с 09.10 прокси пропускает effort для `anth-glm-5.3`, маппинг тот же, что у `ai-glm` - `ai-eltex` (путь прокси `/anthropic`, 20.10): запрос идёт на Anthropic-эндпоинт Z.ai как есть,
(на стороне Z.ai). Как и `ai-glm`, стартует на `max` через CLI `--effort`, `AI_EFFORT=high ai-eltex` effort у `glm-5.3` работает: `low` - без размышлений, дальше `medium` < `high` < `max`. Как и `ai-glm`, стартует на `max`
работает. У `zai-glm-5.3` и `zai-glm-5.3-flash` (тоже в `/model` у `ai-eltex`, 19.10) effort не работает: через CLI `--effort`, `AI_EFFORT=high ai-eltex` работает. У `ai-eltex-qwen` прокси effort игнорирует, `/effort` и `AI_EFFORT` там ни на что не влияют.
low и max дают одинаковый расход. У `ai-eltex-qwen` прокси effort игнорирует, `/effort` и `AI_EFFORT` там ни на что не влияют.

View File

@@ -161,7 +161,7 @@ bash tests/test_network.sh # полные тесты
- `ai-deepseek` - Claude Code через DeepSeek API - `ai-deepseek` - Claude Code через DeepSeek API
- `ai-kimi` - Claude Code через официальный Kimi Code API (`api.kimi.com/coding`) - `ai-kimi` - Claude Code через официальный Kimi Code API (`api.kimi.com/coding`)
- `ai-glm` - Claude Code через Zhipu AI GLM 5.3 API (`api.z.ai/api/anthropic`) - `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)), три модели в `/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` - Claude Code через корпоративный LLM-прокси Eltex (`http://llm.eltex.loc:4000`, LiteLLM, team-подписка GLM). Ключ выдают админы ([дока](https://intdocs.eltex.loc/pages/viewpage.action?pageId=262329878)), Claude Code ходит в сырой путь прокси `/anthropic`, который пересылает запросы на Anthropic-эндпоинт Z.ai как есть; модель `glm-5.3` во всех слотах `/model` (картинки через серверный `analyze_image`, как в `ai-glm`, effort, окно 1M); если ключу недоступна `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-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-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-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, картинки видят обе модели

View File

@@ -1731,13 +1731,11 @@ _eltex_check_key() {
_handle_openai_api_response "Eltex LLM" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Лимит team-подписки исчерпан - обратись к админам" _handle_openai_api_response "Eltex LLM" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Лимит team-подписки исчерпан - обратись к админам"
} }
# _eltex_pick_models: три модели ключа из _CLAUDE_TEST_BODY (последний ответ /v1/models) # _eltex_pick_models: модель glm-5.3 из _CLAUDE_TEST_BODY (последний ответ /v1/models).
# для слотов пикера: model - anth-glm-5.3 (по умолчанию), model_zai - glm-5.3 через # Если имя с суффиксом (glm-5.3-2), берём точное имя, иначе первое с тем же префиксом, кроме
# OpenAI-эндпоинт, model_flash - zai-glm-5.3-flash. У части ключей имена с суффиксом # flash; имена групп доступа (ZAI-Test-Group) так не попадут. Список не распарсился
# (zai-glm-5.3-2-2), поэтому точное имя, иначе первое с тем же префиксом; имена групп # (429/сетевая ошибка/5xx) - стандартное имя наугад. Распарсился, но glm-5.3 нет - ошибка
# доступа (ZAI-Test-Group) так не попадут. Список не распарсился (429/сетевая ошибка/5xx) - # в stderr, model пустой, возврат 1: дефолтный запуск невозможен.
# стандартные имена наугад. Распарсился, но anth-glm-5.3 нет - ошибка в stderr, model
# пустой, возврат 1: дефолтный запуск невозможен. Нет zai/flash - слот получит model.
_eltex_pick_models() { _eltex_pick_models() {
local picked avail local picked avail
if ! picked=$(echo "$_CLAUDE_TEST_BODY" | python3 -c ' if ! picked=$(echo "$_CLAUDE_TEST_BODY" | python3 -c '
@@ -1747,16 +1745,15 @@ def pick(exact, prefix, skip=None):
if exact in ids: if exact in ids:
return exact return exact
return next((i for i in ids if i.startswith(prefix) and not (skip and skip in i)), "-") return next((i for i in ids if i.startswith(prefix) and not (skip and skip in i)), "-")
print(pick("anth-glm-5.3", "anth-glm-5.3-"), pick("zai-glm-5.3", "zai-glm-5.3-", "flash"), print(pick("glm-5.3", "glm-5.3-", "flash"), " ".join(ids) or "(пусто)")' 2>/dev/null); then
pick("zai-glm-5.3-flash", "zai-glm-5.3-flash"), " ".join(ids) or "(пусто)")' 2>/dev/null); then model="glm-5.3"
model="anth-glm-5.3"; model_zai="zai-glm-5.3"; model_flash="zai-glm-5.3-flash"
return 0 return 0
fi fi
read -r model model_zai model_flash avail <<< "$picked" read -r model avail <<< "$picked"
if [ "$model" = "-" ]; then if [ "$model" = "-" ]; then
model="" model=""
echo -e "\033[0;31m[МОДЕЛЬ]\033[0m Ключу недоступна модель anth-glm-5.3. Прокси отдаёт: $avail" >&2 echo -e "\033[0;31m[МОДЕЛЬ]\033[0m Ключу недоступна модель glm-5.3. Прокси отдаёт: $avail" >&2
echo " Напиши админам про доступ к anth-glm-5.3 или задай модель явно: ELTEX_MODEL=<имя> ai-eltex" >&2 echo " Напиши админам про доступ к glm-5.3 или задай модель явно: ELTEX_MODEL=<имя> ai-eltex" >&2
return 1 return 1
fi fi
return 0 return 0
@@ -1776,7 +1773,7 @@ if [ -n "$api_key" ]; then
elif [ $ret -ne 0 ]; then elif [ $ret -ne 0 ]; then
exit 1 exit 1
elif [ -z "${ELTEX_MODEL:-}" ] && ! _eltex_pick_models; then elif [ -z "${ELTEX_MODEL:-}" ] && ! _eltex_pick_models; then
# Ключ валиден, но anth-glm-5.3 недоступна (не та группа доступа) - авто-логаут, # Ключ валиден, но glm-5.3 недоступна (не та группа доступа) - авто-логаут,
# как при 401: такой ключ не годится для дефолтного запуска, сразу просим новый. # как при 401: такой ключ не годится для дефолтного запуска, сразу просим новый.
echo " Сохранённый ключ удалён (авто-логаут)." echo " Сохранённый ключ удалён (авто-логаут)."
rm -f "$key_file" rm -f "$key_file"
@@ -1817,40 +1814,36 @@ if [ -z "$api_key" ]; then
fi fi
fi fi
# Модель по умолчанию: ELTEX_MODEL, иначе anth-glm-5.3 - деплойменты на Anthropic-эндпоинте # Модель: ELTEX_MODEL, иначе glm-5.3. Claude Code ходит в сырой путь прокси /anthropic: он
# Z.ai: настоящая glm-5.3, картинки через серверный analyze_image, effort работает. # байт-в-байт пересылает запрос на Anthropic-эндпоинт Z.ai (api.z.ai/api/anthropic), как у
# Первую модель из /v1/models НЕ берём: там бывают не те модели и имена групп доступа # ai-glm, поэтому картинки работают через серверный analyze_image (20.10: Read png ->
# (ZAI-Test-Group), которые не запустить. Если у ключа нет anth-glm-5.3 - авто-логаут выше # server_tool_use analyze_image -> верный ответ, так же, как напрямую). Обычный /v1/messages
# (свежевведённый такой ключ не сохраняется), вместо 400/403 внутри Claude Code. # прокси для этого не годится: LiteLLM в нём режет картинки. Первую модель из
# Модели не выбирались (429/сетевая ошибка или задан ELTEX_MODEL) - выбираем здесь. # /v1/models НЕ берём: там бывают не те модели и имена групп доступа (ZAI-Test-Group),
# которые не запустить. Если у ключа нет glm-5.3 - авто-логаут выше (свежевведённый такой
# ключ не сохраняется), вместо 400/403 внутри Claude Code. Модель не выбиралась
# (429/сетевая ошибка или задан ELTEX_MODEL) - выбираем здесь.
[ -n "${model:-}" ] || _eltex_pick_models 2>/dev/null || true [ -n "${model:-}" ] || _eltex_pick_models 2>/dev/null || true
model="${ELTEX_MODEL:-${model:-anth-glm-5.3}}" model="${ELTEX_MODEL:-${model:-glm-5.3}}"
[ "${model_zai:--}" = "-" ] && model_zai="$model" # Окно 1M через суффикс [1m], а не глобальным CLAUDE_CODE_MAX_CONTEXT_TOKENS (у glm-5.3 на
[ "${model_flash:--}" = "-" ] && model_flash="$model" # Anthropic-эндпоинте Z.ai 07.10 прошёл запрос на 598K); суффикс Claude Code в API не шлёт.
# Окно контекста по модели, а не глобальным CLAUDE_CODE_MAX_CONTEXT_TOKENS: у glm-5.3 на
# Anthropic-эндпоинте 1M (07.10 через прокси прошёл запрос на 598K), суффикс [1m] Claude Code
# в API не шлёт. zai-glm-5.3 и flash - стандартные 200K с автокомпактом (flash 19.10 принял
# 173K; окно больше 200K у них не проверено, а за пределом прокси отвечает "Prompt exceeds
# max length" вместо компакта).
opus_model="$model" opus_model="$model"
case "$model" in anth-glm-5.3*) opus_model="${model}[1m]" ;; esac case "$model" in glm-5.3*) opus_model="${model}[1m]" ;; esac
echo "Модель: $model" echo "Модель: $model"
echo "В /model также: $model_zai (OpenAI-эндпоинт, без картинок), $model_flash (быстрая)"
_PROMPT_FILE=$(mktemp /tmp/ai-sys-prompt.XXXXXX) _PROMPT_FILE=$(mktemp /tmp/ai-sys-prompt.XXXXXX)
trap 'rm -f "$_PROMPT_FILE"' EXIT INT TERM trap 'rm -f "$_PROMPT_FILE"' EXIT INT TERM
_build_ai_sys_prompt > "$_PROMPT_FILE" _build_ai_sys_prompt > "$_PROMPT_FILE"
export AI_LAUNCHER=eltex export AI_LAUNCHER=eltex
export CLAUDE_CONFIG_DIR="$HOME/.config/ai-setup/cfg/eltex" export CLAUDE_CONFIG_DIR="$HOME/.config/ai-setup/cfg/eltex"
# Пикер: opus = anth-glm-5.3 (по умолчанию), sonnet = zai-glm-5.3 (OpenAI-эндпоинт: без # Пикер: все слоты (opus, sonnet, haiku) = glm-5.3, других моделей ai-eltex не использует.
# картинок и effort), haiku = zai-glm-5.3-flash (видит картинки сама, effort нет; слот haiku # Незаданный слот показал бы чужой Claude, а haiku Claude Code берёт и для фоновых задач
# Claude Code берёт и для фоновых задач вроде заголовков - на flash это дешевле). # вроде заголовков. availableModels НЕ задаём (он схлопывает пикер).
# availableModels НЕ задаём (он схлопывает пикер); незаданный слот показал бы чужой Claude.
# Effort как у ai-glm: старт на max через CLI --effort (перебивает effortLevel из # Effort как у ai-glm: старт на max через CLI --effort (перебивает effortLevel из
# settings.json; AI_EFFORT=max в settings Claude Code не принимает и шлёт high), /effort # settings.json; AI_EFFORT=max в settings Claude Code не принимает и шлёт high), /effort
# в сессии работает. С 09.10 прокси пропускает output_config.effort для anth-glm-5.3: # в сессии работает. Z.ai учитывает output_config.effort (20.10 через /anthropic):
# low/medium/high/max = 68/83/~4-6K/~4-5K выходных токенов (Z.ai: low,medium->low; # low - без размышлений, дальше medium < high < max.
# high,xhigh->high; max). CLAUDE_CODE_ALWAYS_ENABLE_EFFORT - страховка, как в ai-glm. # CLAUDE_CODE_ALWAYS_ENABLE_EFFORT - страховка, как в ai-glm.
# Имена-алиасы claude-* на прокси НЕ используем: claude-3-5-sonnet Claude Code считает # Имена-алиасы claude-* на прокси НЕ используем: claude-3-5-sonnet Claude Code считает
# старым Claude 3.5 Sonnet (без effort и thinking, max_tokens=8192); у claude-5-5-sonnet # старым Claude 3.5 Sonnet (без effort и thinking, max_tokens=8192); у claude-5-5-sonnet
# один деплоймент на всех, модели говорят, что она Claude Sonnet, а statusline показывает # один деплоймент на всех, модели говорят, что она Claude Sonnet, а statusline показывает
@@ -1858,11 +1851,10 @@ export CLAUDE_CONFIG_DIR="$HOME/.config/ai-setup/cfg/eltex"
# "You are powered by": разброс внутри одного имени больше разницы между именами). # "You are powered by": разброс внутри одного имени больше разницы между именами).
# CLAUDE_CODE_MAX_OUTPUT_TOKENS=64000 вместо дефолтных 32000, как у ai-glm. # CLAUDE_CODE_MAX_OUTPUT_TOKENS=64000 вместо дефолтных 32000, как у ai-glm.
# API_TIMEOUT_MS из доки админов: прокси бывает медленным. # API_TIMEOUT_MS из доки админов: прокси бывает медленным.
# Окно anth-glm-5.3 - 1M через [1m] (см. выше). Автокомпакт на 200K из доки админов и # Окно glm-5.3 - 1M через [1m] (см. выше). Автокомпакт на 200K из доки админов и
# CLAUDE_CODE_DISABLE_1M_CONTEXT не ставим: ctx: показывал бы занятость в ~5 раз больше # CLAUDE_CODE_DISABLE_1M_CONTEXT не ставим: ctx: показывал бы занятость в ~5 раз больше
# реальной, а ответ с analyze_image (Z.ai считает usage за оба прохода модели, ~x2) мог бы # реальной.
# досрочно запустить компакт. # ENABLE_TOOL_SEARCH НЕ включаем: на Anthropic-эндпоинте Z.ai
# ENABLE_TOOL_SEARCH НЕ включаем: на anthropic-деплойментах прокси (Z.ai /api/anthropic)
# GLM с ним путается - ищет Read/Bash через ToolSearch и решает, что их нет. Цена - # GLM с ним путается - ищет Read/Bash через ToolSearch и решает, что их нет. Цена -
# схемы встроенных тулов в каждом запросе (~+11K входных токенов). # схемы встроенных тулов в каждом запросе (~+11K входных токенов).
_setup_isolated_config eltex opus xhigh '' _setup_isolated_config eltex opus xhigh ''
@@ -1876,17 +1868,17 @@ description: Удалить сохранённый ключ ai-eltex (обраб
--- ---
Хук не сработал: удали ~/.config/ai-setup/eltex_key вручную и перезапусти ai-eltex (или запусти AI_RESET_KEY=1 ai-eltex). Хук не сработал: удали ~/.config/ai-setup/eltex_key вручную и перезапусти ai-eltex (или запусти AI_RESET_KEY=1 ai-eltex).
CMDEOF CMDEOF
ANTHROPIC_BASE_URL="$eltex_base_url" \ ANTHROPIC_BASE_URL="$eltex_base_url/anthropic" \
ANTHROPIC_AUTH_TOKEN="$api_key" \ ANTHROPIC_AUTH_TOKEN="$api_key" \
ANTHROPIC_DEFAULT_OPUS_MODEL="$opus_model" \ ANTHROPIC_DEFAULT_OPUS_MODEL="$opus_model" \
ANTHROPIC_DEFAULT_OPUS_MODEL_NAME="$model" \ ANTHROPIC_DEFAULT_OPUS_MODEL_NAME="$model" \
ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION="GLM 5.3, Anthropic-эндпоинт: картинки, effort, окно 1M" \ ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION="GLM 5.3: картинки, effort, окно 1M" \
ANTHROPIC_DEFAULT_SONNET_MODEL="$model_zai" \ ANTHROPIC_DEFAULT_SONNET_MODEL="$opus_model" \
ANTHROPIC_DEFAULT_SONNET_MODEL_NAME="$model_zai" \ ANTHROPIC_DEFAULT_SONNET_MODEL_NAME="$model" \
ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION="GLM 5.3, OpenAI-эндпоинт: без картинок и effort, окно 200K" \ ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION="GLM 5.3: картинки, effort, окно 1M" \
ANTHROPIC_DEFAULT_HAIKU_MODEL="$model_flash" \ ANTHROPIC_DEFAULT_HAIKU_MODEL="$opus_model" \
ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME="$model_flash" \ ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME="$model" \
ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION="GLM 5.3 Flash: быстрая, картинки видит сама, без effort, окно 200K" \ ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION="GLM 5.3: картинки, effort, окно 1M" \
CLAUDE_CODE_SUBAGENT_MODEL="$opus_model" \ CLAUDE_CODE_SUBAGENT_MODEL="$opus_model" \
CLAUDE_CODE_MAX_OUTPUT_TOKENS=64000 \ CLAUDE_CODE_MAX_OUTPUT_TOKENS=64000 \
CLAUDE_CODE_ALWAYS_ENABLE_EFFORT=1 \ CLAUDE_CODE_ALWAYS_ENABLE_EFFORT=1 \

View File

@@ -113,7 +113,7 @@ test_glm_official_api() {
test_eltex_claude_launcher() { test_eltex_claude_launcher() {
if echo "$ELTEX_SECTION" | grep -q 'claude --dangerously-skip-permissions' \ if echo "$ELTEX_SECTION" | grep -q 'claude --dangerously-skip-permissions' \
&& echo "$ELTEX_SECTION" | grep -q 'ELTEX_BASE_URL:-http://llm.eltex.loc:4000' \ && echo "$ELTEX_SECTION" | grep -q 'ELTEX_BASE_URL:-http://llm.eltex.loc:4000' \
&& echo "$ELTEX_SECTION" | grep -q 'ANTHROPIC_BASE_URL="$eltex_base_url"' \ && echo "$ELTEX_SECTION" | grep -q 'ANTHROPIC_BASE_URL="$eltex_base_url/anthropic"' \
&& echo "$ELTEX_SECTION" | grep -q 'CLAUDE_CONFIG_DIR="$HOME/.config/ai-setup/cfg/eltex"' \ && echo "$ELTEX_SECTION" | grep -q 'CLAUDE_CONFIG_DIR="$HOME/.config/ai-setup/cfg/eltex"' \
&& echo "$ELTEX_SECTION" | grep -q 'key_file="$HOME/.config/ai-setup/eltex_key"'; then && echo "$ELTEX_SECTION" | grep -q 'key_file="$HOME/.config/ai-setup/eltex_key"'; then
ok "ai-eltex: launches claude via llm.eltex.loc with its own key and config dir" ok "ai-eltex: launches claude via llm.eltex.loc with its own key and config dir"
@@ -125,19 +125,19 @@ test_eltex_claude_launcher() {
# ── ai-eltex: модель берётся из /v1/models ключа ────────────────────────── # ── ai-eltex: модель берётся из /v1/models ключа ──────────────────────────
test_eltex_model_from_key() { test_eltex_model_from_key() {
if echo "$ELTEX_SECTION" | grep -q '/v1/models' \ if echo "$ELTEX_SECTION" | grep -q '/v1/models' \
&& echo "$ELTEX_SECTION" | grep -Fq 'model="${ELTEX_MODEL:-${model:-anth-glm-5.3}}"' \ && echo "$ELTEX_SECTION" | grep -Fq 'model="${ELTEX_MODEL:-${model:-glm-5.3}}"' \
&& echo "$ELTEX_SECTION" | grep -Fq 'pick("anth-glm-5.3", "anth-glm-5.3-")' \ && echo "$ELTEX_SECTION" | grep -Fq 'pick("glm-5.3", "glm-5.3-", "flash")' \
&& ! echo "$ELTEX_SECTION" | grep -q 'ids\[0\]' \ && ! echo "$ELTEX_SECTION" | grep -q 'ids\[0\]' \
&& echo "$ELTEX_SECTION" | grep -q 'ANTHROPIC_DEFAULT_OPUS_MODEL="$opus_model"' \ && echo "$ELTEX_SECTION" | grep -q 'ANTHROPIC_DEFAULT_OPUS_MODEL="$opus_model"' \
&& echo "$ELTEX_SECTION" | grep -q 'ANTHROPIC_DEFAULT_SONNET_MODEL="$model_zai"' \ && echo "$ELTEX_SECTION" | grep -q 'ANTHROPIC_DEFAULT_SONNET_MODEL="$opus_model"' \
&& echo "$ELTEX_SECTION" | grep -q 'ANTHROPIC_DEFAULT_HAIKU_MODEL="$model_flash"'; then && echo "$ELTEX_SECTION" | grep -q 'ANTHROPIC_DEFAULT_HAIKU_MODEL="$opus_model"'; then
ok "ai-eltex: anth-glm-5.3 by default, never the first /v1/models entry (ELTEX_MODEL overrides)" ok "ai-eltex: glm-5.3 by default, never the first /v1/models entry (ELTEX_MODEL overrides)"
else else
fail "ai-eltex: must default to anth-glm-5.3 (not the first /v1/models entry), ELTEX_MODEL overrides" fail "ai-eltex: must default to glm-5.3 (not the first /v1/models entry), ELTEX_MODEL overrides"
fi fi
} }
# ── ai-eltex: три модели ключа по слотам пикера (имена с суффиксом тоже) ───── # ── ai-eltex: glm-5.3 во всех слотах пикера (имена с суффиксом тоже) ───────
test_eltex_three_slots() { test_eltex_three_slots() {
local tmp ok_run=1 out local tmp ok_run=1 out
tmp=$(mktemp -d) tmp=$(mktemp -d)
@@ -154,32 +154,32 @@ echo "SLOTS opus=$ANTHROPIC_DEFAULT_OPUS_MODEL sonnet=$ANTHROPIC_DEFAULT_SONNET_
CLAUDEEOF CLAUDEEOF
chmod +x "$tmp/mock/curl" "$tmp/mock/claude" chmod +x "$tmp/mock/curl" "$tmp/mock/claude"
echo sk-test > "$tmp/home/.config/ai-setup/eltex_key" echo sk-test > "$tmp/home/.config/ai-setup/eltex_key"
# Как у Бокова: zai-модель под именем с суффиксом, плюс имя группы доступа # Как на прокси: flash идёт первой, плюс имя группы доступа - во всех слотах glm-5.3
out=$(HOME="$tmp/home" PATH="$tmp/mock:$PATH" \ out=$(HOME="$tmp/home" PATH="$tmp/mock:$PATH" \
MODELS_BODY='{"data":[{"id":"zai-glm-5.3-flash"},{"id":"anth-glm-5.3"},{"id":"zai-glm-5.3-2-2"},{"id":"ZAI-Test-Group"}]}' \ MODELS_BODY='{"data":[{"id":"glm-5.3-flash"},{"id":"glm-5.3"},{"id":"ZAI-Test-Group"}]}' \
bash "$tmp/bin/ai-eltex" 2>&1) bash "$tmp/bin/ai-eltex" 2>&1)
echo "$out" | grep -Fq 'SLOTS opus=anth-glm-5.3[1m] sonnet=zai-glm-5.3-2-2 haiku=zai-glm-5.3-flash sub=anth-glm-5.3[1m]' || ok_run=0 echo "$out" | grep -Fq 'SLOTS opus=glm-5.3[1m] sonnet=glm-5.3[1m] haiku=glm-5.3[1m] sub=glm-5.3[1m]' || ok_run=0
# Нет zai и flash - их слоты получают модель по умолчанию # Имена с суффиксом: модель не путается с flash
out=$(HOME="$tmp/home" PATH="$tmp/mock:$PATH" \ out=$(HOME="$tmp/home" PATH="$tmp/mock:$PATH" \
MODELS_BODY='{"data":[{"id":"anth-glm-5.3"},{"id":"ZAI-Test-Group"}]}' \ MODELS_BODY='{"data":[{"id":"glm-5.3-flash-2"},{"id":"glm-5.3-2"}]}' \
bash "$tmp/bin/ai-eltex" 2>&1) bash "$tmp/bin/ai-eltex" 2>&1)
echo "$out" | grep -Fq 'SLOTS opus=anth-glm-5.3[1m] sonnet=anth-glm-5.3 haiku=anth-glm-5.3' || ok_run=0 echo "$out" | grep -Fq 'SLOTS opus=glm-5.3-2[1m] sonnet=glm-5.3-2[1m] haiku=glm-5.3-2[1m]' || ok_run=0
if [ "$ok_run" -eq 1 ]; then if [ "$ok_run" -eq 1 ]; then
ok "ai-eltex: picker slots anth-glm-5.3[1m] / zai-glm-5.3* / zai-glm-5.3-flash, missing ones fall back to default" ok "ai-eltex: glm-5.3[1m] in every picker slot, flash never picked"
else else
fail "ai-eltex: picker slots must be anth-glm-5.3[1m] / zai-glm-5.3* / zai-glm-5.3-flash with fallback to default" fail "ai-eltex: every picker slot must be glm-5.3[1m], flash must never be picked"
fi fi
rm -rf "$tmp" rm -rf "$tmp"
} }
# ── ai-eltex: нет anth-glm-5.3 у ключа -> авто-логаут, свежий ключ не сохраняется ── # ── ai-eltex: нет glm-5.3 у ключа -> авто-логаут, свежий ключ не сохраняется ──
test_eltex_autologout_on_missing_model() { test_eltex_autologout_on_missing_model() {
local tmp ok_run=1 rc=0 out local tmp ok_run=1 rc=0 out
tmp=$(mktemp -d) tmp=$(mktemp -d)
mkdir -p "$tmp/bin" "$tmp/mock" "$tmp/home/.config/ai-setup" mkdir -p "$tmp/bin" "$tmp/mock" "$tmp/home/.config/ai-setup"
echo "$HELPERS_SECTION" | HELPERS_FILE="$tmp/bin/ai-api-helpers.sh" bash echo "$HELPERS_SECTION" | HELPERS_FILE="$tmp/bin/ai-api-helpers.sh" bash
echo "$ELTEX_SECTION" | BIN_DIR="$tmp/bin" bash echo "$ELTEX_SECTION" | BIN_DIR="$tmp/bin" bash
# Прокси отвечает 200, но anth-glm-5.3 в списке моделей ключа нет # Прокси отвечает 200, но glm-5.3 в списке моделей ключа нет
cat > "$tmp/mock/curl" <<'CURLEOF' cat > "$tmp/mock/curl" <<'CURLEOF'
#!/usr/bin/env bash #!/usr/bin/env bash
if [[ "$*" == *"/v1/models"* ]]; then if [[ "$*" == *"/v1/models"* ]]; then
@@ -193,16 +193,16 @@ CURLEOF
# Сохранённый ключ: авто-логаут (файл удалён), новый ключ не вводим -> выход 1 # Сохранённый ключ: авто-логаут (файл удалён), новый ключ не вводим -> выход 1
echo sk-old > "$tmp/home/.config/ai-setup/eltex_key" echo sk-old > "$tmp/home/.config/ai-setup/eltex_key"
out=$(printf 'n\n' | HOME="$tmp/home" PATH="$tmp/mock:$PATH" \ out=$(printf 'n\n' | HOME="$tmp/home" PATH="$tmp/mock:$PATH" \
MODELS_BODY='{"data":[{"id":"zai-glm-5.3-flash-2"},{"id":"ZAI-Test-Group"}]}' \ MODELS_BODY='{"data":[{"id":"glm-5.3-flash-2"},{"id":"ZAI-Test-Group"}]}' \
bash "$tmp/bin/ai-eltex" 2>&1) || rc=$? bash "$tmp/bin/ai-eltex" 2>&1) || rc=$?
[ "$rc" -eq 1 ] || ok_run=0 [ "$rc" -eq 1 ] || ok_run=0
echo "$out" | grep -q 'Ключу недоступна модель anth-glm-5.3' || ok_run=0 echo "$out" | grep -q 'Ключу недоступна модель glm-5.3' || ok_run=0
echo "$out" | grep -q 'авто-логаут' || ok_run=0 echo "$out" | grep -q 'авто-логаут' || ok_run=0
[ ! -f "$tmp/home/.config/ai-setup/eltex_key" ] || ok_run=0 [ ! -f "$tmp/home/.config/ai-setup/eltex_key" ] || ok_run=0
# Свежий ключ без anth-glm-5.3 не сохраняется вовсе # Свежий ключ без glm-5.3 не сохраняется вовсе
rc=0 rc=0
out=$(printf 'sk-new\n' | HOME="$tmp/home" PATH="$tmp/mock:$PATH" \ out=$(printf 'sk-new\n' | HOME="$tmp/home" PATH="$tmp/mock:$PATH" \
MODELS_BODY='{"data":[{"id":"zai-glm-5.3-flash-2"}]}' \ MODELS_BODY='{"data":[{"id":"glm-5.3-flash-2"}]}' \
bash "$tmp/bin/ai-eltex" 2>&1) || rc=$? bash "$tmp/bin/ai-eltex" 2>&1) || rc=$?
[ "$rc" -eq 1 ] || ok_run=0 [ "$rc" -eq 1 ] || ok_run=0
echo "$out" | grep -q 'Ключ НЕ сохранён' || ok_run=0 echo "$out" | grep -q 'Ключ НЕ сохранён' || ok_run=0
@@ -211,15 +211,15 @@ CURLEOF
echo sk-keep > "$tmp/home/.config/ai-setup/eltex_key" echo sk-keep > "$tmp/home/.config/ai-setup/eltex_key"
rc=0 rc=0
out=$(HOME="$tmp/home" PATH="$tmp/mock:$PATH" \ out=$(HOME="$tmp/home" PATH="$tmp/mock:$PATH" \
MODELS_BODY='{"data":[{"id":"zai-glm-5.3-flash-2"}]}' \ MODELS_BODY='{"data":[{"id":"glm-5.3-flash-2"}]}' \
ELTEX_MODEL=anth-glm-5.3-2 bash "$tmp/bin/ai-eltex" 2>&1) || rc=$? ELTEX_MODEL=glm-5.3-2 bash "$tmp/bin/ai-eltex" 2>&1) || rc=$?
[ "$rc" -eq 0 ] || ok_run=0 [ "$rc" -eq 0 ] || ok_run=0
echo "$out" | grep -q 'Модель: anth-glm-5.3-2' || ok_run=0 echo "$out" | grep -q 'Модель: glm-5.3-2' || ok_run=0
[ "$(cat "$tmp/home/.config/ai-setup/eltex_key")" = "sk-keep" ] || ok_run=0 [ "$(cat "$tmp/home/.config/ai-setup/eltex_key")" = "sk-keep" ] || ok_run=0
if [ "$ok_run" -eq 1 ]; then if [ "$ok_run" -eq 1 ]; then
ok "ai-eltex: missing anth-glm-5.3 auto-logouts saved key, fresh key not saved, ELTEX_MODEL bypasses" ok "ai-eltex: missing glm-5.3 auto-logouts saved key, fresh key not saved, ELTEX_MODEL bypasses"
else else
fail "ai-eltex: missing anth-glm-5.3 must auto-logout (saved key removed, fresh key not saved, ELTEX_MODEL bypass)" fail "ai-eltex: missing glm-5.3 must auto-logout (saved key removed, fresh key not saved, ELTEX_MODEL bypass)"
fi fi
rm -rf "$tmp" rm -rf "$tmp"
} }
@@ -235,13 +235,13 @@ test_eltex_no_tool_search() {
# ── ai-eltex: окно 1M, как у ai-glm (иначе ctx: завышен в ~5 раз) ───────── # ── ai-eltex: окно 1M, как у ai-glm (иначе ctx: завышен в ~5 раз) ─────────
test_eltex_context_window() { test_eltex_context_window() {
if echo "$ELTEX_SECTION" | grep -Fq 'anth-glm-5.3*) opus_model="${model}[1m]"' \ if echo "$ELTEX_SECTION" | grep -Fq 'glm-5.3*) opus_model="${model}[1m]"' \
&& ! echo "$ELTEX_SECTION" | grep -q '^CLAUDE_CODE_MAX_CONTEXT_TOKENS=' \ && ! echo "$ELTEX_SECTION" | grep -q '^CLAUDE_CODE_MAX_CONTEXT_TOKENS=' \
&& ! echo "$ELTEX_SECTION" | grep -q '^CLAUDE_CODE_DISABLE_1M_CONTEXT=' \ && ! echo "$ELTEX_SECTION" | grep -q '^CLAUDE_CODE_DISABLE_1M_CONTEXT=' \
&& ! echo "$ELTEX_SECTION" | grep -q '^CLAUDE_CODE_AUTO_COMPACT_WINDOW='; then && ! echo "$ELTEX_SECTION" | grep -q '^CLAUDE_CODE_AUTO_COMPACT_WINDOW='; then
ok "ai-eltex: 1M window only for anth-glm-5.3 via [1m], no global MAX_CONTEXT / DISABLE_1M" ok "ai-eltex: 1M window for glm-5.3 via [1m], no global MAX_CONTEXT / DISABLE_1M"
else else
fail "ai-eltex: 1M only for anth-glm-5.3 via [1m], no global CLAUDE_CODE_MAX_CONTEXT_TOKENS / DISABLE_1M / AUTO_COMPACT_WINDOW" fail "ai-eltex: 1M for glm-5.3 via [1m], no global CLAUDE_CODE_MAX_CONTEXT_TOKENS / DISABLE_1M / AUTO_COMPACT_WINDOW"
fi fi
} }