diff --git a/.agents/scripts/cron-dispatch.sh b/.agents/scripts/cron-dispatch.sh index 1c66cb733..d03b9d61b 100755 --- a/.agents/scripts/cron-dispatch.sh +++ b/.agents/scripts/cron-dispatch.sh @@ -13,6 +13,11 @@ set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" || exit +# Source shared-constants for resolve_model_tier() (t132.7) +# shellcheck source=shared-constants.sh +source "${SCRIPT_DIR}/shared-constants.sh" + # Configuration readonly CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/aidevops" readonly CONFIG_FILE="$CONFIG_DIR/cron-jobs.json" @@ -256,6 +261,9 @@ main() { timeout=$(echo "$job" | jq -r '.timeout // 600') model=$(echo "$job" | jq -r '.model // "anthropic/claude-sonnet-4-20250514"') notify=$(echo "$job" | jq -r '.notify // "none"') + + # Resolve tier names to full model strings (t132.7) + model=$(resolve_model_tier "$model") log_info "Job: $name" log_info "Task: $task" diff --git a/.agents/scripts/cron-helper.sh b/.agents/scripts/cron-helper.sh index 070670bb4..f82d6f178 100755 --- a/.agents/scripts/cron-helper.sh +++ b/.agents/scripts/cron-helper.sh @@ -214,7 +214,7 @@ cmd_add() { ensure_setup local schedule="" task="" name="" notify="none" timeout="$DEFAULT_TIMEOUT" - local workdir="" model="$DEFAULT_MODEL" paused=false + local workdir="" model="$DEFAULT_MODEL" paused=false provider="" while [[ $# -gt 0 ]]; do case "$1" in @@ -225,10 +225,20 @@ cmd_add() { --timeout) [[ $# -lt 2 ]] && { log_error "--timeout requires a value"; return 1; }; timeout="$2"; shift 2 ;; --workdir) [[ $# -lt 2 ]] && { log_error "--workdir requires a value"; return 1; }; workdir="$2"; shift 2 ;; --model) [[ $# -lt 2 ]] && { log_error "--model requires a value"; return 1; }; model="$2"; shift 2 ;; + --provider) [[ $# -lt 2 ]] && { log_error "--provider requires a value"; return 1; }; provider="$2"; shift 2 ;; --paused) paused=true; shift ;; *) log_error "Unknown option: $1"; return 1 ;; esac done + + # Resolve tier names to full model strings (t132.7) + model=$(resolve_model_tier "$model") + + # Apply provider override if specified (t132.7) + if [[ -n "$provider" && "$model" == *"/"* ]]; then + local model_id="${model#*/}" + model="${provider}/${model_id}" + fi # Validate required fields if [[ -z "$schedule" ]]; then @@ -672,7 +682,9 @@ ADD OPTIONS: --notify mail|none Notification method (default: none) --timeout SECONDS Max execution time (default: 600) --workdir PATH Working directory (default: current) - --model MODEL AI model to use + --model TIER_OR_MODEL AI model: tier name (haiku/sonnet/opus/flash/pro/grok) + or full provider/model string + --provider PROVIDER Override provider (e.g., openrouter, google) --paused Create in paused state LOGS OPTIONS: diff --git a/.agents/scripts/runner-helper.sh b/.agents/scripts/runner-helper.sh index d9e87202a..8d1b4e3dc 100755 --- a/.agents/scripts/runner-helper.sh +++ b/.agents/scripts/runner-helper.sh @@ -5,8 +5,8 @@ # Each runner gets its own AGENTS.md (personality), config, and optional memory namespace. # # Usage: -# runner-helper.sh create [--description "desc"] [--model provider/model] [--workdir path] -# runner-helper.sh run "prompt" [--attach URL] [--model provider/model] [--format json] [--timeout N] +# runner-helper.sh create [--description "desc"] [--model tier_or_model] [--provider name] [--workdir path] +# runner-helper.sh run "prompt" [--attach URL] [--model tier_or_model] [--provider name] [--format json] [--timeout N] # runner-helper.sh status # runner-helper.sh list [--format json] # runner-helper.sh edit # Open AGENTS.md in $EDITOR @@ -235,17 +235,27 @@ cmd_create() { return 1 fi - local description="" model="$DEFAULT_MODEL" workdir="" + local description="" model="$DEFAULT_MODEL" workdir="" provider="" while [[ $# -gt 0 ]]; do case "$1" in --description) [[ $# -lt 2 ]] && { log_error "--description requires a value"; return 1; }; description="$2"; shift 2 ;; --model) [[ $# -lt 2 ]] && { log_error "--model requires a value"; return 1; }; model="$2"; shift 2 ;; + --provider) [[ $# -lt 2 ]] && { log_error "--provider requires a value"; return 1; }; provider="$2"; shift 2 ;; --workdir) [[ $# -lt 2 ]] && { log_error "--workdir requires a value"; return 1; }; workdir="$2"; shift 2 ;; *) log_error "Unknown option: $1"; return 1 ;; esac done + # Resolve tier names to full model strings (t132.7) + model=$(resolve_model_tier "$model") + + # Apply provider override if specified (t132.7) + if [[ -n "$provider" && "$model" == *"/"* ]]; then + local model_id="${model#*/}" + model="${provider}/${model_id}" + fi + if [[ -z "$description" ]]; then description="Runner: $name" fi @@ -340,11 +350,13 @@ cmd_run() { fi local attach="" model="" format="" cmd_timeout="$DEFAULT_TIMEOUT" continue_session=false + local provider="" while [[ $# -gt 0 ]]; do case "$1" in --attach) [[ $# -lt 2 ]] && { log_error "--attach requires a value"; return 1; }; attach="$2"; shift 2 ;; --model) [[ $# -lt 2 ]] && { log_error "--model requires a value"; return 1; }; model="$2"; shift 2 ;; + --provider) [[ $# -lt 2 ]] && { log_error "--provider requires a value"; return 1; }; provider="$2"; shift 2 ;; --format) [[ $# -lt 2 ]] && { log_error "--format requires a value"; return 1; }; format="$2"; shift 2 ;; --timeout) [[ $# -lt 2 ]] && { log_error "--timeout requires a value"; return 1; }; cmd_timeout="$2"; shift 2 ;; --continue|-c) continue_session=true; shift ;; @@ -355,13 +367,20 @@ cmd_run() { local dir dir=$(runner_dir "$name") - # Resolve model (flag > config > default) + # Resolve model (flag > config > default), with tier name support (t132.7) if [[ -z "$model" ]]; then model=$(runner_config "$name" "model") if [[ -z "$model" ]]; then model="$DEFAULT_MODEL" fi fi + model=$(resolve_model_tier "$model") + + # Apply provider override if specified (t132.7) + if [[ -n "$provider" && "$model" == *"/"* ]]; then + local model_id="${model#*/}" + model="${provider}/${model_id}" + fi # Resolve workdir local workdir @@ -868,12 +887,15 @@ COMMANDS: CREATE OPTIONS: --description "DESC" Runner description - --model PROVIDER/MODEL AI model (default: anthropic/claude-sonnet-4-20250514) + --model TIER_OR_MODEL AI model: tier name (haiku/sonnet/opus/flash/pro/grok) + or full provider/model string (default: sonnet) + --provider PROVIDER Override provider (e.g., openrouter, google) --workdir PATH Default working directory RUN OPTIONS: --attach URL Attach to running OpenCode server (avoids MCP cold boot) - --model PROVIDER/MODEL Override model for this run + --model TIER_OR_MODEL Override model: tier name or provider/model string + --provider PROVIDER Override provider for this run --format json Output format (default or json) --timeout SECONDS Max execution time (default: 600) --continue, -c Continue previous session diff --git a/.agents/scripts/shared-constants.sh b/.agents/scripts/shared-constants.sh index e8b7ffa5a..e07819236 100755 --- a/.agents/scripts/shared-constants.sh +++ b/.agents/scripts/shared-constants.sh @@ -890,6 +890,94 @@ cleanup_sqlite_backups() { # Export all constants for use in other scripts # ============================================================================= +# ============================================================================= +# Model tier resolution (t132.7) +# Shared function for resolving tier names to full provider/model strings. +# Used by runner-helper.sh, cron-helper.sh, cron-dispatch.sh. +# Tries: 1) fallback-chain-helper.sh (availability-aware) +# 2) Static mapping (always works) +# ============================================================================= + +####################################### +# Resolve a model tier name to a full provider/model string (t132.7) +# Accepts both tier names (haiku, sonnet, opus, flash, pro, grok, coding, eval, health) +# and full provider/model strings (passed through unchanged). +# Returns the resolved model string on stdout. +####################################### +resolve_model_tier() { + local tier="${1:-coding}" + + # If already a full provider/model string (contains /), return as-is + if [[ "$tier" == *"/"* ]]; then + echo "$tier" + return 0 + fi + + # Try fallback-chain-helper.sh for availability-aware resolution + local chain_helper="${BASH_SOURCE[0]%/*}/fallback-chain-helper.sh" + if [[ -x "$chain_helper" ]]; then + local resolved + resolved=$("$chain_helper" resolve "$tier" --quiet 2>/dev/null) || true + if [[ -n "$resolved" ]]; then + echo "$resolved" + return 0 + fi + fi + + # Static fallback: map tier names to concrete models + case "$tier" in + opus|coding) + echo "anthropic/claude-opus-4-6" + ;; + sonnet|eval) + echo "anthropic/claude-sonnet-4-20250514" + ;; + haiku|health) + echo "anthropic/claude-3-5-haiku-20241022" + ;; + flash) + echo "google/gemini-2.5-flash-preview-05-20" + ;; + pro) + echo "google/gemini-2.5-pro-preview-06-05" + ;; + grok) + echo "xai/grok-3" + ;; + *) + # Unknown tier — return as-is (may be a model name without provider) + echo "$tier" + ;; + esac + + return 0 +} + +####################################### +# Detect available AI CLI backends (t132.7) +# Returns a newline-separated list of available backends. +# Checks: opencode, claude +####################################### +detect_ai_backends() { + local -a backends=() + + if command -v opencode &>/dev/null; then + backends+=("opencode") + fi + + if command -v claude &>/dev/null; then + backends+=("claude") + fi + + if [[ ${#backends[@]} -eq 0 ]]; then + echo "none" + return 1 + fi + + printf '%s\n' "${backends[@]}" + return 0 +} + # This ensures all constants are available when this file is sourced export CONTENT_TYPE_JSON CONTENT_TYPE_FORM USER_AGENT export HTTP_OK HTTP_CREATED HTTP_BAD_REQUEST HTTP_UNAUTHORIZED HTTP_FORBIDDEN HTTP_NOT_FOUND HTTP_INTERNAL_ERROR