# OpenCode SDK for Rust The OpenCode SDK for Rust (`opencode-sdk-rs`) is a type-safe client library for interacting with the OpenCode platform API. It provides complete coverage of all API resources including App, Config, Event, File, Find, Session, and Tui endpoints. The SDK is built on `tokio` and `hpx` for high-performance async HTTP operations with automatic retries using exponential backoff, structured error handling, and Server-Sent Events (SSE) streaming support. The library follows idiomatic Rust patterns with builder configuration, typed request/response structs using `serde` serialization, and a comprehensive error hierarchy. It supports environment-based configuration via `OPENCODE_BASE_URL` and programmatic configuration through the builder pattern. All API responses are strongly typed, enabling compile-time safety when working with the OpenCode platform. ## Client Initialization Create and configure the OpenCode client using environment variables or the builder pattern. ```rust use opencode_sdk_rs::{Opencode, OpencodeError}; use std::time::Duration; #[tokio::main] async fn main() -> Result<(), OpencodeError> { // Create client from environment (reads OPENCODE_BASE_URL or defaults to localhost:54321) let client = Opencode::new()?; // Or use the builder for custom configuration let client = Opencode::builder() .base_url("http://my-server:54321") .timeout(Duration::from_secs(30)) .max_retries(3) .build()?; println!("Connected to: {}", client.base_url()); Ok(()) } ``` ## App Resource Access application information, initialize the app, list available modes, and retrieve provider configurations. ```rust use opencode_sdk_rs::{Opencode, OpencodeError}; use opencode_sdk_rs::resources::{AppLogParams, LogLevel}; use std::collections::HashMap; #[tokio::main] async fn main() -> Result<(), OpencodeError> { let client = Opencode::new()?; // Get application information let app = client.app().get(None).await?; println!("Hostname: {}", app.hostname); println!("Git repository: {}", app.git); println!("Root path: {}", app.path.root); println!("CWD: {}", app.path.cwd); // Initialize the application let initialized = client.app().init(None).await?; println!("App initialized: {}", initialized); // List available operational modes let modes = client.app().modes(None).await?; for mode in &modes { println!("Mode: {} with {} tools", mode.name, mode.tools.len()); if let Some(model) = &mode.model { println!(" Model: {} ({})", model.model_id, model.provider_id); } } // Get providers and their default models let providers = client.app().providers(None).await?; for provider in &providers.providers { println!("Provider: {} ({})", provider.name, provider.id); for (model_id, model) in &provider.models { println!(" Model: {} - context: {} tokens", model.name, model.limit.context); } } // Send a log entry let log_params = AppLogParams { level: LogLevel::Info, message: "SDK connection established".to_string(), service: "my-app".to_string(), extra: Some(HashMap::from([ ("version".into(), serde_json::json!("1.0.0")), ])), }; client.app().log(&log_params, None).await?; Ok(()) } ``` ## Session Resource Manage conversation sessions including creation, chat messages, listing, sharing, and reverting. ```rust use opencode_sdk_rs::{Opencode, OpencodeError}; use opencode_sdk_rs::resources::{ SessionChatParams, SessionInitParams, SessionRevertParams, SessionSummarizeParams, PartInput, TextPartInput, Message, Part }; use std::collections::HashMap; #[tokio::main] async fn main() -> Result<(), OpencodeError> { let client = Opencode::new()?; // Create a new session let session = client.session().create(None).await?; println!("Created session: {} - {}", session.id, session.title); // Send a chat message let chat_params = SessionChatParams { model_id: "gpt-4o".to_string(), provider_id: "openai".to_string(), parts: vec![ PartInput::Text(TextPartInput { text: "Hello, can you help me write a function?".to_string(), id: None, synthetic: None, time: None, }), ], message_id: None, mode: Some("code".to_string()), system: None, tools: Some(HashMap::from([ ("bash".into(), true), ("file_write".into(), true), ])), }; let response = client.session().chat(&session.id, &chat_params, None).await?; println!("Response from {}: cost ${:.6}", response.model_id, response.cost); println!("Tokens - input: {}, output: {}", response.tokens.input, response.tokens.output); // List all sessions let sessions = client.session().list(None).await?; for s in &sessions { println!("Session: {} - {} (updated: {})", s.id, s.title, s.time.updated); } // Get messages from a session let messages = client.session().messages(&session.id, None).await?; for item in &messages { match &item.info { Message::User(u) => println!("User message: {}", u.id), Message::Assistant(a) => println!("Assistant message: {} ({})", a.id, a.mode), } for part in &item.parts { match part { Part::Text(t) => println!(" Text: {}...", &t.text[..50.min(t.text.len())]), Part::Tool(t) => println!(" Tool: {} - {:?}", t.tool, t.state), _ => {} } } } // Share a session let shared = client.session().share(&session.id, None).await?; if let Some(share) = &shared.share { println!("Shared at: {}", share.url); } // Revert to a previous message let revert_params = SessionRevertParams { message_id: "msg_001".to_string(), part_id: None, }; let reverted = client.session().revert(&session.id, &revert_params, None).await?; println!("Reverted to message: {:?}", reverted.revert); // Summarize a session let summarize_params = SessionSummarizeParams { model_id: "gpt-4o-mini".to_string(), provider_id: "openai".to_string(), }; client.session().summarize(&session.id, &summarize_params, None).await?; // Delete a session client.session().delete(&session.id, None).await?; Ok(()) } ``` ## File Resource Read file contents and check file status in the project. ```rust use opencode_sdk_rs::{Opencode, OpencodeError}; use opencode_sdk_rs::resources::{FileReadParams, FileStatus}; #[tokio::main] async fn main() -> Result<(), OpencodeError> { let client = Opencode::new()?; // Read a file's content let read_params = FileReadParams { path: "src/main.rs".to_string(), }; let file_content = client.file().read(&read_params).await?; println!("File type: {:?}", file_content.file_type); println!("Content length: {} bytes", file_content.content.len()); println!("First 200 chars: {}...", &file_content.content[..200.min(file_content.content.len())]); // Get status of all modified files in the project let files = client.file().status().await?; println!("\nModified files ({}):", files.len()); for file in &files { let status_str = match file.status { FileStatus::Added => "A", FileStatus::Modified => "M", FileStatus::Deleted => "D", }; println!(" [{}] {} (+{}, -{})", status_str, file.path, file.added, file.removed); } Ok(()) } ``` ## Find Resource Search for files, symbols, and text patterns in the codebase. ```rust use opencode_sdk_rs::{Opencode, OpencodeError}; use opencode_sdk_rs::resources::{FindFilesParams, FindSymbolsParams, FindTextParams}; #[tokio::main] async fn main() -> Result<(), OpencodeError> { let client = Opencode::new()?; // Search for files by name pattern let files_params = FindFilesParams { query: "*.rs".to_string(), }; let files = client.find().files(&files_params).await?; println!("Found {} Rust files:", files.len()); for path in &files { println!(" {}", path); } // Search for symbols (functions, structs, etc.) let symbols_params = FindSymbolsParams { query: "Opencode".to_string(), }; let symbols = client.find().symbols(&symbols_params).await?; println!("\nFound {} symbols:", symbols.len()); for symbol in &symbols { println!(" {} (kind: {}) at {}:{}:{}", symbol.name, symbol.kind, symbol.location.uri, symbol.location.range.start.line, symbol.location.range.start.character ); } // Search for text patterns in files let text_params = FindTextParams { pattern: "TODO".to_string(), }; let matches = client.find().text(&text_params).await?; println!("\nFound {} TODO comments:", matches.len()); for m in &matches { println!(" {}:{} - {}", m.path.text, m.line_number, m.lines.text.trim()); for submatch in &m.submatches { println!(" Match at offset {}-{}: '{}'", submatch.start, submatch.end, submatch.match_info.text); } } Ok(()) } ``` ## Config Resource Retrieve the current application configuration including providers, modes, and keybindings. ```rust use opencode_sdk_rs::{Opencode, OpencodeError}; use opencode_sdk_rs::resources::{ShareMode, Layout, McpConfig}; #[tokio::main] async fn main() -> Result<(), OpencodeError> { let client = Opencode::new()?; // Get the full configuration let config = client.config().get(None).await?; // Check theme and basic settings if let Some(theme) = &config.theme { println!("Theme: {}", theme); } if let Some(model) = &config.model { println!("Default model: {}", model); } if let Some(username) = &config.username { println!("Username: {}", username); } // Check share mode match &config.share { Some(ShareMode::Auto) => println!("Auto-sharing enabled"), Some(ShareMode::Manual) => println!("Manual sharing"), Some(ShareMode::Disabled) => println!("Sharing disabled"), None => println!("Share mode not configured"), } // List configured providers if let Some(providers) = &config.provider { println!("\nConfigured providers:"); for (name, provider_config) in providers { println!(" {} - {} models", name, provider_config.models.len()); if let Some(api) = &provider_config.api { println!(" API: {}", api); } } } // List MCP servers if let Some(mcp) = &config.mcp { println!("\nMCP servers:"); for (name, mcp_config) in mcp { match mcp_config { McpConfig::Local(local) => { println!(" {} (local): {:?}", name, local.command); } McpConfig::Remote(remote) => { println!(" {} (remote): {}", name, remote.url); } } } } // Check mode configurations if let Some(modes) = &config.mode { println!("\nMode configurations:"); for (mode_name, mode_config) in modes { println!(" {}: model={:?}, disabled={:?}", mode_name, mode_config.model, mode_config.disable); } } Ok(()) } ``` ## Event Resource (SSE Streaming) Subscribe to real-time server-sent events for session updates, file changes, and more. ```rust use opencode_sdk_rs::{Opencode, OpencodeError}; use opencode_sdk_rs::resources::EventListResponse; use futures_core::Stream; #[tokio::main] async fn main() -> Result<(), OpencodeError> { let client = Opencode::new()?; // Subscribe to the SSE event stream let mut stream = client.event().list().await?; println!("Listening for events..."); // Process events as they arrive use futures_core::StreamExt; while let Some(event_result) = stream.next().await { match event_result { Ok(event) => { match event { EventListResponse::SessionUpdated { properties } => { println!("Session updated: {} - {}", properties.info.id, properties.info.title); } EventListResponse::SessionDeleted { properties } => { println!("Session deleted: {}", properties.info.id); } EventListResponse::SessionIdle { properties } => { println!("Session idle: {}", properties.session_id); } EventListResponse::SessionError { properties } => { if let Some(err) = &properties.error { println!("Session error: {:?}", err); } } EventListResponse::MessageUpdated { properties } => { println!("Message updated: {:?}", properties.info); } EventListResponse::MessagePartUpdated { properties } => { println!("Part updated: {:?}", properties.part); } EventListResponse::FileEdited { properties } => { println!("File edited: {}", properties.file); } EventListResponse::FileWatcherUpdated { properties } => { println!("File watcher: {:?} - {}", properties.event, properties.file); } EventListResponse::InstallationUpdated { properties } => { println!("Installation updated to version: {}", properties.version); } EventListResponse::PermissionUpdated { properties } => { println!("Permission updated: {} - {}", properties.id, properties.title); } _ => println!("Other event received"), } } Err(e) => { eprintln!("Event stream error: {}", e); break; } } } Ok(()) } ``` ## TUI Resource Control the terminal user interface programmatically. ```rust use opencode_sdk_rs::{Opencode, OpencodeError}; use opencode_sdk_rs::resources::TuiAppendPromptParams; #[tokio::main] async fn main() -> Result<(), OpencodeError> { let client = Opencode::new()?; // Append text to the TUI prompt let params = TuiAppendPromptParams { text: "Write a function to calculate fibonacci numbers".to_string(), }; let success = client.tui().append_prompt(¶ms).await?; println!("Prompt appended: {}", success); // Open the help panel let help_opened = client.tui().open_help().await?; println!("Help panel opened: {}", help_opened); Ok(()) } ``` ## Error Handling Handle SDK errors with the typed error hierarchy for proper retry logic and error recovery. ```rust use opencode_sdk_rs::{Opencode, OpencodeError}; #[tokio::main] async fn main() { let client = match Opencode::new() { Ok(c) => c, Err(e) => { eprintln!("Failed to create client: {}", e); return; } }; // Handle different error types match client.session().list(None).await { Ok(sessions) => { println!("Found {} sessions", sessions.len()); } Err(OpencodeError::Api { status: 404, .. }) => { eprintln!("Resource not found"); } Err(OpencodeError::Api { status: 401, .. }) => { eprintln!("Authentication required"); } Err(OpencodeError::Api { status: 429, message, .. }) => { eprintln!("Rate limited: {}", message); } Err(OpencodeError::Api { status, message, .. }) if status >= 500 => { eprintln!("Server error {}: {}", status, message); } Err(OpencodeError::Timeout) => { eprintln!("Request timed out - will be automatically retried"); } Err(OpencodeError::Connection { message, .. }) => { eprintln!("Connection error: {}", message); } Err(e) if e.is_retryable() => { eprintln!("Transient error (retryable): {}", e); } Err(e) => { eprintln!("Fatal error: {}", e); // Check if it's a timeout if e.is_timeout() { eprintln!("This was a timeout error"); } // Get status code if available if let Some(status) = e.status() { eprintln!("HTTP status: {}", status); } } } } ``` ## Request Options Override Override default client settings on a per-request basis. ```rust use opencode_sdk_rs::{Opencode, OpencodeError, RequestOptions}; use http::HeaderMap; use std::time::Duration; #[tokio::main] async fn main() -> Result<(), OpencodeError> { let client = Opencode::new()?; // Create custom request options let mut extra_headers = HeaderMap::new(); extra_headers.insert("X-Custom-Header", "custom-value".parse().unwrap()); extra_headers.insert("X-Request-ID", "req-12345".parse().unwrap()); let options = RequestOptions { extra_headers: Some(extra_headers), timeout: Some(Duration::from_secs(60)), // Override timeout max_retries: Some(5), // Override max retries }; // Use custom options for a specific request let app = client.app().get(Some(&options)).await?; println!("App info with custom options: {}", app.hostname); // Default options (None) use client-level settings let sessions = client.session().list(None).await?; println!("Sessions with default options: {}", sessions.len()); Ok(()) } ``` ## Summary The OpenCode SDK for Rust provides a comprehensive and type-safe interface for building applications that integrate with the OpenCode platform. Common use cases include building AI-powered development tools, creating automated coding assistants, integrating OpenCode capabilities into IDEs or CLI tools, and monitoring session activity through the SSE event stream. The SDK handles all the complexity of HTTP communication, retry logic, and response parsing, allowing developers to focus on application logic. Integration patterns typically involve creating a single `Opencode` client instance that can be shared across your application (the client is `Clone`-able). For long-running applications, subscribing to the event stream enables real-time updates without polling. The builder pattern allows fine-tuning of timeouts and retry behavior for different deployment environments, while per-request options provide flexibility for specific API calls that may need different settings.