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

@@ -113,7 +113,7 @@ test_glm_official_api() {
test_eltex_claude_launcher() {
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 '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 '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"
@@ -125,19 +125,19 @@ test_eltex_claude_launcher() {
# ── ai-eltex: модель берётся из /v1/models ключа ──────────────────────────
test_eltex_model_from_key() {
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 'pick("anth-glm-5.3", "anth-glm-5.3-")' \
&& echo "$ELTEX_SECTION" | grep -Fq 'model="${ELTEX_MODEL:-${model:-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 'ANTHROPIC_DEFAULT_OPUS_MODEL="$opus_model"' \
&& echo "$ELTEX_SECTION" | grep -q 'ANTHROPIC_DEFAULT_SONNET_MODEL="$model_zai"' \
&& echo "$ELTEX_SECTION" | grep -q 'ANTHROPIC_DEFAULT_HAIKU_MODEL="$model_flash"'; then
ok "ai-eltex: anth-glm-5.3 by default, never the first /v1/models entry (ELTEX_MODEL overrides)"
&& echo "$ELTEX_SECTION" | grep -q 'ANTHROPIC_DEFAULT_SONNET_MODEL="$opus_model"' \
&& echo "$ELTEX_SECTION" | grep -q 'ANTHROPIC_DEFAULT_HAIKU_MODEL="$opus_model"'; then
ok "ai-eltex: glm-5.3 by default, never the first /v1/models entry (ELTEX_MODEL overrides)"
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
}
# ── ai-eltex: три модели ключа по слотам пикера (имена с суффиксом тоже) ─────
# ── ai-eltex: glm-5.3 во всех слотах пикера (имена с суффиксом тоже) ───────
test_eltex_three_slots() {
local tmp ok_run=1 out
tmp=$(mktemp -d)
@@ -154,32 +154,32 @@ echo "SLOTS opus=$ANTHROPIC_DEFAULT_OPUS_MODEL sonnet=$ANTHROPIC_DEFAULT_SONNET_
CLAUDEEOF
chmod +x "$tmp/mock/curl" "$tmp/mock/claude"
echo sk-test > "$tmp/home/.config/ai-setup/eltex_key"
# Как у Бокова: zai-модель под именем с суффиксом, плюс имя группы доступа
# Как на прокси: flash идёт первой, плюс имя группы доступа - во всех слотах glm-5.3
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)
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
# Нет zai и flash - их слоты получают модель по умолчанию
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
# Имена с суффиксом: модель не путается с flash
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)
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
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
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
rm -rf "$tmp"
}
# ── ai-eltex: нет anth-glm-5.3 у ключа -> авто-логаут, свежий ключ не сохраняется ──
# ── ai-eltex: нет glm-5.3 у ключа -> авто-логаут, свежий ключ не сохраняется ──
test_eltex_autologout_on_missing_model() {
local tmp ok_run=1 rc=0 out
tmp=$(mktemp -d)
mkdir -p "$tmp/bin" "$tmp/mock" "$tmp/home/.config/ai-setup"
echo "$HELPERS_SECTION" | HELPERS_FILE="$tmp/bin/ai-api-helpers.sh" bash
echo "$ELTEX_SECTION" | BIN_DIR="$tmp/bin" bash
# Прокси отвечает 200, но anth-glm-5.3 в списке моделей ключа нет
# Прокси отвечает 200, но glm-5.3 в списке моделей ключа нет
cat > "$tmp/mock/curl" <<'CURLEOF'
#!/usr/bin/env bash
if [[ "$*" == *"/v1/models"* ]]; then
@@ -193,16 +193,16 @@ CURLEOF
# Сохранённый ключ: авто-логаут (файл удалён), новый ключ не вводим -> выход 1
echo sk-old > "$tmp/home/.config/ai-setup/eltex_key"
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=$?
[ "$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
[ ! -f "$tmp/home/.config/ai-setup/eltex_key" ] || ok_run=0
# Свежий ключ без anth-glm-5.3 не сохраняется вовсе
# Свежий ключ без glm-5.3 не сохраняется вовсе
rc=0
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=$?
[ "$rc" -eq 1 ] || 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"
rc=0
out=$(HOME="$tmp/home" PATH="$tmp/mock:$PATH" \
MODELS_BODY='{"data":[{"id":"zai-glm-5.3-flash-2"}]}' \
ELTEX_MODEL=anth-glm-5.3-2 bash "$tmp/bin/ai-eltex" 2>&1) || rc=$?
MODELS_BODY='{"data":[{"id":"glm-5.3-flash-2"}]}' \
ELTEX_MODEL=glm-5.3-2 bash "$tmp/bin/ai-eltex" 2>&1) || rc=$?
[ "$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
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
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
rm -rf "$tmp"
}
@@ -235,13 +235,13 @@ test_eltex_no_tool_search() {
# ── ai-eltex: окно 1M, как у ai-glm (иначе ctx: завышен в ~5 раз) ─────────
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_DISABLE_1M_CONTEXT=' \
&& ! 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
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
}