Compact Universal Client for Agents

What CUCA is

An asynchronous Rust client for LLM backends: one unified request, one typed block stream, eight provider adapters, twelve plugins and six services.

In one line
One UnifiedRequest goes in, one AgentResponseStream of typed blocks comes out, whichever backend answers
You need
Rust 1.98 or newer, and a checkout of the repository
Read this if
You are deciding whether cuca fits, or looking for where a fact lives on this site

cuca is a Rust library crate for talking to language models. It builds a request, sends it to one of eight providers, and gives the caller a stream of normalized blocks. Seven providers are HTTP servers and one runs a GGUF model inside your process. Everything beyond that core sits behind a Cargo feature.

The shape of a turn

A caller builds a UnifiedRequest, passes it to CucaClient::generate_stream, and polls the returned AgentResponseStream. Plugins can change the request on the way out and each block on the way back.

One turn through CucaClient::generate_stream Five stages, left to right. A UnifiedRequest passes through the on_request hook of every registered plugin. The provider adapter sends it to the backend and translates the reply. The adapter produces a stream of MessageContentBlock values. Each block passes through every plugin's on_stream_chunk hook before it reaches the caller. When the stream ends, every plugin's on_response_complete hook runs once. One turn through CucaClient::generate_stream Unified Request plugins on_request provider adapter MessageContent Block stream plugins on_stream_chunk on_response_complete runs once when the stream ends

Vendor protocols exist only inside the provider adapter. The caller and the plugins see the same types for every backend.

Each stream item is a Result<MessageContentBlock, CucaError>. MessageContentBlock has five variants: Text, ImageBase64, Thinking, ToolCall and ToolResult.

pub type AgentResponseStream =
    Pin<Box<dyn Stream<Item = Result<MessageContentBlock, CucaError>> + Send>>;

The same five variants arrive from Anthropic's Messages API, Gemini's streamGenerateContent, a llama.cpp server on loopback, or the in-process model. Why the request has the fields it has is on The unified request and stream.

Nothing compiles by default

The crate sets default = []. Its eight provider adapters each sit behind a provider-* feature, and there are twelve plugin-* features and six service-* features. src/lib.rs stops the build with a compile_error! until at least one provider-* feature is on.

CUCA requires one provider-* feature; enable the adapter for the backend you use.

No backend and no dependency is implicit. reqwest, tokio, wasmtime, rmcp, tiktoken-rs, jsonschema and opentelemetry are optional and enabled only by the feature that needs them. The feature grid and the edges between features are on The feature matrix.

Where to go

  • Stream a first reply: a local llama.cpp server, one cargo command, text on stdout.
  • The unified request and stream: the design behind the request and block types.
  • Providers: eight adapters, each with its default base URL, auth header, route and behavior.
  • Plugins: twelve pipeline capabilities, each with its feature flag, entry type, hooks, config defaults and caps.
  • Services: six explicit-call capabilities, driven by direct method calls instead of hooks.
  • How-to guides: point the client at another server, write a plugin, run the live suite.
  • Reference: the feature matrix, error types, environment variables and the public API surface.
  • Rustdoc holds signatures, field docs and generic bounds for each item.

Next page: Stream a first reply.

↑↓ to navigate ↵ to open esc to close