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}