Use the provider-neutral model protocol
Understand requests, ordered content, capability negotiation, canonical streaming, responses, usage, and provider extensions.
The Model boundary
runifold-model is usable without an Agent. Its object-safe Model contract
opens a canonical event stream and can collect that stream into the same
ModelResponse used by non-streaming callers. ModelCallContext carries only
invocation identity, optional owning Run identity, deadline, and cancellation.
Credentials, endpoints, HTTP clients, retries, and routing remain adapter or middleware configuration. This keeps the invocation protocol composable and testable.
Lossless content
Messages contain ordered content parts rather than one flattened string. Normalized variants preserve text, media, reasoning, tool calls, tool results, refusals, citations, and usage. Unknown provider data uses namespaced extensions instead of being silently discarded.
Visible answer text and reasoning remain distinct. Application code that needs
the whole response should inspect ModelResponse; concatenating text deltas
throws away the evidence required for tools, cost, diagnostics, and safety.
Capability negotiation
ModelCapabilities describes feature support as native, emulated,
unsupported, or unknown. A request policy can require strict support, allow
emulation, or accept best effort. Degradation must appear as warnings.
Treat capability discovery as evidence for one provider/model combination, not a universal promise. Test structured output, reasoning, tool calling, embeddings, media, and usage details on the exact route you deploy.
Canonical streaming
A valid stream starts once, opens and completes indexed content blocks, may emit usage snapshots or provider events, and completes exactly once. Ending without a terminal event, completing with an open block, or emitting after completion is a protocol error.
Retry or fallback may happen only before the first canonical event commits the stream. After visible output begins, switching routes could duplicate content or mix two provider responses.
Direct model calls
Use the model layer directly when you need request/response control but not an Agent loop: proxying a model, benchmarking adapters, running batch transformations, implementing custom orchestration, or testing provider semantics.
Use an Agent when the model must repeatedly choose Tools or child Agents. Use a Workflow when application code owns deterministic transitions. These layers compose; none is a mandatory wrapper around the others.
Invoke the low-level Model API
use runifold::{
Message, Model, ModelCallContext, ModelRef, ModelRequest,
};
use runifold_providers::openai::OpenAiClient;
let client = OpenAiClient::from_api_key(std::env::var("OPENAI_API_KEY")?)?;
let request = ModelRequest::new(
ModelRef::new("openai", "gpt-5"),
Message::user("Explain lease fencing in one paragraph"),
);
let response = client.invoke(request, ModelCallContext::new()).await?;
for part in response.content {
if let Some(text) = part.as_text() {
println!("{text}");
}
}Use this surface when building an adapter or when you explicitly do not need
the Agent loop. Inspect canonical content, finish reason, usage, warnings, and
provider extensions; do not parse a provider's raw JSON in application code.
Add cancellation and deadline to ModelCallContext before production.