4 releases (1 stable)
Uses new Rust 2024
| new 1.0.0 | Feb 9, 2026 |
|---|---|
| 0.9.91 | Feb 9, 2026 |
| 0.9.9 | Feb 9, 2026 |
| 0.1.0 | Feb 7, 2026 |
#312 in Asynchronous
Used in 6 crates
120KB
3K
SLoC
Model Provider API
fprovider defines the model provider abstraction for Fiddlesticks.
Its job is simple:
Provide a clean, provider-agnostic way to talk to language models.
Everything else in the system (chat, agents, tools) depends on this layer instead of directly coupling to OpenAI, Anthropic, or anything else.
What lives here
- Core provider traits
- Provider-agnostic request / response types
- Streaming abstractions (tokens, tool calls, events)
- Provider-specific adapters (behind features)
This crate does not:
- Define agent logic
- Define conversation state machines
- Execute tools
- Manage memory or persistence
Those concerns live higher up the stack.
Supported Providers
The currently supported providers are:
- OpenCode Zen
- OpenAI
- Anthropic
Each provider implements the same core traits so they can be swapped without changing agent or chat logic.
Design Goals
- Minimal surface area – only what every provider must support
- Async-first – providers are expected to be network-bound
- Streaming-friendly – even if some providers start non-streaming
- Feature-gated implementations – avoid pulling heavy deps unless needed
- No provider leakage – downstream crates should not need provider-specific types
High-Level Flow
fharness / fchat
|
v
fprovider (traits + adapters)
|
v
External model APIs
Using fprovider from other crates
1) Add dependency
Provider-agnostic usage (recommended default):
[dependencies]
fprovider = { path = "../fprovider" }
If your crate needs OpenAI adapter support, enable the feature:
[dependencies]
fprovider = { path = "../fprovider", features = ["provider-openai"] }
2) Build requests with provider-agnostic types
use fprovider::{Message, ModelRequest, Role};
let request = ModelRequest::builder("gpt-4o-mini")
.message(Message::new(Role::User, "Summarize this file"))
.temperature(0.2)
.max_tokens(512)
.build()?;
3) Depend on traits, not SDK types
Higher crates should accept dyn ModelProvider so provider choice is runtime-configurable:
use std::sync::Arc;
use fprovider::{ModelProvider, ModelRequest, ProviderError};
pub async fn run_once(
provider: Arc<dyn ModelProvider>,
request: ModelRequest,
) -> Result<(), ProviderError> {
let response = provider.complete(request).await?;
let _ = response;
Ok(())
}
4) Register and resolve providers
use fprovider::{ProviderId, ProviderRegistry};
let mut registry = ProviderRegistry::new();
// registry.register(openai_provider);
let provider = registry
.get(ProviderId::OpenAi)
.expect("OpenAI provider is not registered");
5) OpenAI adapter example
use std::sync::Arc;
use reqwest::Client;
use fprovider::{ProviderRegistry, SecureCredentialManager};
use fprovider::adapters::openai::{OpenAiHttpTransport, OpenAiProvider};
let credentials = Arc::new(SecureCredentialManager::new());
credentials.set_openai_api_key("sk-...")?;
let transport = Arc::new(OpenAiHttpTransport::new(Client::new()));
let openai = OpenAiProvider::new(credentials, transport);
let mut registry = ProviderRegistry::new();
registry.register(openai);
6) Streaming consumption
stream(...) returns a stream implementing futures_core::Stream<Item = Result<StreamEvent, ProviderError>>.
This is provider-agnostic and works with standard async ecosystem helpers.
Stream invariants:
- Events are emitted in provider/source order.
- Delta events (
TextDelta,ToolCallDelta) can appear zero or more times. - Completion milestones (
MessageComplete,ResponseComplete) when present arrive after deltas. - Once the stream returns
None, no additional events are emitted.
use futures_util::StreamExt;
use fprovider::prelude::*;
let mut events = provider.stream(request).await?;
while let Some(event) = events.next().await {
match event? {
StreamEvent::TextDelta(delta) => {
let _ = delta;
}
StreamEvent::ToolCallDelta(_) => {}
StreamEvent::MessageComplete(_) => {}
StreamEvent::ResponseComplete(_) => {}
}
}
7) OpenAI auth policy
When provider-openai is enabled, OpenAiProvider only uses API key credentials configured via SecureCredentialManager::set_openai_api_key.
8) Credential lifecycle and auditing
SecureCredentialManager now supports lifecycle metadata and access auditing hooks:
set_api_key_with_ttl(...)to expire API keys after a fixed TTLrotate_api_key(...)andrevoke(...)for explicit key rotation/revocationcredential_metadata(...)for sanitized metadata (created_at,expires_at,last_used_at, access counters)with_observer(...)for audit events that include provider/kind/action and never include secret values
9) Standard retry/backoff and operational hooks
fprovider exposes provider-agnostic resilience primitives:
RetryPolicy: standardized retry attempt limits and exponential backoff settingsProviderOperationHooks: lifecycle hooks for attempts, retries, success, and failureexecute_with_retry(...): helper that applies policy + hooks around async operations
Example:
use std::time::Duration;
use fprovider::prelude::*;
let policy = RetryPolicy {
max_attempts: 4,
initial_backoff: Duration::from_millis(100),
max_backoff: Duration::from_secs(2),
backoff_multiplier: 2.0,
};
let hooks = NoopOperationHooks;
let value = execute_with_retry(
ProviderId::OpenAi,
"complete",
&policy,
&hooks,
|_attempt| async { Ok::<_, ProviderError>("ok") },
|_delay| async {},
)
.await?;
let _ = value;
Feature flags
provider-openai: OpenAI adapter and HTTP transportprovider-anthropic: Anthropic adapter over OpenAI-compatible transportprovider-opencode-zen: OpenCode Zen adapter over OpenAI-compatible transport
Dependencies
~0–15MB
~121K SLoC