feat: поддержка баланса OpenRouter в statusline + обновление модели-пикера
- Добавлен _openrouter_balance() в ai-api-helpers.sh - Statusline теперь показывает баланс для openrouter лаунчера - Обновлены дефолтные модели в ai-openrouter (Grok 4.20 как opus и т.д.) - Улучшена изоляция конфигов для deepseek/kimi/openrouter Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -170,6 +170,45 @@ for info in infos:
|
||||
parts.append(f'{sym}{total}')
|
||||
if parts:
|
||||
print(' '.join(parts))
|
||||
" 2>/dev/null)
|
||||
if [ -n "$new_balance" ]; then
|
||||
echo "$new_balance" > "$cache_file"
|
||||
fi
|
||||
fi
|
||||
) &
|
||||
fi
|
||||
fi
|
||||
elif [ "${AI_LAUNCHER:-}" = "openrouter" ]; then
|
||||
# --- Баланс OpenRouter ---
|
||||
# Моментально показываем кэшированный остаток, в фоне обновляем через API.
|
||||
cache_file="$HOME/.cache/ai-setup/openrouter_balance"
|
||||
if [ -f "$cache_file" ]; then
|
||||
balance=$(head -1 "$cache_file")
|
||||
[ -n "$balance" ] && printf " \033[38;5;78m%s\033[00m" "$balance"
|
||||
fi
|
||||
|
||||
# Фоновое обновление баланса (не чаще раза в 30 секунд)
|
||||
refresh_ts="$HOME/.cache/ai-setup/openrouter_balance_refresh_ts"
|
||||
now=$(date +%s)
|
||||
last=$(cat "$refresh_ts" 2>/dev/null || echo 0)
|
||||
if [ $(( now - last )) -gt 30 ]; then
|
||||
key_file="$HOME/.config/ai-setup/openrouter_key"
|
||||
if [ -f "$key_file" ]; then
|
||||
echo "$now" > "$refresh_ts" 2>/dev/null
|
||||
(
|
||||
api_key=$(cat "$key_file")
|
||||
resp=$(curl -s --max-time 10 "https://openrouter.ai/api/v1/credits" \
|
||||
-H "Authorization: Bearer $api_key" \
|
||||
-H "Accept: application/json" 2>/dev/null)
|
||||
if [ -n "$resp" ]; then
|
||||
new_balance=$(echo "$resp" | python3 -c "
|
||||
import sys, json
|
||||
d = json.load(sys.stdin)
|
||||
data = d.get('data', {})
|
||||
total = data.get('total_credits', 0) or 0
|
||||
usage = data.get('total_usage', 0) or 0
|
||||
remaining = total - usage
|
||||
print(f'${remaining:.2f}')
|
||||
" 2>/dev/null)
|
||||
if [ -n "$new_balance" ]; then
|
||||
echo "$new_balance" > "$cache_file"
|
||||
@@ -179,7 +218,7 @@ if parts:
|
||||
fi
|
||||
fi
|
||||
else
|
||||
# Рейт-лимиты для НЕ-DeepSeek провайдеров
|
||||
# Рейт-лимиты для НЕ-DeepSeek/OpenRouter провайдеров
|
||||
# Кеш специфичен для провайдера (model_id) И аккаунта (account): лимиты привязаны
|
||||
# к аккаунту, поэтому при переключении /switch-account проценты не должны смешиваться.
|
||||
_cache_key=$(echo "${model_id:-unknown}_${account:-}" | sed 's/[^a-zA-Z0-9._-]/_/g')
|
||||
|
||||
@@ -966,6 +966,39 @@ except Exception as e:
|
||||
" 2>/dev/null || echo -e " \033[0;33m[БАЛАНС]\033[0m Ошибка парсинга ответа"
|
||||
}
|
||||
|
||||
# _openrouter_balance: Query OpenRouter credits API and print balance
|
||||
# Uses /api/v1/credits (works with regular API key too)
|
||||
_openrouter_balance() {
|
||||
local api_key="$1"
|
||||
local response
|
||||
response=$(curl -s --max-time 10 "https://openrouter.ai/api/v1/credits" \
|
||||
-H "Authorization: Bearer $api_key" \
|
||||
-H "Accept: application/json" \
|
||||
2>/dev/null || echo "")
|
||||
if [ -z "$response" ]; then
|
||||
echo -e " \033[0;33m[БАЛАНС]\033[0m Не удалось получить баланс (сеть?)"
|
||||
return 1
|
||||
fi
|
||||
echo "$response" | python3 -c "
|
||||
import sys, json, os
|
||||
try:
|
||||
d = json.load(sys.stdin)
|
||||
data = d.get('data', {})
|
||||
total = data.get('total_credits', 0) or 0
|
||||
usage = data.get('total_usage', 0) or 0
|
||||
remaining = total - usage
|
||||
cache_dir = os.path.expanduser('~/.cache/ai-setup')
|
||||
os.makedirs(cache_dir, exist_ok=True)
|
||||
cache_file = os.path.join(cache_dir, 'openrouter_balance')
|
||||
# Зелёный как у DeepSeek: \033[38;5;78m
|
||||
print(f' \033[1;36m💰 Баланс OpenRouter:\033[0m \033[38;5;78m\${remaining:.2f}\033[0m (загружено \${total:.2f}, потрачено \${usage:.2f})')
|
||||
with open(cache_file, 'w') as f:
|
||||
f.write(f'\${remaining:.2f}\n')
|
||||
except Exception as e:
|
||||
print(f' ⚠️ Не удалось разобрать баланс: {e}')
|
||||
" 2>/dev/null || echo -e " \033[0;33m[БАЛАНС]\033[0m Ошибка парсинга ответа"
|
||||
}
|
||||
|
||||
_handle_openai_api_response() {
|
||||
local provider="$1"
|
||||
local code="$2"
|
||||
@@ -1337,8 +1370,12 @@ trap 'rm -f "$_PROMPT_FILE"' EXIT INT TERM
|
||||
_build_ai_sys_prompt > "$_PROMPT_FILE"
|
||||
export AI_LAUNCHER=deepseek
|
||||
export CLAUDE_CONFIG_DIR="$HOME/.config/ai-setup/cfg/deepseek"
|
||||
# Пикер: только DeepSeek V4 Pro (opus) и DeepSeek V4 Flash (haiku), дефолт - Pro
|
||||
_setup_isolated_config deepseek opus high '["opus", "haiku"]'
|
||||
# Пикер: DeepSeek V4 Pro (opus+sonnet, дефолт) и DeepSeek V4 Flash (haiku).
|
||||
# availableModels НЕ задаём: при кастомном провайдере он схлопывает пикер в Default.
|
||||
# Claude Code навязывает 3 слота opus/sonnet/haiku; незаданный слот показал бы чужой
|
||||
# Claude, поэтому sonnet тоже мапим на Pro (лёгкий дубль, но все пункты - DeepSeek).
|
||||
# FABLE не навязывается - не задаём. DISABLE_1M убирает [1M] дубли из пикера.
|
||||
_setup_isolated_config deepseek opus high ''
|
||||
_apply_effort deepseek high
|
||||
ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic \
|
||||
ANTHROPIC_AUTH_TOKEN="$api_key" \
|
||||
@@ -1346,10 +1383,13 @@ ANTHROPIC_DEFAULT_OPUS_MODEL=deepseek-v4-pro \
|
||||
ANTHROPIC_DEFAULT_OPUS_MODEL_NAME="DeepSeek V4 Pro" \
|
||||
ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION="DeepSeek V4 Pro - флагман для сложных задач" \
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL=deepseek-v4-pro \
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL_NAME="DeepSeek V4 Pro" \
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION="DeepSeek V4 Pro - флагман для сложных задач" \
|
||||
ANTHROPIC_DEFAULT_HAIKU_MODEL=deepseek-v4-flash \
|
||||
ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME="DeepSeek V4 Flash" \
|
||||
ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION="DeepSeek V4 Flash - быстрый и дешёвый" \
|
||||
CLAUDE_CODE_SUBAGENT_MODEL=deepseek-v4-flash \
|
||||
CLAUDE_CODE_DISABLE_1M_CONTEXT=1 \
|
||||
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \
|
||||
claude --dangerously-skip-permissions --system-prompt-file "$_PROMPT_FILE" "$@"
|
||||
DEEPSEEKEOF
|
||||
@@ -1417,8 +1457,11 @@ trap 'rm -f "$_PROMPT_FILE"' EXIT INT TERM
|
||||
_build_ai_sys_prompt > "$_PROMPT_FILE"
|
||||
export AI_LAUNCHER=kimi
|
||||
export CLAUDE_CONFIG_DIR="$HOME/.config/ai-setup/cfg/kimi"
|
||||
# Пикер: единственная модель Kimi K2.6 (под алиасом opus)
|
||||
_setup_isolated_config kimi opus high '["opus"]'
|
||||
# Пикер: Kimi K2.6 - единственная модель провайдера. availableModels НЕ задаём
|
||||
# (он схлопывает пикер в Default). Claude Code навязывает 3 слота opus/sonnet/haiku;
|
||||
# незаданный показал бы чужой Claude, поэтому все три мапим на Kimi K2.6
|
||||
# (в пикере 3 одинаковых пункта, но все - Kimi). DISABLE_1M убирает [1M] дубли.
|
||||
_setup_isolated_config kimi opus high ''
|
||||
_apply_effort kimi high
|
||||
ANTHROPIC_BASE_URL=https://api.kimi.com/coding \
|
||||
ANTHROPIC_AUTH_TOKEN="$api_key" \
|
||||
@@ -1426,8 +1469,13 @@ ANTHROPIC_DEFAULT_OPUS_MODEL=kimi-k2.6 \
|
||||
ANTHROPIC_DEFAULT_OPUS_MODEL_NAME="Kimi K2.6" \
|
||||
ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION="Kimi K2.6 (Moonshot AI)" \
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL=kimi-k2.6 \
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL_NAME="Kimi K2.6" \
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION="Kimi K2.6 (Moonshot AI)" \
|
||||
ANTHROPIC_DEFAULT_HAIKU_MODEL=kimi-k2.6 \
|
||||
ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME="Kimi K2.6" \
|
||||
ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION="Kimi K2.6 (Moonshot AI)" \
|
||||
CLAUDE_CODE_SUBAGENT_MODEL=kimi-k2.6 \
|
||||
CLAUDE_CODE_DISABLE_1M_CONTEXT=1 \
|
||||
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \
|
||||
claude --dangerously-skip-permissions --system-prompt-file "$_PROMPT_FILE" "$@"
|
||||
KIMIEOF
|
||||
@@ -1453,10 +1501,13 @@ if [ -n "$api_key" ]; then
|
||||
rm -f "$key_file"
|
||||
api_key=""
|
||||
elif [ $ret -eq 429 ]; then
|
||||
_openrouter_balance "$api_key"
|
||||
echo -n "Продолжить всё равно? (запросы могут не проходить) [y/N] "
|
||||
read -r _ans; case "${_ans:-N}" in [Yy]*) ;; *) exit 1 ;; esac
|
||||
elif [ $ret -ne 0 ]; then
|
||||
exit 1
|
||||
else
|
||||
_openrouter_balance "$api_key"
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -1474,6 +1525,7 @@ if [ -z "$api_key" ]; then
|
||||
echo "$api_key" > "$key_file"
|
||||
chmod 600 "$key_file"
|
||||
echo "Ключ сохранён."
|
||||
_openrouter_balance "$api_key"
|
||||
if [ $ret -eq 429 ]; then
|
||||
echo -n "Продолжить всё равно? (запросы могут не проходить) [y/N] "
|
||||
read -r _ans; case "${_ans:-N}" in [Yy]*) ;; *) exit 1 ;; esac
|
||||
@@ -1495,19 +1547,30 @@ trap 'rm -f "$_PROMPT_FILE"' EXIT INT TERM
|
||||
_build_ai_sys_prompt > "$_PROMPT_FILE"
|
||||
export AI_LAUNCHER=openrouter
|
||||
export CLAUDE_CONFIG_DIR="$HOME/.config/ai-setup/cfg/openrouter"
|
||||
# openrouter - гибкий лаунчер: пикер не ограничиваем (availableModels пустой),
|
||||
# gpt-5.5 добавляем отдельным пунктом и делаем дефолтом
|
||||
# openrouter - модели, которых НЕТ в других ai-* лаунчерах (без anthropic/deepseek/
|
||||
# kimi/gemini). Пикер строится из 4 алиас-слотов + 1 custom-пункта (потолок Claude Code).
|
||||
# availableModels НЕ задаём (он схлопывает пикер). Дефолт - GPT-5.5 (custom-пункт).
|
||||
export ANTHROPIC_CUSTOM_MODEL_OPTION="openai/gpt-5.5"
|
||||
export ANTHROPIC_CUSTOM_MODEL_OPTION_NAME="GPT-5.5"
|
||||
export ANTHROPIC_CUSTOM_MODEL_OPTION_DESCRIPTION="OpenRouter: openai/gpt-5.5"
|
||||
export ANTHROPIC_CUSTOM_MODEL_OPTION_DESCRIPTION="OpenAI GPT-5.5 (OpenRouter)"
|
||||
_setup_isolated_config openrouter "openai/gpt-5.5" high ''
|
||||
_apply_effort openrouter high
|
||||
ANTHROPIC_BASE_URL=https://openrouter.ai/api \
|
||||
ANTHROPIC_AUTH_TOKEN="$api_key" \
|
||||
ANTHROPIC_DEFAULT_OPUS_MODEL=anthropic/claude-4.8-opus \
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL=anthropic/claude-4.6-sonnet \
|
||||
ANTHROPIC_DEFAULT_HAIKU_MODEL=openai/gpt-5.5 \
|
||||
ANTHROPIC_DEFAULT_OPUS_MODEL=x-ai/grok-4.20 \
|
||||
ANTHROPIC_DEFAULT_OPUS_MODEL_NAME="Grok 4.20" \
|
||||
ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION="xAI Grok 4.20 (OpenRouter)" \
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL=qwen/qwen3.7-max \
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL_NAME="Qwen3.7 Max" \
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION="Qwen3.7 Max (OpenRouter)" \
|
||||
ANTHROPIC_DEFAULT_HAIKU_MODEL=minimax/minimax-m3 \
|
||||
ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME="MiniMax M3" \
|
||||
ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION="MiniMax M3 (OpenRouter)" \
|
||||
ANTHROPIC_DEFAULT_FABLE_MODEL=meta-llama/llama-4-maverick \
|
||||
ANTHROPIC_DEFAULT_FABLE_MODEL_NAME="Llama 4 Maverick" \
|
||||
ANTHROPIC_DEFAULT_FABLE_MODEL_DESCRIPTION="Meta Llama 4 Maverick (OpenRouter)" \
|
||||
CLAUDE_CODE_SUBAGENT_MODEL=openai/gpt-5.5 \
|
||||
CLAUDE_CODE_DISABLE_1M_CONTEXT=1 \
|
||||
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \
|
||||
claude --dangerously-skip-permissions --system-prompt-file "$_PROMPT_FILE" "$@"
|
||||
OPENROUTEREOF
|
||||
|
||||
Reference in New Issue
Block a user