{"repo": "aihubmix/skills", "files": {"skills/aihubmixApi/scripts/aihubmixApi.py": {"53938717da78fb7d59acd3309791aca792326c13": {"t": 1785674652.5670595, "n": 114387, "h": "ffa0f7d6056a0a20", "c": "#!/usr/bin/env python3\n\"\"\"\nSmall AIHubMix API helper for Codex skills.\n\nThe script intentionally keeps volatile model data out of the skill. It reads\nmodel metadata from the live models API by default, or from a caller-provided\nJSON file for local validation.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport argparse\nfrom datetime import datetime\nimport io\nimport json\nimport os\nimport platform\nimport re\nimport shutil\nimport subprocess\nimport sys\nimport tarfile\nimport textwrap\nimport urllib.error\nimport urllib.request\nfrom pathlib import Path\nfrom typing import Any\n\n\n# Force UTF-8 stdout/stderr so Chinese output is not mangled to GBK mojibake\n# on Windows or when the stream is redirected/piped (default code page there\n# is cp936, not UTF-8).\nfor _stream in (sys.stdout, sys.stderr):\n try:\n _stream.reconfigure(encoding=\"utf-8\")\n except (AttributeError, ValueError): # non-reconfigurable stream\n pass\n\n\n# Output language for status lines/labels: \"en\" (default, neutral) or \"zh\".\n# Set via the global --lang flag or AIHUBMIX_LANG; main() resolves it into _LANG.\n_LANG = (os.environ.get(\"AIHUBMIX_LANG\") or \"en\").strip().lower()\nif _LANG not in (\"en\", \"zh\"):\n _LANG = \"en\"\n\n\ndef _t(en: str, zh: str) -> str:\n \"\"\"Pick output text by the resolved language (data values are never translated).\"\"\"\n return zh if _LANG == \"zh\" else en\n\n\nDEFAULT_MODELS_URL = \"https://aihubmix.com/api/v1/models\"\nDEFAULT_SERVER_URL = \"https://aihubmix.com\"\nDEFAULT_PRICE_UNIT = \"USD / 1M tokens\"\nSDK_PACKAGE_NAME = \"@aihubmix/ai-sdk-provider\"\nSDK_RECOMMENDED_VERSION = \"2.1.0\"\nSDK_REGISTRY_URL = \"https://registry.npmjs.org/@aihubmix%2Fai-sdk-provider\"\nOPENAPI_REPO_URL = \"https://github.com/AIhubmix/aihubmix-openapi\"\nOPENAPI_JSON_URL = \"https://aihubmix.com/openapi.json\"\nCONTRACT_JSON_URL = \"https://aihubmix.com/contract.json\"\nGATEWAY_ERRORS_URL = \"https://github.com/AIhubmix/aihubmix-openapi/blob/main/gateway/errors.yml\"\n\n# Companion `aihubmix` CLI. The repo URL is the durable anchor shown everywhere\n# (stable even if the install scripts move); the raw install scripts are the\n# per-OS executable entrypoints, derived from the same repo path so there is a\n# single source of truth and the CLI can always be re-found.\nCLI_BIN = \"aihubmix\"\nCLI_REPO = \"AIhubmix/platfrom-cli\"\nCLI_REPO_URL = f\"https://github.com/{CLI_REPO}\"\nCLI_RAW_BASE = f\"https://raw.githubusercontent.com/{CLI_REPO}/main\"\nCLI_INSTALL_PS1 = f\"{CLI_RAW_BASE}/install.ps1\"\nCLI_INSTALL_SH = f\"{CLI_RAW_BASE}/install.sh\"\n\nPROTOCOL_ALIASES = {\n \"chat\": \"chat\",\n \"chat-completions\": \"chat\",\n \"model-response\": \"responses\",\n \"model-responses\": \"responses\",\n \"response\": \"responses\",\n \"completion\": \"completions\",\n \"embedding\": \"embeddings\",\n \"image\": \"images\",\n \"image-generation\": \"images\",\n \"video\": \"videos\",\n \"retrieve-video\": \"video-retrieve\",\n \"download-video\": \"video-content\",\n \"video-download\": \"video-content\",\n \"remix-video\": \"video-remix\",\n \"speech\": \"audio-speech\",\n \"tts\": \"audio-speech\",\n \"transcription\": \"audio-transcriptions\",\n \"translation\": \"audio-translations\",\n \"moderation\": \"moderations\",\n \"message\": \"messages\",\n \"anthropic\": \"messages\",\n \"generate-content\": \"gemini\",\n \"vertex\": \"gemini\",\n \"google\": \"gemini\",\n}\n\nPROTOCOL_SPECS: dict[str, dict[str, Any]] = {\n \"chat\": {\n \"label\": \"OpenAI-Compatible Chat\",\n \"method\": \"POST\",\n \"path\": \"/v1/chat/completions\",\n \"family\": \"openai\",\n },\n \"responses\": {\n \"label\": \"OpenAI Model Responses\",\n \"method\": \"POST\",\n \"path\": \"/v1/responses\",\n \"family\": \"openai\",\n },\n \"completions\": {\n \"label\": \"OpenAI-Compatible Legacy Completion\",\n \"method\": \"POST\",\n \"path\": \"/v1/completions\",\n \"family\": \"openai\",\n },\n \"embeddings\": {\n \"label\": \"OpenAI-Compatible Embeddings\",\n \"method\": \"POST\",\n \"path\": \"/v1/embeddings\",\n \"family\": \"openai\",\n },\n \"images\": {\n \"label\": \"OpenAI-Compatible Image Generation\",\n \"method\": \"POST\",\n \"path\": \"/v1/images/generations\",\n \"family\": \"openai\",\n },\n \"videos\": {\n \"label\": \"OpenAI-Compatible Video Creation\",\n \"method\": \"POST\",\n \"path\": \"/v1/videos\",\n \"family\": \"openai\",\n },\n \"video-retrieve\": {\n \"label\": \"OpenAI-Compatible Video Retrieval\",\n \"method\": \"GET\",\n \"path\": \"/v1/videos/{video_id}\",\n \"family\": \"openai\",\n \"needs_video_id\": True,\n },\n \"video-delete\": {\n \"label\": \"OpenAI-Compatible Video Deletion\",\n \"method\": \"DELETE\",\n \"path\": \"/v1/videos/{video_id}\",\n \"family\": \"openai\",\n \"needs_video_id\": True,\n },\n \"video-content\": {\n \"label\": \"OpenAI-Compatible Video Content Download\",\n \"method\": \"GET\",\n \"path\": \"/v1/videos/{video_id}/content\",\n \"family\": \"openai\",\n \"needs_video_id\": True,\n },\n \"video-remix\": {\n \"label\": \"OpenAI-Compatible Video Remix\",\n \"method\": \"POST\",\n \"path\": \"/v1/videos/{video_id}/remix\",\n \"family\": \"openai\",\n \"needs_video_id\": True,\n },\n \"audio-speech\": {\n \"label\": \"OpenAI-Compatible Speech\",\n \"method\": \"POST\",\n \"path\": \"/v1/audio/speech\",\n \"family\": \"openai\",\n \"binary_response\": True,\n },\n \"audio-transcriptions\": {\n \"label\": \"OpenAI-Compatible Audio Transcription\",\n \"method\": \"POST\",\n \"path\": \"/v1/audio/transcriptions\",\n \"family\": \"openai\",\n \"multipart\": True,\n },\n \"audio-translations\": {\n \"label\": \"OpenAI-Compatible Audio Translation\",\n \"method\": \"POST\",\n \"path\": \"/v1/audio/translations\",\n \"family\": \"openai\",\n \"multipart\": True,\n },\n \"moderations\": {\n \"label\": \"OpenAI-Compatible Moderation\",\n \"method\": \"POST\",\n \"path\": \"/v1/moderations\",\n \"family\": \"openai\",\n },\n \"messages\": {\n \"label\": \"Claude/Anthropic-Compatible Messages\",\n \"method\": \"POST\",\n \"path\": \"/v1/messages\",\n \"family\": \"anthropic\",\n },\n \"gemini\": {\n \"label\": \"Google Vertex/Gemini-Compatible Generate Content\",\n \"method\": \"POST\",\n \"path\": \"/gemini/v1beta/models/{model}:generateContent\",\n \"family\": \"gemini\",\n },\n}\n\n\ndef display_models_source() -> str:\n return DEFAULT_MODELS_URL\n\n\ndef display_openapi_source(filename: str = \"openapi.json\") -> str:\n if filename == \"openapi.json\":\n return OPENAPI_JSON_URL\n if filename == \"contract.json\":\n return CONTRACT_JSON_URL\n return OPENAPI_REPO_URL\n\n\ndef display_error_contract_source() -> str:\n return OPENAPI_JSON_URL\n\n\ndef load_json_source(source: str | None) -> Any:\n # Always fetch live (no caching): every lookup hits the source directly.\n source = source or os.environ.get(\"AIHUBMIX_MODELS_URL\") or DEFAULT_MODELS_URL\n if source == \"-\":\n return json.load(sys.stdin)\n if source.startswith((\"http://\", \"https://\")):\n request = urllib.request.Request(\n source,\n headers={\n \"Accept\": \"application/json\",\n \"User-Agent\": \"aihubmixApi-skill/0.1\",\n },\n )\n try:\n with urllib.request.urlopen(request, timeout=20) as response:\n return json.load(response)\n except urllib.error.HTTPError as exc:\n detail = exc.read().decode(\"utf-8\", errors=\"replace\")\n raise SystemExit(f\"HTTP {exc.code} while fetching {source}: {detail}\") from exc\n except urllib.error.URLError as exc:\n raise SystemExit(f\"Could not fetch {source}: {exc.reason}\") from exc\n\n path = Path(source).expanduser()\n with path.open(\"r\", encoding=\"utf-8\") as handle:\n return json.load(handle)\n\n\ndef extract_models(payload: Any) -> list[dict[str, Any]]:\n return sorted(extract_models_in_source_order(payload), key=lambda item: model_id(item).lower())\n\n\ndef extract_models_in_source_order(payload: Any) -> list[dict[str, Any]]:\n if isinstance(payload, list):\n candidates = payload\n elif isinstance(payload, dict):\n candidates = None\n for key in (\"data\", \"models\", \"items\", \"list\", \"result\"):\n value = payload.get(key)\n if isinstance(value, list):\n candidates = value\n break\n if isinstance(value, dict):\n nested = extract_models_in_source_order(value)\n if nested:\n candidates = nested\n break\n if candidates is None:\n candidates = [payload] if any(k in payload for k in (\"id\", \"model\", \"model_id\")) else []\n else:\n candidates = []\n\n return [item for item in candidates if isinstance(item, dict)]\n\n\ndef model_id(model: dict[str, Any]) -> str:\n for key in (\"model_id\", \"id\", \"model\", \"name\", \"slug\"):\n value = model.get(key)\n if value is not None:\n return str(value)\n return \"\"\n\n\ndef model_label(model: dict[str, Any]) -> str:\n for key in (\"model_name\", \"name\", \"display_name\", \"label\", \"title\"):\n value = model.get(key)\n if value:\n return str(value)\n return model_id(model)\n\n\ndef developer(model: dict[str, Any]) -> str:\n for key in (\"developer\", \"provider\", \"developer_id\", \"provider_id\", \"owned_by\"):\n value = model.get(key)\n if value:\n return str(value)\n return \"-\"\n\n\ndef context_length(model: dict[str, Any]) -> str:\n for key in (\n \"context_length\",\n \"context_window\",\n \"max_context\",\n \"max_context_length\",\n \"input_context_length\",\n ):\n value = model.get(key)\n if value is not None:\n return str(value)\n return \"-\"\n\n\ndef max_output(model: dict[str, Any]) -> str:\n for key in (\"max_output\", \"max_output_tokens\", \"output_context_length\"):\n value = model.get(key)\n if value is not None:\n return str(value)\n return \"-\"\n\n\ndef format_tokens(value: str) -> str:\n if value in {\"\", \"-\"}:\n return \"-\"\n try:\n number = int(float(value))\n except ValueError:\n return value\n if number == 0:\n return \"0\"\n if number >= 1_000_000 and number % 1_000_000 == 0:\n return f\"{number // 1_000_000}M\"\n if number >= 1_000_000:\n compact_value = number / 1_000_000\n return f\"{compact_value:.2f}\".rstrip(\"0\").rstrip(\".\") + \"M\"\n if number >= 1_000 and number % 1_000 == 0:\n return f\"{number // 1_000}K\"\n if number >= 1_000:\n compact_value = number / 1_000\n return f\"{compact_value:.1f}\".rstrip(\"0\").rstrip(\".\") + \"K\"\n return str(number)\n\n\ndef context_display(model: dict[str, Any]) -> str:\n return format_tokens(context_length(model))\n\n\ndef max_output_display(model: dict[str, Any]) -> str:\n return format_tokens(max_output(model))\n\n\ndef compact(value: Any) -> str:\n if value is None:\n return \"-\"\n if isinstance(value, (str, int, float, bool)):\n return str(value)\n if isinstance(value, list):\n return \",\".join(compact(item) for item in value[:8]) or \"-\"\n if isinstance(value, dict):\n parts = []\n for key in sorted(value):\n item = value[key]\n if item is not None:\n parts.append(f\"{key}={compact(item)}\")\n return \"; \".join(parts) or \"-\"\n return str(value)\n\n\ndef pricing(model: dict[str, Any]) -> str:\n value = model.get(\"pricing\") or model.get(\"price\") or model.get(\"prices\")\n return compact(value)\n\n\ndef price_unit() -> str:\n return os.environ.get(\"AIHUBMIX_PRICE_UNIT\", DEFAULT_PRICE_UNIT)\n\n\ndef query_time_label() -> str:\n now = datetime.now().astimezone()\n offset = now.strftime(\"%z\")\n if offset:\n offset_label = f\"UTC{offset[:3]}:{offset[3:]}\"\n else:\n offset_label = \"local timezone\"\n stamp = now.strftime(\"%Y-%m-%d %H:%M:%S\")\n return _t(f\"{stamp} (local time, {offset_label})\", f\"{stamp} (本地时间,{offset_label})\")\n\n\ndef types(model: dict[str, Any]) -> str:\n value = model.get(\"types\") or model.get(\"type\") or model.get(\"endpoints\")\n return compact(value)\n\n\ndef features(model: dict[str, Any]) -> str:\n value = model.get(\"features\") or model.get(\"capabilities\") or model.get(\"input_modalities\")\n return compact(value)\n\n\ndef endpoint_display(model: dict[str, Any]) -> str:\n raw = str(model.get(\"endpoints\") or \"\").strip()\n if not raw:\n model_type = str(model.get(\"types\") or \"\")\n if \"llm\" in model_type and \"image_generation\" not in model_type:\n return \"not declared; use OpenAI-compatible chat example by default\"\n return \"not declared; choose endpoint by model type and verify against contract\"\n labels = {\n \"chat_completions\": \"OpenAI-compatible chat\",\n \"responses\": \"OpenAI Responses\",\n \"gemini_api\": \"Gemini native\",\n \"claude_api\": \"Claude/Anthropic-compatible\",\n \"embeddings\": \"Embeddings\",\n \"image_generation\": \"Image generation\",\n \"audio\": \"Audio\",\n \"moderations\": \"Moderation\",\n \"videos\": \"Video\",\n }\n parts = [part.strip() for part in raw.replace(\";\", \",\").split(\",\") if part.strip()]\n mapped = [labels.get(part, part) for part in parts]\n return \"; \".join(mapped) if mapped else raw\n\n\ndef endpoint_note(model: dict[str, Any]) -> str:\n raw = str(model.get(\"endpoints\") or \"\").strip()\n return raw if raw else \"-\"\n\n\ndef suggested_protocol(model: dict[str, Any]) -> str | None:\n raw_endpoint = compact(model.get(\"endpoints\")).lower()\n raw_type = compact(model.get(\"types\") or model.get(\"type\")).lower()\n searchable = model_search_text(model).lower()\n\n if \"gemini_api\" in raw_endpoint:\n return \"gemini\"\n if \"claude_api\" in raw_endpoint:\n return \"messages\"\n if \"responses\" in raw_endpoint:\n return \"responses\"\n if \"chat_completions\" in raw_endpoint:\n return \"chat\"\n if \"embedding\" in raw_endpoint or \"embedding\" in raw_type:\n return \"embeddings\"\n if any(value in raw_endpoint or value in raw_type for value in (\"image_generation\", \"image\")):\n return \"images\"\n if any(value in raw_endpoint or value in raw_type for value in (\"video\", \"videos\")):\n return \"videos\"\n if any(value in raw_endpoint or value in raw_type for value in (\"speech\", \"tts\")):\n return \"audio-speech\"\n if \"transcription\" in raw_endpoint or \"transcription\" in raw_type:\n return \"audio-transcriptions\"\n if \"translation\" in raw_endpoint or \"translation\" in raw_type:\n return \"audio-translations\"\n if \"moderation\" in raw_endpoint or \"moderation\" in raw_type:\n return \"moderations\"\n if \"audio\" in raw_endpoint or \"audio\" in raw_type:\n return \"audio-transcriptions\"\n if \"llm\" in raw_type or \"chat\" in raw_type:\n return \"chat\"\n if any(value in searchable for value in (\"gemini\", \"claude\", \"anthropic\")):\n if \"gemini\" in searchable:\n return \"gemini\"\n return \"messages\"\n return \"chat\"\n\n\ndef protocol_name(protocol: str) -> str:\n protocol = normalize_protocol(protocol)\n return PROTOCOL_SPECS[protocol][\"label\"]\n\n\ndef protocol_auth_label(protocol: str) -> str:\n header_name, header_value = auth_header(protocol)\n return f\"{header_name}: {header_value}\"\n\n\ndef print_user_overview(selected: list[dict[str, Any]], query: str | None = None) -> None:\n print(\"## Quick Start Overview\")\n print()\n if query:\n print(f\"- Searched: `{query}`\")\n print(f\"- Models shown: `{len(selected)}`\")\n print(f\"- Price unit: `{price_unit()}`\")\n print(\"- How to use: copy a `model_id`, check context/price, then run the example below.\")\n print()\n print(\"| model_id | protocol | context | max_output | price |\")\n print(\"|---|---|---:|---:|---|\")\n for item in selected[:5]:\n protocol = suggested_protocol(item)\n protocol_label = protocol_name(protocol) if protocol else \"verify endpoint by model type\"\n print(\n \"| \"\n + \" | \".join(\n [\n f\"`{model_id(item)}`\",\n protocol_label,\n context_display(item),\n max_output_display(item),\n pricing(item).replace(\"|\", \"\\\\|\"),\n ]\n )\n + \" |\"\n )\n print()\n print(\"## Technical Details\")\n print()\n\n\ndef example_intro(kind: str, os_name: str | None = None) -> str:\n if kind == \"command\":\n shell_name = \"Windows PowerShell\" if os_name == \"windows\" else \"macOS/Linux shell\"\n return (\n f\"Use this {shell_name} example when you want to test the model directly from a terminal. \"\n \"It asks for `AIHUBMIX_API_KEY`, sends one request, and prints the model reply.\"\n )\n if kind == \"js\":\n return (\n \"Use this JavaScript example inside a Node.js project or service. \"\n \"Run the environment setup command below before starting the script.\"\n )\n if kind == \"python\":\n return (\n \"Use this Python example for a quick backend or script test. \"\n \"Run the environment setup command below before starting the script.\"\n )\n return \"Use this example as a minimal starting point.\"\n\n\ndef env_setup_block(kind: str, os_name: str | None = None) -> tuple[str, str]:\n if kind == \"command\":\n return \"\", \"\"\n if os_name == \"windows\":\n return (\n \"PowerShell\",\n '$env:AIHUBMIX_API_KEY = Read-Host \"Paste AIHubMix API Key\"',\n )\n if kind == \"js\":\n return (\n \"bash\",\n 'export AIHUBMIX_API_KEY=\"sk-...\"',\n )\n return (\n \"bash\",\n 'export AIHUBMIX_API_KEY=\"sk-...\"',\n )\n\n\ndef print_env_setup(kind: str, os_name: str | None = None) -> None:\n lang, command = env_setup_block(kind, os_name)\n if not command:\n return\n print(\"先设置环境变量,把 `sk-...` 换成你的 AIHubMix API Key:\")\n print()\n print(f\"```{lang}\")\n print(command)\n print(\"```\")\n print()\n\n\ndef print_model_switch_hint(selected: list[dict[str, Any]]) -> None:\n if len(selected) <= 1:\n return\n shown = \", \".join(f\"`{model_id(item)}`\" for item in selected[:6])\n print(f\"可选模型 ID:{shown}\")\n print(\"想切换模型时,直接把示例里的 `model` / `model_id` 换成对应 ID 即可。\")\n print()\n\n\ndef availability_note(model: dict[str, Any]) -> str:\n desc = str(model.get(\"desc\") or \"\")\n lowered = desc.lower()\n notes = []\n if \"temporarily suspended\" in lowered or \"suspended\" in lowered:\n notes.append(\"access note in desc\")\n if \"expensive\" in lowered or \"high price\" in lowered:\n notes.append(\"cost note in desc\")\n return \", \".join(notes) if notes else \"-\"\n\n\nLATEST_QUERY_TERMS = (\"latest\", \"newest\", \"recent\", \"最新\", \"新模型\", \"最近\")\n\nGENERIC_QUERY_TOKENS = {\n \"aihubmix\",\n \"api\",\n \"info\",\n \"interface\",\n \"latest\",\n \"model\",\n \"models\",\n \"newest\",\n \"price\",\n \"pricing\",\n \"recent\",\n}\n\nMODEL_ALIAS_GROUPS: list[tuple[tuple[str, ...], tuple[str, ...]]] = [\n ((\"月之暗面\", \"moonshot\", \"kimi\"), (\"moonshot\", \"kimi\")),\n ((\"谷歌\", \"google\", \"gemini\"), (\"google\", \"gemini\")),\n ((\"claude\", \"anthropic\"), (\"claude\", \"anthropic\")),\n ((\"opus\",), (\"opus\",)),\n ((\"sonnet\",), (\"sonnet\",)),\n ((\"haiku\",), (\"haiku\",)),\n ((\"openai\", \"chatgpt\", \"gpt\"), (\"openai\", \"gpt\")),\n ((\"qwen\", \"通义\", \"通义千问\", \"千问\", \"alibaba\", \"阿里\"), (\"qwen\", \"通义\", \"千问\", \"alibaba\")),\n ((\"deepseek\", \"深度求索\"), (\"deepseek\", \"深度求索\")),\n ((\"zhipu\", \"智谱\", \"glm\"), (\"zhipu\", \"智谱\", \"glm\")),\n ((\"doubao\", \"豆包\", \"字节\", \"火山\", \"volcengine\"), (\"doubao\", \"volcengine\", \"字节\", \"火山\")),\n ((\"minimax\", \"海螺\"), (\"minimax\", \"hailuo\", \"海螺\")),\n ((\"零一万物\", \"01ai\", \"01-ai\", \"yi\"), (\"01-ai\", \"yi\", \"零一\")),\n ((\"baichuan\", \"百川\"), (\"baichuan\", \"百川\")),\n ((\"mistral\",), (\"mistral\",)),\n ((\"llama\", \"meta\"), (\"llama\", \"meta\")),\n ((\"grok\", \"xai\", \"x.ai\"), (\"grok\", \"xai\", \"x.ai\")),\n]\n\n\ndef normalize_search_text(value: str) -> str:\n return re.sub(r\"[\\W_]+\", \"\", value.lower(), flags=re.UNICODE)\n\n\ndef query_wants_latest(query: str) -> bool:\n lowered = query.lower()\n return any(term in lowered for term in LATEST_QUERY_TERMS)\n\n\ndef model_search_text(model: dict[str, Any]) -> str:\n keys = (\n \"model_id\",\n \"id\",\n \"model\",\n \"model_name\",\n \"name\",\n \"display_name\",\n \"label\",\n \"title\",\n \"developer\",\n \"provider\",\n \"developer_id\",\n \"provider_id\",\n \"owned_by\",\n \"desc\",\n \"description\",\n \"types\",\n \"type\",\n \"endpoints\",\n \"features\",\n \"capabilities\",\n \"input_modalities\",\n )\n return \" \".join(compact(model.get(key)) for key in keys if model.get(key) is not None)\n\n\ndef add_query_group(\n groups: list[list[str]],\n labels: list[str],\n seen: set[tuple[str, ...]],\n label: str,\n terms: tuple[str, ...] | list[str],\n) -> None:\n cleaned = tuple(dict.fromkeys(term.strip().lower() for term in terms if term.strip()))\n if not cleaned:\n return\n key = tuple(sorted(normalize_search_text(term) for term in cleaned if normalize_search_text(term)))\n if not key or key in seen:\n return\n groups.append(list(cleaned))\n labels.append(f\"{label} -> {', '.join(cleaned)}\" if label else \", \".join(cleaned))\n seen.add(key)\n\n\ndef build_query_plan(query: str) -> dict[str, Any]:\n lowered = query.lower()\n compact_query = normalize_search_text(query)\n groups: list[list[str]] = []\n labels: list[str] = []\n seen: set[tuple[str, ...]] = set()\n matched_alias_triggers: set[str] = set()\n\n for triggers, terms in MODEL_ALIAS_GROUPS:\n matched_trigger = None\n for trigger in triggers:\n trigger_lower = trigger.lower()\n trigger_compact = normalize_search_text(trigger)\n if trigger_lower in lowered or (trigger_compact and trigger_compact in compact_query):\n matched_trigger = trigger\n matched_alias_triggers.update(normalize_search_text(item) for item in triggers)\n break\n if matched_trigger:\n add_query_group(groups, labels, seen, matched_trigger, terms)\n\n for token in re.findall(r\"[a-z0-9]+(?:[._-]?[a-z0-9]+)*\", lowered):\n token_compact = normalize_search_text(token)\n if not token_compact or token in GENERIC_QUERY_TOKENS or token_compact in matched_alias_triggers:\n continue\n add_query_group(groups, labels, seen, token, (token,))\n\n if not groups and query.strip():\n add_query_group(groups, labels, seen, \"raw query\", (query.strip(),))\n\n return {\n \"groups\": groups,\n \"labels\": labels,\n \"wants_latest\": query_wants_latest(query),\n \"compact_query\": compact_query,\n }\n\n\ndef term_match_score(model: dict[str, Any], raw_text: str, compact_text: str, term: str) -> int:\n term_lower = term.lower()\n term_compact = normalize_search_text(term)\n model_id_lower = model_id(model).lower()\n model_id_compact = normalize_search_text(model_id(model))\n\n if term_lower == model_id_lower or (term_compact and term_compact == model_id_compact):\n return 100\n if term_lower and term_lower in raw_text:\n return 25\n if term_compact and term_compact in compact_text:\n return 20\n return 0\n\n\ndef model_query_score(model: dict[str, Any], plan: dict[str, Any]) -> int:\n raw_text = model_search_text(model).lower()\n compact_text = normalize_search_text(raw_text)\n groups = plan[\"groups\"]\n if not groups:\n return 0\n\n score = 0\n for group in groups:\n group_score = max(term_match_score(model, raw_text, compact_text, term) for term in group)\n if group_score == 0:\n return 0\n score += group_score\n\n compact_query = plan[\"compact_query\"]\n model_id_compact = normalize_search_text(model_id(model))\n if compact_query and compact_query == model_id_compact:\n score += 200\n elif compact_query and compact_query in model_id_compact:\n score += 80\n return score\n\n\ndef rank_matching_models(\n models: list[dict[str, Any]],\n query: str,\n limit: int | None = None,\n preserve_source_order: bool = False,\n) -> tuple[list[dict[str, Any]], dict[str, Any]]:\n plan = build_query_plan(query)\n scored = [\n (index, model_query_score(item, plan), item)\n for index, item in enumerate(models)\n ]\n matches = [(index, score, item) for index, score, item in scored if score > 0]\n if preserve_source_order:\n ordered = sorted(matches, key=lambda row: row[0])\n else:\n ordered = sorted(matches, key=lambda row: (-row[1], model_id(row[2]).lower()))\n selected = [item for _, _, item in ordered]\n if limit is not None:\n selected = selected[:limit]\n return selected, plan\n\n\ndef find_model(models: list[dict[str, Any]], target: str) -> dict[str, Any] | None:\n selected, _ = rank_matching_models(models, target, limit=1)\n return selected[0] if selected else None\n\n\ndef print_table(headers: list[str], rows: list[list[str]]) -> None:\n widths = [len(header) for header in headers]\n for row in rows:\n for idx, cell in enumerate(row):\n widths[idx] = max(widths[idx], len(cell))\n print(\" | \".join(header.ljust(widths[idx]) for idx, header in enumerate(headers)))\n print(\"-+-\".join(\"-\" * width for width in widths))\n for row in rows:\n print(\" | \".join(cell.ljust(widths[idx]) for idx, cell in enumerate(row)))\n\n\ndef cmd_list(args: argparse.Namespace) -> None:\n models = extract_models(load_json_source(args.source))\n if args.query:\n models, _ = rank_matching_models(models, args.query)\n if args.json:\n print(json.dumps(models[: args.limit], ensure_ascii=False, indent=2))\n return\n rows = []\n for item in models[: args.limit]:\n rows.append(\n [\n model_id(item),\n model_label(item),\n developer(item),\n context_display(item),\n max_output_display(item),\n types(item),\n ]\n )\n print_table([\"model_id\", \"name\", \"provider\", \"context\", \"max_output\", \"types\"], rows)\n print(f\"\\nsource: {display_models_source()}\")\n\n\ndef cmd_get(args: argparse.Namespace) -> None:\n models = extract_models(load_json_source(args.source))\n item = find_model(models, args.model)\n if not item:\n raise SystemExit(f\"Model not found: {args.model}\")\n if args.json:\n print(json.dumps(item, ensure_ascii=False, indent=2))\n return\n rows = [\n [\"model_id\", model_id(item)],\n [\"name\", model_label(item)],\n [\"provider\", developer(item)],\n [\"context_length\", context_display(item)],\n [\"max_output\", max_output_display(item)],\n [\"pricing\", pricing(item)],\n [\"price_unit\", price_unit()],\n [\"types/endpoints\", types(item)],\n [\"features/modalities\", features(item)],\n ]\n print_table([\"field\", \"value\"], rows)\n print(\"\\nRaw model data is available with --json.\")\n\n\ndef cmd_compare(args: argparse.Namespace) -> None:\n models = extract_models(load_json_source(args.source))\n selected = []\n missing = []\n for target in args.models:\n item = find_model(models, target)\n if item:\n selected.append(item)\n else:\n missing.append(target)\n if args.json:\n print(json.dumps({\"models\": selected, \"missing\": missing}, ensure_ascii=False, indent=2))\n return\n rows = []\n for item in selected:\n rows.append(\n [\n model_id(item),\n developer(item),\n context_display(item),\n max_output_display(item),\n pricing(item),\n features(item),\n ]\n )\n print_table([\"model_id\", \"provider\", \"context\", \"max_output\", \"pricing\", \"features\"], rows)\n if missing:\n print(\"\\nmissing: \" + \", \".join(missing))\n print(f\"\\nPricing units: {price_unit()}. Values are displayed exactly as returned by the models API.\")\n\n\ndef cmd_latest_report(args: argparse.Namespace) -> None:\n payload = load_json_source(args.source)\n models = extract_models_in_source_order(payload)\n selected = models[: args.limit]\n if not selected:\n raise SystemExit(\"No models found in the selected source.\")\n\n source = display_models_source()\n unit = price_unit()\n now = query_time_label()\n\n print(f\"# AIHubMix Latest Models Report\")\n print()\n print(f\"- Data source: `{source}`\")\n print(f\"- Query time: `{now}`\")\n print(\"- Latest definition: first records returned by `/api/v1/models` in source order; no model_id sorting is applied.\")\n print(f\"- Price unit: `{unit}`\")\n print(\"- Price values: displayed exactly as returned in each model's `pricing` object.\")\n print()\n print_user_overview(selected)\n print(\"| # | model_id | name | developer_id | pricing | context_length(tokens) | max_output(tokens) | types/endpoints | features | notes |\")\n print(\"|---:|---|---|---:|---|---:|---:|---|---|---|\")\n for index, item in enumerate(selected, start=1):\n type_endpoint = types(item)\n endpoints = compact(item.get(\"endpoints\"))\n if endpoints != \"-\" and endpoints not in type_endpoint:\n type_endpoint = f\"{type_endpoint}; endpoints={endpoints}\" if type_endpoint != \"-\" else f\"endpoints={endpoints}\"\n print(\n \"| \"\n + \" | \".join(\n [\n str(index),\n f\"`{model_id(item)}`\",\n model_label(item).replace(\"|\", \"\\\\|\"),\n developer(item),\n pricing(item).replace(\"|\", \"\\\\|\"),\n context_display(item),\n max_output_display(item),\n type_endpoint.replace(\"|\", \"\\\\|\"),\n features(item).replace(\"|\", \"\\\\|\"),\n availability_note(item).replace(\"|\", \"\\\\|\"),\n ]\n )\n + \" |\"\n )\n print()\n print_model_switch_hint(selected)\n\n first_model = model_id(selected[0])\n protocol = suggested_protocol(selected[0])\n print()\n if protocol is None:\n print(\"## Example\")\n print()\n print(\"The first selected model is not a plain text/chat model. Choose the endpoint by model type and verify it with `protocols` before generating a call example.\")\n return\n\n print(f\"## Suggested {protocol_name(protocol)} Call Example\")\n print()\n print(f\"- URL: `{base_url()}{endpoint_path(protocol, first_model)}`\")\n print(f\"- Auth: `{protocol_auth_label(protocol)}`\")\n print(\"- Replace `model` with any compatible `model_id` from the table when needed.\")\n print()\n os_name = detect_os(args.os)\n print(f\"### Command Line ({'Windows PowerShell' if os_name == 'windows' else 'macOS/Linux shell'})\")\n print()\n print(example_intro(\"command\", os_name))\n print()\n print(\"```\" + (\"powershell\" if os_name == \"windows\" else \"bash\"))\n print(render_cmd_example(protocol, first_model, os_name))\n print(\"```\")\n print()\n print(\"### JavaScript\")\n print()\n print(example_intro(\"js\"))\n print()\n print_env_setup(\"js\", os_name)\n print(\"```js\")\n print(render_js_example(protocol, first_model))\n print(\"```\")\n\n\ndef cmd_report(args: argparse.Namespace) -> None:\n payload = load_json_source(args.source)\n wants_latest = query_wants_latest(args.query)\n models = extract_models_in_source_order(payload) if wants_latest else extract_models(payload)\n selected, query_plan = rank_matching_models(\n models,\n args.query,\n limit=args.limit,\n preserve_source_order=wants_latest,\n )\n if not selected:\n raise SystemExit(f\"No models matched query: {args.query}\")\n\n source = display_models_source()\n now = query_time_label()\n matched_terms = \"; \".join(query_plan[\"labels\"]) if query_plan[\"labels\"] else \"raw query\"\n\n print(\"# AIHubMix Model Interface Report\")\n print()\n print(f\"- Query: `{args.query}`\")\n print(f\"- Normalized query terms: `{matched_terms}`\")\n if query_plan[\"wants_latest\"]:\n print(\"- Latest intent: `detected`; matched rows keep `/api/v1/models` source order.\")\n print(f\"- Data source: `{source}`\")\n print(f\"- Query time: `{now}`\")\n print(f\"- Matched models shown: `{len(selected)}`\")\n print(f\"- Price unit: `{price_unit()}`\")\n print(\"- Price values: displayed exactly as returned in each model's `pricing` object.\")\n print()\n print_user_overview(selected, args.query)\n print(\"| model_id | name | developer_id | pricing | context | max_output | type | callable entrypoints | raw endpoints | features | modalities | notes |\")\n print(\"|---|---|---:|---|---:|---:|---|---|---|---|---|---|\")\n for item in selected:\n print(\n \"| \"\n + \" | \".join(\n [\n f\"`{model_id(item)}`\",\n model_label(item).replace(\"|\", \"\\\\|\"),\n developer(item),\n pricing(item).replace(\"|\", \"\\\\|\"),\n context_display(item),\n max_output_display(item),\n compact(item.get(\"types\")).replace(\"|\", \"\\\\|\"),\n endpoint_display(item).replace(\"|\", \"\\\\|\"),\n endpoint_note(item).replace(\"|\", \"\\\\|\"),\n compact(item.get(\"features\")).replace(\"|\", \"\\\\|\"),\n compact(item.get(\"input_modalities\")).replace(\"|\", \"\\\\|\"),\n availability_note(item).replace(\"|\", \"\\\\|\"),\n ]\n )\n + \" |\"\n )\n print()\n print_model_switch_hint(selected)\n\n first_model = model_id(selected[0])\n protocol = suggested_protocol(selected[0])\n print()\n if protocol is None:\n print(\"## Example\")\n print()\n print(\"The matched model is not a plain text/chat model. Choose the endpoint by model type and verify it with `protocols` before generating a call example.\")\n return\n\n print(f\"## Suggested {protocol_name(protocol)} Endpoint\")\n print()\n print(\"- Method: `POST`\")\n print(f\"- URL: `{base_url()}{endpoint_path(protocol, first_model)}`\")\n print(f\"- Auth: `{protocol_auth_label(protocol)}`\")\n print()\n print(\"## Example\")\n print()\n os_name = detect_os(args.os)\n print(f\"### Command Line ({'Windows PowerShell' if os_name == 'windows' else 'macOS/Linux shell'})\")\n print()\n print(example_intro(\"command\", os_name))\n print()\n print(\"```\" + (\"powershell\" if os_name == \"windows\" else \"bash\"))\n print(render_cmd_example(protocol, first_model, os_name))\n print(\"```\")\n print()\n print(\"### JavaScript\")\n print()\n print(example_intro(\"js\"))\n print()\n print_env_setup(\"js\", os_name)\n print(\"```js\")\n print(render_js_example(protocol, first_model))\n print(\"```\")\n\n\ndef load_openapi() -> dict[str, Any] | None:\n \"\"\"Fetch the live OpenAPI spec from the remote endpoint (no local checkout).\"\"\"\n url = os.environ.get(\"AIHUBMIX_OPENAPI_URL\") or OPENAPI_JSON_URL\n try:\n return json.loads(read_url_bytes(url).decode(\"utf-8\", errors=\"replace\"))\n except ValueError:\n return None\n\n\ndef _error_statuses_from_spec(spec: dict[str, Any]) -> dict[str, dict[str, str]]:\n \"\"\"Collect declared 4XX/5XX responses (code -> description/schema) from the spec.\"\"\"\n statuses: dict[str, dict[str, str]] = {}\n for _method, _path, operation in iter_operations(spec):\n for code, response in (operation.get(\"responses\") or {}).items():\n code = str(code)\n if code in statuses or not re.match(r\"^[45]\\d\\d$|^[45]XX$\", code, re.IGNORECASE):\n continue\n if not isinstance(response, dict):\n continue\n schema_ref = \"#/components/schemas/GatewayError\"\n for media in (response.get(\"content\") or {}).values():\n ref = ((media or {}).get(\"schema\") or {}).get(\"$ref\")\n if ref:\n schema_ref = ref\n break\n statuses[code] = {\n \"description\": str(response.get(\"description\", \"\")).strip(),\n \"schema\": schema_ref,\n }\n return statuses\n\n\ndef load_error_contract() -> dict[str, Any]:\n \"\"\"Derive the GatewayError contract from the live OpenAPI spec (no local checkout).\"\"\"\n spec = load_openapi()\n if not spec:\n raise SystemExit(\n f\"Could not load the OpenAPI spec from {OPENAPI_JSON_URL} to derive the error contract.\"\n )\n return {\n \"source\": display_error_contract_source(),\n \"schema_name\": \"GatewayError\",\n \"envelope\": {\n \"top_level\": \"error\",\n \"required\": [\"error.message\"],\n \"fields\": {\n \"error.message\": \"Human-readable error message.\",\n \"error.type\": \"OpenAI-compatible category; upstream value may pass through and enum is not fixed.\",\n \"error.param\": \"Offending field name, when applicable.\",\n \"error.code\": \"Sub-error code; may be string or integer.\",\n },\n },\n \"request_id\": \"Use the X-Request-Id response header. It is not in the JSON body.\",\n \"legacy_upstream\": \"The legacy upstream field is no longer emitted.\",\n \"statuses\": _error_statuses_from_spec(spec),\n }\n\n\ndef iter_operations(spec: dict[str, Any]) -> list[tuple[str, str, dict[str, Any]]]:\n ops = []\n for path, path_item in (spec.get(\"paths\") or {}).items():\n if not isinstance(path_item, dict):\n continue\n for method, operation in path_item.items():\n if method.lower() not in {\"get\", \"post\", \"put\", \"patch\", \"delete\"}:\n continue\n if isinstance(operation, dict):\n ops.append((method.upper(), path, operation))\n return ops\n\n\ndef cmd_protocols(args: argparse.Namespace) -> None:\n spec = load_openapi()\n if not spec:\n raise SystemExit(f\"Could not load the OpenAPI spec from {OPENAPI_JSON_URL}.\")\n operations = iter_operations(spec)\n if args.json:\n print(json.dumps({\"source\": display_openapi_source(), \"operations\": operations}, ensure_ascii=False, indent=2))\n return\n rows = []\n for method, endpoint, operation in operations:\n security = operation.get(\"security\") or spec.get(\"security\") or []\n rows.append(\n [\n method,\n endpoint,\n operation.get(\"operationId\", \"-\"),\n compact(security),\n \"yes\" if operation.get(\"requestBody\") else \"no\",\n ]\n )\n print_table([\"method\", \"path\", \"operation_id\", \"security\", \"request_body\"], rows)\n print(f\"\\nsource: {display_openapi_source()}\")\n\n\ndef cmd_error_contract(args: argparse.Namespace) -> None:\n contract = load_error_contract()\n if args.json:\n print(json.dumps(contract, ensure_ascii=False, indent=2))\n return\n\n print(\"# AIHubMix Gateway Error Contract\")\n print()\n print(f\"- Source: `{display_error_contract_source()}`\")\n print(f\"- Schema: `{contract['schema_name']}`\")\n print(\"- Body shape: top-level `error` object.\")\n print(\"- Required body field: `error.message`.\")\n print(f\"- Request ID: `{contract['request_id']}`\")\n print(f\"- Legacy upstream field: `{contract['legacy_upstream']}`\")\n print()\n print(\"## GatewayError Fields\")\n print()\n rows = [[field, description] for field, description in contract[\"envelope\"][\"fields\"].items()]\n print_table([\"field\", \"meaning\"], rows)\n print()\n print(\"## Status Responses\")\n print()\n status_order = [\"400\", \"401\", \"403\", \"404\", \"429\", \"5XX\"]\n rows = []\n statuses = contract[\"statuses\"]\n for status in status_order:\n item = statuses.get(status, {})\n rows.append([status, item.get(\"description\", \"-\"), item.get(\"schema\", \"#/components/schemas/GatewayError\")])\n print_table([\"status\", \"description\", \"schema\"], rows)\n print()\n print(\"## Troubleshooting Rule\")\n print()\n print(\"Use the live HTTP status, JSON error body, and `X-Request-Id` as the observed evidence.\")\n print(\"Use this contract to verify the error envelope and status category; do not assume `error.type` is a fixed enum.\")\n\n\ndef load_package_json(path: Path) -> dict[str, Any] | None:\n if not path.exists():\n return None\n with path.open(\"r\", encoding=\"utf-8\") as handle:\n data = json.load(handle)\n return data if isinstance(data, dict) else None\n\n\ndef find_installed_sdk_package(start: str | None = None) -> tuple[dict[str, Any] | None, Path | None]:\n explicit = os.environ.get(\"AIHUBMIX_SDK_PACKAGE_JSON\")\n candidates: list[Path] = []\n if explicit:\n candidates.append(Path(explicit).expanduser())\n cwd = Path(start).expanduser() if start else Path.cwd()\n for path in [cwd, *cwd.parents]:\n candidates.append(path / \"node_modules\" / \"@aihubmix\" / \"ai-sdk-provider\" / \"package.json\")\n seen: set[str] = set()\n for candidate in candidates:\n try:\n resolved = candidate.resolve()\n except OSError:\n resolved = candidate\n key = str(resolved).lower()\n if key in seen:\n continue\n seen.add(key)\n data = load_package_json(candidate)\n if data:\n return data, candidate\n return None, None\n\n\ndef fetch_npm_package_metadata() -> dict[str, Any] | None:\n request = urllib.request.Request(\n SDK_REGISTRY_URL,\n headers={\n \"Accept\": \"application/json\",\n \"User-Agent\": \"aihubmixApi-skill/0.1\",\n },\n )\n try:\n with urllib.request.urlopen(request, timeout=20) as response:\n payload = json.load(response)\n except urllib.error.HTTPError as exc:\n detail = exc.read().decode(\"utf-8\", errors=\"replace\")\n raise SystemExit(f\"HTTP {exc.code} while checking npm registry: {detail}\") from exc\n except urllib.error.URLError as exc:\n raise SystemExit(f\"Could not check npm registry: {exc.reason}\") from exc\n return payload if isinstance(payload, dict) else None\n\n\ndef read_url_bytes(url: str) -> bytes:\n request = urllib.request.Request(\n url,\n headers={\"User-Agent\": \"aihubmixApi-skill/0.1\"},\n )\n try:\n with urllib.request.urlopen(request, timeout=30) as response:\n return response.read()\n except urllib.error.HTTPError as exc:\n detail = exc.read().decode(\"utf-8\", errors=\"replace\")\n raise SystemExit(f\"HTTP {exc.code} while fetching {url}: {detail}\") from exc\n except urllib.error.URLError as exc:\n raise SystemExit(f\"Could not fetch {url}: {exc.reason}\") from exc\n\n\ndef parse_sdk_package_texts(package_json: dict[str, Any], dts_text: str, js_text: str, source: str) -> dict[str, Any]:\n method_candidates = [\n \"languageModel\",\n \"chat\",\n \"responses\",\n \"completion\",\n \"embedding\",\n \"embeddingModel\",\n \"image\",\n \"imageModel\",\n \"textEmbedding\",\n \"textEmbeddingModel\",\n \"transcription\",\n \"transcriptionModel\",\n \"speech\",\n \"speechModel\",\n ]\n tool_candidates = [\"codeInterpreter\", \"fileSearch\", \"imageGeneration\", \"webSearchPreview\", \"webSearch\"]\n export_candidates = [\"aihubmix\", \"createAihubmix\"]\n provider_methods = [name for name in method_candidates if re.search(rf\"\\b{name}\\b\", dts_text)]\n tools = [name for name in tool_candidates if re.search(rf\"\\b{name}\\b\", dts_text)]\n exports = [name for name in export_candidates if re.search(rf\"\\b{name}\\b\", dts_text)]\n env_vars = sorted(set(re.findall(r'\"(AIHUBMIX_[A-Z0-9_]+)\"', js_text + dts_text)))\n base_urls = sorted(set(re.findall(r'\"(https://aihubmix\\.com[^\"]*)\"', js_text + dts_text)))\n header_candidates = [\"Authorization\", \"APP-Code\", \"Content-Type\", \"x-api-key\", \"x-goog-api-key\"]\n headers = [name for name in header_candidates if name in js_text]\n routing = []\n if 'startsWith(\"claude-\")' in js_text:\n routing.append(\"Claude model IDs starting with claude- use Anthropic-compatible messages with x-api-key.\")\n if 'startsWith(\"gemini\")' in js_text or 'startsWith(\"imagen\")' in js_text:\n routing.append(\"Gemini or Imagen model IDs use Gemini native base URL unless suffixed with -nothink or -search.\")\n if \"OpenAICompatibleChatLanguageModel\" in js_text:\n routing.append(\"Other chat models use OpenAI-compatible chat through https://aihubmix.com/v1.\")\n\n return {\n \"source\": source,\n \"package\": str(package_json.get(\"name\") or SDK_PACKAGE_NAME),\n \"version\": str(package_json.get(\"version\") or \"-\"),\n \"exports\": exports,\n \"provider_methods\": provider_methods,\n \"tools\": tools,\n \"environment_variable\": env_vars[0] if env_vars else \"-\",\n \"base_urls\": base_urls,\n \"headers\": headers,\n \"routing\": routing,\n }\n\n\ndef load_sdk_info_from_directory(package_dir: Path, source_label: str) -> dict[str, Any]:\n package_json = load_package_json(package_dir / \"package.json\") or {}\n dts_text = (package_dir / \"dist\" / \"index.d.ts\").read_text(encoding=\"utf-8\")\n js_text = (package_dir / \"dist\" / \"index.js\").read_text(encoding=\"utf-8\")\n return parse_sdk_package_texts(package_json, dts_text, js_text, source_label)\n\n\ndef load_sdk_info_from_tarball(tarball: bytes, source_label: str) -> dict[str, Any]:\n with tarfile.open(fileobj=io.BytesIO(tarball), mode=\"r:gz\") as archive:\n files: dict[str, bytes] = {}\n for name in (\"package/package.json\", \"package/dist/index.d.ts\", \"package/dist/index.js\"):\n member = archive.getmember(name)\n extracted = archive.extractfile(member)\n if not extracted:\n raise SystemExit(f\"Could not read {name} from SDK tarball.\")\n files[name] = extracted.read()\n package_json = json.loads(files[\"package/package.json\"].decode(\"utf-8\"))\n dts_text = files[\"package/dist/index.d.ts\"].decode(\"utf-8\")\n js_text = files[\"package/dist/index.js\"].decode(\"utf-8\")\n return parse_sdk_package_texts(package_json, dts_text, js_text, source_label)\n\n\ndef load_sdk_info(args: argparse.Namespace) -> dict[str, Any]:\n if args.package_dir:\n return load_sdk_info_from_directory(Path(args.package_dir).expanduser(), str(Path(args.package_dir).expanduser()))\n\n installed, package_json_path = find_installed_sdk_package(args.project)\n if package_json_path and args.source in {\"auto\", \"installed\"}:\n return load_sdk_info_from_directory(package_json_path.parent, str(package_json_path.parent))\n if args.source == \"installed\":\n raise SystemExit(\"SDK package is not installed in the selected project path.\")\n\n metadata = fetch_npm_package_metadata()\n target_version = args.version or SDK_RECOMMENDED_VERSION\n versions = metadata.get(\"versions\") or {}\n package_meta = versions.get(target_version)\n if not isinstance(package_meta, dict):\n raise SystemExit(f\"Could not find {SDK_PACKAGE_NAME}@{target_version} in npm registry metadata.\")\n tarball_url = ((package_meta.get(\"dist\") or {}).get(\"tarball\"))\n if not tarball_url:\n raise SystemExit(f\"No tarball URL found for {SDK_PACKAGE_NAME}@{target_version}.\")\n return load_sdk_info_from_tarball(read_url_bytes(str(tarball_url)), f\"npm tarball {SDK_PACKAGE_NAME}@{target_version}\")\n\n\ndef cmd_sdk_info(args: argparse.Namespace) -> None:\n info = load_sdk_info(args)\n if args.json:\n print(json.dumps(info, ensure_ascii=False, indent=2))\n return\n\n print(\"# AIHubMix SDK Provider Info\")\n print()\n print(f\"- Package: `{info['package']}`\")\n print(f\"- Version: `{info['version']}`\")\n print(f\"- Source: `{info['source']}`\")\n print(\"- Data role: SDK/provider usage source; not model price, context, or availability source.\")\n print()\n print(\"## Provider Surface\")\n print()\n rows = []\n for name in info[\"provider_methods\"]:\n if name in {\"chat\", \"languageModel\"}:\n purpose = \"text/chat generation\"\n elif name == \"responses\":\n purpose = \"OpenAI Responses API style model\"\n elif \"embedding\" in name.lower():\n purpose = \"text embeddings\"\n elif name.startswith(\"image\"):\n purpose = \"image generation\"\n elif \"transcription\" in name.lower():\n purpose = \"audio transcription\"\n elif \"speech\" in name.lower():\n purpose = \"speech synthesis\"\n elif name == \"completion\":\n purpose = \"legacy text completion\"\n else:\n purpose = \"provider method\"\n rows.append([name, purpose])\n print_table([\"method\", \"purpose\"], rows)\n print()\n print(\"## Tools\")\n print()\n print(\", \".join(info[\"tools\"]) if info[\"tools\"] else \"-\")\n print()\n print(\"## Auth And Routing\")\n print()\n print_table(\n [\"field\", \"value\"],\n [\n [\"api_key_env\", info[\"environment_variable\"]],\n [\"base_urls\", compact(info[\"base_urls\"])],\n [\"headers\", compact(info[\"headers\"])],\n [\"exports\", compact(info[\"exports\"])],\n ],\n )\n if info[\"routing\"]:\n print()\n print(\"## Routing Notes\")\n for index, item in enumerate(info[\"routing\"], start=1):\n print(f\"{index}. {item}\")\n print()\n print(\"## Boundary\")\n print(\"Use `/api/v1/models` for live model price/context/availability. Use this SDK info for JavaScript provider usage.\")\n\n\ndef version_tuple(version: str) -> tuple[int, ...]:\n parts: list[int] = []\n for part in version.split(\".\"):\n digits = \"\"\n for char in part:\n if char.isdigit():\n digits += char\n else:\n break\n parts.append(int(digits or 0))\n return tuple(parts)\n\n\ndef cmd_sdk_check(args: argparse.Namespace) -> None:\n installed, installed_path = find_installed_sdk_package(args.project)\n installed_version = str(installed.get(\"version\")) if installed else \"-\"\n latest_version = \"-\"\n registry_source = SDK_REGISTRY_URL\n metadata = None\n if not args.offline:\n metadata = fetch_npm_package_metadata()\n latest_version = str((metadata.get(\"dist-tags\") or {}).get(\"latest\") or \"-\")\n target_version = args.version or SDK_RECOMMENDED_VERSION\n\n print(\"# AIHubMix SDK Check\")\n print()\n print(f\"- Package: `{SDK_PACKAGE_NAME}`\")\n print(f\"- Recommended version for this skill: `{SDK_RECOMMENDED_VERSION}`\")\n print(f\"- Target version: `{target_version}`\")\n print(f\"- Installed version: `{installed_version}`\")\n print(f\"- Installed package.json: `{installed_path or '-'}`\")\n print(f\"- Registry latest: `{latest_version}`\")\n print(f\"- Registry source: `{registry_source if not args.offline else 'not checked (--offline)'}`\")\n print(\"- Auto update: `not performed`\")\n\n status = []\n if installed_version == \"-\":\n status.append(\"SDK is not installed in the selected project path.\")\n elif installed_version == target_version:\n status.append(\"Installed SDK matches the target version.\")\n else:\n status.append(\"Installed SDK differs from the target version.\")\n\n if latest_version != \"-\":\n if version_tuple(latest_version) > version_tuple(target_version):\n status.append(\"A newer npm version is available; review changelog before updating.\")\n elif latest_version == target_version:\n status.append(\"Target version is the npm latest version.\")\n else:\n status.append(\"Target version is newer than registry latest; verify the package tag.\")\n else:\n status.append(\"Latest npm version was not checked.\")\n\n print()\n print(\"## Status\")\n for item in status:\n print(f\"- {item}\")\n\n print()\n print(\"## Manual Commands\")\n print()\n print(\"```bash\")\n print(f\"npm view {SDK_PACKAGE_NAME} version\")\n print(f\"npm install {SDK_PACKAGE_NAME}@{target_version} ai\")\n print(\"```\")\n print()\n print(\"Only run install/update commands after the user confirms the target project and version.\")\n\n\ndef base_url() -> str:\n return os.environ.get(\"AIHUBMIX_BASE_URL\", DEFAULT_SERVER_URL).rstrip(\"/\")\n\n\ndef normalize_protocol(protocol: str) -> str:\n normalized = PROTOCOL_ALIASES.get(protocol, protocol)\n if normalized not in PROTOCOL_SPECS:\n raise ValueError(normalized)\n return normalized\n\n\ndef endpoint_path(protocol: str, model: str) -> str:\n protocol = normalize_protocol(protocol)\n path = PROTOCOL_SPECS[protocol][\"path\"]\n return path.replace(\"{model}\", model).replace(\"{video_id}\", \"video_123\")\n\n\ndef example_payload(protocol: str, model: str) -> dict[str, Any]:\n protocol = normalize_protocol(protocol)\n if protocol == \"chat\":\n return {\n \"model\": model,\n \"messages\": [{\"role\": \"user\", \"content\": \"Say hello in one short sentence.\"}],\n }\n if protocol == \"responses\":\n return {\"model\": model, \"input\": \"Say hello in one short sentence.\"}\n if protocol == \"messages\":\n return {\n \"model\": model,\n \"max_tokens\": 128,\n \"messages\": [{\"role\": \"user\", \"content\": \"Say hello in one short sentence.\"}],\n }\n if protocol == \"gemini\":\n return {\"contents\": [{\"role\": \"user\", \"parts\": [{\"text\": \"Say hello in one short sentence.\"}]}]}\n if protocol == \"completions\":\n return {\"model\": model, \"prompt\": \"Say hello in one short sentence.\", \"max_tokens\": 64}\n if protocol == \"embeddings\":\n return {\"model\": model, \"input\": \"Text to embed with AIHubMix.\"}\n if protocol == \"images\":\n return {\"model\": model, \"prompt\": \"A clean product mockup on a white background.\", \"size\": \"1024x1024\"}\n if protocol == \"videos\":\n return {\"model\": model, \"prompt\": \"A short cinematic shot of a city at sunrise.\"}\n if protocol == \"video-remix\":\n return {\"model\": model, \"prompt\": \"Make the video brighter and more cinematic.\"}\n if protocol == \"audio-speech\":\n return {\"model\": model, \"input\": \"Hello from AIHubMix.\", \"voice\": \"alloy\", \"response_format\": \"mp3\"}\n if protocol in {\"audio-transcriptions\", \"audio-translations\"}:\n return {\"model\": model, \"file\": \"@/path/to/audio.mp3\"}\n if protocol == \"moderations\":\n return {\"model\": model, \"input\": \"I want to check whether this text is safe.\"}\n if protocol in {\"video-retrieve\", \"video-delete\", \"video-content\"}:\n return {}\n raise ValueError(protocol)\n\n\ndef auth_header(protocol: str) -> tuple[str, str]:\n protocol = normalize_protocol(protocol)\n family = PROTOCOL_SPECS[protocol][\"family\"]\n if family == \"anthropic\":\n return \"x-api-key\", \"$AIHUBMIX_API_KEY\"\n if family == \"gemini\":\n return \"x-goog-api-key\", \"$AIHUBMIX_API_KEY\"\n return \"Authorization\", \"Bearer $AIHUBMIX_API_KEY\"\n\n\ndef detect_os(requested: str) -> str:\n if requested != \"auto\":\n return requested\n system = platform.system().lower()\n if system.startswith(\"win\"):\n return \"windows\"\n if system == \"darwin\":\n return \"mac\"\n return \"linux\"\n\n\ndef powershell_literal(value: str) -> str:\n return \"'\" + value.replace(\"'\", \"''\") + \"'\"\n\n\ndef powershell_response_printer() -> str:\n return (\n \"if ($response.choices -and $response.choices.Count -gt 0) {\\n\"\n \" [Console]::WriteLine($response.choices[0].message.content)\\n\"\n \"} elseif ($response.output_text) {\\n\"\n \" [Console]::WriteLine($response.output_text)\\n\"\n \"} elseif ($response.content -and $response.content.Count -gt 0) {\\n\"\n \" [Console]::WriteLine($response.content[0].text)\\n\"\n \"} elseif ($response.candidates -and $response.candidates.Count -gt 0) {\\n\"\n \" [Console]::WriteLine($response.candidates[0].content.parts[0].text)\\n\"\n \"} else {\\n\"\n \" $response | ConvertTo-Json -Depth 20\\n\"\n \"}\\n\"\n )\n\n\ndef powershell_response_printer_for(protocol: str) -> str:\n protocol = normalize_protocol(protocol)\n if protocol == \"embeddings\":\n return (\n \"if ($response.data -and $response.data.Count -gt 0) {\\n\"\n \" [Console]::WriteLine(\\\"embedding dimensions: \\\" + $response.data[0].embedding.Count)\\n\"\n \"} else {\\n\"\n \" $response | ConvertTo-Json -Depth 20\\n\"\n \"}\\n\"\n )\n if protocol == \"images\":\n return (\n \"if ($response.data -and $response.data.Count -gt 0) {\\n\"\n \" $item = $response.data[0]\\n\"\n \" if ($item.url) { [Console]::WriteLine($item.url) } elseif ($item.b64_json) { [Console]::WriteLine($item.b64_json) } else { $item | ConvertTo-Json -Depth 20 }\\n\"\n \"} else {\\n\"\n \" $response | ConvertTo-Json -Depth 20\\n\"\n \"}\\n\"\n )\n if protocol in {\"videos\", \"video-remix\", \"video-retrieve\"}:\n return (\n \"if ($response.id) {\\n\"\n \" [Console]::WriteLine(\\\"video id: \\\" + $response.id)\\n\"\n \"} else {\\n\"\n \" $response | ConvertTo-Json -Depth 20\\n\"\n \"}\\n\"\n )\n if protocol in {\"audio-transcriptions\", \"audio-translations\"}:\n return (\n \"if ($response.text) {\\n\"\n \" [Console]::WriteLine($response.text)\\n\"\n \"} else {\\n\"\n \" $response | ConvertTo-Json -Depth 20\\n\"\n \"}\\n\"\n )\n if protocol == \"moderations\":\n return (\n \"if ($response.results -and $response.results.Count -gt 0) {\\n\"\n \" $response.results[0] | ConvertTo-Json -Depth 20\\n\"\n \"} else {\\n\"\n \" $response | ConvertTo-Json -Depth 20\\n\"\n \"}\\n\"\n )\n return powershell_response_printer()\n\n\ndef shell_response_parser() -> str:\n return (\n \"import json,sys; d=json.load(sys.stdin); \"\n \"text=(d.get('output_text') or ((d.get('choices') or [{}])[0].get('message') or {}).get('content') \"\n \"or ((d.get('content') or [{}])[0]).get('text') \"\n \"or ((((d.get('candidates') or [{}])[0].get('content') or {}).get('parts') or [{}])[0]).get('text')); \"\n \"print(text if text is not None else json.dumps(d, ensure_ascii=False))\"\n )\n\n\ndef shell_response_parser_for(protocol: str) -> str:\n protocol = normalize_protocol(protocol)\n if protocol == \"embeddings\":\n return (\n \"import json,sys; d=json.load(sys.stdin); data=d.get('data') or []; \"\n \"print('embedding dimensions: ' + str(len((data[0] if data else {}).get('embedding') or [])))\"\n )\n if protocol == \"images\":\n return (\n \"import json,sys; d=json.load(sys.stdin); item=(d.get('data') or [{}])[0]; \"\n \"print(item.get('url') or item.get('b64_json') or json.dumps(d, ensure_ascii=False))\"\n )\n if protocol in {\"videos\", \"video-remix\", \"video-retrieve\"}:\n return (\n \"import json,sys; d=json.load(sys.stdin); \"\n \"print(('video id: ' + str(d.get('id'))) if d.get('id') else json.dumps(d, ensure_ascii=False))\"\n )\n if protocol in {\"audio-transcriptions\", \"audio-translations\"}:\n return \"import json,sys; d=json.load(sys.stdin); print(d.get('text') or json.dumps(d, ensure_ascii=False))\"\n if protocol == \"moderations\":\n return (\n \"import json,sys; d=json.load(sys.stdin); \"\n \"print(json.dumps((d.get('results') or [d])[0], ensure_ascii=False))\"\n )\n return shell_response_parser()\n\n\ndef render_cmd_example(protocol: str, model: str, os_name: str) -> str:\n protocol = normalize_protocol(protocol)\n url = base_url() + endpoint_path(protocol, model)\n payload = example_payload(protocol, model)\n spec = PROTOCOL_SPECS[protocol]\n method = spec[\"method\"]\n is_get_or_delete = method in {\"GET\", \"DELETE\"}\n is_multipart = bool(spec.get(\"multipart\"))\n is_binary = bool(spec.get(\"binary_response\") or protocol == \"video-content\")\n needs_video_id = bool(spec.get(\"needs_video_id\"))\n body = json.dumps(payload, ensure_ascii=False, indent=2)\n header_name, _ = auth_header(protocol)\n\n if os_name == \"windows\":\n if spec[\"family\"] == \"anthropic\":\n auth_line = '\"x-api-key\" = $AIHUBMIX_API_KEY'\n elif spec[\"family\"] == \"gemini\":\n auth_line = '\"x-goog-api-key\" = $AIHUBMIX_API_KEY'\n else:\n auth_line = '\"Authorization\" = \"Bearer $AIHUBMIX_API_KEY\"'\n if is_get_or_delete:\n if is_binary:\n return (\n \"$OutputEncoding = [System.Text.Encoding]::UTF8\\n\"\n \"[Console]::OutputEncoding = [System.Text.Encoding]::UTF8\\n\"\n \"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12\\n\"\n \"$AIHUBMIX_API_KEY = Read-Host \\\"Paste AIHubMix API Key\\\"\\n\"\n \"$videoId = Read-Host \\\"Paste video id\\\"\\n\"\n f\"$url = {powershell_literal(url)}.Replace('video_123', $videoId)\\n\\n\"\n \"Invoke-WebRequest `\\n\"\n \" -Uri $url `\\n\"\n \" -Method Get `\\n\"\n f\" -Headers @{{ {auth_line} }} `\\n\"\n \" -OutFile 'aihubmix-video.mp4'\\n\\n\"\n \"[Console]::WriteLine('Saved to aihubmix-video.mp4')\"\n )\n return (\n \"$OutputEncoding = [System.Text.Encoding]::UTF8\\n\"\n \"[Console]::OutputEncoding = [System.Text.Encoding]::UTF8\\n\"\n \"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12\\n\"\n \"$AIHUBMIX_API_KEY = Read-Host \\\"Paste AIHubMix API Key\\\"\\n\"\n \"$videoId = Read-Host \\\"Paste video id\\\"\\n\"\n f\"$url = {powershell_literal(url)}.Replace('video_123', $videoId)\\n\\n\"\n \"$response = Invoke-RestMethod `\\n\"\n \" -Uri $url `\\n\"\n f\" -Method {method.title()} `\\n\"\n f\" -Headers @{{ {auth_line}; \\\"Accept\\\" = \\\"application/json\\\" }}\\n\\n\"\n f\"{powershell_response_printer_for(protocol)}\"\n )\n if is_multipart:\n return (\n \"$OutputEncoding = [System.Text.Encoding]::UTF8\\n\"\n \"[Console]::OutputEncoding = [System.Text.Encoding]::UTF8\\n\"\n \"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12\\n\"\n \"$AIHUBMIX_API_KEY = Read-Host \\\"Paste AIHubMix API Key\\\"\\n\"\n \"$audioPath = Read-Host \\\"Paste local audio file path\\\"\\n\\n\"\n \"$form = @{\\n\"\n f\" model = {powershell_literal(model)}\\n\"\n \" file = Get-Item -LiteralPath $audioPath\\n\"\n \"}\\n\\n\"\n \"$response = Invoke-RestMethod `\\n\"\n f\" -Uri {powershell_literal(url)} `\\n\"\n \" -Method Post `\\n\"\n f\" -Headers @{{ {auth_line}; \\\"Accept\\\" = \\\"application/json\\\" }} `\\n\"\n \" -Form $form\\n\\n\"\n f\"{powershell_response_printer_for(protocol)}\"\n )\n if needs_video_id:\n return (\n \"$OutputEncoding = [System.Text.Encoding]::UTF8\\n\"\n \"[Console]::OutputEncoding = [System.Text.Encoding]::UTF8\\n\"\n \"[Console]::InputEncoding = [System.Text.Encoding]::UTF8\\n\"\n \"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12\\n\"\n \"$AIHUBMIX_API_KEY = Read-Host \\\"Paste AIHubMix API Key\\\"\\n\"\n \"$videoId = Read-Host \\\"Paste video id\\\"\\n\"\n f\"$url = {powershell_literal(url)}.Replace('video_123', $videoId)\\n\\n\"\n \"$body = @'\\n\"\n f\"{body}\\n\"\n \"'@\\n\\n\"\n \"$response = Invoke-RestMethod `\\n\"\n \" -Uri $url `\\n\"\n f\" -Method {method.title()} `\\n\"\n f\" -Headers @{{ {auth_line}; \\\"Accept\\\" = \\\"application/json\\\" }} `\\n\"\n \" -ContentType \\\"application/json\\\" `\\n\"\n \" -Body $body\\n\\n\"\n f\"{powershell_response_printer_for(protocol)}\\n\"\n \"# To inspect the full response, uncomment the next line:\\n\"\n \"# $response | ConvertTo-Json -Depth 20\"\n )\n if is_binary:\n return (\n \"$OutputEncoding = [System.Text.Encoding]::UTF8\\n\"\n \"[Console]::OutputEncoding = [System.Text.Encoding]::UTF8\\n\"\n \"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12\\n\"\n \"$AIHUBMIX_API_KEY = Read-Host \\\"Paste AIHubMix API Key\\\"\\n\\n\"\n \"$body = @'\\n\"\n f\"{body}\\n\"\n \"'@\\n\\n\"\n \"Invoke-WebRequest `\\n\"\n f\" -Uri {powershell_literal(url)} `\\n\"\n \" -Method Post `\\n\"\n f\" -Headers @{{ {auth_line} }} `\\n\"\n \" -ContentType \\\"application/json\\\" `\\n\"\n \" -Body $body `\\n\"\n \" -OutFile 'aihubmix-output.mp3'\\n\\n\"\n \"[Console]::WriteLine('Saved to aihubmix-output.mp3')\"\n )\n return (\n \"$OutputEncoding = [System.Text.Encoding]::UTF8\\n\"\n \"[Console]::OutputEncoding = [System.Text.Encoding]::UTF8\\n\"\n \"[Console]::InputEncoding = [System.Text.Encoding]::UTF8\\n\"\n \"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12\\n\"\n \"$AIHUBMIX_API_KEY = Read-Host \\\"Paste AIHubMix API Key\\\"\\n\\n\"\n \"$body = @'\\n\"\n f\"{body}\\n\"\n \"'@\\n\\n\"\n \"$response = Invoke-RestMethod `\\n\"\n f\" -Uri {powershell_literal(url)} `\\n\"\n \" -Method Post `\\n\"\n f\" -Headers @{{ {auth_line}; \\\"Accept\\\" = \\\"application/json\\\" }} `\\n\"\n \" -ContentType \\\"application/json\\\" `\\n\"\n \" -Body $body\\n\\n\"\n f\"{powershell_response_printer_for(protocol)}\\n\"\n \"# To inspect the full response, uncomment the next line:\\n\"\n \"# $response | ConvertTo-Json -Depth 20\"\n )\n\n if spec[\"family\"] == \"anthropic\":\n auth_arg = '-H \"x-api-key: $AIHUBMIX_API_KEY\"'\n elif spec[\"family\"] == \"gemini\":\n auth_arg = '-H \"x-goog-api-key: $AIHUBMIX_API_KEY\"'\n else:\n auth_arg = '-H \"Authorization: Bearer $AIHUBMIX_API_KEY\"'\n if is_get_or_delete:\n if is_binary:\n return textwrap.dedent(\n f\"\"\"\\\n read -rsp \"Paste AIHubMix API Key: \" AIHUBMIX_API_KEY; echo\n read -rp \"Paste video id: \" video_id\n\n curl -L -s {json.dumps(url)} \\\\\n {auth_arg} \\\\\n -o aihubmix-video.mp4\n\n echo \"Saved to aihubmix-video.mp4\"\n \"\"\"\n ).strip().replace(\"video_123\", \"${video_id}\")\n return textwrap.dedent(\n f\"\"\"\\\n read -rsp \"Paste AIHubMix API Key: \" AIHUBMIX_API_KEY; echo\n read -rp \"Paste video id: \" video_id\n\n curl -s -X {method} {json.dumps(url)} \\\\\n {auth_arg} \\\\\n -H \"Accept: application/json\" \\\\\n | python3 -c {json.dumps(shell_response_parser_for(protocol))}\n \"\"\"\n ).strip().replace(\"video_123\", \"${video_id}\")\n if is_multipart:\n return textwrap.dedent(\n f\"\"\"\\\n read -rsp \"Paste AIHubMix API Key: \" AIHUBMIX_API_KEY; echo\n read -rp \"Paste local audio file path: \" audio_path\n\n curl -s {json.dumps(url)} \\\\\n {auth_arg} \\\\\n -H \"Accept: application/json\" \\\\\n -F model={json.dumps(model)} \\\\\n -F file=@\"$audio_path\" \\\\\n | python3 -c {json.dumps(shell_response_parser_for(protocol))}\n \"\"\"\n ).strip()\n if needs_video_id:\n compact_payload_video = json.dumps(payload, ensure_ascii=False)\n return textwrap.dedent(\n f\"\"\"\\\n read -rsp \"Paste AIHubMix API Key: \" AIHUBMIX_API_KEY; echo\n read -rp \"Paste video id: \" video_id\n\n curl -s -X {method} {json.dumps(url)} \\\\\n {auth_arg} \\\\\n -H \"Content-Type: application/json\" \\\\\n -d {json.dumps(compact_payload_video)} \\\\\n | python3 -c {json.dumps(shell_response_parser_for(protocol))}\n \"\"\"\n ).strip().replace(\"video_123\", \"${video_id}\")\n if is_binary:\n compact_payload_binary = json.dumps(payload, ensure_ascii=False)\n return textwrap.dedent(\n f\"\"\"\\\n read -rsp \"Paste AIHubMix API Key: \" AIHUBMIX_API_KEY; echo\n\n curl -L -s {json.dumps(url)} \\\\\n {auth_arg} \\\\\n -H \"Content-Type: application/json\" \\\\\n -d {json.dumps(compact_payload_binary)} \\\\\n -o aihubmix-output.mp3\n\n echo \"Saved to aihubmix-output.mp3\"\n \"\"\"\n ).strip()\n compact_payload = json.dumps(payload, ensure_ascii=False)\n return textwrap.dedent(\n f\"\"\"\\\n read -rsp \"Paste AIHubMix API Key: \" AIHUBMIX_API_KEY; echo\n\n curl -s {json.dumps(url)} \\\\\n {auth_arg} \\\\\n -H \"Content-Type: application/json\" \\\\\n -d {json.dumps(compact_payload)} \\\\\n | python3 -c {json.dumps(shell_response_parser_for(protocol))}\n \"\"\"\n ).strip()\n\n\ndef render_js_example(protocol: str, model: str) -> str:\n protocol = normalize_protocol(protocol)\n if protocol == \"chat\":\n return textwrap.dedent(\n f\"\"\"\\\n import {{ aihubmix }} from \"@aihubmix/ai-sdk-provider\";\n import {{ generateText }} from \"ai\";\n\n const {{ text }} = await generateText({{\n model: aihubmix({json.dumps(model)}),\n prompt: \"Say hello in one short sentence.\",\n }});\n\n console.log(text);\n \"\"\"\n ).strip()\n\n payload = example_payload(protocol, model)\n path = endpoint_path(protocol, model)\n spec = PROTOCOL_SPECS[protocol]\n header_name, header_value = auth_header(protocol)\n header_value_js = (\n \"process.env.AIHUBMIX_API_KEY\"\n if spec[\"family\"] in {\"anthropic\", \"gemini\"}\n else '`Bearer ${process.env.AIHUBMIX_API_KEY}`'\n )\n payload_js = json.dumps(payload, ensure_ascii=False, indent=2)\n payload_body_lines = [\" body: JSON.stringify(\" + payload_js.splitlines()[0]]\n payload_body_lines.extend(\" \" + line for line in payload_js.splitlines()[1:])\n payload_body_lines[-1] = payload_body_lines[-1] + \"),\"\n if spec.get(\"multipart\"):\n return textwrap.dedent(\n f\"\"\"\\\n import {{ createReadStream }} from \"node:fs\";\n\n const form = new FormData();\n form.set(\"model\", {json.dumps(model)});\n form.set(\"file\", createReadStream(process.env.AUDIO_FILE ?? \"./audio.mp3\"));\n\n const response = await fetch(\"{base_url()}{path}\", {{\n method: \"POST\",\n headers: {{\n {json.dumps(header_name)}: {header_value_js},\n }},\n body: form,\n }});\n\n if (!response.ok) {{\n throw new Error(`${{response.status}} ${{await response.text()}}`);\n }}\n\n const data = await response.json();\n console.log(data.text ?? JSON.stringify(data, null, 2));\n \"\"\"\n ).strip()\n if spec.get(\"binary_response\"):\n return \"\\n\".join(\n [\n 'import { writeFile } from \"node:fs/promises\";',\n \"\",\n f'const response = await fetch(\"{base_url()}{path}\", {{',\n ' method: \"POST\",',\n \" headers: {\",\n f\" {json.dumps(header_name)}: {header_value_js},\",\n ' \"Content-Type\": \"application/json\",',\n \" },\",\n *payload_body_lines,\n \"});\",\n \"\",\n \"if (!response.ok) {\",\n \" throw new Error(`${response.status} ${await response.text()}`);\",\n \"}\",\n \"\",\n 'await writeFile(\"aihubmix-output.mp3\", Buffer.from(await response.arrayBuffer()));',\n 'console.log(\"Saved to aihubmix-output.mp3\");',\n ]\n )\n if protocol == \"video-content\":\n return textwrap.dedent(\n f\"\"\"\\\n import {{ writeFile }} from \"node:fs/promises\";\n\n const videoId = process.env.AIHUBMIX_VIDEO_ID ?? \"video_123\";\n const response = await fetch(\"{base_url()}{path}\".replace(\"video_123\", videoId), {{\n method: \"GET\",\n headers: {{\n {json.dumps(header_name)}: {header_value_js},\n }},\n }});\n\n if (!response.ok) {{\n throw new Error(`${{response.status}} ${{await response.text()}}`);\n }}\n\n await writeFile(\"aihubmix-video.mp4\", Buffer.from(await response.arrayBuffer()));\n console.log(\"Saved to aihubmix-video.mp4\");\n \"\"\"\n ).strip()\n if spec[\"method\"] in {\"GET\", \"DELETE\"}:\n return textwrap.dedent(\n f\"\"\"\\\n const videoId = process.env.AIHUBMIX_VIDEO_ID ?? \"video_123\";\n const response = await fetch(\"{base_url()}{path}\".replace(\"video_123\", videoId), {{\n method: {json.dumps(spec[\"method\"])},\n headers: {{\n {json.dumps(header_name)}: {header_value_js},\n \"Accept\": \"application/json\",\n }},\n }});\n\n if (!response.ok) {{\n throw new Error(`${{response.status}} ${{await response.text()}}`);\n }}\n\n const data = await response.json();\n console.log(data.id ? `video id: ${{data.id}}` : JSON.stringify(data, null, 2));\n \"\"\"\n ).strip()\n if spec.get(\"needs_video_id\"):\n path_expr = f'\"{base_url()}{path}\".replace(\"video_123\", videoId)'\n return \"\\n\".join(\n [\n 'const videoId = process.env.AIHUBMIX_VIDEO_ID ?? \"video_123\";',\n f\"const response = await fetch({path_expr}, {{\",\n f' method: {json.dumps(spec[\"method\"])},',\n \" headers: {\",\n f\" {json.dumps(header_name)}: {header_value_js},\",\n ' \"Content-Type\": \"application/json\",',\n \" },\",\n *payload_body_lines,\n \"});\",\n \"\",\n \"if (!response.ok) {\",\n \" throw new Error(`${response.status} ${await response.text()}`);\",\n \"}\",\n \"\",\n \"const data = await response.json();\",\n \"console.log(data.id ? `video id: ${data.id}` : JSON.stringify(data, null, 2));\",\n ]\n )\n payload_json = json.dumps(payload, ensure_ascii=False, indent=2)\n payload_lines = [\" body: JSON.stringify(\" + payload_json.splitlines()[0]]\n payload_lines.extend(\" \" + line for line in payload_json.splitlines()[1:])\n payload_lines[-1] = payload_lines[-1] + \"),\"\n return \"\\n\".join(\n [\n f'const response = await fetch(\"{base_url()}{path}\", {{',\n f' method: {json.dumps(spec[\"method\"])},',\n \" headers: {\",\n f\" {json.dumps(header_name)}: {header_value_js},\",\n ' \"Content-Type\": \"application/json\",',\n \" },\",\n *payload_lines,\n \"});\",\n \"\",\n \"if (!response.ok) {\",\n \" throw new Error(`${response.status} ${await response.text()}`);\",\n \"}\",\n \"\",\n \"const data = await response.json();\",\n \"const text =\",\n \" data.output_text ??\",\n \" data.choices?.[0]?.message?.content ??\",\n \" data.content?.[0]?.text ??\",\n \" data.candidates?.[0]?.content?.parts?.[0]?.text ??\",\n \" data.data?.[0]?.url ??\",\n \" data.data?.[0]?.b64_json ??\",\n \" data.text ??\",\n \" (data.id ? `video id: ${data.id}` : undefined);\",\n \"\",\n \"console.log(text ?? JSON.stringify(data, null, 2));\",\n ]\n )\n\n\ndef cmd_example(args: argparse.Namespace) -> None:\n protocol = args.protocol\n model = args.model\n os_name = detect_os(args.os)\n if args.lang == \"both\":\n print(f\"## Command Line ({'Windows PowerShell' if os_name == 'windows' else 'macOS/Linux shell'})\")\n print()\n print(example_intro(\"command\", os_name))\n print()\n print(\"```\" + (\"powershell\" if os_name == \"windows\" else \"bash\"))\n print(render_cmd_example(protocol, model, os_name))\n print(\"```\")\n print()\n print(\"## JavaScript\")\n print()\n print(example_intro(\"js\"))\n print()\n print_env_setup(\"js\", os_name)\n print(\"```js\")\n print(render_js_example(protocol, model))\n print(\"```\")\n return\n\n url = base_url() + endpoint_path(protocol, model)\n payload = example_payload(protocol, model)\n header_name, header_value = auth_header(protocol)\n body = json.dumps(payload, ensure_ascii=False, indent=2)\n\n if args.lang == \"json\":\n print(body)\n return\n\n if args.lang == \"curl\":\n print(render_cmd_example(protocol, model, os_name))\n return\n\n if args.lang == \"python\":\n if normalize_protocol(protocol) == \"chat\":\n print(\n textwrap.dedent(\n f\"\"\"\\\n import os\n from openai import OpenAI\n\n client = OpenAI(\n api_key=os.environ[\"AIHUBMIX_API_KEY\"],\n base_url=\"{base_url()}/v1\",\n )\n\n response = client.chat.completions.create(\n model={model!r},\n messages=[{{\"role\": \"user\", \"content\": \"Say hello in one short sentence.\"}}],\n )\n print(response.choices[0].message.content)\n \"\"\"\n ).strip()\n )\n else:\n print(\"# Use the command-line or JavaScript example for this endpoint shape.\")\n return\n\n if args.lang == \"js\":\n print(render_js_example(protocol, model))\n return\n\n raise ValueError(args.lang)\n\n\ndef parse_error_body(body_text: str | None, body_file: str | None) -> dict[str, Any] | None:\n if body_file:\n body_text = Path(body_file).read_text(encoding=\"utf-8\")\n if not body_text:\n return None\n try:\n data = json.loads(body_text)\n return data if isinstance(data, dict) else {\"raw\": data}\n except json.JSONDecodeError:\n return {\"raw\": body_text}\n\n\ndef cmd_troubleshoot(args: argparse.Namespace) -> None:\n body = parse_error_body(args.body, args.body_file)\n error = body.get(\"error\") if isinstance(body, dict) and isinstance(body.get(\"error\"), dict) else {}\n message = error.get(\"message\") or (body or {}).get(\"message\") if isinstance(body, dict) else None\n kind = error.get(\"type\")\n param = error.get(\"param\")\n code = error.get(\"code\")\n\n print(\"Observed\")\n print_table(\n [\"field\", \"value\"],\n [\n [\"status\", str(args.status or \"-\")],\n [\"endpoint\", args.endpoint or \"-\"],\n [\"model\", args.model or \"-\"],\n [\"error.type\", str(kind or \"-\")],\n [\"error.param\", str(param or \"-\")],\n [\"error.code\", str(code or \"-\")],\n [\"error.message\", str(message or \"-\")],\n [\"request_id\", args.request_id or \"check X-Request-Id response header\"],\n ],\n )\n\n if args.contract:\n contract = load_error_contract()\n status_key = \"5XX\" if args.status and args.status >= 500 else str(args.status or \"\")\n status_contract = contract[\"statuses\"].get(status_key, {})\n is_gateway_shape = isinstance(error, dict) and bool(message)\n print(\"\\nContract Match\")\n print_table(\n [\"field\", \"value\"],\n [\n [\"contract_source\", display_error_contract_source()],\n [\"schema\", contract[\"schema_name\"]],\n [\"status_contract\", status_contract.get(\"description\", \"not declared\")],\n [\"body_shape\", \"GatewayError-like\" if is_gateway_shape else \"not enough evidence\"],\n [\"request_id_rule\", contract[\"request_id\"]],\n [\"error_type_rule\", contract[\"envelope\"][\"fields\"][\"error.type\"]],\n ],\n )\n\n print(\"\\nSuggested checks\")\n suggestions = []\n status = args.status\n if status in (401, 403):\n suggestions.extend(\n [\n \"Confirm AIHUBMIX_API_KEY is present and sent with the right header for the endpoint.\",\n \"Use Authorization: Bearer for OpenAI-shaped endpoints, x-api-key for /v1/messages, and x-goog-api-key or ?key= for Gemini-shaped endpoints.\",\n ]\n )\n elif status == 404:\n suggestions.extend(\n [\n \"Confirm the endpoint path matches the protocol contract.\",\n \"Confirm the model exists in /api/v1/models and the model ID is copied exactly.\",\n ]\n )\n elif status == 400:\n suggestions.extend(\n [\n \"Validate the request body against aihubmix-openapi openapi.json or contract.json.\",\n \"Check whether the model supports the requested endpoint, modality, and feature fields.\",\n ]\n )\n elif status == 429:\n suggestions.append(\"Check rate limits, quota, and retry policy before re-sending the same request.\")\n elif status and status >= 500:\n suggestions.extend(\n [\n \"Treat this as gateway or upstream failure; preserve status, error body, and X-Request-Id.\",\n \"Retry only when the operation is idempotent or the client can tolerate duplicate work.\",\n ]\n )\n else:\n suggestions.append(\"Start with status, error body, endpoint, model ID, and X-Request-Id.\")\n\n if args.model:\n suggestions.append(\"Run `models get ` to inspect context_length, max_output, endpoints, and features.\")\n if args.endpoint:\n suggestions.append(\"Run `protocols` to inspect the endpoint security scheme and requestBody contract.\")\n for index, item in enumerate(suggestions, start=1):\n print(f\"{index}. {item}\")\n\n\n# --- Preflight (doctor) + image-input candidates --------------------------\n\n\ndef _openai_base(explicit: str | None = None) -> str:\n \"\"\"OpenAI-compatible base URL, tolerant of a trailing /v1 already present.\"\"\"\n raw = (explicit or os.environ.get(\"AIHUBMIX_BASE_URL\") or DEFAULT_SERVER_URL).rstrip(\"/\")\n return raw if raw.endswith(\"/v1\") else raw + \"/v1\"\n\n\ndef _http_request(\n url: str,\n method: str = \"GET\",\n headers: dict[str, str] | None = None,\n body: dict[str, Any] | None = None,\n timeout: float = 30,\n) -> tuple[int | None, str]:\n \"\"\"Minimal JSON HTTP call. Returns (status, text); status is None on network error.\"\"\"\n data = json.dumps(body).encode(\"utf-8\") if body is not None else None\n request = urllib.request.Request(url, data=data, method=method, headers=headers or {})\n try:\n with urllib.request.urlopen(request, timeout=timeout) as response:\n status = getattr(response, \"status\", None) or response.getcode()\n return status, response.read().decode(\"utf-8\", errors=\"replace\")\n except urllib.error.HTTPError as exc:\n return exc.code, exc.read().decode(\"utf-8\", errors=\"replace\")\n except urllib.error.URLError as exc:\n return None, f\"URLError: {exc.reason}\"\n except (TimeoutError, OSError) as exc:\n return None, f\"network error: {exc}\"\n\n\ndef _mask_secret(secret: str) -> str:\n secret = (secret or \"\").strip()\n if not secret:\n return \"(none)\"\n if len(secret) <= 12:\n return secret[:2] + \"…\" + secret[-2:]\n return secret[:5] + \"…\" + secret[-4:]\n\n\ndef _redact(text: str, *secrets: str) -> str:\n \"\"\"Scrub keys from upstream text (AIHubMix echoes the key in invalid-key errors).\"\"\"\n out = text or \"\"\n for secret in secrets:\n secret = (secret or \"\").strip()\n if len(secret) >= 6:\n out = out.replace(secret, \"[REDACTED]\")\n if secret.lower().startswith(\"sk-\"):\n out = out.replace(secret[3:], \"[REDACTED]\")\n return re.sub(r\"sk-[A-Za-z0-9]{6,}\", \"[REDACTED]\", out)\n\n\ndef _extract_chat_reply(body: str) -> str | None:\n try:\n data = json.loads(body)\n content = data[\"choices\"][0][\"message\"][\"content\"]\n except (ValueError, KeyError, IndexError, TypeError):\n return None\n if isinstance(content, list):\n return \" \".join(part.get(\"text\", \"\") for part in content if isinstance(part, dict)).strip()\n return str(content).strip()\n\n\ndef _looks_like_auth_error(body: str) -> bool:\n low = (body or \"\").lower()\n return any(\n token in low\n for token in (\n \"invalid key\", \"invalid api key\", \"incorrect api key\", \"unauthorized\",\n \"authentication\", \"no permission\", \"无效\", \"鉴权\", \"未授权\", \"令牌\",\n )\n )\n\n\ndef _looks_like_balance_error(body: str) -> bool:\n low = (body or \"\").lower()\n return any(\n token in low\n for token in (\n \"insufficient balance\", \"insufficient_quota\", \"insufficient funds\", \"no balance\",\n \"balance is not enough\", \"arrearage\", \"余额不足\", \"余额已用\", \"欠费\", \"额度不足\",\n )\n )\n\n\ndef _solid_png_data_url(rgb: tuple[int, int, int] = (20, 90, 210), size: int = 64) -> str:\n \"\"\"Build a tiny solid-color PNG data URL (stdlib only) for probing image input.\"\"\"\n import base64\n import struct\n import zlib\n\n def chunk(tag: bytes, payload: bytes) -> bytes:\n return (\n struct.pack(\">I\", len(payload))\n + tag\n + payload\n + struct.pack(\">I\", zlib.crc32(tag + payload) & 0xFFFFFFFF)\n )\n\n signature = b\"\\x89PNG\\r\\n\\x1a\\n\"\n ihdr = struct.pack(\">IIBBBBB\", size, size, 8, 2, 0, 0, 0) # 8-bit RGB\n raw = b\"\".join(b\"\\x00\" + bytes(rgb) * size for _ in range(size))\n png = signature + chunk(b\"IHDR\", ihdr) + chunk(b\"IDAT\", zlib.compress(raw)) + chunk(b\"IEND\", b\"\")\n return \"data:image/png;base64,\" + base64.b64encode(png).decode(\"ascii\")\n\n\ndef cmd_doctor(args: argparse.Namespace) -> int:\n key = (args.key or os.environ.get(\"AIHUBMIX_API_KEY\") or \"\").strip()\n base = _openai_base(args.base_url)\n timeout = args.timeout\n\n print(_t(\"AIHubMix preflight (doctor)\", \"AIHubMix 预检 (doctor)\"))\n print(f\"base_url: {base}\")\n print(f\"key: {_mask_secret(key)}\\n\")\n\n if not key:\n print(_t(\"[✗] KEY missing — set AIHUBMIX_API_KEY or pass --key \",\n \"[✗] KEY 未提供 — 设置环境变量 AIHUBMIX_API_KEY 或传 --key \"))\n print(_t(\"\\nResult: FAIL. No API key available.\", \"\\n结论: 失败。没有可用的 API key。\"))\n return 1\n print(_t(\"[✓] KEY provided\", \"[✓] KEY 已提供\"))\n\n headers = {\n \"Authorization\": f\"Bearer {key}\",\n \"Content-Type\": \"application/json\",\n \"Accept\": \"application/json\",\n \"User-Agent\": \"aihubmixApi-skill/0.1\",\n }\n\n # The public /v1/models list does NOT require auth, so it cannot validate a\n # key. Only a real chat call can — use a tiny one as the auth + text probe.\n probe_model = args.model or \"gpt-4o-mini\"\n payload = {\"model\": probe_model, \"messages\": [{\"role\": \"user\", \"content\": \"ping\"}], \"max_tokens\": 8, \"temperature\": 0}\n status, body = _http_request(f\"{base}/chat/completions\", method=\"POST\", headers=headers, body=payload, timeout=timeout)\n reply = _extract_chat_reply(body) if status == 200 else None\n if reply is not None:\n note = \"\" if args.model else _t(\" (probe model gpt-4o-mini)\", \"(探测模型 gpt-4o-mini)\")\n print(_t(f\"[✓] AUTH key valid, {probe_model} call OK{note} → {reply[:60]!r}\",\n f\"[✓] AUTH key 有效,{probe_model} 调用成功{note} → {reply[:60]!r}\"))\n elif status == 402 or _looks_like_balance_error(body):\n print(_t(f\"[✗] BILLING key valid but insufficient balance/quota — HTTP {status}: {_redact(body, key)[:300]}\",\n f\"[✗] 余额 key 有效但余额/额度不足 — HTTP {status}: {_redact(body, key)[:300]}\"))\n print(_t(\"\\nResult: FAIL (billing). The key works; the account needs a top-up.\",\n \"\\n结论: 失败(计费)。key 本身可用,但账户需要充值。\"))\n return 1\n elif status in (401, 403) or _looks_like_auth_error(body):\n print(_t(f\"[✗] AUTH invalid key / no permission — HTTP {status}: {_redact(body, key)[:300]}\",\n f\"[✗] AUTH key 无效/无权限 — HTTP {status}: {_redact(body, key)[:300]}\"))\n print(_t(\"\\nResult: FAIL. Check the key is correct, enabled, and funded.\",\n \"\\n结论: 失败。请检查 key 是否正确、已启用、有余额。\"))\n return 1\n else:\n info = f\"HTTP {status}\" if status else _t(\"network unreachable\", \"网络不可达\")\n print(_t(f\"[⚠] AUTH unconfirmed — probing with {probe_model} returned {info}: {_redact(body, key)[:200]}\",\n f\"[⚠] AUTH 未能确认 — 用 {probe_model} 探测返回 {info}: {_redact(body, key)[:200]}\"))\n print(_t(\" Usually the model is not available to your key (key likely fine); retry with --model .\",\n \" 多半是该模型对你的 key 不可用,而非 key 失效;用 --model <你有权限的模型> 重测。\"))\n print(_t(\"\\nResult: NOT PASSED (could not confirm key). Retry with --model .\",\n \"\\n结论: 未通过(无法确认 key)。建议用 --model 指定可用模型重测。\"))\n return 1\n\n overall_ok = True\n\n if args.image:\n if not args.model:\n print(_t(\"[–] IMAGE skipped (image probe needs --model ; you/the LLM pick which vision model)\",\n \"[–] IMAGE 跳过(图片实测需要 --model ;该测哪个视觉模型由你/LLM 决定)\"))\n else:\n payload = {\n \"model\": args.model,\n \"max_tokens\": 16,\n \"temperature\": 0,\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": [\n {\"type\": \"text\", \"text\": \"What is the dominant color of this image? Answer with one word.\"},\n {\"type\": \"image_url\", \"image_url\": {\"url\": _solid_png_data_url()}},\n ],\n }\n ],\n }\n status, body = _http_request(f\"{base}/chat/completions\", method=\"POST\", headers=headers, body=payload, timeout=timeout)\n reply = _extract_chat_reply(body) if status == 200 else None\n if reply is not None:\n print(_t(f\"[✓] IMAGE {args.model} accepts image input → {reply[:60]!r}\",\n f\"[✓] IMAGE {args.model} 接受图片输入 → {reply[:60]!r}\"))\n else:\n overall_ok = False\n print(_t(f\"[✗] IMAGE {args.model} no image support or call failed — HTTP {status}: {_redact(body, key)[:300]}\",\n f\"[✗] IMAGE {args.model} 不支持图片或调用失败 — HTTP {status}: {_redact(body, key)[:300]}\"))\n\n print()\n if overall_ok:\n tail = (\n _t(\"Tested capabilities all OK.\", \"已实测的能力均正常。\")\n if args.model\n else _t(\"Key valid; add --model (and --image) to test a specific model.\",\n \"key 有效;加 --model (可再加 --image)可实测具体模型。\")\n )\n print(_t(f\"Result: PASS. {tail}\", f\"结论: 通过。{tail}\"))\n return 0\n print(_t(\"Result: some checks did not pass — see the [✗] items above.\",\n \"结论: 部分检查未通过,见上面的 [✗] 项。\"))\n return 1\n\n\n# Categories that cannot accept chat input, dropped from input-modality\n# candidate ranges (e.g. vision) even when the noisy modality field tags them.\n_NON_CHAT_RE = re.compile(\n r\"(embed|rerank|\\bbge\\b|\\bbce\\b|\\bgte\\b|\\bm3e\\b|jina|moderation|\"\n r\"whisper|\\btts\\b|text-to-speech|speech|transcrib|\"\n r\"flux|stable-diffusion|\\bsdxl\\b|dall-?e|midjourney|imagen|gpt-image|qwen-image|\"\n r\"seedream|seededit|kolors|cogview|ideogram|recraft|hunyuan-image|\"\n r\"\\bveo\\b|sora|kling|runway|video|hailuo|seedance|vidu)\",\n re.IGNORECASE,\n)\n\n\ndef _is_non_chat(model: dict[str, Any]) -> bool:\n \"\"\"Drop models that cannot accept chat input (embeddings, generation, audio, video).\"\"\"\n if suggested_protocol(model) in {\n \"embeddings\", \"images\", \"videos\", \"moderations\",\n \"audio-speech\", \"audio-transcriptions\", \"audio-translations\",\n }:\n return True\n return bool(_NON_CHAT_RE.search(f\"{model_id(model)} {model_label(model)}\"))\n\n\n# Capability discovery is deliberately broad + heuristic. Each spec scores a\n# model by up to three signals and the caller/LLM picks from the range:\n# protocol: suggested_protocol(model) lands in the capability's endpoint set\n# modality: the model's metadata text contains a capability keyword\n# family: the model id/name matches a known family regex\n# protocol/family are treated as reliable; modality-only is noisy. The tool\n# never picks a default and never asserts a hard capability fact.\nCAPABILITY_SPECS: dict[str, dict[str, Any]] = {\n \"vision\": {\n \"label\": \"图片输入 / 看图(chat 模型接受图片)\",\n \"aliases\": [\"image-input\", \"multimodal\", \"mm\", \"vlm\", \"看图\", \"图片输入\", \"多模态\"],\n \"protocols\": None,\n \"modality_tokens\": [\"image\", \"vision\", \"multimodal\", \"visual\"],\n \"require_chat\": True,\n \"family\": (\n r\"gpt-4o|gpt-4\\.1|gpt-4-vision|gpt-4-turbo|gpt-5|chatgpt-4o|o1|o3|o4-mini|\"\n r\"gemini|claude-3|claude-4|claude-sonnet|claude-opus|claude-haiku|\"\n r\"qwen[\\w.-]*vl|qwen-vl|glm-4v|glm-[\\d.]+v|cogvlm|cogagent|yi-vision|yi-vl|\"\n r\"step-1v|step-1o|step-3|internvl|llava|pixtral|moondream|\"\n r\"llama[\\w.-]*vision|grok[\\w.-]*vision|grok-2-vision|grok-4|\"\n r\"doubao[\\w.-]*vision|ernie[\\w.-]*vl|minicpm-v|nova-lite|nova-pro|\"\n r\"phi-3[\\w.-]*vision|phi-4[\\w.-]*multimodal|kimi[\\w.-]*vl|kimi-latest|moonshot[\\w.-]*vl|\"\n r\"mistral-small-3|molmo|deepseek-vl\"\n ),\n },\n \"image-gen\": {\n \"label\": \"图像生成 / 文生图(text-to-image)\",\n \"aliases\": [\"t2i\", \"image-generation\", \"imagegen\", \"生图\", \"文生图\", \"画图\"],\n \"protocols\": {\"images\"},\n \"modality_tokens\": [\"image_generation\", \"text-to-image\"],\n \"require_chat\": False,\n \"family\": (\n r\"flux|kontext|stable-diffusion|\\bsd3\\b|\\bsdxl\\b|dall-?e|midjourney|\\bmj\\b|\"\n r\"imagen|gpt-image|qwen-image|hunyuan-image|hidream|seedream|seededit|kolors|\"\n r\"cogview|ideogram|recraft|wanx|gemini[\\w.-]*image|nano-banana|grok[\\w.-]*image|playground-v\"\n ),\n },\n \"video\": {\n \"label\": \"视频生成(text/image-to-video)\",\n \"aliases\": [\"t2v\", \"video-generation\", \"生视频\", \"文生视频\", \"视频\"],\n \"protocols\": {\"videos\"},\n \"modality_tokens\": [\"video\"],\n \"require_chat\": False,\n \"family\": (\n r\"veo|sora|kling|runway|gen-?3|hailuo|minimax[\\w.-]*video|seedance|vidu|\"\n r\"wan[\\w.-]*|cogvideo|pika|luma|dream-machine|ray-?2|pixverse|mochi|\\bltx\\b\"\n ),\n },\n \"audio-tts\": {\n \"label\": \"语音合成(文字转语音 TTS)\",\n \"aliases\": [\"tts\", \"text-to-speech\", \"speech\", \"语音合成\"],\n \"protocols\": {\"audio-speech\"},\n \"modality_tokens\": [\"speech\", \"text-to-speech\", \"tts\"],\n \"require_chat\": False,\n \"family\": (\n r\"\\btts\\b|text-to-speech|speech-0|cosyvoice|fish[\\w.-]*audio|elevenlabs|\"\n r\"gpt-4o[\\w.-]*tts|qwen[\\w.-]*tts|doubao[\\w.-]*tts|minimax[\\w.-]*speech|index-tts\"\n ),\n },\n \"audio-stt\": {\n \"label\": \"语音识别 / 转写(STT/ASR)\",\n \"aliases\": [\"stt\", \"asr\", \"transcription\", \"whisper\", \"语音识别\", \"转写\"],\n \"protocols\": {\"audio-transcriptions\", \"audio-translations\"},\n \"modality_tokens\": [\"transcription\", \"translation\"],\n \"require_chat\": False,\n \"family\": (\n r\"whisper|transcrib|gpt-4o[\\w.-]*transcribe|qwen[\\w.-]*audio|sensevoice|paraformer|fun-?asr\"\n ),\n },\n \"embedding\": {\n \"label\": \"向量嵌入(embedding)\",\n \"aliases\": [\"embed\", \"embeddings\", \"向量\", \"嵌入\"],\n \"protocols\": {\"embeddings\"},\n \"modality_tokens\": [\"embedding\"],\n \"require_chat\": False,\n \"exclude\": r\"rerank\",\n \"family\": (\n r\"embed|text-embedding|\\bbge\\b|bge-[\\w.-]+|\\bbce\\b|\\bgte\\b|\\bm3e\\b|jina[\\w.-]*embed|\"\n r\"nomic[\\w.-]*embed|qwen[\\w.-]*embedding|conan-embedding|doubao[\\w.-]*embedding|\\bgme\\b\"\n ),\n },\n \"rerank\": {\n \"label\": \"重排(rerank)\",\n \"aliases\": [\"reranker\", \"重排\"],\n \"protocols\": None,\n \"modality_tokens\": [\"rerank\"],\n \"require_chat\": False,\n \"family\": r\"rerank|reranker|bge[\\w.-]*rerank|bce[\\w.-]*rerank|jina[\\w.-]*rerank|qwen3?-?rerank\",\n },\n}\n\nfor _spec in CAPABILITY_SPECS.values():\n _spec[\"_family_re\"] = re.compile(_spec[\"family\"], re.IGNORECASE)\n _spec[\"_exclude_re\"] = re.compile(_spec[\"exclude\"], re.IGNORECASE) if _spec.get(\"exclude\") else None\n\n\ndef resolve_capability(name: str | None) -> str:\n key = (name or \"vision\").strip().lower()\n if key in CAPABILITY_SPECS:\n return key\n for cap, spec in CAPABILITY_SPECS.items():\n if key in [alias.lower() for alias in spec[\"aliases\"]]:\n return cap\n raise SystemExit(f\"未知能力: {name}。可选: {', '.join(CAPABILITY_SPECS)}\")\n\n\ndef _capability_signals(model: dict[str, Any], spec: dict[str, Any]) -> list[str]:\n signals: list[str] = []\n if spec[\"protocols\"] and suggested_protocol(model) in spec[\"protocols\"]:\n signals.append(\"protocol\")\n cap_text = \" \".join(\n compact(model.get(key))\n for key in (\"features\", \"capabilities\", \"input_modalities\", \"modality\", \"modalities\", \"types\", \"type\", \"endpoints\")\n if model.get(key) is not None\n ).lower()\n if any(token in cap_text for token in spec[\"modality_tokens\"]):\n signals.append(\"modality\")\n if spec[\"_family_re\"].search(f\"{model_id(model)} {model_label(model)}\"):\n signals.append(\"family\")\n return signals\n\n\n# --- Companion aihubmix CLI helpers (account data; opt-in only) -------------\n\n\ndef _find_aihubmix() -> str | None:\n \"\"\"Locate the companion aihubmix CLI on PATH or known install locations.\"\"\"\n found = shutil.which(CLI_BIN)\n if found:\n return found\n candidates: list[Path] = []\n local = os.environ.get(\"LOCALAPPDATA\")\n if local:\n candidates.append(Path(local) / \"aihubmix\" / \"bin\" / \"aihubmix.exe\")\n home = Path.home()\n candidates += [home / \".local\" / \"bin\" / CLI_BIN, Path(\"/usr/local/bin\") / CLI_BIN]\n for candidate in candidates:\n try:\n if candidate.is_file():\n return str(candidate)\n except OSError:\n continue\n return None\n\n\ndef _install_aihubmix() -> bool:\n \"\"\"Run the official per-OS installer (explicit / opt-in only). Returns success.\"\"\"\n if platform.system().lower().startswith(\"win\"):\n cmd = [\"powershell\", \"-NoProfile\", \"-Command\", f\"irm {CLI_INSTALL_PS1} | iex\"]\n else:\n cmd = [\"sh\", \"-c\", f\"curl -fsSL {CLI_INSTALL_SH} | sh\"]\n try:\n return subprocess.run(cmd, timeout=300).returncode == 0\n except (OSError, subprocess.SubprocessError):\n return False\n\n\ndef cmd_install_cli(args: argparse.Namespace) -> int:\n existing = _find_aihubmix()\n if existing and not args.force:\n print(_t(f\"aihubmix CLI already installed: {existing}\", f\"aihubmix CLI 已安装:{existing}\"))\n print(_t(f\"Project: {CLI_REPO_URL}\", f\"项目主页:{CLI_REPO_URL}\"))\n return 0\n print(_t(f\"Installing the aihubmix CLI (project: {CLI_REPO_URL}) ...\",\n f\"正在安装 aihubmix CLI(项目主页:{CLI_REPO_URL})...\"))\n if _install_aihubmix() and _find_aihubmix():\n print(_t(\"Done. Next: run `aihubmix login` to authenticate.\",\n \"完成。下一步:运行 `aihubmix login` 登录。\"))\n return 0\n print(_t(f\"Install failed. Install manually — see {CLI_REPO_URL}\",\n f\"安装失败。请手动安装,见 {CLI_REPO_URL}\"))\n return 1\n\n\ndef _my_callable_models(auto_install: bool = False) -> set[str]:\n \"\"\"Lowercased ids the user's token can actually call (`aihubmix models list -j`).\"\"\"\n exe = _find_aihubmix()\n if not exe and auto_install:\n print(_t(\"aihubmix CLI not found; installing (--auto-install) ...\",\n \"未找到 aihubmix CLI,正在自动安装(--auto-install)...\"), file=sys.stderr)\n if _install_aihubmix():\n exe = _find_aihubmix()\n if not exe:\n raise SystemExit(_t(\n f\"--mine needs the aihubmix CLI. Run `install-cli` (or add --auto-install). Project: {CLI_REPO_URL}\",\n f\"--mine 需要 aihubmix CLI。先运行 `install-cli`(或加 --auto-install)。项目主页:{CLI_REPO_URL}\"))\n try:\n result = subprocess.run([exe, \"models\", \"list\", \"-j\"], capture_output=True, text=True, timeout=60)\n except (OSError, subprocess.SubprocessError) as exc:\n raise SystemExit(f\"`aihubmix models list` failed: {exc}\")\n if result.returncode != 0:\n detail = (result.stderr or result.stdout or \"\").strip()\n if \"未登录\" in detail or \"login\" in detail.lower() or \"token\" in detail.lower():\n raise SystemExit(_t(\n f\"Not logged in. Run `aihubmix login` first, or drop --mine. Project: {CLI_REPO_URL}\",\n f\"尚未登录。请先 `aihubmix login`,或去掉 --mine。项目主页:{CLI_REPO_URL}\"))\n raise SystemExit(f\"`aihubmix models list` error: {detail[:200]}\")\n try:\n data = json.loads(result.stdout)\n except ValueError:\n raise SystemExit(\"could not parse `aihubmix models list -j` output\")\n rows = data.get(\"data\") if isinstance(data, dict) else data\n return {\n str(row.get(\"model\")).strip().lower()\n for row in (rows or [])\n if isinstance(row, dict) and row.get(\"model\")\n }\n\n\ndef cmd_candidates(args: argparse.Namespace) -> None:\n cap = resolve_capability(getattr(args, \"capability\", None))\n spec = CAPABILITY_SPECS[cap]\n models = extract_models(load_json_source(args.source))\n\n mine = None\n if getattr(args, \"mine\", False):\n mine = _my_callable_models(auto_install=getattr(args, \"auto_install\", False))\n\n picked: list[tuple[dict[str, Any], bool, str]] = []\n for item in models:\n if spec[\"require_chat\"] and _is_non_chat(item):\n continue\n exclude_re = spec.get(\"_exclude_re\")\n if exclude_re and exclude_re.search(f\"{model_id(item)} {model_label(item)}\"):\n continue\n if mine is not None and model_id(item).strip().lower() not in mine:\n continue # keep only models the user's token can actually call\n signals = _capability_signals(item, spec)\n if not signals:\n continue\n strong = (\"protocol\" in signals) or (\"family\" in signals)\n picked.append((item, strong, \"+\".join(signals)))\n # Reliable protocol/family matches first, then noisier modality-only ones.\n picked.sort(key=lambda row: (0 if row[1] else 1, model_id(row[0]).lower()))\n selected = [(item, signal) for item, _strong, signal in picked[: args.limit]]\n\n if args.json:\n print(json.dumps(\n [\n {\n \"capability\": cap,\n \"mine\": mine is not None,\n \"model_id\": model_id(item),\n \"name\": model_label(item),\n \"signal\": signal,\n \"features\": features(item),\n \"context\": context_display(item),\n }\n for item, signal in selected\n ],\n ensure_ascii=False,\n indent=2,\n ))\n return\n\n mine_en = \" callable by your token\" if mine is not None else \"\"\n mine_zh = \"(已按你的 token 可调过滤)\" if mine is not None else \"\"\n print(_t(\n f\"AIHubMix candidate models for capability '{cap}'{mine_en} (broad heuristic: protocol / family / modality; no default picked)\",\n f\"AIHubMix【{spec['label']}】候选模型{mine_zh}(宽口径启发式:协议 / 家族名 / modality 字段;不替你挑默认)\",\n ))\n if mine is not None and not selected:\n print(_t(\n f\"\\nNone of the '{cap}' candidates are callable by your token. Try without --mine, or check `aihubmix models list`.\",\n f\"\\n你的 token 在【{cap}】下没有可调候选。可去掉 --mine,或查 `aihubmix models list`。\",\n ))\n return\n print(_t(\n \"Note: a candidate is not a guarantee; pick per task (you/the LLM), then confirm with `doctor`.\",\n \"注意:候选不等于确定可用;请由调用方/LLM 按任务从中挑选,再用 `doctor` 实测确认。\",\n ))\n print()\n rows = [\n [model_id(item), model_label(item), signal, features(item) or \"-\", context_display(item)]\n for item, signal in selected\n ]\n print_table([\"model_id\", \"name\", \"signal\", \"features/modalities\", \"context\"], rows)\n mine_foot_en = \" | filtered to your token\" if mine is not None else \"\"\n mine_foot_zh = \" | 已按你的 token 过滤\" if mine is not None else \"\"\n print(_t(\n f\"\\ncapability: {cap} | {len(selected)} candidates{mine_foot_en} | source: {display_models_source()}\",\n f\"\\n能力: {cap} | 共 {len(selected)} 个候选{mine_foot_zh} | source: {display_models_source()}\",\n ))\n print(_t(\n \"signal: protocol=endpoint type; family=known family name (both reliable); modality=metadata keyword only (noisy, verify).\",\n \"signal: protocol=接口类型匹配;family=按已知家族名匹配(均较可靠);modality=仅元数据出现关键词(噪声大,需实测)。\",\n ))\n\n\ndef build_parser() -> argparse.ArgumentParser:\n parser = argparse.ArgumentParser(description=\"Query AIHubMix models and API contract helpers.\")\n parser.add_argument(\n \"--lang\", choices=[\"en\", \"zh\"],\n help=\"Output language for status lines/labels (default: env AIHUBMIX_LANG or en).\",\n )\n subparsers = parser.add_subparsers(dest=\"command\", required=True)\n\n list_parser = subparsers.add_parser(\"list\", help=\"List models from the models API.\")\n list_parser.add_argument(\"--source\", help=\"Models JSON URL, local JSON file, or '-' for stdin.\")\n list_parser.add_argument(\"--query\", help=\"Filter models by substring.\")\n list_parser.add_argument(\"--limit\", type=int, default=50)\n list_parser.add_argument(\"--json\", action=\"store_true\")\n list_parser.set_defaults(func=cmd_list)\n\n get_parser = subparsers.add_parser(\"get\", help=\"Show one model by ID or substring.\")\n get_parser.add_argument(\"model\")\n get_parser.add_argument(\"--source\")\n get_parser.add_argument(\"--json\", action=\"store_true\")\n get_parser.set_defaults(func=cmd_get)\n\n compare_parser = subparsers.add_parser(\"compare\", help=\"Compare pricing and context for models.\")\n compare_parser.add_argument(\"models\", nargs=\"+\")\n compare_parser.add_argument(\"--source\")\n compare_parser.add_argument(\"--json\", action=\"store_true\")\n compare_parser.set_defaults(func=cmd_compare)\n\n latest_parser = subparsers.add_parser(\"latest-report\", help=\"Report the first N models with pricing, context, and an example.\")\n latest_parser.add_argument(\"--source\", help=\"Models JSON URL, local JSON file, or '-' for stdin.\")\n latest_parser.add_argument(\"--limit\", type=int, default=5)\n latest_parser.add_argument(\"--os\", choices=[\"auto\", \"windows\", \"mac\", \"linux\"], default=\"auto\")\n latest_parser.set_defaults(func=cmd_latest_report)\n\n report_parser = subparsers.add_parser(\"report\", help=\"Report models matching a query with pricing, context, endpoint, and examples.\")\n report_parser.add_argument(\"query\")\n report_parser.add_argument(\"--source\", help=\"Models JSON URL, local JSON file, or '-' for stdin.\")\n report_parser.add_argument(\"--limit\", type=int, default=20)\n report_parser.add_argument(\"--os\", choices=[\"auto\", \"windows\", \"mac\", \"linux\"], default=\"auto\")\n report_parser.set_defaults(func=cmd_report)\n\n protocols_parser = subparsers.add_parser(\"protocols\", help=\"Summarize live aihubmix OpenAPI operations (remote).\")\n protocols_parser.add_argument(\"--json\", action=\"store_true\")\n protocols_parser.set_defaults(func=cmd_protocols)\n\n error_contract_parser = subparsers.add_parser(\"error-contract\", help=\"Summarize the GatewayError contract from the live OpenAPI spec (remote).\")\n error_contract_parser.add_argument(\"--json\", action=\"store_true\")\n error_contract_parser.set_defaults(func=cmd_error_contract)\n\n sdk_parser = subparsers.add_parser(\"sdk-check\", help=\"Check @aihubmix/ai-sdk-provider version without installing or updating it.\")\n sdk_parser.add_argument(\"--project\", help=\"Project directory to search for node_modules.\")\n sdk_parser.add_argument(\"--version\", help=\"Target SDK version to compare against.\")\n sdk_parser.add_argument(\"--offline\", action=\"store_true\", help=\"Skip npm registry latest-version check.\")\n sdk_parser.set_defaults(func=cmd_sdk_check)\n\n sdk_info_parser = subparsers.add_parser(\"sdk-info\", help=\"Summarize @aihubmix/ai-sdk-provider capabilities and auth/routing defaults.\")\n sdk_info_parser.add_argument(\"--project\", help=\"Project directory to search for installed node_modules.\")\n sdk_info_parser.add_argument(\"--package-dir\", help=\"Path to an unpacked @aihubmix/ai-sdk-provider package directory.\")\n sdk_info_parser.add_argument(\"--version\", default=SDK_RECOMMENDED_VERSION, help=\"SDK version to inspect from npm when no package is installed.\")\n sdk_info_parser.add_argument(\n \"--source\",\n choices=[\"auto\", \"installed\", \"npm\"],\n default=\"auto\",\n help=\"SDK metadata source. npm reads the package tarball from the npm registry.\",\n )\n sdk_info_parser.add_argument(\"--json\", action=\"store_true\")\n sdk_info_parser.set_defaults(func=cmd_sdk_info)\n\n example_parser = subparsers.add_parser(\"example\", help=\"Generate minimal API call examples.\")\n example_parser.add_argument(\n \"protocol\",\n choices=sorted([*PROTOCOL_SPECS.keys(), *PROTOCOL_ALIASES.keys()]),\n help=\"Protocol or endpoint shape to generate.\",\n )\n example_parser.add_argument(\"--model\", required=True)\n example_parser.add_argument(\"--lang\", choices=[\"both\", \"curl\", \"python\", \"js\", \"json\"], default=\"both\")\n example_parser.add_argument(\"--os\", choices=[\"auto\", \"windows\", \"mac\", \"linux\"], default=\"auto\")\n example_parser.set_defaults(func=cmd_example)\n\n troubleshoot_parser = subparsers.add_parser(\"troubleshoot\", help=\"Explain common API failure checks.\")\n troubleshoot_parser.add_argument(\"--status\", type=int)\n troubleshoot_parser.add_argument(\"--body\", help=\"JSON error body string.\")\n troubleshoot_parser.add_argument(\"--body-file\", help=\"Path to a JSON error body.\")\n troubleshoot_parser.add_argument(\"--endpoint\")\n troubleshoot_parser.add_argument(\"--model\")\n troubleshoot_parser.add_argument(\"--request-id\")\n troubleshoot_parser.add_argument(\"--contract\", action=\"store_true\", help=\"Also fetch the live GatewayError contract (remote) and show a match.\")\n troubleshoot_parser.set_defaults(func=cmd_troubleshoot)\n\n doctor_parser = subparsers.add_parser(\n \"doctor\",\n help=\"Preflight an AIHubMix key: verify auth, then optionally smoke-test text and image input on a chosen model.\",\n )\n doctor_parser.add_argument(\"--key\", help=\"API key. Defaults to env AIHUBMIX_API_KEY. Never printed in full.\")\n doctor_parser.add_argument(\"--base-url\", help=\"OpenAI-compatible base. Defaults to env AIHUBMIX_BASE_URL or https://aihubmix.com/v1.\")\n doctor_parser.add_argument(\"--model\", help=\"Model id to smoke-test (text; also image with --image).\")\n doctor_parser.add_argument(\"--image\", action=\"store_true\", help=\"Also probe image input on --model.\")\n doctor_parser.add_argument(\"--timeout\", type=float, default=30)\n doctor_parser.set_defaults(func=cmd_doctor)\n\n candidates_parser = subparsers.add_parser(\n \"candidates\",\n help=\"List a BROAD candidate range for a capability (vision/image-gen/video/audio-tts/audio-stt/embedding/rerank); caller/LLM picks, tool chooses no default.\",\n )\n candidates_parser.add_argument(\n \"--capability\", \"-c\", required=True,\n help=\"Capability or alias. One of: \" + \", \".join(CAPABILITY_SPECS) + \".\",\n )\n candidates_parser.add_argument(\"--source\", help=\"Models JSON URL, local JSON file, or '-' for stdin.\")\n candidates_parser.add_argument(\"--limit\", type=int, default=40)\n candidates_parser.add_argument(\"--json\", action=\"store_true\")\n candidates_parser.add_argument(\"--mine\", action=\"store_true\", help=\"Filter to models your token can actually call (needs the aihubmix CLI + login).\")\n candidates_parser.add_argument(\"--auto-install\", action=\"store_true\", help=\"With --mine: install the aihubmix CLI automatically if missing.\")\n candidates_parser.set_defaults(func=cmd_candidates)\n\n vision_parser = subparsers.add_parser(\n \"vision-candidates\",\n help=\"Alias for `candidates --capability vision`.\",\n )\n vision_parser.add_argument(\"--source\", help=\"Models JSON URL, local JSON file, or '-' for stdin.\")\n vision_parser.add_argument(\"--limit\", type=int, default=40)\n vision_parser.add_argument(\"--json\", action=\"store_true\")\n vision_parser.add_argument(\"--mine\", action=\"store_true\", help=\"Filter to models your token can actually call.\")\n vision_parser.add_argument(\"--auto-install\", action=\"store_true\", help=\"With --mine: install the aihubmix CLI automatically if missing.\")\n vision_parser.set_defaults(func=cmd_candidates, capability=\"vision\")\n\n install_cli_parser = subparsers.add_parser(\n \"install-cli\",\n help=\"Install the companion aihubmix CLI via its official per-OS installer.\",\n )\n install_cli_parser.add_argument(\"--force\", action=\"store_true\", help=\"Reinstall even if already present.\")\n install_cli_parser.set_defaults(func=cmd_install_cli)\n\n return parser\n\n\ndef main() -> int:\n parser = build_parser()\n args = parser.parse_args()\n global _LANG\n _LANG = (args.lang or os.environ.get(\"AIHUBMIX_LANG\") or \"en\").strip().lower()\n if _LANG not in (\"en\", \"zh\"):\n _LANG = \"en\"\n result = args.func(args)\n return result if isinstance(result, int) else 0\n\n\nif __name__ == \"__main__\":\n raise SystemExit(main())\n"}}}}