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