Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion faigate/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""fusionAIze Gate package."""

__version__ = "2.2.1"
__version__ = "2.2.2"
30 changes: 25 additions & 5 deletions faigate/quota_poller.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading