1# SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2#
3# SPDX-License-Identifier: Unlicense
4
5# Universal agent wrapper with Synthetic API quota tracking
6
7synu() {
8 emulate -L zsh
9 setopt extended_glob
10
11 # If no arguments, just print the quota
12 if [[ $# -lt 1 ]]; then
13 local quota
14 quota=$(_synu_get_quota)
15 if [[ $? -ne 0 ]]; then
16 print -u2 "Error: Could not fetch quota"
17 return 1
18 fi
19 local requests=${quota%% *}
20 local limit=${quota##* }
21 local remaining=$(( limit - requests ))
22 local percent_used=$(( requests * 100.0 / limit ))
23 local percent_int remaining_fmt
24 printf -v percent_int "%.0f" "${percent_used}"
25 printf -v remaining_fmt "%.2f" "${remaining}"
26
27 printf "Usage: "
28 if (( percent_int < 33 )); then
29 print -Pn "%K{green}%F{black}"
30 elif (( percent_int < 67 )); then
31 print -Pn "%K{yellow}%F{black}"
32 else
33 print -Pn "%K{red}%F{black}"
34 fi
35 printf " %s%% " "${percent_int}"
36 print -Pn "%k%f"
37 printf " (%s/%s remaining)\n" "${remaining_fmt}" "${limit}"
38 return 0
39 fi
40
41 # Check for interactive mode: synu i <agent> [args...]
42 if [[ "$1" == "i" ]]; then
43 if [[ $# -lt 2 ]]; then
44 print -u2 "Error: Interactive mode requires an agent name"
45 print -u2 "Usage: synu i <agent> [args...]"
46 return 1
47 fi
48
49 local agent=$2
50 shift 2
51 local -a agent_args=("$@")
52
53 # Source agent definition if it exists
54 local agent_file="${SYNU_PLUGIN_DIR}/functions/_synu_agents/${agent}.zsh"
55 if [[ -f "${agent_file}" ]]; then
56 source "${agent_file}"
57 fi
58
59 # Check for interactive function
60 if ! (( $+functions[_synu_agent_${agent}_interactive] )); then
61 print -u2 "Error: Agent '${agent}' does not support interactive mode"
62 return 1
63 fi
64
65 # Get flags from interactive selection
66 local -a interactive_flags
67 interactive_flags=("${(@f)$(_synu_agent_${agent}_interactive)}")
68 local interactive_status=$?
69 if [[ ${interactive_status} -ne 0 ]]; then
70 return ${interactive_status}
71 fi
72
73
74 # Recursively call synu with selected flags
75 synu "${agent}" ${interactive_flags[@]} ${agent_args[@]}
76 return $?
77 fi
78
79 # Extract agent name (first argument) and remaining args
80 local agent=$1
81 shift
82 local -a agent_args=("$@")
83
84 # Source agent definition if it exists
85 local agent_file="${SYNU_PLUGIN_DIR}/functions/_synu_agents/${agent}.zsh"
86 if [[ -f "${agent_file}" ]]; then
87 source "${agent_file}"
88 fi
89
90 # Check if agent has a configuration function
91 if (( $+functions[_synu_agent_${agent}_configure] )); then
92 # Get flag specification
93 local -a flag_spec
94 if (( $+functions[_synu_agent_${agent}_flags] )); then
95 flag_spec=($(_synu_agent_${agent}_flags))
96 fi
97
98 # Manually extract synu-specific flags while preserving agent flags
99 local -a parsed_flags=()
100 local -a remaining_args=()
101 local -A flag_values=()
102
103 # Known synu flags across all agents (short and long forms)
104 local -a known_short=(L l o s H a m e)
105 local -a known_long=(large light opus sonnet haiku agent model editor-model)
106
107 local i=1
108 while (( i <= ${#agent_args} )); do
109 local arg="${agent_args[i]}"
110 local consumed=0
111
112 # Check for --flag=value format
113 if [[ "${arg}" == --*=* ]]; then
114 local flag_name="${arg%%=*}"
115 flag_name="${flag_name#--}"
116 local flag_value="${arg#*=}"
117
118 if [[ " ${known_long[*]} " == *" ${flag_name} "* ]]; then
119 flag_values[${flag_name}]="${flag_value}"
120 parsed_flags+=(--${flag_name}="${flag_value}")
121 consumed=1
122 fi
123 # Check for --flag value format
124 elif [[ "${arg}" == --* ]]; then
125 local flag_name="${arg#--}"
126
127 if [[ " ${known_long[*]} " == *" ${flag_name} "* ]]; then
128 (( i++ ))
129 if (( i <= ${#agent_args} )); then
130 flag_values[${flag_name}]="${agent_args[i]}"
131 parsed_flags+=(--${flag_name}="${agent_args[i]}")
132 fi
133 consumed=1
134 fi
135 # Check for -f value format (short flags)
136 elif [[ "${arg}" == -? ]]; then
137 local flag_char="${arg#-}"
138
139 if [[ " ${known_short[*]} " == *" ${flag_char} "* ]]; then
140 (( i++ ))
141 if (( i <= ${#agent_args} )); then
142 # Map short to long for storage
143 local long_name
144 case "${flag_char}" in
145 L) long_name="large" ;;
146 l) long_name="light" ;;
147 o) long_name="opus" ;;
148 s) long_name="sonnet" ;;
149 H) long_name="haiku" ;;
150 a) long_name="agent" ;;
151 m) long_name="model" ;;
152 e) long_name="editor-model" ;;
153 esac
154 flag_values[${long_name}]="${agent_args[i]}"
155 parsed_flags+=(--${long_name}="${agent_args[i]}")
156 fi
157 consumed=1
158 fi
159 fi
160
161 if (( ! consumed )); then
162 remaining_args+=("${arg}")
163 fi
164 (( i++ ))
165 done
166
167 agent_args=("${remaining_args[@]}")
168
169 # Configure the agent environment
170 _synu_agent_${agent}_configure ${parsed_flags[@]}
171 if [[ $? -ne 0 ]]; then
172 return 1
173 fi
174 fi
175
176 # Check if agent provides extra CLI arguments (for agents that don't use env vars)
177 local -a extra_args=()
178 if (( $+functions[_synu_agent_${agent}_args] )); then
179 extra_args=($(_synu_agent_${agent}_args))
180 fi
181
182 # Fetch quota before agent execution
183 local quota_before
184 quota_before=$(_synu_get_quota)
185 if [[ $? -ne 0 ]]; then
186 # If quota fetch fails, still execute the agent but warn
187 print -u2 "Warning: Could not fetch quota before execution"
188 quota_before="0 0"
189 fi
190
191 # Parse pre-execution quota values
192 local requests_before=${quota_before%% *}
193 local limit=${quota_before##* }
194
195 # Execute the agent with all arguments passed through unchanged
196 # Use 'command' to bypass function recursion and call the actual binary
197 # extra_args contains agent-specific CLI flags (e.g., -m for opencode)
198 command ${agent} ${extra_args[@]} ${agent_args[@]}
199 local exit_status=$?
200
201 # Clean up environment variables set by agent
202 if (( $+functions[_synu_agent_${agent}_env_vars] )); then
203 local var
204 for var in $(_synu_agent_${agent}_env_vars); do
205 unset "${var}"
206 done
207 fi
208
209 # Fetch quota after agent execution
210 local quota_after
211 quota_after=$(_synu_get_quota)
212 if [[ $? -ne 0 ]]; then
213 # If quota fetch fails, still exit but warn
214 print -u2 "Warning: Could not fetch quota after execution"
215 return ${exit_status}
216 fi
217
218 # Parse post-execution quota values
219 local requests_after=${quota_after%% *}
220 # Note: limit is the same before and after, using pre-execution value
221
222 # Calculate session usage: final_requests - initial_requests
223 local session_usage=$(( requests_after - requests_before ))
224
225 # Calculate remaining quota: limit - requests_after
226 local remaining=$(( limit - requests_after ))
227
228 # Calculate percentage used: (requests_after * 100) / limit
229 local percent_used=$(( requests_after * 100.0 / limit ))
230 local percent_int remaining_fmt session_fmt
231 printf -v percent_int "%.0f" "${percent_used}"
232 printf -v remaining_fmt "%.2f" "${remaining}"
233 printf -v session_fmt "%.1f" "${session_usage}"
234
235 # Display session usage and remaining quota with color
236 # Force a newline first for proper separation from agent output
237 printf "\n"
238 printf "Session: %s requests\n" "${session_fmt}"
239 printf "Overall: "
240
241 # Determine color based on usage percentage
242 # Green: <33%, Yellow: 33-66%, Red: >66%
243 if (( percent_int < 33 )); then
244 print -Pn "%K{green}%F{black}"
245 elif (( percent_int < 67 )); then
246 print -Pn "%K{yellow}%F{black}"
247 else
248 print -Pn "%K{red}%F{black}"
249 fi
250 printf " %s%% " "${percent_int}"
251 print -Pn "%k%f"
252 printf " (%s/%s remaining)\n" "${remaining_fmt}" "${limit}"
253
254 return ${exit_status}
255}