Skip to content

API Reference

This page is a quick, task-oriented reference to the public API. For the exhaustive, always-current API — every method, field, and trait bound — see the generated docs on docs.rs/kova-sdk.

Everything below assumes:

use kova_sdk::prelude::*;
AgentBuilder::new()
.provider(Arc<dyn LlmProvider>) // required
.system_prompt(impl Into<String>) // optional
.inference_config(InferenceConfig) // optional
.tool(Arc<dyn Tool>) // optional, repeatable
.tool_registry(ToolRegistry) // optional (composes with .tool())
.mcp_client(Arc<McpClient>, &str).await? // optional, async
.context_budget(u32) // optional (max prompt tokens; heuristic guard)
.max_iterations(usize) // optional (default 10)
.max_concurrent_tools(usize) // optional (default 10)
.retry_config(RetryConfig) // optional (default 2 retries)
.metrics(Arc<MetricsCollector>) // optional
.with_approval_handler(Arc<dyn ToolApprovalHandler>) // optional
.with_lifecycle_hook(Arc<dyn ToolLifecycleHook>) // optional
.build()? // -> Result<Agent, KovaError>
MethodRequiredDefaultNotes
provideryesThe only required piece
system_promptnoNonePrepended to every turn
inference_confignoall-Nonemodel, max_tokens, temperature, top_p, stop_sequences, response_format
toolnoRepeatable; merged into the registry on build
tool_registrynoemptyComposes with tool
mcp_clientnoAsync; discovers tools at build time
context_budgetnoNoneMax prompt tokens per call (heuristic); over-budget fails with ContextBudgetExceeded before the request
max_iterationsno10Cap on tool-call loop rounds
max_concurrent_toolsno10Semaphore cap for parallel tools
retry_configno2 retries, exp backoffRetryConfig::disabled() to turn off
metricsnoNoneAuto-records latency/tokens/errors/tool durations
with_approval_handlernoNoneGate before each tool execution
with_lifecycle_hooknoNoneObserve tool start/end

The agent is stateless — you own the conversation history. There is no session/memory API; pass a &[ConversationMessage] in, persist new_messages.

// One agentic turn over caller-owned history.
agent.run(messages: &[ConversationMessage]) -> Result<AgentResponse, KovaError>
// With per-call InferenceConfig overrides (unset fields fall back to defaults).
agent.run_with_config(messages, overrides: InferenceConfig) -> Result<AgentResponse, KovaError>
// Constrain the final text to a JSON schema and parse it into T.
agent.run_structured::<T>(messages, format: ResponseFormat) -> Result<(T, AgentResponse), KovaError>
// Pull-based streaming (see AgentEvent below).
agent.run_stream(messages) -> impl Stream<Item = Result<AgentEvent, KovaError>>
// Cancellable variants — the token aborts mid-provider-call / mid-tool with
// KovaError::Cancelled, producing no messages.
agent.run_cancellable(messages, cancel: CancellationToken) -> Result<AgentResponse, KovaError>
agent.run_stream_cancellable(messages, cancel: CancellationToken) -> impl Stream<…>
// Input tokens reported for the most recently completed turn (0 until the first).
agent.last_turn_input_tokens() -> u32
pub struct AgentResponse {
pub text: String, // final assistant text
pub new_messages: Vec<ConversationMessage>, // everything the turn produced — persist this
pub stop_reason: StopReason,
pub usage: UsageStats, // summed across all calls in the turn
pub llm_calls: u64,
pub thinking: Option<String>,
}
pub enum AgentEvent {
TextDelta { text: String },
ThinkingDelta { text: String },
ToolCallStarted { id: String, name: String, input: serde_json::Value },
ToolCallFinished { id: String, name: String, result: String, is_error: bool },
TurnCompleted { response: AgentResponse },
}

All implement LlmProvider and are wrapped in Arc before AgentBuilder::provider. Config surfaces are covered in Configuring a Provider.

// Anthropic (native Messages API; prompt caching + adaptive thinking)
kova_sdk::provider::anthropic::{AnthropicProvider, AnthropicProviderConfig}
// OpenAI-compatible
kova_sdk::provider::openai::{OpenAiCompatibleProvider, OpenAiProviderConfig}
// AWS Bedrock (BedrockProvider::new is async)
kova_sdk::provider::bedrock::{BedrockProvider, BedrockProviderConfig}
// Google Gemini
kova_sdk::provider::gemini::{GeminiProvider, GeminiProviderConfig}
// Ollama
kova_sdk::provider::ollama::{OllamaProvider, OllamaProviderConfig, OllamaThink}

The LlmProvider trait:

#[async_trait]
pub trait LlmProvider: Send + Sync {
async fn chat_completion(&self, messages: &[ConversationMessage], tools: &[ToolDefinition], config: &InferenceConfig) -> Result<ModelResponse, KovaError>;
async fn chat_completion_stream(&self, messages: &[ConversationMessage], tools: &[ToolDefinition], config: &InferenceConfig) -> Result<Pin<Box<dyn Stream<Item = Result<StreamEvent, KovaError>> + Send>>, KovaError>;
async fn list_models(&self) -> Result<Vec<ModelInfo>, KovaError>;
// Default: offline ~4-chars/token heuristic; Anthropic overrides it natively.
async fn count_tokens(&self, messages: &[ConversationMessage], tools: &[ToolDefinition]) -> Result<u32, KovaError>;
}
use kova_sdk::models::ResponseFormat;
let format = ResponseFormat::named("route", schema_value); // or ResponseFormat::new(schema_value)
let (value, response) = agent.run_structured::<MyType>(&messages, format).await?;

