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:
Evan authored and GitHub committed 2026-04-24 00:02:33 +09:00
1 parent dbfb32d9d4
commit d43077afa9
38 files changed
+1146 -1215

No files matched your search

+118 -48
View File
@@ -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")