From a74597dba60028e52da0977af5e996846ce757a9 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, 23 Jun 2026 08:41:38 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20ai-opencode=20=D0=BB=D0=B0=D1=83=D0=BD=D1=87?= =?UTF-8?q?=D0=B5=D1=80=20(OpenCode=20Go=20+=20routatic-proxy)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Новый лаунчер ai-opencode с routatic-proxy на 127.0.0.1:3456 - Автоустановка routatic-proxy под linux/darwin amd64/arm64 - Модели: deepseek-v4-pro (opus/sonnet), kimi-k2.6 (haiku) - AI_RESET_KEY, OPENCODE_GO_API_KEY env fallback, управление opencode_key - Документация: EFFORT_MAPPING.md, QUICK_START.md, README.md - Статус-проверка в setup.sh: ai-opencode, opencode_key, opencode.ai - Тесты: 4 новых теста для ai-opencode в test_fixes.sh Co-Authored-By: Claude --- EFFORT_MAPPING.md | 31 ++++++++--- QUICK_START.md | 2 +- README.md | 6 +- scripts/ai-setup.sh | 131 ++++++++++++++++++++++++++++++++++++++++++++ setup.sh | 6 +- tests/test_fixes.sh | 48 ++++++++++++++++ 6 files changed, 209 insertions(+), 15 deletions(-) diff --git a/EFFORT_MAPPING.md b/EFFORT_MAPPING.md index 5a309c9..df09d8d 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-claude`, `ai-deepseek`, `ai-kimi`, `ai-glm`, `ai-openrouter`, `ai-opencode`. Нативные лаунчеры `ai-gpt` (OpenAI Codex) и `ai-gemini` (Antigravity `agy`) не используют эту схему. У них свои настройки reasoning внутри соответствующего CLI. @@ -12,18 +12,23 @@ ## Таблица маппинга -| Claude Code `/effort` | Anthropic (`ai-claude`) | DeepSeek V4 (`ai-deepseek`) | Kimi K2.7/K2.6 (`ai-kimi`) | GLM 5.2 (`ai-glm`) | OpenRouter/GPT-5.5 (`ai-openrouter`) | -|:---:|:---:|:---:|:---:|:---:|:---:| -| `low` | нативно `low` | провайдер поднимает до `high` | thinking on | на стороне сервера | без локального маппинга | -| `medium` | нативно `medium` | провайдер поднимает до `high` | thinking on | на стороне сервера | без локального маппинга | -| `high` | нативно `high` | нативно `high` | thinking on | на стороне сервера | без локального маппинга | -| `xhigh` | нативно `xhigh` | провайдер поднимает до `max` | thinking on | на стороне сервера | без локального маппинга | -| `max` | нативно `max` | нативно `max` | thinking on | на стороне сервера | без локального маппинга | +| Claude Code `/effort` | Anthropic (`ai-claude`) | DeepSeek V4 (`ai-deepseek`) | Kimi K2.7/K2.6 (`ai-kimi`) | GLM 5.2 (`ai-glm`) | OpenRouter/GPT-5.5 (`ai-openrouter`) | OpenCode Go (`ai-opencode`) | +|:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| `low` | нативно `low` | провайдер поднимает до `high` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга | +| `medium` | нативно `medium` | провайдер поднимает до `high` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга | +| `high` | нативно `high` | нативно `high` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга | +| `xhigh` | нативно `xhigh` | провайдер поднимает до `max` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга | +| `max` | нативно `max` | нативно `max` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга | Для OpenRouter текущий лаунчер не делает локального преобразования effort и отправляет значение дальше через Claude Code/OpenRouter. Конкретная интерпретация зависит от выбранной модели и backend-а OpenRouter. +Для OpenCode Go лаунчер работает через `routatic-proxy`, который транслирует Anthropic-запросы +в OpenAI/Gemini-формат для OpenCode Go API. Локального маппинга effort нет — thinking budget +не транслируется напрямую в OpenCode Go бекенд. Прокси и бекенд-модели выполняют reasoning +на своей стороне независимо от уровня effort. + ## Нативные уровни каждого провайдера ### Anthropic (Claude) @@ -75,6 +80,13 @@ high / max и не вызывает локальный effort proxy. Поэтому `low`/`medium`/`high`/`xhigh`/`max` не преобразуются в shell-лаунчере. +### OpenCode Go (routatic-proxy) + +Текущий `ai-opencode` работает через Claude Code с локальным `routatic-proxy` +(`ANTHROPIC_BASE_URL=http://127.0.0.1:3456`), который транслирует запросы в OpenCode Go API. +Effort не преобразуется локально — thinking budget не имеет прямого эффекта на OpenCode Go бекенд. +Дефолт `ai-opencode`: `high`. + `claude-gpt-effort-proxy.py` все еще генерируется как совместимый helper для старого `claude-code-proxy` backend-а. Если использовать его вручную, он маппит только `xhigh` -> `high`, потому что `claude-code-proxy` принимает `low`, `medium`, `high`, `max`. @@ -88,11 +100,12 @@ high / max | Kimi K2.7/K2.6 | Moonshot API | На стороне сервера | | GLM 5.2 | Zhipu AI 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-openrouter`) +Каждый Claude Code лаунчер (`ai-claude`, `ai-deepseek`, `ai-kimi`, `ai-glm`, `ai-openrouter`, `ai-opencode`) запоминает свой уровень effort отдельно в `settings.json` лаунчера. `/effort` внутри сессии работает для всех уровней. `CLAUDE_CODE_EFFORT_LEVEL` не используется. diff --git a/QUICK_START.md b/QUICK_START.md index d5e3c9a..6d99f5a 100644 --- a/QUICK_START.md +++ b/QUICK_START.md @@ -7,7 +7,7 @@ bash setup.sh ``` Спросит про VLESS-прокси (по умолчанию `Y`; если не нужен, ответь `n`), затем установит лаунчеры -`ai-claude`, `ai-gpt`, `ai-deepseek`, `ai-kimi`, `ai-glm`, `ai-openrouter`, `ai-gemini` +`ai-claude`, `ai-gpt`, `ai-deepseek`, `ai-kimi`, `ai-glm`, `ai-openrouter`, `ai-opencode`, `ai-gemini` в `~/bin`, если он уже есть в `PATH`, иначе в `~/.local/bin/`, и запишет API-ключи. После установки, если команды не видны: diff --git a/README.md b/README.md index 2a4b405..763fbfe 100644 --- a/README.md +++ b/README.md @@ -159,6 +159,7 @@ bash tests/test_network.sh # полные тесты - `ai-kimi` - Claude Code через официальный Kimi Code API (`api.kimi.com/coding`) - `ai-glm` - Claude Code через Zhipu AI GLM 5.2 API (`api.z.ai/api/anthropic`) - `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` - `ai-api-helpers.sh` - вспомогательные функции для лаунчеров - `claude-gpt-effort-proxy.py` - совместимый helper для старого GPT/claude-code-proxy backend @@ -229,13 +230,14 @@ android init - `~/.config/ai-setup/kimi_key` - Kimi API key (права 600) - `~/.config/ai-setup/glm_key` - Zhipu AI (GLM) API key (права 600) - `~/.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` - глобальные правила агентов - `~/.config/ai-setup/network_.conf` - сохранённые сетевые профили (`GATEWAY`, `DEV`, `LOCAL_DNS`, `AMNEZIA_SERVER`, `KILL_SWITCH_EXCEPTIONS`) ## Права запуска агентов - `ai-gpt` использует `--dangerously-bypass-approvals-and-sandbox` -- `ai-claude`, `ai-deepseek`, `ai-kimi`, `ai-glm`, `ai-openrouter`, `ai-gemini` используют `--dangerously-skip-permissions` +- `ai-claude`, `ai-deepseek`, `ai-kimi`, `ai-glm`, `ai-openrouter`, `ai-opencode`, `ai-gemini` используют `--dangerously-skip-permissions` Удобно для локального coding workflow, но это не sandbox для недоверенного кода. @@ -250,7 +252,7 @@ Surgical Changes, Goal-Driven Execution) плюс пользовательски ## Effort Mapping `EFFORT_MAPPING.md` - таблица effort-уровней (`low`/`medium`/`high`/`xhigh`/`max`) -для Claude Code лаунчеров (`ai-claude`, `ai-deepseek`, `ai-kimi`, `ai-glm`, `ai-openrouter`). +для Claude Code лаунчеров (`ai-claude`, `ai-deepseek`, `ai-kimi`, `ai-glm`, `ai-openrouter`, `ai-opencode`). Нативные `ai-gpt` и `ai-gemini` живут отдельно. `claude-gpt-effort-proxy.py` маппит `xhigh` -> `high` для совместимости со старым diff --git a/scripts/ai-setup.sh b/scripts/ai-setup.sh index 02db09f..56e493f 100755 --- a/scripts/ai-setup.sh +++ b/scripts/ai-setup.sh @@ -685,6 +685,54 @@ else info "Android CLI не найден в PATH, пропускаю android init" fi +# ── 5. routatic-proxy (OpenCode Go → Claude Code) ────────── +info "Проверяю routatic-proxy..." +ROUTATIC_BIN="$HOME/.local/bin/routatic-proxy" +[ ! -f "$ROUTATIC_BIN" ] && ROUTATIC_BIN="$(command -v routatic-proxy 2>/dev/null || true)" + +if [ -z "$ROUTATIC_BIN" ] || [ ! -f "$ROUTATIC_BIN" ]; then + info "Устанавливаю routatic-proxy..." + _uname_s=$(uname -s) + _uname_m=$(uname -m) + ROUTATIC_PLATFORM="" + case "${_uname_s}-${_uname_m}" in + Linux-x86_64) ROUTATIC_PLATFORM="linux-amd64" ;; + Linux-aarch64) ROUTATIC_PLATFORM="linux-arm64" ;; + Linux-arm64) ROUTATIC_PLATFORM="linux-arm64" ;; + Darwin-x86_64) ROUTATIC_PLATFORM="darwin-amd64" ;; + Darwin-arm64) ROUTATIC_PLATFORM="darwin-arm64" ;; + Darwin-aarch64) ROUTATIC_PLATFORM="darwin-arm64" ;; + esac + + if [ -n "$ROUTATIC_PLATFORM" ]; then + ROUTATIC_URL="https://github.com/routatic/proxy/releases/latest/download/routatic-proxy_${ROUTATIC_PLATFORM}" + if curl -fsSL "$ROUTATIC_URL" -o "$HOME/.local/bin/routatic-proxy"; then + chmod +x "$HOME/.local/bin/routatic-proxy" + success "routatic-proxy установлен" + else + warn "Не удалось скачать routatic-proxy с GitHub" + fi + else + warn "Неизвестная платформа ${_uname_s} ${_uname_m}. Пропускаю routatic-proxy." + fi +else + success "routatic-proxy уже установлен" +fi + +# Symlink в BIN_DIR если используется ~/bin вместо ~/.local/bin +if [ "$BIN_DIR" != "$HOME/.local/bin" ] && [ -f "$HOME/.local/bin/routatic-proxy" ] && [ ! -f "$BIN_DIR/routatic-proxy" ]; then + ln -sf "$HOME/.local/bin/routatic-proxy" "$BIN_DIR/routatic-proxy" + success "routatic-proxy symlink -> $BIN_DIR/routatic-proxy" +fi + +# Инициализируем конфиг routatic-proxy +if command -v routatic-proxy &>/dev/null; then + if [ ! -f "$HOME/.config/routatic-proxy/config.json" ]; then + routatic-proxy init 2>/dev/null || true + success "routatic-proxy config инициализирован" + fi +fi + # ── 6. Папка для конфигов ──────────────────────────────────── mkdir -p "$CONFIG_DIR" @@ -2145,6 +2193,85 @@ claude --dangerously-skip-permissions --system-prompt-file "$_PROMPT_FILE" "$@" CLAUDEEOF chmod +x "$BIN_DIR/ai-claude" +# === ai-opencode === +cat > "$BIN_DIR/ai-opencode" << 'OPENCODEEOF' +#!/usr/bin/env bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/ai-api-helpers.sh" 2>/dev/null || true + +key_file="$HOME/.config/ai-setup/opencode_key" +api_key="" + +# Принудительный сброс сохранённого ключа через AI_RESET_KEY=1 +if [ "${AI_RESET_KEY:-0}" == "1" ] && [ -f "$key_file" ]; then + rm -f "$key_file" + api_key="" +fi + +[ -f "$key_file" ] && api_key=$(cat "$key_file") + +# Приоритет: env var → файл → ввод +if [ -z "$api_key" ] && [ -n "$OPENCODE_GO_API_KEY" ]; then + api_key="$OPENCODE_GO_API_KEY" + mkdir -p "$(dirname "$key_file")" + printf '%s' "$api_key" > "$key_file" + chmod 600 "$key_file" +fi + +if [ -z "$api_key" ]; then + echo "Получить ключ: https://opencode.ai/en/go" + read -r -p "Введи OpenCode Go ключ (sk-opencode-...): " api_key + [ -z "$api_key" ] && { echo "Выход."; exit 1; } + mkdir -p "$(dirname "$key_file")" + printf '%s' "$api_key" > "$key_file" + chmod 600 "$key_file" + echo "Ключ сохранён." +fi + +# --- routatic-proxy --- +export ROUTATIC_PROXY_API_KEY="$api_key" + +# Запускаем прокси в фоне, если ещё не запущен +if ! routatic-proxy status &>/dev/null; then + echo -n "Запускаю routatic-proxy... " + routatic-proxy serve -b 2>/dev/null + sleep 1 + if routatic-proxy status &>/dev/null; then + echo "готово" + else + echo "НЕ УДАЛОСЬ" + echo "Проверь: routatic-proxy serve" + 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=opencode +export CLAUDE_CONFIG_DIR="$HOME/.config/ai-setup/cfg/opencode" +# Модели OpenCode Go через routatic-proxy. +# deepseek-v4-pro — основной для сложных задач (opus/sonnet), +# kimi-k2.6 — быстрый (haiku), рекомендован routatic-proxy как дефолт. +_setup_isolated_config opencode deepseek-v4-pro high '' +_apply_effort opencode high +ANTHROPIC_BASE_URL="http://127.0.0.1:3456" \ +ANTHROPIC_AUTH_TOKEN="unused" \ +ANTHROPIC_DEFAULT_OPUS_MODEL="deepseek-v4-pro" \ +ANTHROPIC_DEFAULT_OPUS_MODEL_NAME="DeepSeek V4 Pro (Go)" \ +ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION="DeepSeek V4 Pro через OpenCode Go" \ +ANTHROPIC_DEFAULT_SONNET_MODEL="deepseek-v4-pro" \ +ANTHROPIC_DEFAULT_SONNET_MODEL_NAME="DeepSeek V4 Pro (Go)" \ +ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION="DeepSeek V4 Pro через OpenCode Go" \ +ANTHROPIC_DEFAULT_HAIKU_MODEL="kimi-k2.6" \ +ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME="Kimi K2.6 (Go)" \ +ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION="Kimi K2.6 через OpenCode Go" \ +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" "$@" +OPENCODEEOF +chmod +x "$BIN_DIR/ai-opencode" + # ── 8.5. Proxychains инъекция (только в режиме vless) ──────── if [ "$USE_VLESS" -eq 1 ]; then info "Включаю proxychains4 в ai-лаунчеры..." @@ -2155,6 +2282,7 @@ if [ "$USE_VLESS" -eq 1 ]; then sed -i 's/^claude --dangerously-skip-permissions/proxychains4 -f "\$HOME\/\.proxychains-xray\.conf" claude --dangerously-skip-permissions/' "$BIN_DIR/ai-openrouter" sed -i 's|^\([[:space:]]*\)"\$agy_bin" --dangerously-skip-permissions "\$@"|\1proxychains4 -f "\$HOME/.proxychains-xray.conf" "\$agy_bin" --dangerously-skip-permissions "\$@"|' "$BIN_DIR/ai-gemini" sed -i 's/^claude --dangerously-skip-permissions/proxychains4 -f "\$HOME\/\.proxychains-xray\.conf" claude --dangerously-skip-permissions/' "$BIN_DIR/ai-claude" + sed -i 's/^claude --dangerously-skip-permissions/proxychains4 -f "\$HOME\/\.proxychains-xray\.conf" claude --dangerously-skip-permissions/' "$BIN_DIR/ai-opencode" success "proxychains4 интегрирован" fi @@ -2166,6 +2294,7 @@ _setup_isolated_config deepseek opus high '' _setup_isolated_config kimi opus high '' _setup_isolated_config glm opus high '' _setup_isolated_config openrouter "openai/gpt-5.5" high '' +_setup_isolated_config opencode deepseek-v4-pro high '' info "Удаляю старые версии скриптов (claude_*)..." rm -f "$BIN_DIR/claude_gpt" "$BIN_DIR/claude_deepseek" "$BIN_DIR/claude_kimi" "$BIN_DIR/claude_gemini" "$BIN_DIR/claude_api_helpers.sh" @@ -2175,6 +2304,7 @@ 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-openrouter" "$HOME/.local/bin/ai-gemini" "$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 @@ -2194,6 +2324,7 @@ echo -e " ${CYAN}ai-deepseek${NC} - DeepSeek (через Claude Code, echo -e " ${CYAN}ai-kimi${NC} - Kimi K2.7 Code (через Claude Code, API ключ сохраняется)" echo -e " ${CYAN}ai-glm${NC} - GLM 5.2 (через Claude Code, Zhipu AI API)" 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 "" echo " Нативные CLI:" echo -e " ${CYAN}ai-gpt${NC} - OpenAI Codex (нативный CLI, автоустановка)" diff --git a/setup.sh b/setup.sh index c7d0a62..b7ace32 100755 --- a/setup.sh +++ b/setup.sh @@ -177,7 +177,7 @@ case "$choice" in echo "" echo -e "${BLD}AI инструменты:${CLR}" - for cmd in ai-claude ai-gpt ai-deepseek ai-kimi ai-glm ai-openrouter ai-gemini android; do + for cmd in ai-claude ai-gpt ai-deepseek ai-kimi ai-glm ai-openrouter ai-opencode ai-gemini android; do if command -v "$cmd" &>/dev/null; then echo -e " ${GRN}✓${CLR} $cmd" else @@ -188,7 +188,7 @@ case "$choice" in echo "" echo -e "${BLD}API ключи:${CLR}" cfg="$HOME/.config/ai-setup" - for f in deepseek_key kimi_key glm_key openrouter_key; do + for f in deepseek_key kimi_key glm_key openrouter_key opencode_key; do name="${f/_key/}" if [ -s "$cfg/$f" ]; then echo -e " ${GRN}✓${CLR} $name" @@ -199,7 +199,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"; do + 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 _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 f0df0da..01a34f6 100755 --- a/tests/test_fixes.sh +++ b/tests/test_fixes.sh @@ -16,6 +16,7 @@ GPT_SECTION=$(awk '/^cat > "\$BIN_DIR\/ai-gpt"/,/^GPTEOF/' "$SCRIPT") KIMI_SECTION=$(awk '/^cat > "\$BIN_DIR\/ai-kimi"/,/^KIMIEOF/' "$SCRIPT") GLM_SECTION=$(awk '/^cat > "\$BIN_DIR\/ai-glm"/,/^GLMEOF/' "$SCRIPT") GEMINI_SECTION=$(awk '/^cat > "\$BIN_DIR\/ai-gemini"/,/^GEMINIEOF/' "$SCRIPT") +OPENCODE_SECTION=$(awk '/^cat > "\$BIN_DIR\/ai-opencode"/,/^OPENCODEEOF/' "$SCRIPT") HELPERS_SECTION=$(awk '/^cat > "\$HELPERS_FILE"/,/^HELPEREOF/' "$SCRIPT") # ── ai-gpt: auto-install codex ──────────────────────────────────────────── @@ -192,6 +193,49 @@ test_android_skill_exists() { fi } +# ── ai-opencode: launches claude with routatic-proxy backend ────────────────── +test_opencode_claude_launcher() { + if echo "$OPENCODE_SECTION" | grep -q 'claude --dangerously-skip-permissions' \ + && echo "$OPENCODE_SECTION" | grep -q 'ANTHROPIC_BASE_URL'; then + ok "ai-opencode: launches claude with ANTHROPIC_BASE_URL set" + else + fail "ai-opencode: must launch claude with ANTHROPIC_BASE_URL" + fi +} + +# ── ai-opencode: uses routatic-proxy (local proxy) ───────────────────────────── +test_opencode_routatic_proxy() { + if echo "$OPENCODE_SECTION" | grep -q 'routatic-proxy' \ + && echo "$OPENCODE_SECTION" | grep -q '127.0.0.1:3456' \ + && echo "$OPENCODE_SECTION" | grep -q 'ROUTATIC_PROXY_API_KEY'; then + ok "ai-opencode: uses routatic-proxy on 127.0.0.1:3456" + else + fail "ai-opencode: must use routatic-proxy with local proxy and API key env" + fi +} + +# ── ai-opencode: manages opencode_key ────────────────────────────────────────── +test_opencode_key_management() { + if echo "$OPENCODE_SECTION" | grep -q 'opencode_key' \ + && echo "$OPENCODE_SECTION" | grep -q 'AI_RESET_KEY' \ + && echo "$OPENCODE_SECTION" | grep -q 'OPENCODE_GO_API_KEY'; then + ok "ai-opencode: opencode_key with AI_RESET_KEY and OPENCODE_GO_API_KEY env fallback" + else + fail "ai-opencode: must manage opencode_key with reset and env fallback" + fi +} + +# ── ai-opencode: correct default models ──────────────────────────────────────── +test_opencode_models() { + if echo "$OPENCODE_SECTION" | grep -q 'ANTHROPIC_DEFAULT_OPUS_MODEL="deepseek-v4-pro"' \ + && echo "$OPENCODE_SECTION" | grep -q 'ANTHROPIC_DEFAULT_HAIKU_MODEL="kimi-k2.6"' \ + && echo "$OPENCODE_SECTION" | grep -q 'CLAUDE_CODE_DISABLE_1M_CONTEXT=1'; then + ok "ai-opencode: correct default models (deepseek-v4-pro opus, kimi-k2.6 haiku)" + else + fail "ai-opencode: must have deepseek-v4-pro opus and kimi-k2.6 haiku" + fi +} + # ── run all tests ───────────────────────────────────────────────────────────── test_script_syntax test_gpt_autoinstall @@ -210,6 +254,10 @@ test_android_init_called test_android_symlink_to_bin_dir test_no_proxychains_for_android test_android_skill_exists +test_opencode_claude_launcher +test_opencode_routatic_proxy +test_opencode_key_management +test_opencode_models echo "" echo "Results: $PASS passed, $FAIL failed"