Query the provider for an available-model list. Returns ``[]`` on any failure — callers should fall back to the curated list in ``LLM_FALLBACK_MODELS`` / ``EMBEDDING_FALLBACK_MODELS``.
(
console: Console,
strings: dict[str, str],
*,
base_url: str,
api_key: str,
binding: str,
)
| 546 | |
| 547 | |
| 548 | def fetch_models( |
| 549 | console: Console, |
| 550 | strings: dict[str, str], |
| 551 | *, |
| 552 | base_url: str, |
| 553 | api_key: str, |
| 554 | binding: str, |
| 555 | ) -> list[str]: |
| 556 | """Query the provider for an available-model list. |
| 557 | |
| 558 | Returns ``[]`` on any failure — callers should fall back to the curated |
| 559 | list in ``LLM_FALLBACK_MODELS`` / ``EMBEDDING_FALLBACK_MODELS``. |
| 560 | """ |
| 561 | if not base_url: |
| 562 | return [] |
| 563 | |
| 564 | url = base_url.rstrip("/") + "/models" |
| 565 | headers: dict[str, str] = {} |
| 566 | |
| 567 | if binding == "anthropic": |
| 568 | # Anthropic uses different auth headers. |
| 569 | if api_key: |
| 570 | headers["x-api-key"] = api_key |
| 571 | headers["anthropic-version"] = "2023-06-01" |
| 572 | else: |
| 573 | if api_key: |
| 574 | headers["Authorization"] = f"Bearer {api_key}" |
| 575 | |
| 576 | info(console, strings["init.fetch_models"].format(url=url)) |
| 577 | try: |
| 578 | with httpx.Client(timeout=5.0) as client: |
| 579 | response = client.get(url, headers=headers) |
| 580 | response.raise_for_status() |
| 581 | payload = response.json() |
| 582 | except Exception as exc: |
| 583 | warn(console, strings["init.fetch_models_fail"].format(error=str(exc)[:160])) |
| 584 | return [] |
| 585 | |
| 586 | raw_items: list[Any] |
| 587 | if isinstance(payload, dict) and isinstance(payload.get("data"), list): |
| 588 | raw_items = payload["data"] |
| 589 | elif isinstance(payload, dict) and isinstance(payload.get("models"), list): |
| 590 | # Ollama: GET /api/tags returns {"models": [{"name": "...", ...}]} |
| 591 | raw_items = payload["models"] |
| 592 | elif isinstance(payload, list): |
| 593 | raw_items = payload |
| 594 | else: |
| 595 | warn(console, strings["init.fetch_models_fail"].format(error="unexpected response shape")) |
| 596 | return [] |
| 597 | |
| 598 | names: list[str] = [] |
| 599 | for item in raw_items: |
| 600 | if isinstance(item, str): |
| 601 | names.append(item) |
| 602 | elif isinstance(item, dict): |
| 603 | # OpenAI: {"id": "..."}. Ollama: {"name": "..."}. Anthropic: {"id": "..."}. |
| 604 | name = item.get("id") or item.get("name") or item.get("model") |
| 605 | if isinstance(name, str) and name: |