diff --git a/CHANGELOG.md b/CHANGELOG.md index a9a2001..85d881f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # fusionAIze Gate Changelog +## v2.2.2 - 2026-04-18 + +### Fixed + +- **Quota poller ignored concrete provider instances** (`faigate/quota_poller.py`): the dispatcher compared `provider_id` against literal `"deepseek"` / `"kilocode"`, so catalog entries using the real router-facing IDs (`deepseek-chat`, `deepseek-reasoner`, `kilo-sonnet`, `kilo-opus`) silently fell through with "no balance fetcher for provider X". Introduced `_provider_family()` that collapses concrete IDs to balance-polling families, and updated both the fetcher dispatch and the `env_map` API-key lookup (now recognises `KILOCODE_API_KEY` for the kilo family). + ## v2.2.1 - 2026-04-18 ### Fixed diff --git a/faigate/__init__.py b/faigate/__init__.py index 00a6ddf..f1e44f9 100644 --- a/faigate/__init__.py +++ b/faigate/__init__.py @@ -1,3 +1,3 @@ """fusionAIze Gate package.""" -__version__ = "2.2.1" +__version__ = "2.2.2" diff --git a/faigate/quota_poller.py b/faigate/quota_poller.py index 69a62e2..35e0a69 100644 --- a/faigate/quota_poller.py +++ b/faigate/quota_poller.py @@ -219,13 +219,32 @@ def _first_numeric(keys: tuple[str, ...], root: dict[str, Any]) -> float | None: # --------------------------------------------------------------------------- +def _provider_family(provider_id: str) -> str: + """Collapse a concrete provider_id (e.g. ``deepseek-chat``, + ``kilo-sonnet``, ``kilocode``) to its balance-polling family + (``deepseek`` / ``kilo``). Returns the input unchanged if no family + prefix matches. + + This exists because the catalog's ``provider_id`` matches the router's + provider instance (so the dashboard can attribute packages correctly), + but the poller only knows balance fetchers per provider *family*. + """ + if provider_id.startswith("deepseek"): + return "deepseek" + if provider_id.startswith("kilo"): # kilocode, kilo-sonnet, kilo-opus + return "kilo" + return provider_id + + def _resolve_api_key(provider_id: str, providers_cfg: dict[str, Any] | None) -> str | None: """Find the API key for a provider. Env vars first, then config.""" env_map = { "deepseek": "DEEPSEEK_API_KEY", - "kilocode": "KILO_API_KEY", + "kilo": "KILOCODE_API_KEY", + "kilocode": "KILOCODE_API_KEY", } - env_name = env_map.get(provider_id) + family = _provider_family(provider_id) + env_name = env_map.get(family) or env_map.get(provider_id) if env_name: val = os.environ.get(env_name, "").strip() if val: @@ -289,18 +308,19 @@ async def _poll_package( error=f"no API key for {provider_id} (set {provider_id.upper()}_API_KEY)", ) + family = _provider_family(provider_id) try: - if provider_id == "deepseek": + if family == "deepseek": total, used = await _fetch_deepseek_balance(client, api_key) endpoint = "https://api.deepseek.com/user/balance" - elif provider_id == "kilocode": + elif family == "kilo": total, used, endpoint = await _fetch_kilo_balance(client, api_key) else: return PollResult( package_id=pkg_id, provider_id=provider_id, ok=False, - error=f"no balance fetcher for provider {provider_id}", + error=f"no balance fetcher for provider {provider_id} (family={family})", ) except Exception as exc: # noqa: BLE001 — poller must never crash caller return PollResult( diff --git a/pyproject.toml b/pyproject.toml index a26fa8f..203120d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "faigate" -version = "2.2.1" +version = "2.2.2" description = "Local OpenAI-compatible routing gateway for OpenClaw and other AI-native clients." readme = "README.md" license = "Apache-2.0"