167 lines
4.6 KiB
Bash
Executable File
167 lines
4.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage:
|
|
switch-models.sh --non-main-primary <catalog-id> --non-main-fallbacks <m1,m2,...> [--clear-session-pins] [--pattern <old-model-pattern>]
|
|
|
|
Example:
|
|
switch-models.sh \
|
|
--non-main-primary "nanogpt/zai-org/glm-5" \
|
|
--non-main-fallbacks "lightning_ai/lightning-ai/kimi-k2.5,nanogpt/zai-org/glm-4.7" \
|
|
--clear-session-pins \
|
|
--pattern "gemini-3-flash"
|
|
|
|
Notes:
|
|
- Updates agents.list[].model for home/security/research.
|
|
- Keeps main/default model untouched.
|
|
- Validates candidates against live /v1/models.
|
|
- Optionally removes matching per-session model pins.
|
|
EOF
|
|
}
|
|
|
|
PRIMARY=""
|
|
FALLBACKS=""
|
|
CLEAR_PINS=0
|
|
PATTERN="gemini-3-flash"
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--non-main-primary)
|
|
PRIMARY="${2:-}"
|
|
shift 2
|
|
;;
|
|
--non-main-fallbacks)
|
|
FALLBACKS="${2:-}"
|
|
shift 2
|
|
;;
|
|
--clear-session-pins)
|
|
CLEAR_PINS=1
|
|
shift
|
|
;;
|
|
--pattern)
|
|
PATTERN="${2:-}"
|
|
shift 2
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "Unknown arg: $1" >&2
|
|
usage >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z "$PRIMARY" || -z "$FALLBACKS" ]]; then
|
|
echo "ERROR: --non-main-primary and --non-main-fallbacks are required" >&2
|
|
exit 1
|
|
fi
|
|
|
|
IFS=',' read -ra FB <<< "$FALLBACKS"
|
|
if [[ ${#FB[@]} -lt 1 ]]; then
|
|
echo "ERROR: at least 1 fallback required" >&2
|
|
exit 1
|
|
fi
|
|
|
|
for m in "$PRIMARY" "${FB[@]}"; do
|
|
m_clean="$(echo "${m#llm-proxy/}" | xargs)"
|
|
"$SCRIPT_DIR/validate-model.sh" "$m_clean" >/dev/null
|
|
echo "validated-live: $m_clean"
|
|
done
|
|
|
|
STATE_FILE="${OPENCLAW_STATE_DIR:-$HOME/.openclaw}/openclaw.json"
|
|
if [[ ! -f "$STATE_FILE" ]]; then
|
|
for alt in \
|
|
"${OPENCLAW_STATE_DIR:-$HOME/.openclaw/state}/openclaw.json" \
|
|
"${OPENCLAW_CONFIG_DIR:-$HOME/.openclaw/config}/openclaw.json" \
|
|
"/opt/openclaw/state/openclaw.json"; do
|
|
if [[ -f "$alt" ]]; then
|
|
STATE_FILE="$alt"
|
|
break
|
|
fi
|
|
done
|
|
fi
|
|
|
|
[[ -f "$STATE_FILE" ]] || { echo "ERROR: openclaw.json not found" >&2; exit 1; }
|
|
|
|
# Validate against local configured catalog too (gateway uses this on restart)
|
|
node - <<'NODE' "$STATE_FILE" "$PRIMARY" "$FALLBACKS"
|
|
const fs=require('fs');
|
|
const p=process.argv[2];
|
|
const primary=process.argv[3].replace(/^llm-proxy\//,'');
|
|
const fallbacks=process.argv[4].split(',').map(s=>s.trim().replace(/^llm-proxy\//,'')).filter(Boolean);
|
|
const j=JSON.parse(fs.readFileSync(p,'utf8'));
|
|
const catalog=new Set((j.models?.providers?.['llm-proxy']?.models||[]).map(m=>m.id));
|
|
const missing=[];
|
|
if(!catalog.has(primary)) missing.push(primary);
|
|
for (const f of fallbacks) if(!catalog.has(f)) missing.push(f);
|
|
if (missing.length) {
|
|
console.error('ERROR: target models missing from local llm-proxy catalog in openclaw.json');
|
|
for (const m of [...new Set(missing)]) console.error(' - '+m);
|
|
process.exit(2);
|
|
}
|
|
console.log('validated-local-catalog: ok');
|
|
NODE
|
|
|
|
primary_full="llm-proxy/${PRIMARY#llm-proxy/}"
|
|
raw_fb="${FALLBACKS}"
|
|
fb_json="$(node - <<'NODE' "$PRIMARY" "$raw_fb"
|
|
const primary=process.argv[2].replace(/^llm-proxy\//,'');
|
|
const raw=process.argv[3].split(',').map(s=>s.trim().replace(/^llm-proxy\//,'')).filter(Boolean);
|
|
const seen=new Set();
|
|
const out=[];
|
|
for (const item of raw) {
|
|
if (item===primary) continue;
|
|
if (seen.has(item)) continue;
|
|
seen.add(item);
|
|
out.push(`llm-proxy/${item}`);
|
|
}
|
|
process.stdout.write(JSON.stringify(out));
|
|
NODE
|
|
)"
|
|
|
|
for aid in home security research; do
|
|
cmd1="openclaw config set agents.list[\"$aid\"].model.primary $primary_full"
|
|
echo "$cmd1"
|
|
eval "$cmd1"
|
|
cmd2="openclaw config set agents.list[\"$aid\"].model.fallbacks '$fb_json' --json"
|
|
echo "$cmd2"
|
|
eval "$cmd2"
|
|
done
|
|
|
|
if [[ "$CLEAR_PINS" -eq 1 ]]; then
|
|
echo "clearing matching session model pins pattern=$PATTERN"
|
|
for aid in home security research; do
|
|
sess="/home/node/.openclaw/agents/${aid}/sessions/sessions.json"
|
|
[[ -f "$sess" ]] || continue
|
|
node - <<'NODE' "$sess" "$PATTERN" "$aid"
|
|
const fs=require('fs');
|
|
const file=process.argv[2];
|
|
const pattern=new RegExp(process.argv[3],'i');
|
|
const aid=process.argv[4];
|
|
const j=JSON.parse(fs.readFileSync(file,'utf8'));
|
|
let removed=0;
|
|
for (const [k,v] of Object.entries(j)) {
|
|
const model=(v&&v.model)?String(v.model):'';
|
|
if (model && pattern.test(model)) {
|
|
delete v.model;
|
|
removed++;
|
|
}
|
|
}
|
|
fs.writeFileSync(file, JSON.stringify(j,null,2)+'\n');
|
|
console.log(`agent=${aid} removed_model_pins=${removed}`);
|
|
NODE
|
|
done
|
|
fi
|
|
|
|
echo "running post-change audit..."
|
|
"$SCRIPT_DIR/audit-model-state.sh" "$PATTERN"
|
|
|
|
echo "done. restart gateway to apply runtime changes."
|