synu.zsh

  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}