diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 36918030..26fe5a90 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -398,6 +398,8 @@ jobs:
- uses: actions/checkout@v4
- name: ProviderName generator drift (checked-in files must match generator output)
run: python3 scripts/gen_provider_names.py --check
+ - name: Providers doc generator drift (docs/api/providers.md must match generator output)
+ run: python3 scripts/gen_providers_doc.py --check
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: actions/setup-node@v4
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 7d30e33a..ce84ddbb 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -8,7 +8,7 @@ to set up a development environment, run the tests, and submit changes.
```
aimux/
├── aimux-core/ # Core abstractions: LanguageModel / Provider / Message / StreamPart
-├── aimux-providers/ # 325 provider implementations + cassettes
+├── aimux-providers/ # provider implementations + cassettes (counts: docs/api/providers.md)
├── aimux-stream/ # SSE / NDJSON stream parsing
├── aimux-provider-utils/ # HTTP utilities: retry, backoff, error parsing, API-key loading
├── aimux-ffi/ # C ABI (opaque handle + JSON + push callback) for non-native bindings
@@ -72,6 +72,9 @@ aimux distinguishes three kinds of providers:
provider-specific behavior.
3. **Modality-specific** — speech, image, video, transcription, etc.
+Step-by-step checklists (with the generator and CI rules) live in
+[docs/contributing/adding-a-provider.md](docs/contributing/adding-a-provider.md).
+
Before submitting a provider, read `rfc/0006-provider-development.md` for the
minimum acceptance criteria, core contracts, and required tests.
diff --git a/README.md b/README.md
index 731a120f..e636bc1b 100644
--- a/README.md
+++ b/README.md
@@ -4,12 +4,12 @@
-> **A unified LLM access layer written in Rust. One API for 329 AI providers.**
+> **A unified LLM access layer written in Rust. One API for [327 AI providers](docs/api/providers.md).**
[](https://github.com/arcships/aimux/actions/workflows/ci.yml)
[](LICENSE)
[](https://www.rust-lang.org/)
-[](docs/api/providers.md)
+[](docs/api/providers.md)
[](bindings/)
[](https://crates.io/crates/aimux-core)
[](https://www.npmjs.com/package/@arcships/aimux)
@@ -30,12 +30,13 @@ difference: aimux is an access layer, those are orchestration layers.
## Why aimux
-- **329 provider modules** — 251 registry-backed OpenAI-compatible
- (unified `provider(name, ...)` entry) + 10 native protocol
- implementations (OpenAI, Anthropic, Google, Bedrock, Vertex, Azure, Cohere,
- Mistral, xAI, Anthropic-AWS) + 68 standalone/modality/local/search providers
- (OpenRouter, DeepSeek, Ollama, vLLM, ElevenLabs, KlingAI, Tavily, …).
- Full list: [docs/api/providers.md](docs/api/providers.md).
+- **327 providers** (as of 2026-09-24) — 251 registry-backed OpenAI-compatible
+ (unified `provider(name, ...)` entry) + 76 typed providers (native protocol
+ implementations such as OpenAI/Anthropic/Google/Bedrock/Vertex, local
+ engines like Ollama/vLLM, and speech/image/video/search modality providers).
+ Counts by category and the full list live in
+ [docs/api/providers.md](docs/api/providers.md) — generated, and the single
+ source of truth for provider counts (only the badge above repeats the total).
- **Unified, object-safe interface** — the `LanguageModel` trait supports
`Box` so providers are interchangeable without changing call sites.
- **Full multimodal** — text, streaming, tool calling, embeddings, image,
@@ -49,7 +50,7 @@ difference: aimux is an access layer, those are orchestration layers.
fallback (RFC-0021); `MoaModel` aggregates parallel reference models
mixture-of-agents style (RFC-0022). Both are plain `LanguageModel`s.
- **Config-driven provider registry** — `provider-registry.json` describes
- each of the 251 OpenAI-compatible providers (base URL, env var, profile
+ each registry-backed OpenAI-compatible provider (base URL, env var, profile
quirks: top_k, tools, response_format, streaming usage, max_tokens key);
one unified `provider(name, ...)` entry in every binding.
- **Fast and small** — Rust core, release profile tuned for binary size
@@ -96,7 +97,7 @@ middleware, and telemetry per request).
```
aimux/
├── aimux-core # Core abstractions: LanguageModel / Provider / Message / StreamPart
-├── aimux-providers # 329 provider implementations (251 registry-backed + native)
+├── aimux-providers # Provider implementations — registry-backed + typed (docs/api/providers.md)
├── aimux-stream # SSE / NDJSON stream parsing
├── aimux-provider-utils # One-exchange HTTP helpers, response handlers, API-key loading
├── aimux-ffi # C ABI (opaque handles + JSON results + owned aimux_error_t *) for non-native bindings
@@ -120,7 +121,7 @@ cargo add aimux-core aimux-providers
| Crate | Description | crates.io |
|-------|-------------|-----------|
| `aimux-core` | Core abstractions: `LanguageModel` / `Provider` / `Message` / `StreamPart` | [crates.io](https://crates.io/crates/aimux-core) |
-| `aimux-providers` | 325 provider implementations | [crates.io](https://crates.io/crates/aimux-providers) |
+| `aimux-providers` | Provider implementations — [registry-backed + typed](docs/api/providers.md) | [crates.io](https://crates.io/crates/aimux-providers) |
| `aimux-stream` | SSE / NDJSON stream parsing | [crates.io](https://crates.io/crates/aimux-stream) |
| `aimux-provider-utils` | One-exchange HTTP helpers and typed response handlers | [crates.io](https://crates.io/crates/aimux-provider-utils) |
| `aimux-ffi` | C ABI for non-native bindings | [crates.io](https://crates.io/crates/aimux-ffi) |
@@ -283,8 +284,9 @@ let model = provider_from_env("deepseek", "deepseek-chat", None)?;
// model usage is identical — it's all dyn LanguageModel
```
-All 251 OpenAI-compatible providers are registry-backed: `provider(name, ...)`
-in every binding, with typed `ProviderName` (enum/union/consts per language).
+All registry-backed OpenAI-compatible providers share one entry:
+`provider(name, ...)` in every binding, with typed `ProviderName`
+(enum/union/consts per language).
The retired per-provider shell types (`XxxConfig`/`XxxProvider`) are gone —
see [docs/API.md](docs/API.md#providers).
@@ -294,17 +296,19 @@ see [docs/API.md](docs/API.md#providers).
## Provider coverage
-| Type | Count | Examples |
-|------|:-----:|----------|
-| Native protocol | 10 | OpenAI, Anthropic, Google, Bedrock, Vertex, Azure, Cohere, Mistral, xAI, Anthropic-AWS |
-| OpenAI-compatible (registry) | 251 | Groq, Fireworks, Together, Perplexity, Ollama Cloud, DeepSeek, Alibaba Tongyi, Zhipu, Baidu, Tencent, Moonshot, SiliconFlow… |
-| OpenAI-compatible (standalone + Vertex-hosted) | 35 | OpenRouter, Hugging Face, Ollama, vLLM, SGLang, Llama.cpp, LiteLLM Proxy, Vertex-hosted DeepSeek/Qwen/Llama… |
-| Speech / transcription | 10 | ElevenLabs, Deepgram, AssemblyAI, AWS Polly, Cartesia, Hume, Gladia, RevAI, LMNT, Fal |
-| Image / video | 8 | Black Forest Labs, Replicate, Luma, Prodia, KlingAI, Recraft, Stability, RunwayML |
-| Embeddings / rerank / search | 13 | Voyage, Jina, Tavily, Exa, Firecrawl, Serper, SearXNG, You.com… |
-| Other (Responses API, Bedrock/Mantle) | 2 | generic Responses API wrapper, Bedrock Mantle |
+Counts by category live in
+[docs/api/providers.md](docs/api/providers.md#totals) — generated from the
+registry and `lib.rs`, and the single source of truth. The rough shape of
+the roster:
-Full list: [rfc/0004-provider-inventory.md](rfc/0004-provider-inventory.md).
+- **Native protocol** — OpenAI, Anthropic, Google, Bedrock, Vertex, Azure, Cohere, Mistral, xAI, Anthropic-AWS, Voyage, Codex, OpenRouter
+- **OpenAI-compatible (registry-backed)** — Groq, Fireworks, Together, Perplexity, Ollama Cloud, DeepSeek, Alibaba Tongyi, Zhipu, Baidu, Tencent, Moonshot, SiliconFlow…
+- **OpenAI-compatible (standalone + Vertex-hosted)** — Hugging Face, Ollama, vLLM, SGLang, Llama.cpp, LiteLLM Proxy, Vertex-hosted DeepSeek/Qwen/Llama…
+- **Speech / transcription** — ElevenLabs, Deepgram, AssemblyAI, AWS Polly, Cartesia, Hume, Gladia, RevAI, LMNT, Fal
+- **Image / video** — Black Forest Labs, Replicate, Luma, Prodia, KlingAI, Recraft, Stability, RunwayML
+- **Embeddings / rerank / search** — Voyage, Jina, Tavily, Exa, Firecrawl, Serper, SearXNG, You.com…
+
+Full list with per-category counts: [docs/api/providers.md](docs/api/providers.md).
## Language bindings
@@ -338,7 +342,8 @@ Tests run on cassette playback — no network and no keys. See
|-----|----------|
| [docs/API.md](docs/API.md) | **API overview** — shared reference + links to per-language guides |
| [docs/api/reference.md](docs/api/reference.md) | **API reference** — public types & functions lookup |
-| [docs/api/providers.md](docs/api/providers.md) | **Provider list** — all 325 providers with entry points (generated) |
+| [docs/contributing/adding-a-provider.md](docs/contributing/adding-a-provider.md) | **Adding a provider** — the three-case checklist (registry row / new protocol / single modality) |
+| [docs/api/providers.md](docs/api/providers.md) | **Provider list** — every provider with entry points and the canonical counts (generated) |
| [docs/api/](docs/api/) | **Per-language API guides** — Node.js, Python, Rust, Go, C/C++, Swift, Kotlin, Flutter |
| [docs/error-model.md](docs/error-model.md) | **错误模型** — 跨语言错误形态与兼容性约定 |
| [docs/PROJECT-OVERVIEW.md](docs/PROJECT-OVERVIEW.md) | Project overview, design decisions, benchmarks |
diff --git a/docs/API.md b/docs/API.md
index b60eea45..59659077 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -1,6 +1,7 @@
# aimux API Documentation
-> Unified LLM service access layer — one API to access 325 AI providers
+> Unified LLM service access layer — one API to access every provider
+> ([providers.md](api/providers.md) carries the count)
## Table of Contents
@@ -63,7 +64,7 @@ model)` (Go), `Model.openai(apiKey, modelId)` (Java, Kotlin, Flutter),
Multimodal, local-inference and search providers have their own constructors
too — full list: [reference.md](api/reference.md).
-### OpenAI-compatible (251)
+### OpenAI-compatible (registry-backed)
One function in every binding:
@@ -85,7 +86,7 @@ provider(name, api_key?, model_id, config?) // all languages
```
字符串形式(`provider("groq", ...)`)在全部语言中同样可用——两种写法等价。
-- Full list (251, name / env var / base URL): [providers.md](api/providers.md)
+- Full list (name / env var / base URL): [providers.md](api/providers.md)
- Custom endpoint: registry name + `base_url` override, or the OpenAI
constructor with a base URL
diff --git a/docs/PROJECT-OVERVIEW.md b/docs/PROJECT-OVERVIEW.md
index 6f227c29..a5442aca 100644
--- a/docs/PROJECT-OVERVIEW.md
+++ b/docs/PROJECT-OVERVIEW.md
@@ -19,7 +19,7 @@ aimux does not do agent loops, RAG, or orchestration — it focuses solely on un
| Metric | Value |
|------|------|
| Rust code | 144,500+ lines |
-| AI providers | 329 (251 registry-backed OpenAI-compatible + 10 native protocols + 38 standalone/local/speech/image/video) |
+| AI providers | 327 (251 registry-backed OpenAI-compatible + 76 typed; counts by category in [api/providers.md](api/providers.md), generated) |
| Modality traits | 8 (text/embedding/image/video/speech/transcription/reranking/search) |
| Test cassettes | 2,650 recorded replays |
| Test files | 118 |
@@ -102,7 +102,7 @@ The comparison between aimux and the OpenAI official SDK is truly equivalent —
| Trait | Capability | Example Providers |
|-------|------|-------------|
-| `LanguageModel` | Text generation + streaming + tool calling | OpenAI / Anthropic / Google / DeepSeek / 325 providers |
+| `LanguageModel` | Text generation + streaming + tool calling | OpenAI / Anthropic / Google / DeepSeek / all providers ([providers.md](api/providers.md)) |
| `EmbeddingModel` | Vector embeddings | OpenAI / Cohere / Voyage / generic-compatible |
| `ImageModel` | Image generation | Black Forest Labs / Replicate / Fal / KlingAI |
| `VideoModel` | Video generation | Google Veo / Replicate |
@@ -113,15 +113,18 @@ The comparison between aimux and the OpenAI official SDK is truly equivalent —
### Categorization of providers
-| Type | Count | Representatives |
-|------|:---:|------|
-| Native protocol | 11 | OpenAI, Anthropic, Google, Bedrock, Vertex, Azure, Cohere, Mistral, xAI, DeepSeek |
-| OpenAI-compatible (registry) | 251 | Groq, Fireworks, Together, Perplexity, Ollama, OpenRouter, Alibaba Tongyi, Zhipu, Baidu, Tencent, iFlytek, Moonshot AI, SiliconFlow… |
-| Voice/transcription | 7 | ElevenLabs, Deepgram, AssemblyAI, Cartesia… |
-| Image/video | 8 | Black Forest Labs, Replicate, Fal, KlingAI… |
-| Search | 11 | Tavily, Exa, Serper, Firecrawl… |
+Counts by category live in [api/providers.md](api/providers.md#totals)
+(generated — the single source of truth). Representatives:
+
+| Type | Representatives |
+|------|------|
+| Native protocol | OpenAI, Anthropic, Google, Bedrock, Vertex, Azure, Cohere, Mistral, xAI, Codex, OpenRouter |
+| OpenAI-compatible (registry) | Groq, Fireworks, Together, Perplexity, Ollama, Alibaba Tongyi, Zhipu, Baidu, Tencent, Moonshot AI, SiliconFlow… |
+| Voice/transcription | ElevenLabs, Deepgram, AssemblyAI, Cartesia… |
+| Image/video | Black Forest Labs, Replicate, Fal, KlingAI… |
+| Search | Tavily, Exa, Serper, Firecrawl… |
-See [rfc/0004-provider-inventory.md](../rfc/0004-provider-inventory.md) for the full list.
+See [api/providers.md](api/providers.md) for the full list with counts.
All registry providers are accessed via the unified `provider(name, ...)` entry point
— see [API.md](API.md#providers).
@@ -158,10 +161,10 @@ aimux/
│ ├── ImageModel / VideoModel
│ ├── SpeechModel / TranscriptionModel
│ └── RerankingModel / SearchModel
-├── aimux-providers # 325 provider implementations
-│ ├── 11 native protocols # standalone model + convert, handles provider-specific differences
-│ ├── 251 OpenAI compatible # registry-backed: provider-registry.json + provider(name, ...) entry (RFC-0017 phase 4)
-│ └── modalities/search # voice / image / video / search implementations
+├── aimux-providers # provider implementations (canonical counts: docs/api/providers.md)
+│ ├── native protocols # standalone model + convert, handles provider-specific differences
+│ ├── OpenAI compatible # registry-backed: provider-registry.json + provider(name, ...) entry (RFC-0017 phase 4)
+│ └── modalities/search # voice / image / video / search implementations
├── aimux-stream # SSE / NDJSON streaming parsing
├── aimux-provider-utils # One-exchange HTTP helpers, response handlers, API-key loading
├── aimux-ffi # C ABI (FFI infrastructure, shared by all bindings)
@@ -270,10 +273,10 @@ Fundamental difference: **aimux is an access layer, LangChain/Mastra is an orche
Your app
└── Orchestration layer (LangChain / Mastra / custom loop)
└── Access layer (aimux) ← here
- └── 325 AI providers
+ └── AI providers (count: docs/api/providers.md)
```
-aimux does not compete with LangChain; instead it serves as the layer beneath LangChain — LangChain handles the agent loop / RAG / chain, while aimux handles unified access to 325 providers. Running the access layer in Rust delivers performance and memory behavior far exceeding a JS-implemented access layer.
+aimux does not compete with LangChain; instead it serves as the layer beneath LangChain — LangChain handles the agent loop / RAG / chain, while aimux handles unified access to every provider ([providers.md](api/providers.md)). Running the access layer in Rust delivers performance and memory behavior far exceeding a JS-implemented access layer.
### aimux vs rig / rust-genai
@@ -405,7 +408,7 @@ Not a single trick, but systematic design choices:
| Does not do | Zod validation / middleware / telemetry | Does | Extra CPU per request |
| Compilation | AOT compiled to native code | JIT | Cold start + steady state |
-### 3. The way to unify 325 providers
+### 3. The way to unify all providers
Don't write an independent model for each provider — that would explode. Use `OpenAICompatProfile` to describe the differences:
@@ -420,7 +423,7 @@ let profile = OpenAICompatProfile {
};
```
-The 11 native protocols have independent models + convert (handling differences such as Anthropic message format / Google generateContent / Bedrock SigV4), while the 251 OpenAI-compatible providers are registry-backed (provider-registry.json + unified provider(name, ...) entry, RFC-0017 phase 4).
+The native protocol providers have independent models + convert (handling differences such as Anthropic message format / Google generateContent / Bedrock SigV4), while the OpenAI-compatible providers are registry-backed (provider-registry.json + unified provider(name, ...) entry, RFC-0017 phase 4).
### 4. Recorded testing with 2650 cassettes
@@ -436,7 +439,7 @@ aimux's type design directly targets Vercel AI SDK V4 provider types — the `Ge
### Completed
-- [x] 329 providers integrated (10 native + 251 registry-backed OpenAI-compatible + voice/image/video/search)
+- [x] 327 providers integrated (registry-backed OpenAI-compatible + native/modality typed; counts by category in [providers.md](api/providers.md), generated)
- [x] 8 modality traits (text/embedding/image/video/speech/transcription/reranking/search)
- [x] 7 language bindings (Node/Python/Swift/Kotlin/Flutter/C/Rust)
- [x] 2650 cassette recorded tests
diff --git a/docs/README.md b/docs/README.md
index 11df171f..c490bfd2 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -8,7 +8,7 @@ Public documentation for aimux — a unified LLM access layer written in Rust.
|-----|----------|
| [API.md](API.md) | **API overview** — features, shared reference tables, factory functions, coverage matrix |
| [api/reference.md](api/reference.md) | **API reference** — all public types & functions, with sources |
-| [api/providers.md](api/providers.md) | **Provider list** — all 325 providers with entry points (generated) |
+| [api/providers.md](api/providers.md) | **Provider list** — every provider with entry points and the canonical counts (generated) |
| [api/gaps.md](api/gaps.md) | **Binding API gaps** — per-binding missing API tracking (Swift/Kotlin/Flutter multimodal, Go base_url, search factories, C ABI `_with_base`), with C ABI function mapping and reference implementations |
| [api/](api/) | **Per-language guides** — Node.js, Python, Rust, Go, C/C++, Swift, Kotlin, Flutter, Java |
| [error-model.md](error-model.md) | **错误模型** — 错误来源、跨语言映射、所有权与兼容性约定 |
diff --git a/docs/api/providers.md b/docs/api/providers.md
index 68dc55a5..5639cdd7 100644
--- a/docs/api/providers.md
+++ b/docs/api/providers.md
@@ -2,8 +2,33 @@
> **GENERATED** by `scripts/gen_providers_doc.py` — do not edit by hand.
> Regenerate with: `python scripts/gen_providers_doc.py`
-
-**251 registry-backed OpenAI-compatible providers** (construct via `provider(name, ...)` / `ProviderName`) + **78 non-registry providers** (construct via the typed factories listed below).
+> CI verifies with `--check`; the totals below are the one source of
+> truth for provider counts (#177).
+
+## Totals
+
+| category | count |
+|----------|-------|
+| Registry-backed OpenAI-compatible (`provider(name, ...)` / `ProviderName`) | 251 |
+| Non-registry: Native protocol providers | 13 |
+| Non-registry: OpenAI-compatible thin wrappers (second batch) | 5 |
+| Non-registry: Speech-only providers (TTS) | 4 |
+| Non-registry: Transcription-only providers (STT) | 5 |
+| Non-registry: Image-only providers | 4 |
+| Non-registry: Video-only providers | 1 |
+| Non-registry: Generic Responses API wrapper | 1 |
+| Non-registry: Bulk-generated thin-wrapper providers | 16 |
+| Non-registry: Modality-specific providers (non-language, e.g. rerank-only) | 1 |
+| Non-registry: AWS Polly speech (TTS) provider — SigV4 authenticated, speech modality only | 1 |
+| Non-registry: Recraft image provider (OpenAI Images-compatible + Recraft extension fields) | 1 |
+| Non-registry: Stability image provider (image modality only) | 1 |
+| Non-registry: Video-only provider (runwayml) | 1 |
+| Non-registry: P1 thin-wrapper providers (provider-research batch) | 1 |
+| Non-registry: Vertex AI MaaS partner-model providers (OpenAI-compatible thin wrappers) | 10 |
+| Non-registry: Search-only providers (web search modality) | 11 |
+| **Total providers** | **327** |
+
+**251 registry-backed OpenAI-compatible providers** (construct via `provider(name, ...)` / `ProviderName`) + **76 non-registry providers** (construct via the typed factories listed below).
## Registry-backed (OpenAI-compatible) — 251
@@ -265,12 +290,10 @@
These providers are **not** name-addressable: `provider("anthropic", ...)` fails with `NoSuchProvider`. Use the typed entry points below (Rust type names; per-binding constructors: see [reference.md](reference.md)).
-### Native protocol providers
+### Native protocol providers — 13
| module | typed entry points |
|--------|--------------------|
-| `replay` | — |
-| `catalogue` | `Catalogue` |
| `anthropic` | `AnthropicConfig` / `AnthropicProvider` |
| `anthropic_aws` | `AnthropicAwsConfig` / `AnthropicAwsProvider` |
| `azure` | `AzureConfig` / `AzureProvider` |
@@ -285,7 +308,7 @@ These providers are **not** name-addressable: `provider("anthropic", ...)` fails
| `openrouter` | `OpenRouterConfig` / `OpenRouterProvider` |
| `xai` | `XAIConfig` / `XAIProvider` |
-### OpenAI-compatible thin wrappers (second batch)
+### OpenAI-compatible thin wrappers (second batch) — 5
| module | typed entry points |
|--------|--------------------|
@@ -295,7 +318,7 @@ These providers are **not** name-addressable: `provider("anthropic", ...)` fails
| `mistralrs` | `MistralrsConfig` / `MistralrsProvider` |
| `ollama` | `OllamaConfig` / `OllamaProvider` |
-### Speech-only providers (TTS)
+### Speech-only providers (TTS) — 4
| module | typed entry points |
|--------|--------------------|
@@ -304,7 +327,7 @@ These providers are **not** name-addressable: `provider("anthropic", ...)` fails
| `hume` | `HumeConfig` / `HumeProvider` |
| `lmnt` | `LMNTConfig` / `LMNTProvider` |
-### Transcription-only providers (STT)
+### Transcription-only providers (STT) — 5
| module | typed entry points |
|--------|--------------------|
@@ -314,7 +337,7 @@ These providers are **not** name-addressable: `provider("anthropic", ...)` fails
| `gladia` | `GladiaConfig` / `GladiaProvider` |
| `revai` | `RevaiConfig` / `RevaiProvider` |
-### Image-only providers
+### Image-only providers — 4
| module | typed entry points |
|--------|--------------------|
@@ -323,19 +346,19 @@ These providers are **not** name-addressable: `provider("anthropic", ...)` fails
| `prodia` | `ProdiaConfig` / `ProdiaProvider` |
| `replicate` | `ReplicateConfig` / `ReplicateProvider` |
-### Video-only providers
+### Video-only providers — 1
| module | typed entry points |
|--------|--------------------|
| `klingai` | `KlingAIConfig` / `KlingAIProvider` |
-### Generic Responses API wrapper
+### Generic Responses API wrapper — 1
| module | typed entry points |
|--------|--------------------|
| `open_responses` | `OpenResponsesConfig` / `OpenResponsesProvider` |
-### Bulk-generated thin-wrapper providers
+### Bulk-generated thin-wrapper providers — 16
| module | typed entry points |
|--------|--------------------|
@@ -356,43 +379,43 @@ These providers are **not** name-addressable: `provider("anthropic", ...)` fails
| `vllm` | `VllmConfig` / `VllmProvider` |
| `xinference` | `XinferenceConfig` / `XinferenceProvider` |
-### Modality-specific providers (non-language, e.g. rerank-only)
+### Modality-specific providers (non-language, e.g. rerank-only) — 1
| module | typed entry points |
|--------|--------------------|
| `jina_ai` | `JinaAiConfig` / `JinaAiProvider` |
-### AWS Polly speech (TTS) provider — SigV4 authenticated, speech modality only
+### AWS Polly speech (TTS) provider — SigV4 authenticated, speech modality only — 1
| module | typed entry points |
|--------|--------------------|
| `aws_polly` | `AwsPollyConfig` / `AwsPollyProvider` |
-### Recraft image provider (OpenAI Images-compatible + Recraft extension fields)
+### Recraft image provider (OpenAI Images-compatible + Recraft extension fields) — 1
| module | typed entry points |
|--------|--------------------|
| `recraft` | `RecraftConfig` / `RecraftProvider` |
-### Stability image provider (image modality only)
+### Stability image provider (image modality only) — 1
| module | typed entry points |
|--------|--------------------|
| `stability` | `StabilityConfig` / `StabilityProvider` |
-### Video-only provider (runwayml)
+### Video-only provider (runwayml) — 1
| module | typed entry points |
|--------|--------------------|
| `runwayml` | `RunwaymlConfig` / `RunwaymlProvider` |
-### P1 thin-wrapper providers (provider-research batch)
+### P1 thin-wrapper providers (provider-research batch) — 1
| module | typed entry points |
|--------|--------------------|
| `bedrock_mantle` | `BedrockMantleConfig` / `BedrockMantleProvider` |
-### Vertex AI MaaS partner-model providers (OpenAI-compatible thin wrappers). Each wraps the shared OpenAIProvider against the Vertex AI MaaS OpenAPI endpoint, authenticating with a Google Cloud Bearer token
+### Vertex AI MaaS partner-model providers (OpenAI-compatible thin wrappers). Each wraps the shared OpenAIProvider against the Vertex AI MaaS OpenAPI endpoint, authenticating with a Google Cloud Bearer token — 10
| module | typed entry points |
|--------|--------------------|
@@ -407,7 +430,7 @@ These providers are **not** name-addressable: `provider("anthropic", ...)` fails
| `vertex_ai_qwen_models` | `VertexAiQwenModelsConfig` / `VertexAiQwenModelsProvider` |
| `vertex_ai_zai_models` | `VertexAiZaiModelsConfig` / `VertexAiZaiModelsProvider` |
-### Search-only providers (web search modality)
+### Search-only providers (web search modality) — 11
| module | typed entry points |
|--------|--------------------|
@@ -422,3 +445,4 @@ These providers are **not** name-addressable: `provider("anthropic", ...)` fails
| `tavily` | `TavilyConfig` / `TavilyProvider` |
| `tinyfish` | `TinyfishConfig` / `TinyfishProvider` |
| `you_com` | `YouComConfig` / `YouComProvider` |
+
diff --git a/docs/api/reference.md b/docs/api/reference.md
index ab1b5e71..3e4133a3 100644
--- a/docs/api/reference.md
+++ b/docs/api/reference.md
@@ -16,7 +16,7 @@
| `ReasoningEffort` | [types.rs](../../aimux-core/src/types.rs) | [ReasoningEffort.ts](../../bindings/node/src/types/ReasoningEffort.ts) | 7 levels, passed through verbatim |
| `AbortSignal` | [abort_signal.rs](../../aimux-core/src/abort_signal.rs) | — (runtime handle) | Rust: options field; Node: `AbortBridge` + JS `AbortSignal` |
| `ProviderOptions` | [provider.rs](../../aimux-providers/src/provider.rs) | `ProviderConfig` (per binding) | `base_url` / `headers` / `organization` / `project` / `max_retries` / `body_overrides` |
-| `ProviderName` | [provider_name.rs](../../aimux-providers/src/provider_name.rs) | [ProviderName.ts](../../bindings/node/src/types/ProviderName.ts) | one constant per registry provider (251); also generated for Go / Python / Java / Kotlin / Swift / Dart (`scripts/gen_provider_names.py`) |
+| `ProviderName` | [provider_name.rs](../../aimux-providers/src/provider_name.rs) | [ProviderName.ts](../../bindings/node/src/types/ProviderName.ts) | one constant per registry provider (count: [providers.md](providers.md)); also generated for Go / Python / Java / Kotlin / Swift / Dart (`scripts/gen_provider_names.py`) |
| `GenerateTextResult` | [generate.rs](../../aimux-core/src/generate.rs) | [GenerateTextResult.ts](../../bindings/node/src/types/GenerateTextResult.ts) | `text`, `tool_calls`, `usage`, `warnings`, `raw` |
| `StreamTextResult` | [generate.rs](../../aimux-core/src/generate.rs) | — | use `StreamPart` while iterating |
| `StreamPart` | [stream_part.rs](../../aimux-core/src/stream_part.rs) | [StreamPart.ts](../../bindings/node/src/types/StreamPart.ts) | TextDelta / ToolCallDelta / Finish / … |
@@ -86,7 +86,7 @@ Per-modality entry points are trait methods on the model types
|----------|-------|
| `generateText(model, prompt, options?, signal?)` / `streamText(...)` | typed wrappers; `signal` = `AbortSignal` |
| `openai()` / `anthropic()` / `deepseek()` / `google()` | native typed path; `deepseek()` is registry-backed |
-| `provider(name, apiKey?, modelId, config?)` | registry-backed (251); typed `ProviderName` |
+| `provider(name, apiKey?, modelId, config?)` | registry-backed (rows in [providers.md](providers.md)); typed `ProviderName` |
| `openaiEmbedding` / `cohereEmbedding` / `googleEmbedding` | embeddings |
| `openaiSpeech` / `openaiTranscription` | speech / STT |
| `openaiImage` / `googleImage` / `googleVideo` | image / video |
@@ -133,8 +133,9 @@ model constructors (`openai_new`, `anthropic_new`, …), `*_generate`,
## Providers
-The complete list — 251 registry-backed (name / display / env var / base URL)
-and 76 typed-factory providers grouped by category — is in
+The complete list — registry-backed (name / display / env var / base URL)
+and typed-factory providers grouped by category, with the canonical
+counts — is in
[providers.md](providers.md). It is generated from
`aimux-providers/src/provider_registry.json` + `aimux-providers/src/lib.rs`:
`python scripts/gen_providers_doc.py`.
diff --git a/docs/contributing/adding-a-provider.md b/docs/contributing/adding-a-provider.md
new file mode 100644
index 00000000..da09fb1e
--- /dev/null
+++ b/docs/contributing/adding-a-provider.md
@@ -0,0 +1,146 @@
+# Adding a provider
+
+Three cases cover every provider in aimux today. Find yours, follow the
+checklist, and CI verifies the generated parts automatically.
+
+Related: [CONTRIBUTING.md](../../CONTRIBUTING.md) (dev setup),
+[RFC-0017](../../rfc/0017-provider-config-dx.md) (the registry),
+[RFC-0003](../../rfc/0003-test-cassette.md) (cassettes),
+[#166](https://github.com/arcships/aimux/issues/166) (the roadmap that will
+simplify some steps below — noted per step).
+
+## Which case am I in?
+
+| Case | The vendor speaks… | You add | Example |
+|---|---|---|---|
+| 1. OpenAI-compatible | OpenAI Chat Completions wire format, own base URL | one registry row + derived cassettes | `deepinfra` |
+| 2. A new protocol | its own HTTP API for text generation | a `src//` module implementing `do_generate` / `do_stream` | `cohere` |
+| 3. Single modality | any API, but only one non-text modality | a typed shell + one modality model | `serper` (search), `lmnt` (speech) |
+
+If the vendor is OpenAI-compatible **and** needs quirks beyond a `profile`
+flag (different auth header, non-standard error body, …), it is case 2, not
+case 1.
+
+## Case 1 — OpenAI-compatible vendor (registry row)
+
+1. Add one row to `aimux-providers/src/provider_registry.json` (file is
+ sorted by `name`; follow the flat 5-key shape):
+
+ ```json
+ { "name": "example", "display": "Example", "env_var": "EXAMPLE_API_KEY",
+ "base_url": "https://api.example.com/v1", "profile": {} }
+ ```
+
+ `profile` carries the known quirks — `supports_top_k`, `supports_tools`,
+ `supports_response_format`, `stream_usage_key`, `max_tokens_key` — see
+ the rows that already use one (e.g. `groq`). Anything beyond body-shape
+ flags rides in per-call `body_overrides` (RFC-0017); if the quirk changes
+ auth or error shapes, it is case 2.
+
+2. Run both generators and commit their output:
+
+ ```sh
+ python scripts/gen_provider_names.py # ProviderName in 8 languages
+ python scripts/gen_providers_doc.py # docs/api/providers.md (totals + list)
+ ```
+
+ CI runs both with `--check` in the `contract-tests` job and fails if the
+ committed output is stale.
+
+3. Derive replay cassettes: add a tuple to `PROVIDERS` in
+ `scripts/generate_thin_wrapper_cassettes.py`, then run it. It derives
+ `thin_wrapper_nonstream.json` / `thin_wrapper_stream.json` under
+ `aimux-providers/tests/cassettes//` from **real OpenAI recordings**
+ (rewriting request path and model id — not fake data), because a
+ registry-backed provider's requests are byte-for-byte OpenAI shape.
+
+4. Nothing else: `provider("example", ...)` now works in every binding, and
+ env-var key loading follows `env_var` automatically.
+
+> Roadmap note: #166 B2 turns the 33 standalone thin wrappers into registry
+> rows and makes `conformance_test.rs` iterate the registry; the
+> cassette-derivation step above is then replaced by that suite.
+
+## Case 2 — a new protocol
+
+1. New directory `aimux-providers/src//`:
+
+ - `model.rs` — `Provider` implementing `LanguageModel`:
+ `do_generate` / `do_stream` (plus other modality traits if the vendor
+ offers them: embeddings, image, transcription…);
+ - `convert.rs` — unified message format ⇄ vendor wire format, both
+ directions, including the error-body shape;
+ - `types.rs` — vendor request/response structs (`#[serde(default)]` on
+ optional fields).
+
+ Reference implementations, smallest to largest: `mistral/`, `cohere/`,
+ `openai/`, `anthropic/`, `google/`, `bedrock/` (event stream + SigV4).
+
+2. Wire it up in `aimux-providers/src/lib.rs` **under the right section
+ comment** — the comment is what `gen_providers_doc.py` reads as the
+ category (and the totals table):
+
+ ```rust
+ pub mod ;
+ pub use ::{Config, Provider};
+ ```
+
+3. Record real cassettes under `tests/cassettes//` (RFC-0003; no
+ network, no keys at test time) and add a `_conformance` module to
+ `tests/conformance_test.rs` — `do_generate_returns_text` and
+ `do_stream_returns_parts` against those cassettes, mirroring the existing
+ 20 modules.
+
+4. Note: protocol providers are **not** name-addressable today — callers use
+ the typed factories (`CohereProvider::new(...)`), and
+ `provider("cohere", ...)` fails with `NoSuchProvider`. A registry row with
+ a `protocol` field arrives with #166 B1 (the `Protocol` enum +
+ `from_resolved`); until then, do not add protocol providers to the
+ registry JSON.
+
+## Case 3 — single-modality vendor
+
+1. One file `aimux-providers/src/.rs` (see `serper.rs` for search,
+ `lmnt.rs` for speech; `cartesia/` for a vendor with two modalities):
+
+ - `Config { api_key, base_url }` with `new` / `with_base_url` /
+ `from_env`;
+ - `Provider` — a shell whose only job is returning the modality
+ model (`search_model()`, `speech_model()`, `image_model()`, …) and
+ reporting its `name()`;
+ - the modality model implementing the trait's operation
+ (`SearchModel::do_search`, `SpeechModel::do_speak`, …) as: build the
+ request body → send through the provider-utils HTTP helpers → map the
+ response into aimux types. Implement **only** the transform functions;
+ transport, retry and timeouts are not your problem (Core owns them).
+
+2. `pub mod` + `pub use` in `lib.rs` under the modality's section comment
+ (search-only, speech-only, image-only, video-only, …).
+
+3. Cassettes + unit tests asserting the mapped result shapes.
+
+## Generator and script rules
+
+- **A generator may stay in `scripts/` only if its output carries a
+ "GENERATED — do not edit" header and CI runs it with `--check`.** Today
+ that is `gen_provider_names.py` and `gen_providers_doc.py` (the
+ `contract-tests` job). `gen_ts_types.py` regenerates the ts-rs TypeScript
+ types through `cargo test -p aimux-core --lib export` (the export tests
+ are the gate).
+- `docs/api/providers.md` is the single source of truth for provider counts
+ (#177). Never hand-write a provider count anywhere else — link to the
+ page (the README states the total once, with a date, next to that link).
+- One-off scripts carry their retirement condition in the roadmap (#166):
+ `generate_thin_wrapper_cassettes.py` is deleted once conformance tests
+ iterate the registry (A1/B2); `convert_cassettes.py`,
+ `convert_pydantic_ai.py`, `extract_litellm_bases.py`,
+ `scan_litellm_urls.py` are import/audit one-offs that can be deleted after
+ their data lands; `responses_similarity_audit.py` served RFC-0012 §3.5.
+
+## Before opening the PR
+
+- [ ] Generators run, output committed (CI `--check` green).
+- [ ] Cassettes committed; `cargo test -p aimux-providers` passes offline.
+- [ ] Case 2: conformance module added. Case 3: unit tests for the mapped shapes.
+- [ ] `docs/api/providers.md` regenerated — the totals table reflects your addition.
+- [ ] No provider-count literals introduced anywhere else.
diff --git a/rfc/0002-provider-improvements.md b/rfc/0002-provider-improvements.md
index 8898a8ab..defb75dc 100644
--- a/rfc/0002-provider-improvements.md
+++ b/rfc/0002-provider-improvements.md
@@ -1,5 +1,7 @@
# Provider Adapter Layer Improvements
+> **Status**: IMPLEMENTED (2026-07-28 — OpenAICompatProfile 差异描述 + 145 个 thin wrapper 集成,见下方 Implementation Progress;"配置即数据"的后续演进为 [RFC-0017](0017-provider-config-dx.md) 的 provider registry;cassette 测试独立为 [RFC-0003](0003-test-cassette.md))
+
## Current Problems
Thin-wrapper providers differ only in URL and environment variables, with no customization points. DeepSeek's reasoning field is dropped directly, as the code comments themselves admit.
diff --git a/rfc/0003-test-cassette.md b/rfc/0003-test-cassette.md
index afc7acd4..e094f84b 100644
--- a/rfc/0003-test-cassette.md
+++ b/rfc/0003-test-cassette.md
@@ -1,5 +1,7 @@
# Test cassette proposal
+> **Status**: IMPLEMENTED (in force — cassette 回放是 provider 测试的标准路径,`aimux-providers/tests/cassettes/` 持续扩充;录制/回放机制的补全跟踪见 #167)
+
## Goal
Tests do not depend on the network or keys. When running tests, replay cassette files instead of making real calls to provider APIs.
diff --git a/rfc/0004-provider-inventory.md b/rfc/0004-provider-inventory.md
index 420cf2d6..79dce644 100644
--- a/rfc/0004-provider-inventory.md
+++ b/rfc/0004-provider-inventory.md
@@ -1,5 +1,7 @@
# Provider Inventory and Implementation Status
+> **Status**: SUPERSEDED (2026-07-28 快照,数字不再维护 — 现行事实来源为 [provider_registry.json](../aimux-providers/src/provider_registry.json)([RFC-0017](0017-provider-config-dx.md));与 models.dev 的对账跟踪见 #171)
+
## 1. Providers Currently Implemented in aimux
**A total of 172 provider modules** (as of 2026-07-28).
diff --git a/rfc/0005-protocol-conversion.md b/rfc/0005-protocol-conversion.md
index f9df36b3..efdf477f 100644
--- a/rfc/0005-protocol-conversion.md
+++ b/rfc/0005-protocol-conversion.md
@@ -1,5 +1,7 @@
# Protocol Conversion and Adapter Layer Design
+> **Status**: ACCEPTED (调研定案 2026-07-28 — 不做跨协议转换,那是网关的职责;aimux 只做"统一接口调用各 provider"的 SDK。适配层差异表达的落地路线为 [RFC-0002](0002-provider-improvements.md) 的配置描述结构,后由 [RFC-0017](0017-provider-config-dx.md) registry 延续)
+
> Scanned 104 projects under reference/, recording each project's protocol-conversion logic and provider adapter layer design.
> The focus is not the provider list (see [0004-provider-inventory.md](0004-provider-inventory.md)), but **how to unify different providers' protocols**.
diff --git a/rfc/0005-rename-to-aimux.md b/rfc/0005-rename-to-aimux.md
index 43e9fa8d..670dec0e 100644
--- a/rfc/0005-rename-to-aimux.md
+++ b/rfc/0005-rename-to-aimux.md
@@ -1,6 +1,6 @@
# RFC 0005: Rename to aimux
-> **Status**: Decided plan, pending execution
+> **Status**: EXECUTED (2026-08 完成 — 仓库 / crates / FFI 符号均为 aimux,无旧名残留;本文正文亦随之批量替换,故部分前后文读作同义反复)
> **Decision**: Error type `AiMuxError` · repository name `aimux` · brand positioning "inspired by Vercel AI SDK" · script batch execution + verification
## 1. Background and Motivation
diff --git a/rfc/0009-request-resilience.md b/rfc/0009-request-resilience.md
index 1617f7fa..ba36bb0e 100644
--- a/rfc/0009-request-resilience.md
+++ b/rfc/0009-request-resilience.md
@@ -5,7 +5,7 @@
> **Scope**: `aimux-provider-utils` references three specific design points from catcher (connection-pool config, jitter backoff, fixed timeouts) and implements request-layer optimization using reqwest natively + the existing retry.rs, without introducing a catcher-http dependency
> **Related**: [RFC-0002](0002-provider-improvements.md) provider adapter layer improvements, [RFC-0003](0003-test-cassette.md) test cassette plan
>
-> **Superseded in part by [RFC-0031](0031-ai-sdk-request-pipeline.md)**:
+> **Superseded in part by [RFC-0031](../docs/ai-sdk-request-pipeline.md)**:
> shared client/pool/connect-timeout remain, but HTTP-layer retry and fixed
> whole-response timeout no longer describe the current architecture. Retry
> and operation deadlines now belong to Core; provider-utils performs one
diff --git a/rfc/0016-align-with-aisdk.md b/rfc/0016-align-with-aisdk.md
index 2f72a554..489598ee 100644
--- a/rfc/0016-align-with-aisdk.md
+++ b/rfc/0016-align-with-aisdk.md
@@ -1,12 +1,12 @@
# RFC-0016: 对齐 Vercel AI SDK 能力缺口
-> **Status**: DRAFT (pending review) — 实施状态与下游 wrapper 影响见 [§7](#7-实施状态截至-2026-08-02)(2026-08-02 更新)
+> **Status**: IMPLEMENTED (2026-09-24 核定) — §2 缺口绝大多数落地:H1/H2/H3、M1-M7、M9-M12 经 #99(2026-08-13,M6 proxy/M7 聚合/M11 流聚合/M12 generateObject)、#164(2026-09-12,请求管线对齐:重试分层/错误域/超时)、#165(工具输入解析边界)落地;M13 经核不做、H4 明确不做;工具调用修复由 [RFC-0035](0035-host-side-tool-call-repair.md) 扩展。未落地余项(M8 StreamPart 变体等)逐条追踪见 [§7.2](#72-未落地清单逐条追踪)
> **Date**: 2026-08-01
> **Scope**: 系统对比 aimux 0.1.2 与 Vercel AI SDK (`@ai-sdk/openai` / `@ai-sdk/openai-compatible` / `@ai-sdk/provider` V4) 的接口与实现,识别能力缺口,并按优先级规划补齐路径
> **Related**: [RFC-0009](0009-request-resilience.md) request resilience(retry/timeout,本 RFC 的 abortSignal/timeout 缺口与之相关),[RFC-0014](0014-logging.md) 统一日志体系(可观测性缺口依赖本 RFC 的 span 树)
>
> **Request-pipeline sections are superseded by
-> [RFC-0031](0031-ai-sdk-request-pipeline.md)**: retry/timeout ownership moved
+> [RFC-0031](../docs/ai-sdk-request-pipeline.md)**: retry/timeout ownership moved
> to Core, provider-utils now exposes one-exchange API helpers plus response
> handlers, and the old `send_timed` / `send_stream_timed` implementation was
> removed. RFC-0016 M3's first-SSE-event peek is intentionally retained as
@@ -190,12 +190,12 @@ aimux 的 `OpenAIResponsesModel`([responses/mod.rs](../aimux-providers/src/opena
> 逐项用户影响、范围修正与实施排序见 [§7.8](#78-中优先级缺口复审2026-08-02双-review-后逐项核定)(2026-08-02 核定:M6 缩窄为 proxy、M7 实锤差距、M11/M12 澄清、M13 拆分)
- ~~**M2** includeRawChunks~~ — 已落地,见 [§7.1](#71-已落地相对本-rfc-起草时)(2026-08-05)
- ~~**M3** logprobs 请求~~ — 已落地,见 [§7.1](#71-已落地相对本-rfc-起草时)(2026-08-05)
-- **M6** 自定义 fetch / proxy — 仍是进程级共享 reqwest client
-- **M7** 顶层结果聚合 — `GenerateTextResult` 仅 text/tool_calls([generate.rs:96](../aimux-core/src/generate.rs#L96));reasoning/files/sources 在 `raw.content`,无 `responseMessages`
+- ~~**M6** 自定义 fetch / proxy~~ — 已落地(缩窄形态:proxy 配置):`aimux_init_proxy` / `ProxyConfig`(FFI + bindings 透传),env 代理始终自动生效;自定义 fetch 经核不做(§7.8 范围修正)(#99,2026-08-13)
+- ~~**M7** 顶层结果聚合~~ — 已落地:`GenerateTextResult` / `StreamTextResultAggregated` 顶层携带 `reasoning`/`reasoning_text`/`sources`/`files`/`response_messages`([result.rs](../aimux-core/src/result.rs))(#99,2026-08-13)
- **M8** 缺 variant — `tool-approval-request`/`custom`/`reasoning-file` 均无;`source` 无 document 子类型([stream_part.rs:156](../aimux-core/src/stream_part.rs#L156))
- ~~**M10** usage.raw~~ — 已落地,见 [§7.1](#71-已落地相对本-rfc-起草时)(2026-08-05)
-- **M11** streamText 聚合器 — Node 仍 yield 原始 StreamPart JSON([lib.rs:63](../bindings/node/src/lib.rs#L63));core 仅 `StreamTextResult::text()`
-- **M12** 结构化 output / generateObject — 无
+- ~~**M11** streamText 聚合器~~ — 已落地:`consume_stream_text` → `StreamTextResultAggregated`(FFI + bindings)(#99,2026-08-13)
+- ~~**M12** 结构化 output / generateObject~~ — 已落地:`generate_object` + `GenerateObjectResult`(core/FFI/bindings)(#99,2026-08-13)
- ~~**M13** 生命周期 callbacks~~ — **经核对不做**(2026-08-14)。onStart/onEnd 为编排层语法糖,用户外包调用(非流式)或 `consume()`(流式,§7.1)零成本等价。AI SDK 该设计服务于其多步循环 + telemetry 生态(参见 `GenerateTextStartEvent` 携带 `activeTools`/`toolOrder`/`steps`、与 OTel dispatcher 绑定);前者 aimux 不做(H4 §7.5),后者已有 RFC-0014 `tracing` span 覆盖。做它需 FFI 宿主回调 wire 改动 + 8 语言 wrapper(类别②最高成本),收益无新能力。
**低优先级(2026-08-14 核对 AI SDK 源码后逐项核定):**
diff --git a/rfc/0020-external-provider-config.md b/rfc/0020-external-provider-config.md
index 29f2bff5..a0a7819b 100644
--- a/rfc/0020-external-provider-config.md
+++ b/rfc/0020-external-provider-config.md
@@ -1,6 +1,6 @@
# RFC-0020: 外部 Provider 配置
-> **Status**: DRAFT (pending review)
+> **Status**: IMPLEMENTED (运行时 overlay 落地于 #97(2026-08-12)— `provider::register_provider` 运行时注册/覆盖条目,查找顺序 overlay → 内置 registry;FFI `aimux_register_providers` 及 Node/Python 透传)
> **Date**: 2026-08-05
> **Scope**: `aimux-providers` 新增运行时覆盖层,允许外部(配置文件 / 编程式 API)注册、覆盖 OpenAI 兼容 provider 条目;各 binding 薄透传
> **Related**: [RFC-0017](0017-provider-config-dx.md) 配置 DX(本 RFC 落地其 §363 预留的"用户覆盖内置条目"语义)、[RFC-0019](0019-session-affinity.md) 会话亲和、[调研报告](../docs/external-provider-config-research.md)
diff --git a/rfc/0021-composite-model-routing.md b/rfc/0021-composite-model-routing.md
index b83b9275..7218810c 100644
--- a/rfc/0021-composite-model-routing.md
+++ b/rfc/0021-composite-model-routing.md
@@ -1,6 +1,6 @@
# RFC-0021: Composite Model 与 Model 路由
-> **Status**: DRAFT (pending review)
+> **Status**: IMPLEMENTED (RouterModel + composite 骨架落地于 #100(2026-08-14);后续随 #164 请求管线对齐更新)
> **Date**: 2026-08-05
> **Scope**: `aimux-core` 新增 composite model 基础设施(实现 `LanguageModel` trait 的组合模型)+ `RouterModel`(规则路由 / fallback / 可插拔策略)+ 内置策略(RuleRouter / LLM 分类器 / 视觉分流)
> **Related**: [RFC-0022](0022-moa-single-fanout.md) MoA(共用 composite 骨架)、[RFC-0016](0016-align-with-aisdk.md) AISDK 对齐、[RFC-0005](0005-protocol-conversion.md) 定位边界
diff --git a/rfc/0022-moa-single-fanout.md b/rfc/0022-moa-single-fanout.md
index 37a6c091..226e28c6 100644
--- a/rfc/0022-moa-single-fanout.md
+++ b/rfc/0022-moa-single-fanout.md
@@ -1,6 +1,6 @@
# RFC-0022: MoA 单次扇出聚合
-> **Status**: DRAFT (pending review)
+> **Status**: IMPLEMENTED (MoaModel 落地于 #100(2026-08-14),与 RFC-0021 共用 composite 骨架;后续随 #164 请求管线对齐更新)
> **Date**: 2026-08-05
> **Scope**: `aimux-core` 新增 `MoaModel`——实现 `LanguageModel` trait 的 Mixture-of-Agents 单次扇出聚合模型(reference models 并行跑 → 输出拼进 aggregator prompt → aggregator 跑 → 返回),不含 agent loop
> **Related**: [RFC-0021](0021-composite-model-routing.md) Composite Model 骨架(共用)、[RFC-0016](0016-align-with-aisdk.md) H4 边界(不做多步循环)
diff --git a/rfc/0027-list-models-coverage.md b/rfc/0027-list-models-coverage.md
index 01c93754..664fe6bb 100644
--- a/rfc/0027-list-models-coverage.md
+++ b/rfc/0027-list-models-coverage.md
@@ -1,5 +1,6 @@
# RFC-0027 list_models 覆盖跟踪
+> **Status**: IMPLEMENTED (真 LLM provider 292/292 覆盖,基线 2026-08-06;本页为基线快照,新增 provider 的覆盖随 registry 演进,不再逐家更新)
> 目标:覆盖全部**真 LLM provider**(有 chat/completions 能力、语义上有"模型列表"的 provider)。
> modality-only(speech/image/embed/search)不在范围,保留 trait 默认 `Unsupported`。
>
diff --git a/scripts/gen_providers_doc.py b/scripts/gen_providers_doc.py
index c61c4112..ad4db61b 100644
--- a/scripts/gen_providers_doc.py
+++ b/scripts/gen_providers_doc.py
@@ -5,14 +5,24 @@
- aimux-providers/src/provider_registry.json (registry of OpenAI-compatible entries)
- aimux-providers/src/lib.rs (non-registry modules + categories)
+A `pub mod` in lib.rs counts as a provider module iff its `pub use` re-exports
+a typed surface (`*Provider` / `*Config` / `*Model`). Registry machinery
+(`provider`, `provider_name`), `replay` and `catalogue` export none of these
+and are excluded without a hand-kept list.
+
+The generated page carries the provider totals (registry rows, per-category
+typed providers, grand total); other docs reference it instead of repeating
+numbers (#177).
+
Usage:
- python scripts/gen_providers_doc.py
- # writes docs/api/providers.md, prints the module count for verification
+ python scripts/gen_providers_doc.py # writes docs/api/providers.md
+ python scripts/gen_providers_doc.py --check # exit 1 if the page is stale
"""
import json
import pathlib
import re
+import sys
ROOT = pathlib.Path(__file__).resolve().parent.parent
REGISTRY = ROOT / "aimux-providers" / "src" / "provider_registry.json"
@@ -117,6 +127,17 @@ def module_exports(module):
return exports
+def is_provider_module(module):
+ """True when the module re-exports a typed provider surface.
+
+ A provider module exposes `*Provider` / `*Config` / `*Model` types.
+ Registry machinery (`provider`, `provider_name`), `replay` and
+ `catalogue` re-export none of these, so they are excluded without a
+ hand-kept deny-list (#177).
+ """
+ return any(e.endswith(("Provider", "Config", "Model")) for e in module_exports(module))
+
+
def display_from_exports(exports):
"""Derive a human name: AnthropicProvider / AnthropicConfig -> Anthropic."""
for prefix in ("ProviderConfig", "Provider", "Config", "Model"):
@@ -126,29 +147,52 @@ def display_from_exports(exports):
return None
-def main():
+def build_page():
+ """Return (page_text, totals) without touching the filesystem."""
registry = load_registry()
- sections = parse_lib_rs()
+ sections = [
+ (title, [m for m in mods if is_provider_module(m)])
+ for title, mods in parse_lib_rs()
+ ]
+ sections = [(title, mods) for title, mods in sections if mods]
registry_names = {e["name"] for e in registry}
- lines = []
- w = lines.append
-
# Overlap sanity check: a module name should not also be a registry entry.
for _, mods in sections:
for m in mods:
if m in registry_names:
print(f"WARNING: module `{m}` also exists in provider_registry.json")
+ non_registry = sum(len(mods) for _, mods in sections)
+ total = len(registry) + non_registry
+
+ lines = []
+ w = lines.append
+
w("# aimux providers")
w("")
w("> **GENERATED** by `scripts/gen_providers_doc.py` — do not edit by hand.")
w("> Regenerate with: `python scripts/gen_providers_doc.py`")
+ w("> CI verifies with `--check`; the totals below are the one source of")
+ w("> truth for provider counts (#177).")
+ w("")
+ w("## Totals")
+ w("")
+ w("| category | count |")
+ w("|----------|-------|")
+ w(f"| Registry-backed OpenAI-compatible (`provider(name, ...)` / `ProviderName`) | {len(registry)} |")
+ for title, mods in sections:
+ # First sentence only — some lib.rs section comments run on; the full
+ # title stays on the section heading below. The lookahead keeps
+ # abbreviations such as "e.g." from ending the "sentence".
+ short = re.split(r"(?<=[.!?])\s(?=[A-Z0-9])", title, maxsplit=1)[0].rstrip(".")
+ w(f"| Non-registry: {short} | {len(mods)} |")
+ w(f"| **Total providers** | **{total}** |")
w("")
w(
f"**{len(registry)} registry-backed OpenAI-compatible providers** "
f"(construct via `provider(name, ...)` / `ProviderName`) + "
- f"**{sum(len(m) for _, m in sections) - 2} non-registry providers** "
+ f"**{non_registry} non-registry providers** "
"(construct via the typed factories listed below)."
)
w("")
@@ -169,10 +213,7 @@ def main():
)
w("")
for title, mods in sections:
- mods = [m for m in mods if m not in ("provider", "provider_name")]
- if not mods:
- continue
- w(f"### {title}")
+ w(f"### {title} — {len(mods)}")
w("")
w("| module | typed entry points |")
w("|--------|--------------------|")
@@ -186,13 +227,42 @@ def main():
w(f"| `{mod}` | {cols} |")
w("")
- out = "\n".join(lines)
- OUT.write_text(out, encoding="utf-8")
+ page = "\n".join(lines) + "\n"
+ totals = {
+ "registry": len(registry),
+ "non_registry": non_registry,
+ "total": total,
+ }
+ return page, totals
- total = len(registry) + sum(len(m) for _, m in sections) - 2
+
+def main() -> int:
+ check = "--check" in sys.argv[1:]
+ page, totals = build_page()
+ if check:
+ on_disk = OUT.read_text(encoding="utf-8") if OUT.is_file() else None
+ if on_disk != page:
+ print(
+ f"STALE: {OUT.relative_to(ROOT)} — run "
+ "scripts/gen_providers_doc.py and commit the result",
+ file=sys.stderr,
+ )
+ return 1
+ print(
+ f"{OUT.relative_to(ROOT)} up to date "
+ f"(registry={totals['registry']} non-registry={totals['non_registry']} "
+ f"total={totals['total']})"
+ )
+ return 0
+ OUT.write_text(page, encoding="utf-8")
print(f"wrote {OUT}")
- print(f"registry={len(registry)} non-registry modules={sum(len(m) for _, m in sections) - 2} total={total}")
+ print(
+ f"registry={totals['registry']} "
+ f"non-registry modules={totals['non_registry']} "
+ f"total={totals['total']}"
+ )
+ return 0
if __name__ == "__main__":
- main()
+ sys.exit(main())