Skip to content
Open
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
1 change: 1 addition & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,7 @@ ccproxy:
api.openai.com: openai
generativelanguage.googleapis.com: google
openrouter.ai: openrouter
router.requesty.ai: requesty
mitmproxy:
ssl_insecure: true
web_host: 127.0.0.1
Expand Down
6 changes: 6 additions & 0 deletions src/ccproxy/specs/model_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ def _perplexity_model_ids() -> list[str]:
"deepseek": [
"deepseek-v4",
],
"requesty": [
"openai/gpt-4o-mini",
"anthropic/claude-sonnet-4-5",
"google/gemini-2.5-flash",
],
"perplexity": _perplexity_model_ids(),
}
"""Provider → model IDs floor list. Updated alongside provider releases."""
Expand All @@ -60,6 +65,7 @@ def _perplexity_model_ids() -> list[str]:
_PROVIDER_ENDPOINTS: dict[str, str] = {
"anthropic": "https://api.anthropic.com/v1/models",
"openrouter": "https://openrouter.ai/api/v1/models",
"requesty": "https://router.requesty.ai/v1/models",
}
"""Provider → upstream ``/v1/models`` URL for live merge. gemini is omitted
because it requires GCP project context that ccproxy doesn't have at
Expand Down
35 changes: 35 additions & 0 deletions tests/test_model_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ def test_static_floor_contains_known_gemini_models() -> None:
assert "gemini-2.5-flash" in ids


def test_static_floor_contains_known_requesty_models() -> None:
"""The floor includes known Requesty (OpenAI-compatible gateway) model IDs."""
catalog = build_catalog()
ids = {entry["id"] for entry in catalog["data"]}
assert "openai/gpt-4o-mini" in ids
assert "anthropic/claude-sonnet-4-5" in ids


def test_owned_by_matches_provider_keys() -> None:
"""Each entry's ``owned_by`` is one of the provider keys in STATIC_MODEL_CATALOG."""
catalog = build_catalog()
Expand Down Expand Up @@ -88,6 +96,33 @@ def handler(request: httpx.Request) -> httpx.Response:
assert ids.count("claude-opus-4-7") == 1


def test_refresh_merges_live_requesty_models() -> None:
"""``refresh=True`` unions live Requesty models with the static floor (deduped)."""
set_config_instance(CCProxyConfig())

def handler(request: httpx.Request) -> httpx.Response:
if "router.requesty.ai" in str(request.url):
return httpx.Response(
200,
json={
"object": "list",
"data": [
# one new model not in the floor
{"id": "deepseek/deepseek-chat", "object": "model", "created": 1700000000},
# one duplicate of a floor entry
{"id": "openai/gpt-4o-mini", "object": "model"},
],
},
)
return httpx.Response(404)

catalog = build_catalog(refresh=True, transport=httpx.MockTransport(handler))
ids = [entry["id"] for entry in catalog["data"]]
assert "deepseek/deepseek-chat" in ids
# The duplicate floor entry is not double-counted (deduped by (owned_by, id)).
assert ids.count("openai/gpt-4o-mini") == 1


def test_refresh_provider_failure_falls_back_to_floor() -> None:
"""A provider HTTP failure does not remove its floor entries from the result."""
set_config_instance(CCProxyConfig())
Expand Down