This commit is contained in:
Witwe Bolte 2026-04-24 17:26:27 -05:00 committed by GitHub
commit e9fd9f6109
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 100 additions and 0 deletions

View file

@ -182,6 +182,14 @@ PROVIDER_REGISTRY: Dict[str, ProviderConfig] = {
api_key_env_vars=("GOOGLE_API_KEY", "GEMINI_API_KEY"),
base_url_env_var="GEMINI_BASE_URL",
),
"ppq": ProviderConfig(
id="ppq",
name="PPQ (PayPerQ)",
auth_type="api_key",
inference_base_url="https://api.ppq.ai",
api_key_env_vars=("PPQ_API_KEY",),
base_url_env_var="PPQ_BASE_URL",
),
"zai": ProviderConfig(
id="zai",
name="Z.AI / GLM",
@ -1090,6 +1098,7 @@ def resolve_provider(
# Normalize provider aliases
_PROVIDER_ALIASES = {
"glm": "zai", "z-ai": "zai", "z.ai": "zai", "zhipu": "zai",
"payperq": "ppq", "ppq.ai": "ppq",
"google": "gemini", "google-gemini": "gemini", "google-ai-studio": "gemini",
"x-ai": "xai", "x.ai": "xai", "grok": "xai",
"kimi": "kimi-coding", "kimi-for-coding": "kimi-coding", "moonshot": "kimi-coding",

View file

@ -177,6 +177,40 @@ _PROVIDER_MODELS: dict[str, list[str]] = {
"gemini-2.5-pro",
"grok-code-fast-1",
],
"ppq": [
# Curated PPQ list — mirrors the OpenRouter selection using PPQ's native IDs.
# PPQ uses shorter IDs for some providers (e.g. claude-opus-4.7 vs anthropic/claude-opus-4.7).
# Verified against live /v1/models catalog (329 models as of 2026-04-18).
"claude-opus-4.7",
"anthropic/claude-opus-4.6",
"claude-sonnet-4.6",
"qwen/qwen3.6-plus",
"anthropic/claude-sonnet-4.5",
"claude-haiku-4.5",
"gpt-5.4",
"gpt-5.4-mini",
"gpt-5.4-pro",
"gpt-5.4-nano",
"gpt-5.3-codex",
"google/gemini-3-pro-image-preview",
"gemini-3-flash-preview",
"google/gemini-3.1-pro-preview",
"google/gemini-3.1-flash-lite-preview",
"xiaomi/mimo-v2-pro",
"z-ai/glm-5.1",
"z-ai/glm-5-turbo",
"z-ai/glm-5v-turbo",
"moonshotai/kimi-k2.5",
"minimax/minimax-m2.7",
"minimax/minimax-m2.5",
"qwen/qwen3.5-plus-02-15",
"qwen/qwen3.5-35b-a3b",
"stepfun/step-3.5-flash",
"x-ai/grok-4.20",
"nvidia/nemotron-3-super-120b-a12b",
"arcee-ai/trinity-large-thinking",
"arcee-ai/trinity-large-preview",
],
"gemini": [
"gemini-3.1-pro-preview",
"gemini-3-pro-preview",
@ -725,6 +759,7 @@ CANONICAL_PROVIDERS: list[ProviderEntry] = [
ProviderEntry("huggingface", "Hugging Face", "Hugging Face Inference Providers (20+ open models)"),
ProviderEntry("gemini", "Google AI Studio", "Google AI Studio (Gemini models — native Gemini API)"),
ProviderEntry("google-gemini-cli", "Google Gemini (OAuth)", "Google Gemini via OAuth + Code Assist (free tier supported; no API key needed)"),
ProviderEntry("ppq", "PPQ (PayPerQ)", "PPQ (PayPerQ multi-provider gateway)"),
ProviderEntry("deepseek", "DeepSeek", "DeepSeek (DeepSeek-V3, R1, coder — direct API)"),
ProviderEntry("xai", "xAI", "xAI (Grok models — direct API)"),
ProviderEntry("zai", "Z.AI / GLM", "Z.AI / GLM (Zhipu AI direct API)"),
@ -752,6 +787,8 @@ _PROVIDER_ALIASES = {
"z-ai": "zai",
"z.ai": "zai",
"zhipu": "zai",
"payperq": "ppq",
"ppq.ai": "ppq",
"github": "copilot",
"github-copilot": "copilot",
"github-models": "copilot",
@ -1748,6 +1785,20 @@ def provider_model_ids(provider: Optional[str], *, force_refresh: bool = False)
return live
except Exception:
pass
if normalized == "ppq":
# Try live PPQ /v1/models endpoint
try:
from hermes_cli.auth import resolve_api_key_provider_credentials
creds = resolve_api_key_provider_credentials("ppq")
api_key = str(creds.get("api_key") or "").strip()
base_url = str(creds.get("base_url") or "").strip()
if api_key and base_url:
live = fetch_api_models(api_key, base_url)
if live:
return live
except Exception:
pass
if normalized == "anthropic":
live = _fetch_anthropic_models()
if live:

View file

@ -90,6 +90,13 @@ HERMES_OVERLAYS: Dict[str, HermesOverlay] = {
extra_env_vars=("GLM_API_KEY", "ZAI_API_KEY", "Z_AI_API_KEY"),
base_url_env_var="GLM_BASE_URL",
),
"ppq": HermesOverlay(
transport="openai_chat",
is_aggregator=True,
extra_env_vars=("PPQ_API_KEY",),
base_url_override="https://api.ppq.ai",
base_url_env_var="PPQ_BASE_URL",
),
"kimi-for-coding": HermesOverlay(
transport="openai_chat",
base_url_env_var="KIMI_BASE_URL",
@ -203,6 +210,10 @@ ALIASES: Dict[str, str] = {
"z.ai": "zai",
"zhipu": "zai",
# ppq
"payperq": "ppq",
"ppq.ai": "ppq",
# xai
"x-ai": "xai",
"x.ai": "xai",
@ -313,6 +324,7 @@ _LABEL_OVERRIDES: Dict[str, str] = {
"copilot-acp": "GitHub Copilot ACP",
"stepfun": "StepFun Step Plan",
"xiaomi": "Xiaomi MiMo",
"ppq": "PPQ (PayPerQ)",
"local": "Local endpoint",
"bedrock": "AWS Bedrock",
"ollama-cloud": "Ollama Cloud",

View file

@ -33,6 +33,7 @@ class TestProviderRegistry:
("copilot-acp", "GitHub Copilot ACP", "external_process"),
("copilot", "GitHub Copilot", "api_key"),
("huggingface", "Hugging Face", "api_key"),
("ppq", "PPQ (PayPerQ)", "api_key"),
("zai", "Z.AI / GLM", "api_key"),
("xai", "xAI", "api_key"),
("nvidia", "NVIDIA NIM", "api_key"),
@ -67,6 +68,12 @@ class TestProviderRegistry:
assert pconfig.base_url_env_var == "NVIDIA_BASE_URL"
assert pconfig.inference_base_url == "https://integrate.api.nvidia.com/v1"
def test_ppq_env_vars(self):
pconfig = PROVIDER_REGISTRY["ppq"]
assert pconfig.api_key_env_vars == ("PPQ_API_KEY",)
assert pconfig.base_url_env_var == "PPQ_BASE_URL"
assert pconfig.inference_base_url == "https://api.ppq.ai"
def test_copilot_env_vars(self):
pconfig = PROVIDER_REGISTRY["copilot"]
assert pconfig.api_key_env_vars == ("COPILOT_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN")
@ -138,6 +145,7 @@ class TestProviderRegistry:
PROVIDER_ENV_VARS = (
"OPENROUTER_API_KEY", "OPENAI_API_KEY", "ANTHROPIC_API_KEY", "ANTHROPIC_TOKEN",
"CLAUDE_CODE_OAUTH_TOKEN",
"PPQ_API_KEY",
"GLM_API_KEY", "ZAI_API_KEY", "Z_AI_API_KEY",
"KIMI_API_KEY", "KIMI_BASE_URL", "STEPFUN_API_KEY", "STEPFUN_BASE_URL",
"MINIMAX_API_KEY", "MINIMAX_CN_API_KEY",

View file

@ -157,6 +157,8 @@ class TestNormalizeProvider:
def test_known_aliases(self):
assert normalize_provider("glm") == "zai"
assert normalize_provider("payperq") == "ppq"
assert normalize_provider("ppq.ai") == "ppq"
assert normalize_provider("kimi") == "kimi-coding"
assert normalize_provider("moonshot") == "kimi-coding"
assert normalize_provider("step") == "stepfun"
@ -173,6 +175,7 @@ class TestProviderLabel:
assert provider_label("stepfun") == "StepFun Step Plan"
assert provider_label("copilot") == "GitHub Copilot"
assert provider_label("copilot-acp") == "GitHub Copilot ACP"
assert provider_label("ppq") == "PPQ (PayPerQ)"
assert provider_label("auto") == "Auto"
def test_unknown_provider_preserves_original_name(self):
@ -210,6 +213,23 @@ class TestProviderModelIds:
):
assert provider_model_ids("stepfun") == ["step-3.5-flash", "step-3-agent-lite"]
def test_ppq_prefers_live_catalog(self):
with patch("hermes_cli.auth.resolve_api_key_provider_credentials", return_value={
"api_key": "***", "base_url": "https://api.ppq.ai",
}), patch("hermes_cli.models.fetch_api_models", return_value=[
"claude-sonnet-4.6", "openai/gpt-5.4",
]):
assert provider_model_ids("ppq") == ["claude-sonnet-4.6", "openai/gpt-5.4"]
def test_ppq_falls_back_to_curated(self):
"""When live fetch fails, PPQ falls back to the curated _PROVIDER_MODELS list."""
with patch("hermes_cli.auth.resolve_api_key_provider_credentials", return_value={
"api_key": "", "base_url": "https://api.ppq.ai",
}):
ids = provider_model_ids("ppq")
assert len(ids) > 0
assert "claude-opus-4.7" in ids
def test_copilot_prefers_live_catalog(self):
with patch("hermes_cli.auth.resolve_api_key_provider_credentials", return_value={"api_key": "gh-token"}), \
patch("hermes_cli.models._fetch_github_models", return_value=["gpt-5.4", "claude-sonnet-4.6"]):