fix(openrouter): harden quickstart setup and model validation

This commit is contained in:
Sundaram Kumar Jha
2026-03-18 10:39:58 +05:30
parent 80dfb429d7
commit 4e2951854b
5 changed files with 418 additions and 194 deletions
+131 -102
View File
@@ -46,7 +46,6 @@ prompt_yes_no() {
else
prompt="$prompt [y/N] "
fi
read -r -p "$prompt" response
response="${response:-$default}"
[[ "$response" =~ ^[Yy] ]]
@@ -741,12 +740,17 @@ prompt_model_selection() {
local model_hc_result=""
local model_hc_valid=""
local model_hc_msg=""
local model_hc_canonical=""
local model_hc_base="${SELECTED_API_BASE:-https://openrouter.ai/api/v1}"
echo -n " Verifying model id... "
model_hc_result="$(uv run python "$SCRIPT_DIR/scripts/check_llm_key.py" "openrouter" "$openrouter_key" "$model_hc_base" "$normalized_model" 2>/dev/null)" || true
model_hc_valid="$(echo "$model_hc_result" | $PYTHON_CMD -c "import json,sys; print(json.loads(sys.stdin.read()).get('valid',''))" 2>/dev/null)" || true
model_hc_msg="$(echo "$model_hc_result" | $PYTHON_CMD -c "import json,sys; print(json.loads(sys.stdin.read()).get('message',''))" 2>/dev/null)" || true
model_hc_canonical="$(echo "$model_hc_result" | $PYTHON_CMD -c "import json,sys; print(json.loads(sys.stdin.read()).get('model',''))" 2>/dev/null)" || true
if [ "$model_hc_valid" = "True" ]; then
if [ -n "$model_hc_canonical" ]; then
normalized_model="$model_hc_canonical"
fi
echo -e "${GREEN}ok${NC}"
elif [ "$model_hc_valid" = "False" ]; then
echo -e "${RED}failed${NC}"
@@ -865,70 +869,73 @@ save_configuration() {
max_context_tokens=120000
fi
uv run python -c "
import json
from datetime import datetime, timezone
from pathlib import Path
cfg_path = Path.home() / '.hive' / 'configuration.json'
cfg_path.parent.mkdir(parents=True, exist_ok=True)
config = {
'llm': {
'provider': '$provider_id',
'model': '$model',
'max_tokens': $max_tokens,
'max_context_tokens': $max_context_tokens,
'api_key_env_var': '$env_var'
},
'created_at': datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%S+00:00')
}
if '$use_claude_code_sub' == 'true':
config['llm']['use_claude_code_subscription'] = True
# No api_key_env_var needed for Claude Code subscription
config['llm'].pop('api_key_env_var', None)
if '$use_codex_sub' == 'true':
config['llm']['use_codex_subscription'] = True
# No api_key_env_var needed for Codex subscription
config['llm'].pop('api_key_env_var', None)
if '$api_base':
config['llm']['api_base'] = '$api_base'
tmp_path = cfg_path.parent / (cfg_path.name + '.tmp')
with open(tmp_path, 'w', encoding='utf-8') as f:
json.dump(config, f, indent=2)
tmp_path.replace(cfg_path)
print(json.dumps(config, indent=2))
"
}
# Verify that configuration was persisted exactly as selected.
# Args: provider_id env_var model [api_base]
verify_configuration() {
local provider_id="$1"
local env_var="$2"
local model="$3"
local api_base="${4:-}"
uv run python -c "
uv run python - \
"$provider_id" \
"$env_var" \
"$model" \
"$max_tokens" \
"$max_context_tokens" \
"$use_claude_code_sub" \
"$api_base" \
"$use_codex_sub" \
"$(date -u +"%Y-%m-%dT%H:%M:%S+00:00")" 2>/dev/null <<'PY'
import json
import sys
from pathlib import Path
cfg_path = Path.home() / '.hive' / 'configuration.json'
with open(cfg_path, encoding='utf-8-sig') as f:
cfg = json.load(f)
llm = cfg.get('llm', {})
(
provider_id,
env_var,
model,
max_tokens,
max_context_tokens,
use_claude_code_sub,
api_base,
use_codex_sub,
created_at,
) = sys.argv[1:10]
ok = (llm.get('provider') == '$provider_id' and llm.get('model') == '$model')
if '$env_var':
ok = ok and (llm.get('api_key_env_var') == '$env_var')
if '$api_base':
ok = ok and (llm.get('api_base') == '$api_base')
cfg_path = Path.home() / ".hive" / "configuration.json"
cfg_path.parent.mkdir(parents=True, exist_ok=True)
if not ok:
print(json.dumps(llm, indent=2))
sys.exit(1)
"
try:
with open(cfg_path, encoding="utf-8-sig") as f:
config = json.load(f)
except (OSError, json.JSONDecodeError):
config = {}
config["llm"] = {
"provider": provider_id,
"model": model,
"max_tokens": int(max_tokens),
"max_context_tokens": int(max_context_tokens),
"api_key_env_var": env_var,
}
config["created_at"] = created_at
if use_claude_code_sub == "true":
config["llm"]["use_claude_code_subscription"] = True
config["llm"].pop("api_key_env_var", None)
else:
config["llm"].pop("use_claude_code_subscription", None)
if use_codex_sub == "true":
config["llm"]["use_codex_subscription"] = True
config["llm"].pop("api_key_env_var", None)
else:
config["llm"].pop("use_codex_subscription", None)
if api_base:
config["llm"]["api_base"] = api_base
else:
config["llm"].pop("api_base", None)
tmp_path = cfg_path.with_name(cfg_path.name + ".tmp")
with open(tmp_path, "w", encoding="utf-8") as f:
json.dump(config, f, indent=2)
tmp_path.replace(cfg_path)
print(json.dumps(config, indent=2))
PY
}
# Source shell rc file to pick up existing env vars (temporarily disable set -e)
@@ -1009,28 +1016,36 @@ PREV_MODEL=""
PREV_ENV_VAR=""
PREV_SUB_MODE=""
if [ -f "$HIVE_CONFIG_FILE" ]; then
eval "$(uv run python -c "
import json, sys
eval "$(uv run python - 2>/dev/null <<'PY'
import json
from pathlib import Path
cfg_path = Path.home() / ".hive" / "configuration.json"
try:
cfg_path = Path.home() / '.hive' / 'configuration.json'
with open(cfg_path, encoding='utf-8-sig') as f:
with open(cfg_path, encoding="utf-8-sig") as f:
c = json.load(f)
llm = c.get('llm', {})
print(f'PREV_PROVIDER={llm.get(\"provider\", \"\")}')
print(f'PREV_MODEL={llm.get(\"model\", \"\")}')
print(f'PREV_ENV_VAR={llm.get(\"api_key_env_var\", \"\")}')
sub = ''
if llm.get('use_claude_code_subscription'): sub = 'claude_code'
elif llm.get('use_codex_subscription'): sub = 'codex'
elif llm.get('use_kimi_code_subscription'): sub = 'kimi_code'
elif llm.get('provider', '') == 'minimax' or 'api.minimax.io' in llm.get('api_base', ''): sub = 'minimax_code'
elif llm.get('provider', '') == 'hive' or 'adenhq.com' in llm.get('api_base', ''): sub = 'hive_llm'
elif 'api.z.ai' in llm.get('api_base', ''): sub = 'zai_code'
print(f'PREV_SUB_MODE={sub}')
llm = c.get("llm", {})
print(f"PREV_PROVIDER={llm.get(\"provider\", \"\")}")
print(f"PREV_MODEL={llm.get(\"model\", \"\")}")
print(f"PREV_ENV_VAR={llm.get(\"api_key_env_var\", \"\")}")
sub = ""
if llm.get("use_claude_code_subscription"):
sub = "claude_code"
elif llm.get("use_codex_subscription"):
sub = "codex"
elif llm.get("use_kimi_code_subscription"):
sub = "kimi_code"
elif llm.get("provider", "") == "minimax" or "api.minimax.io" in llm.get("api_base", ""):
sub = "minimax_code"
elif llm.get("provider", "") == "hive" or "adenhq.com" in llm.get("api_base", ""):
sub = "hive_llm"
elif "api.z.ai" in llm.get("api_base", ""):
sub = "zai_code"
print(f"PREV_SUB_MODE={sub}")
except Exception:
pass
" 2>/dev/null)" || true
PY
)" || true
fi
# Compute default menu number from previous config (only if credential is still valid)
@@ -1494,17 +1509,6 @@ if [ -n "$SELECTED_PROVIDER_ID" ]; then
echo -e "${YELLOW} Could not write ~/.hive/configuration.json. Please rerun quickstart.${NC}"
exit 1
fi
VERIFY_API_BASE=""
if [ "$SUBSCRIPTION_MODE" = "zai_code" ]; then
VERIFY_API_BASE="https://api.z.ai/api/coding/paas/v4"
elif [ "$SUBSCRIPTION_MODE" = "minimax_code" ] || [ "$SUBSCRIPTION_MODE" = "kimi_code" ] || [ "$SELECTED_PROVIDER_ID" = "openrouter" ]; then
VERIFY_API_BASE="${SELECTED_API_BASE:-}"
fi
if ! verify_configuration "$SELECTED_PROVIDER_ID" "$SELECTED_ENV_VAR" "$SELECTED_MODEL" "$VERIFY_API_BASE"; then
echo -e "${RED}failed${NC}"
echo -e "${YELLOW} Configuration verification failed for ~/.hive/configuration.json.${NC}"
exit 1
fi
echo -e "${GREEN}${NC}"
echo -e " ${DIM}~/.hive/configuration.json${NC}"
fi
@@ -1518,24 +1522,46 @@ echo ""
echo -e "${GREEN}${NC} Browser automation enabled"
# Patch gcu_enabled into configuration.json
uv run python -c "
if [ -f "$HIVE_CONFIG_FILE" ]; then
if ! uv run python - <<'PY'
import json
from datetime import datetime, timezone
from pathlib import Path
cfg_path = Path.home() / '.hive' / 'configuration.json'
cfg_path.parent.mkdir(parents=True, exist_ok=True)
if cfg_path.exists():
with open(cfg_path, encoding='utf-8-sig') as f:
config = json.load(f)
else:
config = {'created_at': datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%S+00:00')}
config['gcu_enabled'] = True
tmp_path = cfg_path.parent / (cfg_path.name + '.tmp')
with open(tmp_path, 'w', encoding='utf-8') as f:
cfg_path = Path.home() / ".hive" / "configuration.json"
with open(cfg_path, encoding="utf-8-sig") as f:
config = json.load(f)
config["gcu_enabled"] = True
tmp_path = cfg_path.with_name(cfg_path.name + ".tmp")
with open(tmp_path, "w", encoding="utf-8") as f:
json.dump(config, f, indent=2)
tmp_path.replace(cfg_path)
"
PY
then
echo -e "${RED}failed${NC}"
echo -e "${YELLOW} Could not update ~/.hive/configuration.json with browser automation settings.${NC}"
exit 1
fi
else
if ! uv run python - "$(date -u +"%Y-%m-%dT%H:%M:%S+00:00")" <<'PY'
import json
import sys
from pathlib import Path
cfg_path = Path.home() / ".hive" / "configuration.json"
cfg_path.parent.mkdir(parents=True, exist_ok=True)
config = {
"gcu_enabled": True,
"created_at": sys.argv[1],
}
with open(cfg_path, "w", encoding="utf-8") as f:
json.dump(config, f, indent=2)
PY
then
echo -e "${RED}failed${NC}"
echo -e "${YELLOW} Could not create ~/.hive/configuration.json for browser automation settings.${NC}"
exit 1
fi
fi
echo ""
@@ -1782,7 +1808,6 @@ if [ "$CODEX_AVAILABLE" = true ]; then
echo ""
fi
# Setup-only mode: quickstart never auto-launches the dashboard.
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BOLD}IMPORTANT: Load your new configuration${NC}"
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
@@ -1800,7 +1825,11 @@ echo ""
echo -e "${BOLD}Run an Agent:${NC}"
echo ""
echo -e " Launch the interactive dashboard when you're ready:"
if [ "$FRONTEND_BUILT" = true ]; then
echo -e " Quickstart only sets things up. Launch the dashboard when you're ready:"
else
echo -e " Frontend build was skipped or failed. Once the dashboard is available, launch it with:"
fi
echo -e " ${CYAN}hive open${NC}"
echo ""
echo -e "${DIM}Run ./quickstart.sh again to reconfigure.${NC}"