The Tinfoil Rust SDK is a thin wrapper around the async-openai crate that provides secure communication with Tinfoil enclaves. It uses the same types and request/response shapes as async-openai and adds automatic verification that the endpoint is running in a secure Tinfoil enclave, TLS certificate pinning, and attestation validation.
Migrating from async-openai to Tinfoil is a small change — the SDK exposes the same chat / audio / embeddings handlers under tinfoil::Client:
// Before (async-openai)- use async_openai::{Client, config::OpenAIConfig};-- let client = Client::with_config(- OpenAIConfig::new().with_api_key("OPENAI_API_KEY")- );// After (Tinfoil)+ use tinfoil::Client;++ let client = Client::new_default().await?;
tinfoil::Client derefs to async_openai::Client, so all OpenAI methods (client.chat(), client.embeddings(), client.audio(), …) work unchanged.
use tinfoil::Client;#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { // 1. Create a client let client = Client::new_default().await?; // 2. Use client as you would async_openai::Client // see https://docs.rs/async-openai for API documentation Ok(())}
The common request/response types live under tinfoil::chat, tinfoil::audio, and tinfoil::embeddings — all re-exports of the corresponding async_openai::types::* modules.
use tinfoil::chat::{ ChatCompletionRequestMessage, ChatCompletionRequestUserMessage, ChatCompletionRequestUserMessageContent, CreateChatCompletionRequestArgs,};use tinfoil::Client;#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let client = Client::new_default().await?; let request = CreateChatCompletionRequestArgs::default() .model("deepseek-v4-pro") .messages(vec![ChatCompletionRequestMessage::User( ChatCompletionRequestUserMessage { content: ChatCompletionRequestUserMessageContent::Text( "Summarize the key risks in a large technical migration plan.".to_string(), ), name: None, }, )]) .build()?; let response = client.chat().create(request).await?; println!("{}", response.choices[0].message.content.as_deref().unwrap_or("")); Ok(())}
GLM-5.1
use tinfoil::chat::{ ChatCompletionRequestMessage, ChatCompletionRequestUserMessage, ChatCompletionRequestUserMessageContent, CreateChatCompletionRequestArgs,};use tinfoil::Client;#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let client = Client::new_default().await?; let request = CreateChatCompletionRequestArgs::default() .model("glm-5-1") .messages(vec![ChatCompletionRequestMessage::User( ChatCompletionRequestUserMessage { content: ChatCompletionRequestUserMessageContent::Text( "Solve this step by step: If a train travels 120 miles in 2 hours, \ and then increases its speed by 25% for the next 3 hours, how far \ does it travel in total?".to_string(), ), name: None, }, )]) .build()?; let response = client.chat().create(request).await?; println!("{}", response.choices[0].message.content.as_deref().unwrap_or("")); Ok(())}
Qwen3-VL 30B
use tinfoil::chat::{ ChatCompletionRequestMessage, ChatCompletionRequestMessageContentPartImage, ChatCompletionRequestUserMessage, ChatCompletionRequestUserMessageContent, ChatCompletionRequestUserMessageContentPart, CreateChatCompletionRequestArgs, ImageUrl,};use tinfoil::multimodal::ImageUrlExt;use tinfoil::Client;#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let client = Client::new_default().await?; // ImageUrlExt::from_path encodes the file as a base64 data URL. let image = ImageUrl::from_path("image.jpg")?; let request = CreateChatCompletionRequestArgs::default() .model("qwen3-vl-30b") .messages(vec![ChatCompletionRequestMessage::User( ChatCompletionRequestUserMessage { content: ChatCompletionRequestUserMessageContent::Array(vec![ ChatCompletionRequestUserMessageContentPart::Text( "What's in this image?".into(), ), ChatCompletionRequestUserMessageContentPart::ImageUrl( ChatCompletionRequestMessageContentPartImage { image_url: image }, ), ]), name: None, }, )]) .build()?; let response = client.chat().create(request).await?; println!("{}", response.choices[0].message.content.as_deref().unwrap_or("")); Ok(())}
Kimi K2.6
use tinfoil::chat::{ ChatCompletionRequestMessage, ChatCompletionRequestUserMessage, ChatCompletionRequestUserMessageContent, CreateChatCompletionRequestArgs,};use tinfoil::Client;#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let client = Client::new_default().await?; let request = CreateChatCompletionRequestArgs::default() .model("kimi-k2-6") .messages(vec![ChatCompletionRequestMessage::User( ChatCompletionRequestUserMessage { content: ChatCompletionRequestUserMessageContent::Text( "Review this codebase and suggest architectural improvements for \ better modularity and testability.".to_string(), ), name: None, }, )]) .build()?; let response = client.chat().create(request).await?; println!("{}", response.choices[0].message.content.as_deref().unwrap_or("")); Ok(())}
Gemma 4 31B
use tinfoil::chat::{ ChatCompletionRequestMessage, ChatCompletionRequestUserMessage, ChatCompletionRequestUserMessageContent, CreateChatCompletionRequestArgs,};use tinfoil::Client;#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let client = Client::new_default().await?; let request = CreateChatCompletionRequestArgs::default() .model("gemma4-31b") .messages(vec![ChatCompletionRequestMessage::User( ChatCompletionRequestUserMessage { content: ChatCompletionRequestUserMessageContent::Text( "Explain the trade-offs between accuracy and latency in a production AI system.".to_string(), ), name: None, }, )]) .build()?; let response = client.chat().create(request).await?; println!("{}", response.choices[0].message.content.as_deref().unwrap_or("")); Ok(())}
Llama 3.3 70B
use tinfoil::chat::{ ChatCompletionRequestMessage, ChatCompletionRequestUserMessage, ChatCompletionRequestUserMessageContent, CreateChatCompletionRequestArgs,};use tinfoil::Client;#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let client = Client::new_default().await?; let request = CreateChatCompletionRequestArgs::default() .model("llama3-3-70b") .messages(vec![ChatCompletionRequestMessage::User( ChatCompletionRequestUserMessage { content: ChatCompletionRequestUserMessageContent::Text( "What are the key differences between renewable and non-renewable \ energy sources?".to_string(), ), name: None, }, )]) .build()?; let response = client.chat().create(request).await?; println!("{}", response.choices[0].message.content.as_deref().unwrap_or("")); Ok(())}
GPT-OSS 120B
use tinfoil::chat::{ ChatCompletionRequestMessage, ChatCompletionRequestUserMessage, ChatCompletionRequestUserMessageContent, CreateChatCompletionRequestArgs,};use tinfoil::Client;#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let client = Client::new_default().await?; let request = CreateChatCompletionRequestArgs::default() .model("gpt-oss-120b") .messages(vec![ChatCompletionRequestMessage::User( ChatCompletionRequestUserMessage { content: ChatCompletionRequestUserMessageContent::Text( "Analyze the trade-offs between different database architectures for \ a high-traffic e-commerce platform.".to_string(), ), name: None, }, )]) .build()?; let response = client.chat().create(request).await?; println!("{}", response.choices[0].message.content.as_deref().unwrap_or("")); Ok(())}
Tinfoil exposes a few request/response fields the upstream OpenAI schema does not (for example web_search_options, pii_check_options, structured-output regex / choice constraints, and custom finish_reason values). To use them without async-openai rejecting the response, send the request through client.chat_relaxed():
use serde_json::json;use tinfoil::Client;#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let client = Client::new_default().await?; let body = client.chat_relaxed().request() .model("gpt-oss-120b") .messages([json!({ "role": "user", "content": "What's the latest on AI safety?" })]) .web_search_with_options(json!({ "max_results": 5 })); let response = client.chat_relaxed().create(body).await?; if let Some(content) = response.content() { println!("{}", content); } Ok(())}
Streaming uses the same builder and surfaces vendor-specific events (such as web_search_call) verbatim:
use futures_util::StreamExt;use serde_json::json;use tinfoil::Client;#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let client = Client::new_default().await?; let body = client.chat_relaxed().request() .model("gpt-oss-120b") .messages([json!({ "role": "user", "content": "Explain quantum entanglement" })]); let mut stream = client.chat_relaxed().create_stream(body).await?; while let Some(chunk) = stream.next().await { let chunk = chunk?; if let Some(delta) = chunk.delta_content() { print!("{}", delta); } else if chunk.is_vendor_event() { println!("[event] {:?}", chunk.event_type()); } } Ok(())}
This library is a thin wrapper around the async-openai crate that can be used with Tinfoil. All methods and types are identical. See the async-openai documentation for complete API usage.