{"repo": "agentic-systems-summer-2026/sdi-4243-agentic-systems-summer-2026-classroom-collective-assignment-agentic-systems-course", "files": {"scripts/select-model.sh": {"d5509fabbf620425cb3aa819a41c221caeaa7826": {"t": 1785747173.7852256, "n": 12938, "h": "0f0136da61844abe", "c": "#!/usr/bin/env bash\n# Switch the OpenClaw model.\n#\n# Asks where your PRIMARY model should come from — the OU LiteLLM Sandbox (the\n# course endpoint, no per-token cost) or OpenRouter (your own key and credit) —\n# then lets you pick from that catalog. Then asks the same for your FALLBACK,\n# which you can decline. Primary and fallback may come from different providers.\n#\n# Either key works on its own. With only one, the provider question is skipped\n# for the primary and reduced to \"that provider or no fallback\" for the backup.\n#\n# The gateway hot-reloads. For a chat you are already in, switch with /model.\nset -uo pipefail\n# Make 'openclaw' findable in non-interactive shells.\nsource \"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)/_env.sh\" 2>/dev/null || true\nLITELLM_BASE_URL=\"${LITELLM_BASE_URL:-https://litellm.lib.ou.edu}\"\nENV_FILE=\"${HOME}/.openclaw/.env\"\noubase=\"${LITELLM_BASE_URL%/}\"\n\nread_env() { # read_env VAR -> value from process env or ~/.openclaw/.env\n local var=\"$1\" val=\"${!1:-}\"\n [[ -z \"${val}\" && -f \"${ENV_FILE}\" ]] && val=\"$(grep -E \"^${var}=\" \"${ENV_FILE}\" | tail -n1 | cut -d= -f2- || true)\"\n printf '%s' \"${val}\"\n}\nusage() {\n cat <<'EOF'\nUsage: bash scripts/select-model.sh\n\n Walks you through choosing a primary model and an optional fallback.\n For each, you first choose a provider (OU AI Sandbox or OpenRouter),\n then pick from that provider's catalog.\nEOF\n}\nfor arg in \"$@\"; do\n case \"${arg}\" in\n -h|--help) usage; exit 0 ;;\n *) echo \"Unknown option '${arg}'.\"; usage; exit 1 ;;\n esac\ndone\n# ---- prerequisites --------------------------------------------------------\nneed() { command -v \"$1\" >/dev/null 2>&1 || { echo \"❌ Required tool '$1' not found — $2\"; exit 1; }; }\nneed curl \"rebuild the Codespace or install curl.\"\nneed python3 \"rebuild the Codespace or install python3.\"\nneed openclaw \"OpenClaw isn't on PATH yet — start the Gateway task first, or run: bash .devcontainer/setup.sh\"\n# ---- which providers are available? ---------------------------------------\nOR_KEY=\"$(read_env OPENROUTER_API_KEY)\"\nLL_KEY=\"$(read_env LITELLM_API_KEY)\"\n[[ \"${OR_KEY}\" == \"sk-or-REPLACE_ME\" ]] && OR_KEY=\"\"\n[[ \"${LL_KEY}\" == \"sk-REPLACE_ME\" ]] && LL_KEY=\"\"\nHAVE_OR=0; [[ -n \"${OR_KEY}\" ]] && HAVE_OR=1\nHAVE_LL=0; [[ -n \"${LL_KEY}\" ]] && HAVE_LL=1\nif (( ! HAVE_OR && ! HAVE_LL )); then\n echo \"❌ No model provider key found.\"\n echo\n echo \" You need at least one of:\"\n echo \" • OU AI Sandbox key (LITELLM_API_KEY) — the course's first choice, no per-token cost.\"\n echo \" • Your own OpenRouter key (OPENROUTER_API_KEY) — https://openrouter.ai (Settings → Keys).\"\n echo\n echo \" Set one with: bash scripts/set-key.sh\"\n exit 1\nfi\n# ---- catalogs (fetched once, on demand) -----------------------------------\nOU=(); OU_LOADED=0\nOR_IDS=(); OR_LABELS=(); OR_LOADED=0\n\nensure_ou_catalog() {\n (( OU_LOADED )) && return 0\n echo \"Fetching OU models from ${oubase} ...\" >&2\n local http=000 url\n for url in \"${oubase}/v1/models\" \"${oubase}/models\"; do\n http=\"$(curl -s -m 20 -o /tmp/ou_models.json -w '%{http_code}' -H \"Authorization: Bearer ${LL_KEY}\" \"${url}\" || echo 000)\"\n [[ \"${http}\" == \"200\" ]] && break\n done\n case \"${http}\" in\n 200) ;;\n 401|403) echo \"❌ OU Sandbox key rejected (HTTP ${http}).\" >&2; exit 1 ;;\n 000) echo \"❌ Could not reach ${oubase} (network/endpoint issue). Check the URL or try again.\" >&2; exit 1 ;;\n *) echo \"❌ OU gateway returned HTTP ${http}. Details in /tmp/ou_models.json\" >&2; exit 1 ;;\n esac\n mapfile -t OU < <(python3 -c 'import json\nfor m in json.load(open(\"/tmp/ou_models.json\")).get(\"data\",[]): print(m[\"id\"])' 2>/dev/null | sort -u)\n ((${#OU[@]})) || { echo \"❌ No models parsed from the OU response (/tmp/ou_models.json may be malformed).\" >&2; exit 1; }\n OU_LOADED=1\n}\nensure_or_catalog() {\n (( OR_LOADED )) && return 0\n # Listing is public, but a bad key makes the choice moot — check it first.\n local kc\n kc=\"$(curl -s -m 15 -o /dev/null -w '%{http_code}' -H \"Authorization: Bearer ${OR_KEY}\" https://openrouter.ai/api/v1/key || echo 000)\"\n if [[ \"${kc}\" != \"200\" ]]; then\n echo \"⚠️ OpenRouter key check returned HTTP ${kc} — it may be invalid, disabled, or out of credit.\" >&2\n (( HAVE_LL )) && echo \" The OU AI Sandbox is available instead.\" >&2\n local yn=\"\"\n read -rp \"Continue with OpenRouter anyway? [y/N] \" yn &2\n if ! curl -fsS -m 30 \"https://openrouter.ai/api/v1/models?supported_parameters=tools\" -o /tmp/or_models.json; then\n echo \"❌ Could not reach OpenRouter (network/endpoint). Try again in a moment.\" >&2\n return 1\n fi\n local rows=()\n mapfile -t rows < <(python3 - <<'PY'\nimport json\ndata = json.load(open(\"/tmp/or_models.json\")).get(\"data\", [])\npopular = {\"anthropic\",\"openai\",\"google\",\"x-ai\",\"meta-llama\",\"mistralai\",\n \"qwen\",\"deepseek\",\"z-ai\",\"moonshotai\",\"minimax\"}\ndef perM(v):\n try: return float(v) * 1_000_000\n except Exception: return None\nrows = []\nfor m in data:\n mid = m.get(\"id\", \"\")\n vendor = mid.split(\"/\")[0] if \"/\" in mid else mid\n arch = m.get(\"architecture\", {}) or {}\n if \"text\" not in (arch.get(\"input_modalities\") or []): continue\n if \"tools\" not in (m.get(\"supported_parameters\") or []): continue\n if vendor not in popular: continue\n pr = m.get(\"pricing\", {}) or {}\n pin, pout = perM(pr.get(\"prompt\")), perM(pr.get(\"completion\"))\n free = (pin == 0 and pout == 0)\n ctx = m.get(\"context_length\") or 0\n ctxs = f\"{ctx // 1000}k\" if ctx else \"?\"\n if free: price = \"FREE\"\n elif pin is not None and pout is not None: price = f\"${pin:.2f}/${pout:.2f} /M\"\n else: price = \"price n/a\"\n rows.append((0 if free else 1, pin if pin is not None else 9e9, vendor, mid,\n f\"{price:<16} {mid} ({ctxs})\"))\nrows.sort(key=lambda r: (r[0], r[1], r[2], r[3]))\n# OpenRouter's own Free Models Router: zero-cost, picks a free model per\n# request and filters for tool support itself. The ?supported_parameters=tools\n# catalog filter excludes routers, so add it explicitly at the top. Other\n# openrouter/* routers stay hidden — some fan out to paid models and would\n# drain a student key fast.\nrows.insert(0, (0, 0.0, \"openrouter\", \"openrouter/free\",\n f\"{'FREE':<16} openrouter/free (router — picks a free, tool-capable model per request; rate-limited, fine for smoke tests)\"))\nfor r in rows: print(f\"{r[3]}\\t{r[4]}\")\nPY\n)\n if ! ((${#rows[@]})); then\n echo \"❌ No tool-capable models from popular vendors returned (OpenRouter's catalog may have shifted).\" >&2\n return 1\n fi\n local row\n OR_IDS=(); OR_LABELS=()\n for row in \"${rows[@]}\"; do\n OR_IDS+=(\"${row%%$'\\t'*}\")\n OR_LABELS+=(\"${row#*$'\\t'}\")\n done\n OR_LOADED=1\n}\n# ---- provider question ----------------------------------------------------\n# choose_provider -> prints \"ou\" | \"or\" | \"none\"\nchoose_provider() {\n local role=\"$1\" allow_none=\"$2\"\n local opts=() vals=() i ans=\"\"\n if (( HAVE_LL )); then opts+=(\"OU AI Sandbox (LiteLLM) — course endpoint, no per-token cost\"); vals+=(\"ou\"); fi\n if (( HAVE_OR )); then opts+=(\"OpenRouter — your own key and your own credit\"); vals+=(\"or\"); fi\n if (( allow_none )); then opts+=(\"No ${role,,} model\"); vals+=(\"none\"); fi\n # Only one real choice and nothing to decline: take it silently.\n if (( ${#vals[@]} == 1 )); then printf '%s' \"${vals[0]}\"; return 0; fi\n echo >&2\n echo \"Where should your ${role} model come from?\" >&2\n for i in \"${!opts[@]}\"; do printf \" %d) %s\\n\" \"$((i+1))\" \"${opts[$i]}\" >&2; done\n echo >&2\n while :; do\n read -rp \"Choice: \" ans &2; exit 1; }\n ans=\"${ans// /}\"\n if [[ \"${ans}\" =~ ^[0-9]+$ ]] && (( ans >= 1 && ans <= ${#vals[@]} )); then\n printf '%s' \"${vals[$((ans-1))]}\"; return 0\n fi\n echo \"Please enter a number between 1 and ${#vals[@]}.\" >&2\n done\n}\n# ---- model pickers --------------------------------------------------------\n# pick_ou -> prints newline-separated refs (may be empty)\npick_ou() {\n local role=\"$1\" multi=\"$2\" i ans=\"\"\n ensure_ou_catalog\n echo >&2; echo \"OU AI Sandbox models:\" >&2\n for i in \"${!OU[@]}\"; do printf \" %3d) %s\\n\" \"$((i+1))\" \"${OU[$i]}\" >&2; done\n echo >&2\n if (( multi )); then\n read -rp \"${role} number(s), comma-separated (blank = none): \" ans = 1 && n <= ${#OU[@]} )); then\n printf 'litellm/%s\\n' \"${OU[$((n-1))]}\"\n else\n echo \"⚠️ Ignoring invalid entry '${n}'.\" >&2\n fi\n done\n return 0\n fi\n while :; do\n read -rp \"${role} model number: \" ans &2; exit 1; }\n ans=\"${ans// /}\"\n if [[ \"${ans}\" =~ ^[0-9]+$ ]] && (( ans >= 1 && ans <= ${#OU[@]} )); then\n printf 'litellm/%s\\n' \"${OU[$((ans-1))]}\"; return 0\n fi\n echo \"Please enter a number between 1 and ${#OU[@]}.\" >&2\n done\n}\n# pick_or -> prints newline-separated refs (may be empty)\npick_or() {\n local role=\"$1\" multi=\"$2\" i ans=\"\"\n ensure_or_catalog || return 1\n echo >&2\n echo \"OpenRouter models (tool-capable; free first, then by price — remember it's your own credit):\" >&2\n for i in \"${!OR_LABELS[@]}\"; do printf \" %3d) %s\\n\" \"$((i+1))\" \"${OR_LABELS[$i]}\" >&2; done\n echo >&2\n if (( multi )); then\n read -rp \"${role} number(s), comma-separated (blank = none): \" ans = 1 && n <= ${#OR_IDS[@]} )); then\n printf 'openrouter/%s\\n' \"${OR_IDS[$((n-1))]}\"\n else\n echo \"⚠️ Ignoring invalid entry '${n}'.\" >&2\n fi\n done\n return 0\n fi\n while :; do\n read -rp \"${role} model number: \" ans &2; exit 1; }\n ans=\"${ans// /}\"\n if [[ \"${ans}\" =~ ^[0-9]+$ ]] && (( ans >= 1 && ans <= ${#OR_IDS[@]} )); then\n printf 'openrouter/%s\\n' \"${OR_IDS[$((ans-1))]}\"; return 0\n fi\n echo \"Please enter a number between 1 and ${#OR_IDS[@]}.\" >&2\n done\n}\n# ---- apply ----------------------------------------------------------------\napply_models() { # apply_models [fallback-refs...]\n local primary=\"$1\"; shift\n echo\n echo \"→ primary: ${primary}\"\n if ! openclaw models set \"${primary}\"; then\n echo \"❌ Could not set primary '${primary}'.\"\n echo \" Run 'openclaw models list' for valid refs, and check the gateway is running.\"\n exit 1\n fi\n openclaw models fallbacks clear >/dev/null 2>&1 || true\n local f added=0\n for f in \"$@\"; do\n [[ -n \"${f}\" ]] || continue\n # A fallback identical to the primary can never help — drop it.\n if [[ \"${f}\" == \"${primary}\" ]]; then\n echo \"⚠️ Skipping fallback '${f}' — it is the same as your primary.\"\n continue\n fi\n echo \"→ fallback: ${f}\"\n if openclaw models fallbacks add \"${f}\"; then added=$((added+1)); else echo \"⚠️ Couldn't add fallback '${f}' — skipped.\"; fi\n done\n (( added == 0 )) && echo \"→ fallback: none\"\n echo; echo \"Current model configuration:\"\n openclaw models status 2>/dev/null || echo \"⚠️ 'openclaw models status' unavailable (is the gateway running?).\"\n echo \"(Hot-reloaded for new sessions. For the chat you're in now, switch with /model.)\"\n}\nwarn_weak_fallback() {\n local f\n for f in \"$@\"; do\n if [[ \"${f,,}\" == *\"30b\"* || \"${f,,}\" == *\"coder\"* ]]; then\n echo\n echo \"ℹ️ Note: '${f}' is a small model, used only when your primary is unavailable.\"\n echo \" If your agent starts inventing results or claiming it has no network access,\"\n echo \" check which model is actually answering — you may have failed over.\"\n return\n fi\n done\n}\n# ---- primary --------------------------------------------------------------\nPRIMARY=\"\"\nwhile [[ -z \"${PRIMARY}\" ]]; do\n prov=\"$(choose_provider \"PRIMARY\" 0)\"\n case \"${prov}\" in\n ou) PRIMARY=\"$(pick_ou \"Primary\" 0)\" ;;\n or) PRIMARY=\"$(pick_or \"Primary\" 0)\" || {\n if (( HAVE_LL )); then echo \"Falling back to the OU AI Sandbox.\" >&2; PRIMARY=\"$(pick_ou \"Primary\" 0)\"; else exit 1; fi\n } ;;\n esac\ndone\n# ---- fallback -------------------------------------------------------------\nFB=()\nprov=\"$(choose_provider \"FALLBACK\" 1)\"\ncase \"${prov}\" in\n ou) mapfile -t FB < <(pick_ou \"Fallback\" 1) ;;\n or) mapfile -t FB < <(pick_or \"Fallback\" 1) || FB=() ;;\n none) FB=() ;;\nesac\n# ---- go -------------------------------------------------------------------\napply_models \"${PRIMARY}\" ${FB[@]+\"${FB[@]}\"}\nwarn_weak_fallback ${FB[@]+\"${FB[@]}\"}\n"}, "1de80ca72543a6c81ab7361cdcb8e8cfc5b9bae4": {"t": 1785770975.5156803, "n": 12938, "h": "0f0136da61844abe", "c": "#!/usr/bin/env bash\n# Switch the OpenClaw model.\n#\n# Asks where your PRIMARY model should come from — the OU LiteLLM Sandbox (the\n# course endpoint, no per-token cost) or OpenRouter (your own key and credit) —\n# then lets you pick from that catalog. Then asks the same for your FALLBACK,\n# which you can decline. Primary and fallback may come from different providers.\n#\n# Either key works on its own. With only one, the provider question is skipped\n# for the primary and reduced to \"that provider or no fallback\" for the backup.\n#\n# The gateway hot-reloads. For a chat you are already in, switch with /model.\nset -uo pipefail\n# Make 'openclaw' findable in non-interactive shells.\nsource \"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)/_env.sh\" 2>/dev/null || true\nLITELLM_BASE_URL=\"${LITELLM_BASE_URL:-https://litellm.lib.ou.edu}\"\nENV_FILE=\"${HOME}/.openclaw/.env\"\noubase=\"${LITELLM_BASE_URL%/}\"\n\nread_env() { # read_env VAR -> value from process env or ~/.openclaw/.env\n local var=\"$1\" val=\"${!1:-}\"\n [[ -z \"${val}\" && -f \"${ENV_FILE}\" ]] && val=\"$(grep -E \"^${var}=\" \"${ENV_FILE}\" | tail -n1 | cut -d= -f2- || true)\"\n printf '%s' \"${val}\"\n}\nusage() {\n cat <<'EOF'\nUsage: bash scripts/select-model.sh\n\n Walks you through choosing a primary model and an optional fallback.\n For each, you first choose a provider (OU AI Sandbox or OpenRouter),\n then pick from that provider's catalog.\nEOF\n}\nfor arg in \"$@\"; do\n case \"${arg}\" in\n -h|--help) usage; exit 0 ;;\n *) echo \"Unknown option '${arg}'.\"; usage; exit 1 ;;\n esac\ndone\n# ---- prerequisites --------------------------------------------------------\nneed() { command -v \"$1\" >/dev/null 2>&1 || { echo \"❌ Required tool '$1' not found — $2\"; exit 1; }; }\nneed curl \"rebuild the Codespace or install curl.\"\nneed python3 \"rebuild the Codespace or install python3.\"\nneed openclaw \"OpenClaw isn't on PATH yet — start the Gateway task first, or run: bash .devcontainer/setup.sh\"\n# ---- which providers are available? ---------------------------------------\nOR_KEY=\"$(read_env OPENROUTER_API_KEY)\"\nLL_KEY=\"$(read_env LITELLM_API_KEY)\"\n[[ \"${OR_KEY}\" == \"sk-or-REPLACE_ME\" ]] && OR_KEY=\"\"\n[[ \"${LL_KEY}\" == \"sk-REPLACE_ME\" ]] && LL_KEY=\"\"\nHAVE_OR=0; [[ -n \"${OR_KEY}\" ]] && HAVE_OR=1\nHAVE_LL=0; [[ -n \"${LL_KEY}\" ]] && HAVE_LL=1\nif (( ! HAVE_OR && ! HAVE_LL )); then\n echo \"❌ No model provider key found.\"\n echo\n echo \" You need at least one of:\"\n echo \" • OU AI Sandbox key (LITELLM_API_KEY) — the course's first choice, no per-token cost.\"\n echo \" • Your own OpenRouter key (OPENROUTER_API_KEY) — https://openrouter.ai (Settings → Keys).\"\n echo\n echo \" Set one with: bash scripts/set-key.sh\"\n exit 1\nfi\n# ---- catalogs (fetched once, on demand) -----------------------------------\nOU=(); OU_LOADED=0\nOR_IDS=(); OR_LABELS=(); OR_LOADED=0\n\nensure_ou_catalog() {\n (( OU_LOADED )) && return 0\n echo \"Fetching OU models from ${oubase} ...\" >&2\n local http=000 url\n for url in \"${oubase}/v1/models\" \"${oubase}/models\"; do\n http=\"$(curl -s -m 20 -o /tmp/ou_models.json -w '%{http_code}' -H \"Authorization: Bearer ${LL_KEY}\" \"${url}\" || echo 000)\"\n [[ \"${http}\" == \"200\" ]] && break\n done\n case \"${http}\" in\n 200) ;;\n 401|403) echo \"❌ OU Sandbox key rejected (HTTP ${http}).\" >&2; exit 1 ;;\n 000) echo \"❌ Could not reach ${oubase} (network/endpoint issue). Check the URL or try again.\" >&2; exit 1 ;;\n *) echo \"❌ OU gateway returned HTTP ${http}. Details in /tmp/ou_models.json\" >&2; exit 1 ;;\n esac\n mapfile -t OU < <(python3 -c 'import json\nfor m in json.load(open(\"/tmp/ou_models.json\")).get(\"data\",[]): print(m[\"id\"])' 2>/dev/null | sort -u)\n ((${#OU[@]})) || { echo \"❌ No models parsed from the OU response (/tmp/ou_models.json may be malformed).\" >&2; exit 1; }\n OU_LOADED=1\n}\nensure_or_catalog() {\n (( OR_LOADED )) && return 0\n # Listing is public, but a bad key makes the choice moot — check it first.\n local kc\n kc=\"$(curl -s -m 15 -o /dev/null -w '%{http_code}' -H \"Authorization: Bearer ${OR_KEY}\" https://openrouter.ai/api/v1/key || echo 000)\"\n if [[ \"${kc}\" != \"200\" ]]; then\n echo \"⚠️ OpenRouter key check returned HTTP ${kc} — it may be invalid, disabled, or out of credit.\" >&2\n (( HAVE_LL )) && echo \" The OU AI Sandbox is available instead.\" >&2\n local yn=\"\"\n read -rp \"Continue with OpenRouter anyway? [y/N] \" yn &2\n if ! curl -fsS -m 30 \"https://openrouter.ai/api/v1/models?supported_parameters=tools\" -o /tmp/or_models.json; then\n echo \"❌ Could not reach OpenRouter (network/endpoint). Try again in a moment.\" >&2\n return 1\n fi\n local rows=()\n mapfile -t rows < <(python3 - <<'PY'\nimport json\ndata = json.load(open(\"/tmp/or_models.json\")).get(\"data\", [])\npopular = {\"anthropic\",\"openai\",\"google\",\"x-ai\",\"meta-llama\",\"mistralai\",\n \"qwen\",\"deepseek\",\"z-ai\",\"moonshotai\",\"minimax\"}\ndef perM(v):\n try: return float(v) * 1_000_000\n except Exception: return None\nrows = []\nfor m in data:\n mid = m.get(\"id\", \"\")\n vendor = mid.split(\"/\")[0] if \"/\" in mid else mid\n arch = m.get(\"architecture\", {}) or {}\n if \"text\" not in (arch.get(\"input_modalities\") or []): continue\n if \"tools\" not in (m.get(\"supported_parameters\") or []): continue\n if vendor not in popular: continue\n pr = m.get(\"pricing\", {}) or {}\n pin, pout = perM(pr.get(\"prompt\")), perM(pr.get(\"completion\"))\n free = (pin == 0 and pout == 0)\n ctx = m.get(\"context_length\") or 0\n ctxs = f\"{ctx // 1000}k\" if ctx else \"?\"\n if free: price = \"FREE\"\n elif pin is not None and pout is not None: price = f\"${pin:.2f}/${pout:.2f} /M\"\n else: price = \"price n/a\"\n rows.append((0 if free else 1, pin if pin is not None else 9e9, vendor, mid,\n f\"{price:<16} {mid} ({ctxs})\"))\nrows.sort(key=lambda r: (r[0], r[1], r[2], r[3]))\n# OpenRouter's own Free Models Router: zero-cost, picks a free model per\n# request and filters for tool support itself. The ?supported_parameters=tools\n# catalog filter excludes routers, so add it explicitly at the top. Other\n# openrouter/* routers stay hidden — some fan out to paid models and would\n# drain a student key fast.\nrows.insert(0, (0, 0.0, \"openrouter\", \"openrouter/free\",\n f\"{'FREE':<16} openrouter/free (router — picks a free, tool-capable model per request; rate-limited, fine for smoke tests)\"))\nfor r in rows: print(f\"{r[3]}\\t{r[4]}\")\nPY\n)\n if ! ((${#rows[@]})); then\n echo \"❌ No tool-capable models from popular vendors returned (OpenRouter's catalog may have shifted).\" >&2\n return 1\n fi\n local row\n OR_IDS=(); OR_LABELS=()\n for row in \"${rows[@]}\"; do\n OR_IDS+=(\"${row%%$'\\t'*}\")\n OR_LABELS+=(\"${row#*$'\\t'}\")\n done\n OR_LOADED=1\n}\n# ---- provider question ----------------------------------------------------\n# choose_provider -> prints \"ou\" | \"or\" | \"none\"\nchoose_provider() {\n local role=\"$1\" allow_none=\"$2\"\n local opts=() vals=() i ans=\"\"\n if (( HAVE_LL )); then opts+=(\"OU AI Sandbox (LiteLLM) — course endpoint, no per-token cost\"); vals+=(\"ou\"); fi\n if (( HAVE_OR )); then opts+=(\"OpenRouter — your own key and your own credit\"); vals+=(\"or\"); fi\n if (( allow_none )); then opts+=(\"No ${role,,} model\"); vals+=(\"none\"); fi\n # Only one real choice and nothing to decline: take it silently.\n if (( ${#vals[@]} == 1 )); then printf '%s' \"${vals[0]}\"; return 0; fi\n echo >&2\n echo \"Where should your ${role} model come from?\" >&2\n for i in \"${!opts[@]}\"; do printf \" %d) %s\\n\" \"$((i+1))\" \"${opts[$i]}\" >&2; done\n echo >&2\n while :; do\n read -rp \"Choice: \" ans &2; exit 1; }\n ans=\"${ans// /}\"\n if [[ \"${ans}\" =~ ^[0-9]+$ ]] && (( ans >= 1 && ans <= ${#vals[@]} )); then\n printf '%s' \"${vals[$((ans-1))]}\"; return 0\n fi\n echo \"Please enter a number between 1 and ${#vals[@]}.\" >&2\n done\n}\n# ---- model pickers --------------------------------------------------------\n# pick_ou -> prints newline-separated refs (may be empty)\npick_ou() {\n local role=\"$1\" multi=\"$2\" i ans=\"\"\n ensure_ou_catalog\n echo >&2; echo \"OU AI Sandbox models:\" >&2\n for i in \"${!OU[@]}\"; do printf \" %3d) %s\\n\" \"$((i+1))\" \"${OU[$i]}\" >&2; done\n echo >&2\n if (( multi )); then\n read -rp \"${role} number(s), comma-separated (blank = none): \" ans = 1 && n <= ${#OU[@]} )); then\n printf 'litellm/%s\\n' \"${OU[$((n-1))]}\"\n else\n echo \"⚠️ Ignoring invalid entry '${n}'.\" >&2\n fi\n done\n return 0\n fi\n while :; do\n read -rp \"${role} model number: \" ans &2; exit 1; }\n ans=\"${ans// /}\"\n if [[ \"${ans}\" =~ ^[0-9]+$ ]] && (( ans >= 1 && ans <= ${#OU[@]} )); then\n printf 'litellm/%s\\n' \"${OU[$((ans-1))]}\"; return 0\n fi\n echo \"Please enter a number between 1 and ${#OU[@]}.\" >&2\n done\n}\n# pick_or -> prints newline-separated refs (may be empty)\npick_or() {\n local role=\"$1\" multi=\"$2\" i ans=\"\"\n ensure_or_catalog || return 1\n echo >&2\n echo \"OpenRouter models (tool-capable; free first, then by price — remember it's your own credit):\" >&2\n for i in \"${!OR_LABELS[@]}\"; do printf \" %3d) %s\\n\" \"$((i+1))\" \"${OR_LABELS[$i]}\" >&2; done\n echo >&2\n if (( multi )); then\n read -rp \"${role} number(s), comma-separated (blank = none): \" ans = 1 && n <= ${#OR_IDS[@]} )); then\n printf 'openrouter/%s\\n' \"${OR_IDS[$((n-1))]}\"\n else\n echo \"⚠️ Ignoring invalid entry '${n}'.\" >&2\n fi\n done\n return 0\n fi\n while :; do\n read -rp \"${role} model number: \" ans &2; exit 1; }\n ans=\"${ans// /}\"\n if [[ \"${ans}\" =~ ^[0-9]+$ ]] && (( ans >= 1 && ans <= ${#OR_IDS[@]} )); then\n printf 'openrouter/%s\\n' \"${OR_IDS[$((ans-1))]}\"; return 0\n fi\n echo \"Please enter a number between 1 and ${#OR_IDS[@]}.\" >&2\n done\n}\n# ---- apply ----------------------------------------------------------------\napply_models() { # apply_models [fallback-refs...]\n local primary=\"$1\"; shift\n echo\n echo \"→ primary: ${primary}\"\n if ! openclaw models set \"${primary}\"; then\n echo \"❌ Could not set primary '${primary}'.\"\n echo \" Run 'openclaw models list' for valid refs, and check the gateway is running.\"\n exit 1\n fi\n openclaw models fallbacks clear >/dev/null 2>&1 || true\n local f added=0\n for f in \"$@\"; do\n [[ -n \"${f}\" ]] || continue\n # A fallback identical to the primary can never help — drop it.\n if [[ \"${f}\" == \"${primary}\" ]]; then\n echo \"⚠️ Skipping fallback '${f}' — it is the same as your primary.\"\n continue\n fi\n echo \"→ fallback: ${f}\"\n if openclaw models fallbacks add \"${f}\"; then added=$((added+1)); else echo \"⚠️ Couldn't add fallback '${f}' — skipped.\"; fi\n done\n (( added == 0 )) && echo \"→ fallback: none\"\n echo; echo \"Current model configuration:\"\n openclaw models status 2>/dev/null || echo \"⚠️ 'openclaw models status' unavailable (is the gateway running?).\"\n echo \"(Hot-reloaded for new sessions. For the chat you're in now, switch with /model.)\"\n}\nwarn_weak_fallback() {\n local f\n for f in \"$@\"; do\n if [[ \"${f,,}\" == *\"30b\"* || \"${f,,}\" == *\"coder\"* ]]; then\n echo\n echo \"ℹ️ Note: '${f}' is a small model, used only when your primary is unavailable.\"\n echo \" If your agent starts inventing results or claiming it has no network access,\"\n echo \" check which model is actually answering — you may have failed over.\"\n return\n fi\n done\n}\n# ---- primary --------------------------------------------------------------\nPRIMARY=\"\"\nwhile [[ -z \"${PRIMARY}\" ]]; do\n prov=\"$(choose_provider \"PRIMARY\" 0)\"\n case \"${prov}\" in\n ou) PRIMARY=\"$(pick_ou \"Primary\" 0)\" ;;\n or) PRIMARY=\"$(pick_or \"Primary\" 0)\" || {\n if (( HAVE_LL )); then echo \"Falling back to the OU AI Sandbox.\" >&2; PRIMARY=\"$(pick_ou \"Primary\" 0)\"; else exit 1; fi\n } ;;\n esac\ndone\n# ---- fallback -------------------------------------------------------------\nFB=()\nprov=\"$(choose_provider \"FALLBACK\" 1)\"\ncase \"${prov}\" in\n ou) mapfile -t FB < <(pick_ou \"Fallback\" 1) ;;\n or) mapfile -t FB < <(pick_or \"Fallback\" 1) || FB=() ;;\n none) FB=() ;;\nesac\n# ---- go -------------------------------------------------------------------\napply_models \"${PRIMARY}\" ${FB[@]+\"${FB[@]}\"}\nwarn_weak_fallback ${FB[@]+\"${FB[@]}\"}\n"}, "f1673243cdd735aa2655b0663a6efca23e9253d6": {"t": 1785770976.8410652, "n": 13568, "h": "c9f7f0227b5db269", "c": "#!/usr/bin/env bash\n# Switch the OpenClaw model — primary AND fallback — from either provider:\n# • OU AI Sandbox (LiteLLM gateway) — needs LITELLM_API_KEY\n# • OpenRouter — needs OPENROUTER_API_KEY\n# Both catalogs are polled up front. You only see providers you hold a key\n# for; with no keys at all the script explains and exits. Prices are shown\n# per million tokens (input/output) in the menus and after selection.\n# The gateway hot-reloads (no restart).\nset -uo pipefail\n# Make 'openclaw' findable in non-interactive shells.\nsource \"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)/_env.sh\" 2>/dev/null || true\n\nLITELLM_BASE_URL=\"${LITELLM_BASE_URL:-https://litellm.lib.ou.edu}\"\nENV_FILE=\"${HOME}/.openclaw/.env\"\noubase=\"${LITELLM_BASE_URL%/}\"\nTTY=\"${SELECT_MODEL_TTY:-/dev/tty}\" # overridable for testing\nTOP_N=\"${SELECT_MODEL_TOP_N:-20}\"\n\nread_env() { # read_env VAR -> value from process env or ~/.openclaw/.env\n local var=\"$1\" val=\"${!1:-}\"\n [[ -z \"${val}\" && -f \"${ENV_FILE}\" ]] && val=\"$(grep -E \"^${var}=\" \"${ENV_FILE}\" | tail -n1 | cut -d= -f2- || true)\"\n printf '%s' \"${val}\"\n}\n\n# ---- prerequisites --------------------------------------------------------\nneed() { command -v \"$1\" >/dev/null 2>&1 || { echo \"❌ Required tool '$1' not found — $2\"; exit 1; }; }\nneed curl \"rebuild the Codespace or install curl.\"\nneed python3 \"rebuild the Codespace or install python3.\"\nneed openclaw \"OpenClaw isn't on PATH yet — start the Gateway task first, or run: bash .devcontainer/setup.sh\"\n\n# ---- key detection --------------------------------------------------------\nOU_KEY=\"$(read_env LITELLM_API_KEY)\"\n[[ \"${OU_KEY}\" == \"sk-REPLACE_ME\" ]] && OU_KEY=\"\"\nOR_KEY=\"$(read_env OPENROUTER_API_KEY)\"\n\nif [[ -z \"${OU_KEY}\" && -z \"${OR_KEY}\" ]]; then\n echo \"❌ No API keys found — nothing to select a model from.\"\n echo \" • OU AI Sandbox key: run bash scripts/set-key.sh (LITELLM_API_KEY, starts with sk-)\"\n echo \" • OpenRouter key: add the OPENROUTER_API_KEY Codespaces secret (starts with sk-or-)\"\n echo \" Add at least one, then re-run this script.\"\n exit 1\nfi\n\n# ---- poll OU catalog (if key) ---------------------------------------------\n# MODELS/PRICES file format (tab-separated): ref \\t display-line\n# ref is the full OpenClaw model ref, e.g. litellm/Qwen3 Coder 30B\nOU_ROWS_FILE=/tmp/select_model_ou_rows.tsv; : > \"${OU_ROWS_FILE}\"\nif [[ -n \"${OU_KEY}\" ]]; then\n echo \"Polling OU AI Sandbox catalog (${oubase}) ...\"\n http=000\n for url in \"${oubase}/v1/models\" \"${oubase}/models\"; do\n http=\"$(curl -s -m 20 -o /tmp/ou_models.json -w '%{http_code}' -H \"Authorization: Bearer ${OU_KEY}\" \"${url}\" || echo 000)\"\n [[ \"${http}\" == \"200\" ]] && break\n done\n case \"${http}\" in\n 200) ;;\n 401|403) echo \"❌ OU key rejected (HTTP ${http}). Fix it with: bash scripts/set-key.sh\"; [[ -z \"${OR_KEY}\" ]] && exit 1; echo \" Continuing with OpenRouter only.\"; OU_KEY=\"\" ;;\n 000) echo \"❌ Could not reach ${oubase}.\"; [[ -z \"${OR_KEY}\" ]] && exit 1; echo \" Continuing with OpenRouter only.\"; OU_KEY=\"\" ;;\n *) echo \"❌ OU gateway returned HTTP ${http}.\"; [[ -z \"${OR_KEY}\" ]] && exit 1; echo \" Continuing with OpenRouter only.\"; OU_KEY=\"\" ;;\n esac\nfi\nif [[ -n \"${OU_KEY}\" ]]; then\n # Pricing (best effort): LiteLLM /model/info exposes per-token costs when\n # the gateway is configured with them; otherwise we show \"price n/a\".\n curl -s -m 20 -H \"Authorization: Bearer ${OU_KEY}\" \"${oubase}/model/info\" -o /tmp/ou_model_info.json \\\n || curl -s -m 20 -H \"Authorization: Bearer ${OU_KEY}\" \"${oubase}/v1/model/info\" -o /tmp/ou_model_info.json \\\n || : > /tmp/ou_model_info.json\n python3 - > \"${OU_ROWS_FILE}\" <<'PY'\nimport json\ndef load(p):\n try:\n with open(p) as f: return json.load(f)\n except Exception: return {}\nmodels = sorted({m.get(\"id\",\"\") for m in load(\"/tmp/ou_models.json\").get(\"data\",[]) if m.get(\"id\")})\nprices = {}\nfor e in load(\"/tmp/ou_model_info.json\").get(\"data\",[]) or []:\n name = e.get(\"model_name\") or \"\"\n info = e.get(\"model_info\") or {}\n ci, co = info.get(\"input_cost_per_token\"), info.get(\"output_cost_per_token\")\n try:\n if ci is not None and co is not None:\n prices[name] = (float(ci)*1e6, float(co)*1e6)\n except Exception:\n pass\nfor mid in models:\n p = prices.get(mid)\n if p and (p[0] or p[1]): price = f\"${p[0]:.2f}/${p[1]:.2f} /M\"\n elif p: price = \"FREE\"\n else: price = \"price n/a\"\n print(f\"litellm/{mid}\\t{price:<18} {mid}\")\nPY\n [[ -s \"${OU_ROWS_FILE}\" ]] || { echo \"❌ No models parsed from the OU response.\"; [[ -z \"${OR_KEY}\" ]] && exit 1; echo \" Continuing with OpenRouter only.\"; OU_KEY=\"\"; }\nfi\n\n# ---- poll OpenRouter catalog + weekly rankings (if key) --------------------\nOR_ROWS_FILE=/tmp/select_model_or_rows.tsv; : > \"${OR_ROWS_FILE}\"\nOR_CATALOG=/tmp/or_models.json\nif [[ -n \"${OR_KEY}\" ]]; then\n kc=\"$(curl -s -m 15 -o /dev/null -w '%{http_code}' -H \"Authorization: Bearer ${OR_KEY}\" https://openrouter.ai/api/v1/key || echo 000)\"\n if [[ \"${kc}\" != \"200\" ]]; then\n echo \"⚠️ OpenRouter key check returned HTTP ${kc} — it may be invalid, expired, or out of credit.\"\n echo \" Models will still be listed, but calls will fail at runtime until the key works.\"\n fi\n echo \"Polling OpenRouter catalog + weekly popularity rankings ...\"\n if ! curl -fsS -m 30 \"https://openrouter.ai/api/v1/models\" -o \"${OR_CATALOG}\"; then\n echo \"❌ Could not reach OpenRouter (network/endpoint).\"\n [[ -z \"${OU_KEY}\" ]] && exit 1\n echo \" Continuing with OU Sandbox only.\"; OR_KEY=\"\"\n else\n # Rankings are best-effort (undocumented endpoint); on failure we fall\n # back to tool-capable models from major vendors sorted free-then-cheapest.\n curl -fsS -m 30 \"https://openrouter.ai/api/frontend/models/find?order=top-weekly\" -o /tmp/or_rankings.json || : > /tmp/or_rankings.json\n python3 - \"${TOP_N}\" > \"${OR_ROWS_FILE}\" <<'PY'\nimport json, sys\nTOP_N = int(sys.argv[1])\ndef load(p):\n try:\n with open(p) as f: return json.load(f)\n except Exception: return {}\ncatalog = {m.get(\"id\"): m for m in load(\"/tmp/or_models.json\").get(\"data\",[]) if m.get(\"id\")}\ndef tool_capable(m): return \"tools\" in (m.get(\"supported_parameters\") or [])\ndef perM(v):\n try: return float(v)*1e6\n except Exception: return None\ndef fmt(m):\n pr = m.get(\"pricing\") or {}\n pin, pout = perM(pr.get(\"prompt\")), perM(pr.get(\"completion\"))\n ctx = m.get(\"context_length\") or 0\n ctxs = f\"{ctx//1000}k\" if ctx else \"?\"\n if pin == 0 and pout == 0: price = \"FREE\"\n elif pin is not None and pout is not None: price = f\"${pin:.2f}/${pout:.2f} /M\"\n else: price = \"price n/a\"\n return f\"{price:<18} {m['id']} ({ctxs})\"\nranked = []\nd = load(\"/tmp/or_rankings.json\").get(\"data\") or {}\nfor m in (d.get(\"models\") or []):\n slug = m.get(\"slug\")\n if slug and slug in catalog and tool_capable(catalog[slug]):\n ranked.append(slug)\n if len(ranked) >= TOP_N: break\nif not ranked: # rankings unavailable → curated fallback\n popular = {\"anthropic\",\"openai\",\"google\",\"x-ai\",\"meta-llama\",\"mistralai\",\n \"qwen\",\"deepseek\",\"z-ai\",\"moonshotai\",\"minimax\"}\n rows = []\n for mid, m in catalog.items():\n vendor = mid.split(\"/\")[0]\n if vendor not in popular or not tool_capable(m): continue\n pr = m.get(\"pricing\") or {}\n pin, pout = perM(pr.get(\"prompt\")), perM(pr.get(\"completion\"))\n free = (pin == 0 and pout == 0)\n rows.append((0 if free else 1, pin if pin is not None else 9e9, mid))\n rows.sort()\n ranked = [r[2] for r in rows[:TOP_N]]\n# OpenRouter's own Free Models Router: zero-cost, picks a free model per\n# request and filters for tool support itself. Pinned first if available.\nFREE_ROUTER = \"openrouter/free\"\nif FREE_ROUTER in catalog and FREE_ROUTER not in ranked:\n ranked.insert(0, FREE_ROUTER)\nelif FREE_ROUTER not in catalog:\n print(f\"openrouter/{FREE_ROUTER}\\t{'FREE':<18} {FREE_ROUTER} (rotating free models)\")\nfor slug in ranked:\n print(f\"openrouter/{slug}\\t{fmt(catalog[slug])}\")\nPY\n [[ -s \"${OR_ROWS_FILE}\" ]] || { echo \"❌ No OpenRouter models parsed.\"; [[ -z \"${OU_KEY}\" ]] && exit 1; echo \" Continuing with OU Sandbox only.\"; OR_KEY=\"\"; }\n fi\nfi\n\n# ---- load rows ------------------------------------------------------------\nREFS=(); LINES=()\nif [[ -s \"${OU_ROWS_FILE}\" ]]; then\n while IFS=$'\\t' read -r ref line; do REFS+=(\"${ref}\"); LINES+=(\"${line}\"); done < \"${OU_ROWS_FILE}\"\nfi\nN_OU=${#REFS[@]}\nif [[ -s \"${OR_ROWS_FILE}\" ]]; then\n while IFS=$'\\t' read -r ref line; do REFS+=(\"${ref}\"); LINES+=(\"${line}\"); done < \"${OR_ROWS_FILE}\"\nfi\nN_ALL=${#REFS[@]}\n(( N_ALL )) || { echo \"❌ No models available from any provider.\"; exit 1; }\n\ndescribe() { # describe -> pretty line for that ref\n local ref=\"$1\" i\n for i in \"${!REFS[@]}\"; do [[ \"${REFS[$i]}\" == \"${ref}\" ]] && { echo \"${LINES[$i]}\"; return; }; done\n echo \"${ref}\"\n}\n\n# Validate a typed OpenRouter model id against the live catalog.\n# Prints \"ok \" or \"missing\" or \"notools\".\ncheck_or_model() {\n python3 - \"$1\" <<'PY'\nimport json, sys\nmid = sys.argv[1]\ntry:\n with open(\"/tmp/or_models.json\") as f:\n catalog = {m.get(\"id\"): m for m in json.load(f).get(\"data\",[])}\nexcept Exception:\n catalog = {}\nm = catalog.get(mid)\nif not m: print(\"missing\"); sys.exit()\nif mid != \"openrouter/free\" and \"tools\" not in (m.get(\"supported_parameters\") or []):\n print(\"notools\"); sys.exit()\ndef perM(v):\n try: return float(v)*1e6\n except Exception: return None\npr = m.get(\"pricing\") or {}\npin, pout = perM(pr.get(\"prompt\")), perM(pr.get(\"completion\"))\nif pin == 0 and pout == 0: price = \"FREE\"\nelif pin is not None and pout is not None: price = f\"${pin:.2f}/${pout:.2f} /M\"\nelse: price = \"price n/a\"\nctx = m.get(\"context_length\") or 0\nctxs = f\"{ctx//1000}k\" if ctx else \"?\"\nprint(f\"ok {price:<18} {mid} ({ctxs})\")\nPY\n}\n\nshow_menu() { # show_menu \n local role=\"$1\" i\n echo\n if (( N_OU )); then\n echo \"OU AI Sandbox models (litellm):\"\n for (( i=0; i N_OU )); then\n echo \"OpenRouter — top $((N_ALL-N_OU)) by weekly popularity (tool-capable):\"\n for (( i=N_OU; i -> sets PICKED (ref) and PICKED_DESC; empty PICKED = none\n local role=\"$1\" default_hint choice res\n if [[ \"${role}\" == \"primary\" ]]; then default_hint=\"[default 1]\"; else default_hint=\"[default 0 = none]\"; fi\n while true; do\n show_menu \"${role}\"\n echo\n read -rp \"Pick the ${role} model ${default_hint}: \" choice < \"${TTY}\" || choice=\"\"\n if [[ -z \"${choice}\" ]]; then\n if [[ \"${role}\" == \"primary\" ]]; then choice=1; else PICKED=\"\"; PICKED_DESC=\"(none)\"; return; fi\n fi\n if [[ \"${role}\" == \"fallback\" && \"${choice}\" == \"0\" ]]; then PICKED=\"\"; PICKED_DESC=\"(none)\"; return; fi\n if [[ \"${choice}\" =~ ^[Tt]$ ]]; then\n (( N_ALL > N_OU )) || { echo \"Typed entry needs an OpenRouter key.\"; continue; }\n read -rp \"OpenRouter model id (vendor/model, e.g. anthropic/claude-sonnet-5): \" mid < \"${TTY}\" || mid=\"\"\n [[ -z \"${mid}\" ]] && continue\n res=\"$(check_or_model \"${mid}\")\"\n case \"${res}\" in\n missing) echo \"❌ '${mid}' is not in OpenRouter's live catalog — check the spelling at openrouter.ai/models.\"; continue ;;\n notools) echo \"❌ '${mid}' exists but does not support tool calling, which OpenClaw requires — pick another.\"; continue ;;\n ok\\ *) PICKED=\"openrouter/${mid}\"; PICKED_DESC=\"${res#ok }\"; return ;;\n *) echo \"❌ Could not validate '${mid}' — try again.\"; continue ;;\n esac\n fi\n if [[ \"${choice}\" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= N_ALL )); then\n PICKED=\"${REFS[$((choice-1))]}\"; PICKED_DESC=\"${LINES[$((choice-1))]}\"; return\n fi\n echo \"Please enter a number between 1 and ${N_ALL}$( [[ \"${role}\" == \"fallback\" ]] && echo \", 0 for none,\" ) or T.\"\n done\n}\n\n# ---- choose primary, then fallback (same options both times) ---------------\npick_model primary\nPRIMARY=\"${PICKED}\"; PRIMARY_DESC=\"${PICKED_DESC}\"\necho \"✓ primary: ${PRIMARY_DESC}\"\n\npick_model fallback\nFALLBACK=\"${PICKED}\"; FALLBACK_DESC=\"${PICKED_DESC}\"\n[[ \"${FALLBACK}\" == \"${PRIMARY}\" ]] && { echo \"(fallback same as primary — skipping fallback)\"; FALLBACK=\"\"; FALLBACK_DESC=\"(none)\"; }\necho \"✓ fallback: ${FALLBACK_DESC}\"\n\n# ---- apply ------------------------------------------------------------------\necho\necho \"→ primary: ${PRIMARY}\"\nif ! openclaw models set \"${PRIMARY}\"; then\n echo \"❌ Could not set primary '${PRIMARY}'.\"\n echo \" Run 'openclaw models list' for valid refs, and check the gateway is running.\"\n exit 1\nfi\nopenclaw models fallbacks clear >/dev/null 2>&1 || true\nif [[ -n \"${FALLBACK}\" ]]; then\n echo \"→ fallback: ${FALLBACK}\"\n openclaw models fallbacks add \"${FALLBACK}\" || echo \"⚠️ Couldn't add fallback '${FALLBACK}' — skipped.\"\nfi\n\necho\necho \"Selected configuration (price per 1M tokens, input/output):\"\necho \" primary: ${PRIMARY_DESC}\"\necho \" fallback: ${FALLBACK_DESC}\"\necho\necho \"Current model configuration:\"\nopenclaw models status 2>/dev/null || echo \"⚠️ 'openclaw models status' unavailable (is the gateway running?).\"\necho \"(Hot-reloaded for new sessions. For the chat you're in now, switch with /model.)\"\n"}, "7cbf7064f32284fec088474ace3c9ba27736a608": {"t": 1785770979.0613384, "n": 8131, "h": "31e053c496070a15", "c": "#!/usr/bin/env bash\n# Switch the OpenClaw model. Browses tool-capable OpenRouter models (your own\n# key) and — when an OU LiteLLM Sandbox key is present — the OU Sandbox\n# catalog (the course's first-choice endpoint). Sets primary + optional\n# fallback; the gateway hot-reloads.\nset -uo pipefail\n# Make 'openclaw' findable in non-interactive shells.\nsource \"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)/_env.sh\" 2>/dev/null || true\n\nLITELLM_BASE_URL=\"${LITELLM_BASE_URL:-https://litellm.lib.ou.edu}\"\nENV_FILE=\"${HOME}/.openclaw/.env\"\noubase=\"${LITELLM_BASE_URL%/}\"\n\nread_env() { # read_env VAR -> value from process env or ~/.openclaw/.env\n local var=\"$1\" val=\"${!1:-}\"\n [[ -z \"${val}\" && -f \"${ENV_FILE}\" ]] && val=\"$(grep -E \"^${var}=\" \"${ENV_FILE}\" | tail -n1 | cut -d= -f2- || true)\"\n printf '%s' \"${val}\"\n}\n\napply_models() { # apply_models [fallbacks...]\n local prefix=\"$1\" primary=\"$2\"; shift 2\n echo \"→ primary: ${prefix}${primary}\"\n if ! openclaw models set \"${prefix}${primary}\"; then\n echo \"❌ Could not set primary '${prefix}${primary}'.\"\n echo \" Run 'openclaw models list' for valid refs, and check the gateway is running.\"\n exit 1\n fi\n openclaw models fallbacks clear >/dev/null 2>&1 || true\n local f\n for f in \"$@\"; do\n [[ -n \"${f}\" ]] || continue\n echo \"→ fallback: ${prefix}${f}\"\n openclaw models fallbacks add \"${prefix}${f}\" || echo \"⚠️ Couldn't add fallback '${prefix}${f}' — skipped.\"\n done\n echo; echo \"Current model configuration:\"\n openclaw models status 2>/dev/null || echo \"⚠️ 'openclaw models status' unavailable (is the gateway running?).\"\n echo \"(Hot-reloaded for new sessions. For the chat you're in now, switch with /model.)\"\n}\n\n# ---- prerequisites --------------------------------------------------------\nneed() { command -v \"$1\" >/dev/null 2>&1 || { echo \"❌ Required tool '$1' not found — $2\"; exit 1; }; }\nneed curl \"rebuild the Codespace or install curl.\"\nneed python3 \"rebuild the Codespace or install python3.\"\nneed openclaw \"OpenClaw isn't on PATH yet — start the Gateway task first, or run: bash .devcontainer/setup.sh\"\n\n# ---- OpenRouter catalog (primary) -----------------------------------------\nOR_KEY=\"$(read_env OPENROUTER_API_KEY)\"\n[[ -z \"${OR_KEY}\" || \"${OR_KEY}\" == \"sk-or-REPLACE_ME\" ]] && \\\n { echo \"No OpenRouter key. Create one at https://openrouter.ai (Settings → Keys), then run: bash scripts/set-key.sh\"; exit 1; }\n\n# Validate the key first — listing is public, but selecting a model is moot if the key is bad.\nkc=\"$(curl -s -m 15 -o /dev/null -w '%{http_code}' -H \"Authorization: Bearer ${OR_KEY}\" https://openrouter.ai/api/v1/key || echo 000)\"\nif [[ \"${kc}\" != \"200\" ]]; then\n echo \"⚠️ OpenRouter key check returned HTTP ${kc} — it may be invalid, disabled, or out of credit.\"\n echo \" You can still browse, but the model will fail at runtime until the key works.\"\n read -rp \"Continue anyway? [y/N] \" yn /dev/null | sort -u)\n ((${#OU[@]})) || { echo \"❌ No models parsed from the OU response (/tmp/ou_models.json may be malformed).\"; exit 1; }\n echo; echo \"OU AI Sandbox models (OU LiteLLM):\"\n for i in \"${!OU[@]}\"; do printf \" %3d) %s\\n\" \"$((i+1))\" \"${OU[$i]}\"; done\n echo\n read -rp \"Primary model number [default 1]: \" p ${#OU[@]} )); then echo \"Invalid choice.\"; exit 1; fi\n PRIMARY=\"${OU[$((p-1))]}\"\n read -rp \"Fallback number(s) from the list above, comma-separated (blank = none): \" s N )); then\n echo \"Invalid choice.\"; exit 1\nfi\nPRIMARY=\"${OR_IDS[$((choice-1))]}\"\nread -rp \"Fallback number(s), comma-separated (blank = none): \" s