diff --git a/scripts/install.sh b/scripts/install.sh index e9a0053..c22620a 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -40,6 +40,110 @@ prompt_required() { done } +probe_model_endpoint() { + local label="$1" + local base_url="$2" + local api_key="$3" + local model="$4" + local with_tools="$5" + local output + output=$(LABEL="$label" BASE_URL="$base_url" API_KEY="$api_key" MODEL="$model" WITH_TOOLS="$with_tools" python3 - <<'PY' +import json +import os +import re +import socket +import sys +import urllib.error +import urllib.request + +base_url = os.environ["BASE_URL"].rstrip("/") +raw_key = os.environ["API_KEY"] +model = os.environ["MODEL"] +with_tools = os.environ["WITH_TOOLS"] == "1" + +m = re.fullmatch(r"\$\{([A-Z_][A-Z0-9_]*)\}", raw_key) +if m: + resolved = os.environ.get(m.group(1)) + if resolved is None: + sys.stderr.write(f"warning: API key references {raw_key} but it is not set in this shell\n") + key = raw_key + else: + key = resolved +elif raw_key == "" or raw_key.lower() == "any": + key = "any" +else: + key = raw_key + +url = f"{base_url}/chat/completions" +body = { + "model": model, + "messages": [{"role": "user", "content": "ping"}], + "max_tokens": 1, +} +if with_tools: + body["tools"] = [{ + "type": "function", + "function": { + "name": "ping", + "description": "connectivity probe", + "parameters": {"type": "object", "properties": {}}, + }, + }] + +req = urllib.request.Request( + url, + data=json.dumps(body).encode("utf-8"), + headers={ + "Content-Type": "application/json", + "Authorization": f"Bearer {key}", + }, + method="POST", +) + +def emit(status, detail): + print(f"{status}\t{url}\t{detail}") + +try: + with urllib.request.urlopen(req, timeout=10) as resp: + code = resp.getcode() + raw = resp.read(2048).decode("utf-8", errors="replace") + if 200 <= code < 300: + emit("OK", model) + sys.exit(0) + emit("FAIL", f"HTTP {code}: {raw[:200]}") + sys.exit(1) +except urllib.error.HTTPError as e: + raw = "" + try: + raw = e.read(2048).decode("utf-8", errors="replace") + except Exception: + pass + emit("FAIL", f"HTTP {e.code}: {raw[:200]}") + sys.exit(1) +except socket.timeout: + emit("FAIL", "Timed out after 10s") + sys.exit(1) +except urllib.error.URLError as e: + emit("FAIL", f"Connection error: {e.reason!r}") + sys.exit(1) +except Exception as e: + emit("FAIL", f"Unexpected error: {e!r}") + sys.exit(1) +PY +) + local probe_exit=$? + local status_field url_field detail_field + IFS=$'\t' read -r status_field url_field detail_field <<< "$output" + if [ "$probe_exit" -eq 0 ] && [ "$status_field" = "OK" ]; then + printf ' \u2713 %s model reachable: %s\n' "$label" "$detail_field" + return 0 + fi + printf ' \u2717 %s model check failed:\n' "$label" + printf ' URL: %s\n' "$url_field" + printf ' %s\n' "$detail_field" + return 1 +} + prompt_yes_no() { local prompt_en="$1" local prompt_zh="$2" @@ -221,6 +325,16 @@ ROUTING_BASE_URL=$(prompt_default " Base URL (OpenAI-compatible endpoint)" " ROUTING_MODEL=$(prompt_default " Model name" " 模型名" "your-tool-calling-model") ROUTING_API_KEY=$(prompt_default " API key (use 'any' if no auth needed)" " API key(如果不需要鉴权可填 any)" "${ROUTING_API_KEY:-any}") +echo +if ! probe_model_endpoint "Routing" "$ROUTING_BASE_URL" "$ROUTING_API_KEY" "$ROUTING_MODEL" 1; then + CONTINUE_ANYWAY=$(prompt_yes_no "Routing model check failed. Continue install anyway?" "路由模型检测失败,是否仍要继续安装?" "N") + if [ "$CONTINUE_ANYWAY" != "yes" ]; then + echo "Aborted. No files were modified." + echo "已中止,未修改任何文件。" + exit 1 + fi +fi + echo if [ "$USE_OPENCLAW_DEFAULT" = "yes" ]; then echo "── Upstream LLM (main response model) ──" @@ -248,6 +362,16 @@ else UPSTREAM_MODEL=$(prompt_default " Model name" " 模型名" "$DEFAULT_UPSTREAM_MODEL") fi +echo +if ! probe_model_endpoint "Upstream" "$UPSTREAM_BASE_URL" "$UPSTREAM_API_KEY" "$UPSTREAM_MODEL" 0; then + CONTINUE_ANYWAY=$(prompt_yes_no "Upstream model check failed. Continue install anyway?" "上游模型检测失败,是否仍要继续安装?" "N") + if [ "$CONTINUE_ANYWAY" != "yes" ]; then + echo "Aborted. No files were modified." + echo "已中止,未修改任何文件。" + exit 1 + fi +fi + echo echo "── General ──" @@ -267,6 +391,8 @@ echo " 示例值: /home/mt/tools" TOOLS_BASE_DIR=$(prompt_default " Tools base directory" " 工具根目录" "$HOME/.function-router/scripts") OPENCLAW_CONFIG=$(prompt_default " OpenClaw config path" " OpenClaw 配置路径" "$DEFAULT_OPENCLAW_CONFIG") +echo + mkdir -p "$TARGET_DIR" "$SCRIPTS_DIR" "$LOGS_DIR" cp "$REPO_ROOT/examples/config.example.json" "$CONFIG_PATH" cp "$REPO_ROOT/examples/functions.example.jsonl" "$FUNCTIONS_PATH"