Mapped natively per provider (OpenAI/Anthropic/Gemini/Ollama); Bedrock rejects response_format. See Structured Output.

#[async_trait]
pub trait Tool: Send + Sync {
fn name(&self) -> &str;
fn description(&self) -> &str;
fn parameters_schema(&self) -> serde_json::Value;
async fn execute(&self, args: serde_json::Value) -> Result<ToolResult, KovaError>;
}
// Registry (Arc<RwLock<HashMap>> under the hood; synchronous methods)
let registry = ToolRegistry::new();
registry.register(Arc::new(MyTool)).await;
registry.get("my_tool").await; // Option<Arc<dyn Tool>>
registry.list().await; // Vec<String>
registry.tool_definitions().await; // Vec<ToolDefinition> (cached)

Built-in tools (tools / web-tools features)

Section titled “Built-in tools (tools / web-tools features)”
use kova_sdk::tools::{ToolPolicy, WebPolicy, register_all_tools, register_all_tools_with_policy, fetch_text};
pub struct ToolPolicy {
pub workspace_root: Option<PathBuf>,
pub protected_paths: Vec<PathBuf>,
pub shell_timeout: Duration,
pub web: WebPolicy,
}

See Built-in Tools & ToolPolicy.

pub enum ApprovalDecision { Approved, ApprovedForSession, Denied, DeniedWithReason(String), DeniedAlways }
#[async_trait]
pub trait ToolApprovalHandler: Send + Sync {
async fn approve(&self, tool_name: &str, args: &Value) -> ApprovalDecision;
}
// ToolLifecycleHook — observe tool start/end (register with .with_lifecycle_hook)

The agent holds no history — you own a Vec<ConversationMessage>, pass it to run, and persist response.new_messages. There is no MemoryStore. See Conversations & History.

use kova_sdk::embedding::EmbeddingProvider; // prelude re-export
use kova_sdk::embedding::openai::OpenAiEmbeddingProvider; // feature `openai`
use kova_sdk::embedding::ollama::OllamaEmbeddingProvider; // feature `ollama`
#[async_trait]
pub trait EmbeddingProvider: Send + Sync {
async fn embed(&self, texts: &[String]) -> Result<Vec<Vec<f32>>, KovaError>;
fn dimensions(&self) -> Option<usize> { None }
}
OpenAiEmbeddingProvider::new(base_url, model)?.with_api_key(key).with_dimensions(256);
OllamaEmbeddingProvider::new(base_url, model)?;

See Embeddings.

use kova_sdk::mcp::{McpClient, McpTransport, TokenProvider};
McpTransport::Stdio { command: String, args: Vec<String>, env: HashMap<String, String> }
McpTransport::HttpSse { url: String, headers: HashMap<String, String> }
McpTransport::StreamableHttp { url: String, headers: HashMap<String, String>, auth: Option<Arc<dyn TokenProvider>> }
McpClient::connect(transport).await?
McpClient::connect_with_timeout(transport, Duration).await?
client.tools_list().await? // cached until reconnect
client.tools_call(name, args).await? // (content, is_error); auto-reconnects once on Connection
client.tools_call_with_timeout(name, args, Duration).await?
client.reconnect().await? // re-establish + clear tools/list cache

See Connecting MCP Servers.

Pull-based only — run_stream / run_stream_cancellable yield AgentEvents (see above). No handler to register. See Streaming.

use kova_sdk::telemetry::{TelemetryConfig, ExporterConfig, OtlpProtocol, MetricsCollector};
TelemetryConfig::builder()
.service_name("my-agent")
.log_level(tracing::Level::INFO)
.exporter(ExporterConfig::Otlp { endpoint, protocol: OtlpProtocol::Grpc }) // needs `telemetry`
.sampling_rate(0.5)
.build()
.init()?;
let m = MetricsCollector::new(); // always available
m.record_llm_request(latency_ms, in_tokens, out_tokens);
m.record_tool_invocation(duration_ms, success);
m.record_llm_error();
m.llm_request_count(); m.total_tokens(); m.error_count();
TypeShape
RoleUser · Assistant · System · Tool
ContentBlockText { text } · ToolUse { id, name, input, provider_metadata } · ToolResult { tool_use_id, content, is_error } · Thinking { thinking, signature }
ConversationMessage{ role: Role, content: Vec<ContentBlock> }
ModelResponse{ content, stop_reason, usage: Option<UsageStats>, thinking: Option<String> }
StopReasonEndTurn · ToolUse · MaxTokens · Unknown(String) (as_str() for logging)
UsageStats{ input_tokens, output_tokens, total_tokens, thinking_tokens, cache_read_tokens, cache_creation_tokens: Option<u32> }
InferenceConfig{ model, max_tokens, temperature, top_p, stop_sequences, response_format } — all Option
ResponseFormat{ name: Option<String>, schema: Value }::named(name, schema) / ::new(schema)
ToolDefinition{ name, description, parameters: Value }
ToolResult{ content: String, is_error: bool }
StreamEventContentDelta · ThinkingDelta · ThinkingBlock { thinking, signature } · ToolUseDelta { index, .. } · UsageEvent { input_tokens, output_tokens, thinking_tokens, cache_read_tokens, cache_creation_tokens } · StopEvent · Error
ModelInfo{ id, object, created, owned_by }
ProviderErrorClassAuthInvalid · AuthForbidden · RateLimited { retry_after } · Overloaded · InvalidRequest · NotFound · Other
KovaErrorsee Error Types