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}