.windsurf/rules/rig-provider.md
Use this checklist for provider and vector-store implementations.
Reference implementation: rig-core/src/providers/openai/ (Chat Completions API)
When implementing a new provider, study the OpenAI Chat Completions implementation thoroughly. It demonstrates the complete pattern including both completion and embedding models.
Provider Extension and Builder Structs
#[derive(Debug, Default, Clone, Copy)]
pub struct MyProviderExt;
#[derive(Debug, Default, Clone, Copy)]
pub struct MyProviderBuilder;
Provider Trait Implementation
impl Provider for MyProviderExt {
type Builder = MyProviderBuilder;
const VERIFY_PATH: &'static str = "/models"; // Health check endpoint
fn build<H>(...) -> http_client::Result<Self> {
Ok(Self)
}
}
Capabilities Declaration Explicitly declare what your provider supports:
impl<H> Capabilities<H> for MyProviderExt {
type Completion = Capable<completion::CompletionModel<H>>; // Supported
type Embeddings = Nothing; // Not supported
}
Use Capable<T> for supported features and Nothing for unsupported ones.
ProviderBuilder Implementation
impl ProviderBuilder for MyProviderBuilder {
type Output = MyProviderExt;
type ApiKey = BearerAuth; // Or custom auth type
const BASE_URL: &'static str = "https://api.provider.com/v1";
}
Client Type Aliases
pub type Client<H = reqwest::Client> = client::Client<MyProviderExt, H>;
Model Constants
pub const MODEL_NAME: &str = "model-name";
CompletionModel Implementation
Implement the completion::CompletionModel trait with proper:
TryFrom<CompletionRequest>)Message Type Conversions
Implement TryFrom/From conversions between Rig's message::Message types and your provider's message format.
EmbeddingModel Implementation (if supported)
Implement the EmbeddingModel trait:
MAX_DOCUMENTS for batch embedding limitsembed_texts for batch document embeddingEmbeddingErrorndims() returning the embedding vector dimensionalityCompletionError, EmbeddingError, etc.Vector stores live in separate companion crates (e.g., rig-mongodb, rig-lancedb).
pub trait VectorStoreIndex: WasmCompatSend + WasmCompatSync {
type Filter: SearchFilter + WasmCompatSend + WasmCompatSync;
fn top_n<T: for<'a> Deserialize<'a> + WasmCompatSend>(
&self,
req: VectorSearchRequest<Self::Filter>,
) -> impl Future<Output = Result<Vec<(f64, String, T)>, VectorStoreError>> + WasmCompatSend;
fn top_n_ids(
&self,
req: VectorSearchRequest<Self::Filter>,
) -> impl Future<Output = Result<Vec<(f64, String)>, VectorStoreError>> + WasmCompatSend;
}
top_n and top_n_idsFilter type for your backendWasmCompatSend/WasmCompatSync bounds (see below)VectorStoreError variantsRig supports WebAssembly targets. Since WASM is single-threaded, Send/Sync bounds are unnecessary and can prevent compilation.
#[cfg(not(target_family = "wasm"))]
pub trait WasmCompatSend: Send {} // native
#[cfg(target_family = "wasm")]
pub trait WasmCompatSend {} // wasm
#[cfg(not(target_family = "wasm"))]
pub trait WasmCompatSync: Sync {} // native
#[cfg(target_family = "wasm")]
pub trait WasmCompatSync {} // wasm
Always use WasmCompatSend and WasmCompatSync instead of raw Send and Sync in trait bounds.
// Correct
pub trait MyTrait: WasmCompatSend + WasmCompatSync {
fn do_thing(&self) -> impl Future<Output = ()> + WasmCompatSend;
}
// Incorrect - will break WASM builds
pub trait MyTrait: Send + Sync {
fn do_thing(&self) -> impl Future<Output = ()> + Send;
}
Use WasmBoxedFuture for boxed futures:
pub type WasmBoxedFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>; // native
pub type WasmBoxedFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>; // wasm
Some error types need platform-specific bounds:
#[derive(Debug, thiserror::Error)]
pub enum MyError {
#[cfg(not(target_family = "wasm"))]
#[error("Error: {0}")]
Inner(#[from] Box<dyn std::error::Error + Send + Sync + 'static>),
#[cfg(target_family = "wasm")]
#[error("Error: {0}")]
Inner(#[from] Box<dyn std::error::Error + 'static>),
}