fix(providers): remove ~anthropic, skip ~ prefixes in sync script (#69)
* fix(providers): remove ~anthropic, skip ~ prefixes in sync script
OpenRouter uses ~ prefixes for internal auto-routing aliases (e.g. ~anthropic).
These are not real providers — they already route through openrouter.toml.
The generated ~anthropic.toml was confusing (looked like a stale backup)
and redundant with the existing openrouter provider.
- Delete providers/~anthropic.toml
- Skip provider IDs starting with ~ in sync-pricing.py --create-missing
* fix(providers): remove morph, aider, kwaipilot
- morph: specialized code-editing/patching tool, not a general LLM provider
- aider: CLI meta-tool wrapper (base_url empty), redundant with claude-code/codex-cli/gemini-cli/qwen-code
- kwaipilot: Kwai internal coding assistant routed via OpenRouter, niche
* fix(sync): add morph/aider/kwaipilot to SKIP_PROVIDERS to prevent re-creation
* feat(sync): merge OpenRouter-only providers into openrouter.toml
Instead of generating standalone .toml files that just wrap the OpenRouter
endpoint, merge their models directly into openrouter.toml with the
standard 'openrouter/{provider}/{model}' ID convention.
- Add _build_model_fields() and _model_lines() helpers to deduplicate
model rendering between standalone and merged paths
- Add merge_into_openrouter() that appends new models idempotently
- generate_provider_toml() now only runs for providers in PROVIDER_API
- --create-missing routes OpenRouter-only providers to merge_into_openrouter
* fix(providers): remove 14 OpenRouter-only standalone files
These providers have no direct public API and all route through
openrouter.ai/api/v1. Per the new sync-pricing.py policy, their models
will be merged into openrouter.toml on the next CI run instead of
living in separate files that just wrap the OpenRouter endpoint.
Removed: allenai, deepcogito, essentialai, inclusionai, inflection,
liquid, meituan, nex-agi, nousresearch, prime-intellect, relace,
switchpoint, tngtech, writer
* fix(providers): remove 7 niche providers with no driver support
No dedicated LLM driver code exists for these providers — they rely
purely on OpenAI-compatible passthrough with no special handling.
Removing them reduces registry noise; users can still reach them via
openrouter.toml if needed.
Removed: microsoft, ibm-granite, xiaomi, upstage, inception, aion-labs, arcee-ai
* fix(providers): remove ai21, chutes, venice
All three use ApiFormat::OpenAI with no special handling — pure passthrough.
No registry entry needed; users can reach them via openrouter.toml or by
adding a custom provider.
* docs(providers): rewrite README with full provider catalog and inclusion criteria
- List all 46 providers grouped by category with descriptions
- Document why each provider exists (direct API, unique endpoint, dedicated driver, local, CLI)
- Add inclusion criteria section explaining when to create standalone files vs merging into openrouter.toml
- Document sync script routing logic
- Update model counts: 49→46 providers, 339→232 models
* docs: add comprehensive READMEs for all registry sections + deepinfra provider
- agents/README.md: 32 agents across 7 categories with capability field reference
- channels/README.md: 44 channels across 5 categories with protocol reference table
- hands/README.md: 18 hands across 5 categories with HAND.toml format guide
- mcp/README.md: 33 MCP servers across 5 categories with transport/auth format
- plugins/README.md: 12 plugins with hook protocol documentation
- skills/README.md: 60 skills across 9 categories with SKILL.md format guide
- providers/deepinfra.toml: add DeepInfra serverless inference (5 models)
This commit is contained in:
38 files changed
+1146
-1215
No files matched your search
+118
-48
@@ -34,6 +34,8 @@ PROVIDER_ALIAS = {
|
||||
SKIP_PROVIDERS = {
|
||||
"sao10k", "thedrummer", "undi95", "gryphe", "cognitivecomputations",
|
||||
"anthracite-org", "alpindale", "alfredpros", "mancer",
|
||||
# Specialized coding tools / CLI wrappers — not general LLM providers
|
||||
"morph", "aider", "kwaipilot",
|
||||
}
|
||||
|
||||
# Skip creating NEW provider files for these — they overlap with hand-written
|
||||
@@ -139,8 +141,102 @@ def update_toml_prices(toml_path, models_by_id, dry_run=False):
|
||||
return updated
|
||||
|
||||
|
||||
def _build_model_fields(provider_id, m, model_id_prefix=""):
|
||||
"""Extract and normalise fields for a single OpenRouter model entry.
|
||||
|
||||
Returns a dict of fields, or None if pricing is missing.
|
||||
The caller supplies *model_id_prefix* (e.g. "openrouter/kwaipilot/") so
|
||||
the same helper works for both standalone files and openrouter.toml merges.
|
||||
"""
|
||||
raw_id = m["id"].split("/")[-1] if "/" in m["id"] else m["id"]
|
||||
model_id = f"{model_id_prefix}{raw_id}"
|
||||
display = m.get("name", raw_id)
|
||||
ctx = m.get("context_length", 0)
|
||||
max_out = m.get("top_provider", {}).get("max_completion_tokens", 0)
|
||||
inp, outp = parse_pricing(m)
|
||||
if inp is None:
|
||||
return None
|
||||
|
||||
supports_tools = "tool_use" in str(m.get("supported_parameters", []))
|
||||
supports_vision = "vision" in str(m.get("architecture", {}).get("modality", ""))
|
||||
|
||||
if inp == 0 and outp == 0:
|
||||
tier = "fast"
|
||||
elif inp < 0.5:
|
||||
tier = "fast"
|
||||
elif inp < 3.0:
|
||||
tier = "smart"
|
||||
else:
|
||||
tier = "frontier"
|
||||
|
||||
if not max_out:
|
||||
max_out = min(ctx // 4, 16384) if ctx > 0 else 4096
|
||||
|
||||
return dict(
|
||||
model_id=model_id, display=display, tier=tier,
|
||||
ctx=ctx, max_out=max_out, inp=inp, outp=outp,
|
||||
supports_tools=supports_tools, supports_vision=supports_vision,
|
||||
)
|
||||
|
||||
|
||||
def _model_lines(f):
|
||||
"""Render a model-fields dict as TOML [[models]] lines."""
|
||||
lines = [
|
||||
"[[models]]",
|
||||
f'id = "{f["model_id"]}"',
|
||||
f'display_name = "{f["display"]}"',
|
||||
f'tier = "{f["tier"]}"',
|
||||
f'context_window = {f["ctx"]}',
|
||||
f'max_output_tokens = {f["max_out"]}',
|
||||
f'input_cost_per_m = {f["inp"]}',
|
||||
f'output_cost_per_m = {f["outp"]}',
|
||||
]
|
||||
if f["supports_tools"]:
|
||||
lines.append("supports_tools = true")
|
||||
if f["supports_vision"]:
|
||||
lines.append("supports_vision = true")
|
||||
lines.append("supports_streaming = true")
|
||||
lines.append("")
|
||||
return lines
|
||||
|
||||
|
||||
def merge_into_openrouter(provider_id, models, dry_run=False):
|
||||
"""Append models from an OpenRouter-only provider into openrouter.toml.
|
||||
|
||||
Model IDs get the prefix "openrouter/{provider_id}/" so they stay
|
||||
unambiguous and match the existing openrouter.toml convention.
|
||||
Already-present IDs are skipped to keep the operation idempotent.
|
||||
"""
|
||||
openrouter_path = PROVIDERS_DIR / "openrouter.toml"
|
||||
if not openrouter_path.exists():
|
||||
return 0
|
||||
|
||||
existing = openrouter_path.read_text()
|
||||
existing_ids = set(re.findall(r'^id\s*=\s*"([^"]+)"', existing, re.MULTILINE))
|
||||
|
||||
new_lines = []
|
||||
for m in sorted(models, key=lambda x: x.get("id", "")):
|
||||
prefix = f"openrouter/{provider_id}/"
|
||||
f = _build_model_fields(provider_id, m, model_id_prefix=prefix)
|
||||
if f is None or f["model_id"] in existing_ids:
|
||||
continue
|
||||
# Append provider name to display so provenance is clear in the UI
|
||||
f["display"] = f'{f["display"]} (OpenRouter)'
|
||||
new_lines.extend(_model_lines(f))
|
||||
|
||||
if not new_lines:
|
||||
return 0
|
||||
|
||||
count = sum(1 for l in new_lines if l == "[[models]]")
|
||||
print(f" openrouter.toml: +{count} models from {provider_id}")
|
||||
if not dry_run:
|
||||
with open(openrouter_path, "a") as fh:
|
||||
fh.write("\n" + "\n".join(new_lines))
|
||||
return count
|
||||
|
||||
|
||||
def generate_provider_toml(provider_id, models, dry_run=False):
|
||||
"""Generate a new provider TOML file from OpenRouter data."""
|
||||
"""Generate a new standalone provider TOML file (direct-API providers only)."""
|
||||
our_name = PROVIDER_ALIAS.get(provider_id, provider_id)
|
||||
toml_path = PROVIDERS_DIR / f"{our_name}.toml"
|
||||
|
||||
@@ -150,13 +246,11 @@ def generate_provider_toml(provider_id, models, dry_run=False):
|
||||
if our_name in SKIP_DUPLICATES:
|
||||
return 0
|
||||
|
||||
# Check if provider has a known public API
|
||||
if our_name in PROVIDER_API:
|
||||
base_url, env_key = PROVIDER_API[our_name]
|
||||
else:
|
||||
# No known public API — route through OpenRouter
|
||||
base_url = "https://openrouter.ai/api/v1"
|
||||
env_key = "OPENROUTER_API_KEY"
|
||||
if our_name not in PROVIDER_API:
|
||||
# No direct public API — caller should use merge_into_openrouter instead.
|
||||
return 0
|
||||
|
||||
base_url, env_key = PROVIDER_API[our_name]
|
||||
key_required = "true"
|
||||
|
||||
lines = [
|
||||
@@ -173,45 +267,10 @@ def generate_provider_toml(provider_id, models, dry_run=False):
|
||||
|
||||
count = 0
|
||||
for m in sorted(models, key=lambda x: x.get("id", "")):
|
||||
model_id = m["id"].split("/")[-1] if "/" in m["id"] else m["id"]
|
||||
display = m.get("name", model_id)
|
||||
ctx = m.get("context_length", 0)
|
||||
max_out = m.get("top_provider", {}).get("max_completion_tokens", 0)
|
||||
inp, outp = parse_pricing(m)
|
||||
if inp is None:
|
||||
f = _build_model_fields(provider_id, m)
|
||||
if f is None:
|
||||
continue
|
||||
|
||||
supports_tools = "tool_use" in str(m.get("supported_parameters", []))
|
||||
supports_vision = "vision" in str(m.get("architecture", {}).get("modality", ""))
|
||||
|
||||
# Infer tier from pricing
|
||||
if inp == 0 and outp == 0:
|
||||
tier = "fast"
|
||||
elif inp < 0.5:
|
||||
tier = "fast"
|
||||
elif inp < 3.0:
|
||||
tier = "smart"
|
||||
else:
|
||||
tier = "frontier"
|
||||
|
||||
# Default max_output_tokens if not provided
|
||||
if not max_out:
|
||||
max_out = min(ctx // 4, 16384) if ctx > 0 else 4096
|
||||
|
||||
lines.append("[[models]]")
|
||||
lines.append(f'id = "{model_id}"')
|
||||
lines.append(f'display_name = "{display}"')
|
||||
lines.append(f'tier = "{tier}"')
|
||||
lines.append(f"context_window = {ctx}")
|
||||
lines.append(f"max_output_tokens = {max_out}")
|
||||
lines.append(f"input_cost_per_m = {inp}")
|
||||
lines.append(f"output_cost_per_m = {outp}")
|
||||
if supports_tools:
|
||||
lines.append("supports_tools = true")
|
||||
if supports_vision:
|
||||
lines.append("supports_vision = true")
|
||||
lines.append("supports_streaming = true")
|
||||
lines.append("")
|
||||
lines.extend(_model_lines(f))
|
||||
count += 1
|
||||
|
||||
if count == 0:
|
||||
@@ -257,10 +316,21 @@ def main():
|
||||
for provider_id, models in sorted(by_provider.items()):
|
||||
if provider_id in SKIP_PROVIDERS:
|
||||
continue
|
||||
# Skip OpenRouter internal auto-routing aliases (e.g. "~anthropic")
|
||||
# These are not real providers — they map to openrouter.toml.
|
||||
if provider_id.startswith("~"):
|
||||
continue
|
||||
our_name = PROVIDER_ALIAS.get(provider_id, provider_id)
|
||||
if not (PROVIDERS_DIR / f"{our_name}.toml").exists():
|
||||
if (PROVIDERS_DIR / f"{our_name}.toml").exists():
|
||||
continue
|
||||
if our_name in PROVIDER_API:
|
||||
# Provider has a known direct API — create a standalone file.
|
||||
count = generate_provider_toml(provider_id, models, dry_run=dry_run)
|
||||
total_created += count
|
||||
else:
|
||||
# No direct API — merge models into openrouter.toml instead of
|
||||
# creating a new file that just wraps the OpenRouter endpoint.
|
||||
count = merge_into_openrouter(provider_id, models, dry_run=dry_run)
|
||||
total_created += count
|
||||
|
||||
action = "Would" if dry_run else "Done:"
|
||||
print(f"\n{action} updated {total_updated} prices, created {total_created} new model entries")
|
||||
|
||||
Reference in new issue
Block a user