feat(desktop): /api/env derives provider key membership from unified catalog

The Keys tab now surfaces every keys-tab provider in provider_catalog() (the
`hermes model` universe), synthesizing a card even when the env var has no hand
entry in OPTIONAL_ENV_VARS. Closes the drift where openai-api, kilocode, novita,
tencent-tokenhub, and copilot were CLI-configurable but invisible in the desktop
Providers → API keys tab.

Each provider row now carries backend-derived provider/provider_label grouping
hints so the desktop can group by the same provider identity the CLI picker
uses. Hand OPTIONAL_ENV_VARS prose still wins where present (enrichment, not a
gate). Shared non-provider credentials (e.g. tool-category GITHUB_TOKEN) are
explicitly not hijacked into a provider card — Copilot uses its provider-owned
COPILOT_GITHUB_TOKEN.
This commit is contained in:
Austin Pickett 2026-06-18 23:16:16 -04:00 committed by Teknium
parent 054b8c82fd
commit 3be1326f8d
2 changed files with 139 additions and 8 deletions

View file

@ -1299,6 +1299,48 @@ class TestWebServerEndpoints:
for key, info in data.items():
assert info["channel_managed"] is (key in channel_keys)
def test_get_env_vars_surfaces_catalog_providers(self):
"""Every keys-tab provider in the unified catalog must appear in /api/env
as a provider card, even when it has no hand entry in OPTIONAL_ENV_VARS.
Regression for the GUICLI drift: openai-api, kilocode, novita,
tencent-tokenhub, copilot were configurable via `hermes model` but
invisible in the desktop Providers API keys tab.
"""
from hermes_cli.provider_catalog import provider_catalog
data = self.client.get("/api/env").json()
for d in provider_catalog():
if d.tab != "keys" or not d.api_key_env_vars:
continue
# The PRIMARY credential var must surface as this provider's card.
# (Shared aliases like GITHUB_TOKEN are intentionally left on their
# existing tool category and not hijacked — see the copilot test.)
primary = d.api_key_env_vars[0]
assert primary in data, f"{primary} ({d.slug}) missing from /api/env"
info = data[primary]
assert info["category"] == "provider"
assert info["provider"] == d.slug
assert info["provider_label"] == d.label
def test_get_env_vars_provider_rows_carry_grouping_hints(self):
"""Provider env rows expose the backend `provider`/`provider_label` the
desktop Keys tab groups by (so it no longer relies on prefix guesses)."""
data = self.client.get("/api/env").json()
# OPENAI_API_KEY is a hand-listed protected var AND a catalog provider;
# it must come back tagged to the openai-api provider.
assert data["OPENAI_API_KEY"]["provider"] == "openai-api"
assert data["OPENAI_API_KEY"]["category"] == "provider"
def test_get_env_vars_copilot_uses_provider_token_not_shared_github_token(self):
"""Copilot surfaces as its own provider card via COPILOT_GITHUB_TOKEN;
the shared GITHUB_TOKEN keeps its existing (tool) category."""
data = self.client.get("/api/env").json()
assert data["COPILOT_GITHUB_TOKEN"]["provider"] == "copilot"
assert data["COPILOT_GITHUB_TOKEN"]["category"] == "provider"
# Shared GITHUB_TOKEN must NOT be hijacked into the copilot provider card.
assert data.get("GITHUB_TOKEN", {}).get("provider", "") != "copilot"
def test_platform_scoped_messaging_env_vars_are_channel_managed(self):
from hermes_cli.web_server import (
_MESSAGING_KEYS_PAGE_KEYS,