claude.zsh

  1# SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
  2#
  3# SPDX-License-Identifier: Unlicense
  4
  5# Claude Code agent definition for synu
  6# Provides model configuration for routing through Synthetic API
  7
  8# Fallback defaults (used when no cache entry exists)
  9typeset -g _SYNU_CLAUDE_FALLBACK_OPUS="hf:moonshotai/Kimi-K2-Thinking"
 10typeset -g _SYNU_CLAUDE_FALLBACK_SONNET="hf:zai-org/GLM-4.6"
 11typeset -g _SYNU_CLAUDE_FALLBACK_HAIKU="hf:deepseek-ai/DeepSeek-V3.1-Terminus"
 12typeset -g _SYNU_CLAUDE_FALLBACK_AGENT="hf:zai-org/GLM-4.6"
 13
 14_synu_claude_default() {
 15    local slot=$1
 16    local cached
 17    cached=$(_synu_cache_get claude "${slot}")
 18    if [[ $? -eq 0 ]]; then
 19        echo "${cached}"
 20    else
 21        local var_name="_SYNU_CLAUDE_FALLBACK_${(U)slot}"
 22        echo "${(P)var_name}"
 23    fi
 24}
 25
 26_synu_agent_claude_flags() {
 27    echo "L/large="
 28    echo "l/light="
 29    echo "o/opus="
 30    echo "s/sonnet="
 31    echo "H/haiku="
 32    echo "a/agent="
 33}
 34
 35_synu_agent_claude_env_vars() {
 36    echo ANTHROPIC_BASE_URL
 37    echo ANTHROPIC_AUTH_TOKEN
 38    echo ANTHROPIC_DEFAULT_OPUS_MODEL
 39    echo ANTHROPIC_DEFAULT_SONNET_MODEL
 40    echo ANTHROPIC_DEFAULT_HAIKU_MODEL
 41    echo CLAUDE_CODE_SUBAGENT_MODEL
 42    echo CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC
 43}
 44
 45_synu_agent_claude_configure() {
 46    local -A opts
 47    local -a args
 48
 49    # Parse flags passed from main synu
 50    while [[ $# -gt 0 ]]; do
 51        case "$1" in
 52            --large=*) opts[large]="${1#*=}" ;;
 53            --light=*) opts[light]="${1#*=}" ;;
 54            --opus=*) opts[opus]="${1#*=}" ;;
 55            --sonnet=*) opts[sonnet]="${1#*=}" ;;
 56            --haiku=*) opts[haiku]="${1#*=}" ;;
 57            --agent=*) opts[agent]="${1#*=}" ;;
 58            *) args+=("$1") ;;
 59        esac
 60        shift
 61    done
 62
 63    # Start with defaults (from cache or fallback)
 64    local opus_model=$(_synu_claude_default opus)
 65    local sonnet_model=$(_synu_claude_default sonnet)
 66    local haiku_model=$(_synu_claude_default haiku)
 67    local subagent_model=$(_synu_claude_default agent)
 68
 69    # Apply group overrides
 70    if [[ -n "${opts[large]}" ]]; then
 71        opus_model="${opts[large]}"
 72        sonnet_model="${opts[large]}"
 73        subagent_model="${opts[large]}"
 74    fi
 75
 76    if [[ -n "${opts[light]}" ]]; then
 77        haiku_model="${opts[light]}"
 78    fi
 79
 80    # Apply specific overrides (take precedence over groups)
 81    [[ -n "${opts[opus]}" ]] && opus_model="${opts[opus]}"
 82    [[ -n "${opts[sonnet]}" ]] && sonnet_model="${opts[sonnet]}"
 83    [[ -n "${opts[haiku]}" ]] && haiku_model="${opts[haiku]}"
 84    [[ -n "${opts[agent]}" ]] && subagent_model="${opts[agent]}"
 85
 86    # Export environment variables for Claude Code
 87    export ANTHROPIC_BASE_URL="https://api.synthetic.new/anthropic"
 88    export ANTHROPIC_AUTH_TOKEN="${SYNTHETIC_API_KEY}"
 89    export ANTHROPIC_DEFAULT_OPUS_MODEL="${opus_model}"
 90    export ANTHROPIC_DEFAULT_SONNET_MODEL="${sonnet_model}"
 91    export ANTHROPIC_DEFAULT_HAIKU_MODEL="${haiku_model}"
 92    export CLAUDE_CODE_SUBAGENT_MODEL="${subagent_model}"
 93    export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1
 94}
 95
 96_synu_agent_claude_interactive() {
 97    # Check for gum
 98    if ! (( $+commands[gum] )); then
 99        print -u2 "Error: gum is required for interactive mode. Install: https://github.com/charmbracelet/gum"
100        return 1
101    fi
102
103    # Fetch available models
104    local models_json
105    models_json=$(gum spin --spinner dot --title "Fetching models..." -- \
106        curl -s -H "Authorization: Bearer ${SYNTHETIC_API_KEY}" \
107        "https://api.synthetic.new/openai/v1/models")
108    [[ $? -ne 0 ]] && return 1
109
110    local -a model_names
111    model_names=("${(@f)$(echo "${models_json}" | jq -r '.data[].name')}")
112    [[ $? -ne 0 ]] && return 1
113
114    # Get current models for display
115    local current_opus_id=$(_synu_claude_default opus)
116    local current_sonnet_id=$(_synu_claude_default sonnet)
117    local current_haiku_id=$(_synu_claude_default haiku)
118    local current_agent_id=$(_synu_claude_default agent)
119
120    local current_opus_name=$(echo "${models_json}" | \
121        jq -r --arg id "${current_opus_id}" '.data[] | select(.id == $id) | .name // "unknown"')
122    local current_sonnet_name=$(echo "${models_json}" | \
123        jq -r --arg id "${current_sonnet_id}" '.data[] | select(.id == $id) | .name // "unknown"')
124    local current_haiku_name=$(echo "${models_json}" | \
125        jq -r --arg id "${current_haiku_id}" '.data[] | select(.id == $id) | .name // "unknown"')
126    local current_agent_name=$(echo "${models_json}" | \
127        jq -r --arg id "${current_agent_id}" '.data[] | select(.id == $id) | .name // "unknown"')
128
129    # Prompt for groups vs individual
130    local mode
131    mode=$(gum choose --limit 1 --header "How do you want to select models?" \
132        "Groups" "Individual models")
133    [[ $? -ne 0 ]] && return 1
134
135    # Build flags array
136    local -a flags=()
137
138    if [[ "${mode}" == "Groups" ]]; then
139        # Select which groups to override
140        local -a groups
141        groups=("${(@f)$(gum choose --no-limit \
142            --header "Which group(s) do you want to override?" \
143            "Large (Opus, Sonnet, Sub-agent)" "Light (Haiku)")}")
144        [[ $? -ne 0 ]] && return 1
145
146        local group
147        for group in "${groups[@]}"; do
148            if [[ "${group}" == "Large (Opus, Sonnet, Sub-agent)" ]]; then
149                local model_name
150                model_name=$(printf "%s\n" "${model_names[@]}" | \
151                    gum filter --limit 1 --header "Select model for Large group (opus: ${current_opus_name}, sonnet: ${current_sonnet_name}, agent: ${current_agent_name})" \
152                    --placeholder "Filter models...")
153                [[ $? -ne 0 ]] && return 1
154
155                local model_id
156                model_id=$(echo "${models_json}" | \
157                    jq -r --arg name "${model_name}" '.data[] | select(.name == $name) | .id')
158                [[ -n "${model_id}" ]] && flags+=(--large="${model_id}")
159
160            elif [[ "${group}" == "Light (Haiku)" ]]; then
161                local model_name
162                model_name=$(printf "%s\n" "${model_names[@]}" | \
163                    gum filter --limit 1 --header "Select model for Light group (haiku: ${current_haiku_name})" \
164                    --placeholder "Filter models...")
165                [[ $? -ne 0 ]] && return 1
166
167                local model_id
168                model_id=$(echo "${models_json}" | \
169                    jq -r --arg name "${model_name}" '.data[] | select(.name == $name) | .id')
170                [[ -n "${model_id}" ]] && flags+=(--light="${model_id}")
171            fi
172        done
173    else
174        # Select which individual models to override
175        local -a models
176        models=("${(@f)$(gum choose --no-limit \
177            --header "Which model(s) do you want to override?" \
178            "Opus" "Sonnet" "Haiku" "Sub-agent")}")
179        [[ $? -ne 0 ]] && return 1
180
181        local model_type
182        for model_type in "${models[@]}"; do
183            case "${model_type}" in
184                "Opus")
185                    local model_name
186                    model_name=$(printf "%s\n" "${model_names[@]}" | \
187                        gum filter --limit 1 --header "Select Opus model (current: ${current_opus_name})" \
188                        --placeholder "Filter models...")
189                    [[ $? -ne 0 ]] && return 1
190
191                    local model_id
192                    model_id=$(echo "${models_json}" | \
193                        jq -r --arg name "${model_name}" '.data[] | select(.name == $name) | .id')
194                    [[ -n "${model_id}" ]] && flags+=(--opus="${model_id}")
195                    ;;
196                "Sonnet")
197                    local model_name
198                    model_name=$(printf "%s\n" "${model_names[@]}" | \
199                        gum filter --limit 1 --header "Select Sonnet model (current: ${current_sonnet_name})" \
200                        --placeholder "Filter models...")
201                    [[ $? -ne 0 ]] && return 1
202
203                    local model_id
204                    model_id=$(echo "${models_json}" | \
205                        jq -r --arg name "${model_name}" '.data[] | select(.name == $name) | .id')
206                    [[ -n "${model_id}" ]] && flags+=(--sonnet="${model_id}")
207                    ;;
208                "Haiku")
209                    local model_name
210                    model_name=$(printf "%s\n" "${model_names[@]}" | \
211                        gum filter --limit 1 --header "Select Haiku model (current: ${current_haiku_name})" \
212                        --placeholder "Filter models...")
213                    [[ $? -ne 0 ]] && return 1
214
215                    local model_id
216                    model_id=$(echo "${models_json}" | \
217                        jq -r --arg name "${model_name}" '.data[] | select(.name == $name) | .id')
218                    [[ -n "${model_id}" ]] && flags+=(--haiku="${model_id}")
219                    ;;
220                "Sub-agent")
221                    local model_name
222                    model_name=$(printf "%s\n" "${model_names[@]}" | \
223                        gum filter --limit 1 --header "Select Sub-agent model (current: ${current_agent_name})" \
224                        --placeholder "Filter models...")
225                    [[ $? -ne 0 ]] && return 1
226
227                    local model_id
228                    model_id=$(echo "${models_json}" | \
229                        jq -r --arg name "${model_name}" '.data[] | select(.name == $name) | .id')
230                    [[ -n "${model_id}" ]] && flags+=(--agent="${model_id}")
231                    ;;
232            esac
233        done
234    fi
235
236    # Offer to save as defaults
237    if (( ${#flags} > 0 )); then
238        if gum confirm "Save as default for 'claude'?"; then
239            local flag
240            for flag in "${flags[@]}"; do
241                # Parse --key=value format
242                local key="${flag%%=*}"
243                key="${key#--}"
244                local value="${flag#*=}"
245
246                # Expand group flags to individual slots
247                case "${key}" in
248                    large)
249                        _synu_cache_set claude opus "${value}"
250                        _synu_cache_set claude sonnet "${value}"
251                        _synu_cache_set claude agent "${value}"
252                        ;;
253                    light)
254                        _synu_cache_set claude haiku "${value}"
255                        ;;
256                    *)
257                        _synu_cache_set claude "${key}" "${value}"
258                        ;;
259                esac
260            done
261        fi
262    fi
263
264    # Output flags for caller to use (one per line for proper array capture)
265    printf '%s\n' "${flags[@]}"
266}