mew supports two provider shapes: openai (SSE, delta-based) and
anthropic (SSE, content-block events). To add a new provider, implement
the Provider trait.
The Provider trait
Section titled “The Provider trait”#[async_trait]pub trait Provider: Send + Sync { fn name(&self) -> &str; async fn stream(&self, req: Request) -> Result<EventStream, ProviderError>; async fn list_models(&self) -> Result<Vec<ModelInfo>, ProviderError> { Ok(Vec::new()) }}EventStream is a pinned boxed async stream:
pub type EventStream = std::pin::Pin<Box<dyn Stream<Item = ProviderEvent> + Send>>;The Request struct
Section titled “The Request struct”pub struct Request { pub model: String, pub messages: Vec<Message>, pub tools: Vec<ToolDef>, pub system: String, pub reasoning: Option<ReasoningConfig>, pub params: Option<ChatParams>, pub headers: http::HeaderMap,}model: the model ID (provider-specific, e.g."deepseek-v4-flash")messages: full conversation historytools: tool definitions (name, description, JSON schema)system: system prompt (already merged with persona body if active)reasoning: thinking variant config (provider-specific params)params: temperature, top_p, max_tokens, tool_choice
ProviderEvent variants
Section titled “ProviderEvent variants”The stream yields these events in order:
PartStart { part: Part } ↓PartDelta { part_id, field, delta } (0 or more) ↓PartEnd { part_id } ↓(repeat for each part in the response) ↓MessageEnd { finish, usage, cost }| Event | Fields | Purpose |
|---|---|---|
PartStart |
part: Part (Text, Reasoning, or ToolCall) |
New part begins |
PartDelta |
part_id, field (“text” or “reasoning”), delta |
Incremental content |
PartEnd |
part_id |
Part complete |
MessageEnd |
finish: Finish (Stop, ToolUse, Length, Error), usage: Tokens, cost: f64 |
Turn complete |
OpenAI adapter (mew-provider-openai)
Section titled “OpenAI adapter (mew-provider-openai)”The OpenAI adapter handles OpenAI-compatible APIs (DeepSeek, OpenAI, openai-compatible gateways). Key implementation details:
- Uses
eventsource-streamto parse SSE from the/chat/completionsendpoint. - Maps
choices[0].delta.contenttoPartDelta { field: "text" }. - Tool calls arrive fragmented across multiple deltas. A
ToolCallAccumulatorbuffers partial tool calls by index and emitsPartStart/PartEndonly when a tool call is complete. finish_reasonmaps toFinish::Stop("stop"),Finish::ToolUse("tool_calls"), orFinish::Length("length").- Retry logic: exponential backoff on 429/500/502/503 with
RetryPolicy.
Anthropic adapter (mew-provider-anthropic)
Section titled “Anthropic adapter (mew-provider-anthropic)”The Anthropic adapter handles Anthropic-compatible APIs (Claude, Z.AI, Umans). Key differences from OpenAI:
- SSE events have named types:
content_block_start,content_block_delta,content_block_stop,message_delta,message_stop. - Content blocks are typed:
text,thinking,tool_use. Each maps to aPartvariant. content_block_deltaevents carrydelta.textordelta.thinkingordelta.partial_json(for tool input).message_deltacarries the stop reason and usage.- Thinking blocks become
Part::Reasoning. Tool use becomesPart::ToolCall.
The router (mew-provider-router)
Section titled “The router (mew-provider-router)”The router wraps two providers (small + big) behind the same Provider
trait:
- Selection logic: starts with the small (cheap) model. Switches to
the big (capable) model when:
- Tool calls appear in the response, OR
- The conversation exceeds a turn threshold (default 3)
- Routed wrapper: preserves the display model name so the TUI status line shows what the user chose, even though the actual model may differ per turn.
- Failover: if the small model errors, the router can fall back to big.
A single session can bounce between models without the caller knowing.
The fake provider (mew-provider-fake)
Section titled “The fake provider (mew-provider-fake)”For tests. FakeProvider::new(script) takes a Vec<ProviderEvent> and
replays it with a 10ms delay between events:
async fn stream(&self, _req: Request) -> Result<EventStream, ProviderError> { let script = self.script.clone(); let stream = futures::stream::unfold(script.into_iter(), |mut iter| async move { if let Some(event) = iter.next() { sleep(Duration::from_millis(10)).await; Some((event, iter)) } else { None } }); Ok(Box::pin(stream))}text_response("hello") produces a 4-event script: PartStart, PartDelta,
PartEnd, MessageEnd. tool_call(name, id, input) produces a tool-call
script ending with Finish::ToolUse.
Adding a new provider
Section titled “Adding a new provider”- Create a
mew-provider-<name>crate (or add to an existing adapter). - Implement
Provider. Use the OpenAI or Anthropic adapter as reference. - Register in
build_provider(main.rs):
"my-shape" => { let adapter = MyAdapter::new(provider_id, base_url, model, credential); Ok(Arc::new(adapter))}- Add to config defaults in
mew-config/src/lib.rsif it should be available out of the box, or let users configure it inconfig.toml. - Add catalog entries if the provider has models in models.dev. The catalog provides pricing, context windows, and thinking variant defaults.
- Write tests using
FakeProvideras the baseline and your adapter for integration tests.