A simple .NET client library for the OpenCode API, enabling easy integration of OpenCode's AI coding capabilities into C# applications.
This is a Proof of Concept (POC) .NET client for OpenCode API, built with:
- Refit for type-safe HTTP client
- System.Text.Json for JSON serialization
- xUnit for testing
- ✅ Session Management: create, get, list, delete sessions
- ✅ Prompt Sending: send prompts to AI models (sync + async)
- ✅ Message Retrieval: fetch messages from sessions
- ✅ Todo Support: get todo lists for sessions
- ✅ TUI Control: programmatically control terminal interface (append, submit, clear prompts, execute commands, show toasts)
- ✅ Event Streaming: real-time SSE event streaming with robust error handling
- ✅ Type-Safe API: Refit-based type-safe HTTP calls
- ✅ Async/Await: full async support with cancellation tokens
- ✅ Dependency Injection: proper HttpClient management with IHttpClientFactory support
- ✅ Error Handling: custom exception types for different error scenarios
- ✅ Configuration: flexible options for timeouts, base URL, and default model settings
- ✅ Comprehensive Tests: unit + integration tests
- ✅ Production-Ready: follows .NET best practices and clean code principles
dotnet add package Olbrasoft.OpenCode.DotnetClientOr via Package Manager Console:
Install-Package Olbrasoft.OpenCode.DotnetClient- .NET 10.0 SDK or later
- Running OpenCode server (default:
http://localhost:4096)
git clone https://github.com/Olbrasoft/OpenCode.DotnetClient.git
cd OpenCode.DotnetClient
dotnet builddotnet testNote: Integration tests require a running OpenCode server at http://localhost:4096.
usingOpenCode.DotnetClient;// Create clientusingvarclient=newOpenCodeClient("http://localhost:4096");// Create a new sessionvarsession=awaitclient.CreateSessionAsync("My AI Session");Console.WriteLine($"Session created: {session.Id}");// Send a promptvarresponse=awaitclient.SendPromptAsync(session.Id,"Write a hello world function in C#",providerId:"anthropic",modelId:"claude-3-5-sonnet-20241022");// Get the responseforeach(varpartinresponse.Parts){if(part.Type=="text"){Console.WriteLine(part.Text);}}// List messages in sessionvarmessages=awaitclient.GetMessagesAsync(session.Id);Console.WriteLine($"Total messages: {messages.Count}");// Cleanupawaitclient.DeleteSessionAsync(session.Id);Listen to real-time events from OpenCode server:
usingvareventStream=client.CreateEventStream();awaitforeach(varglobalEventineventStream.StreamGlobalEventsAsync()){Console.WriteLine($"[{globalEvent.Payload.Type}] in {globalEvent.Directory}");// Handle specific event typesswitch(globalEvent.Payload.Type){case"session.status":Console.WriteLine("Session status changed");break;case"message.updated":Console.WriteLine("Message was updated");break;case"todo.updated":Console.WriteLine("Todo list changed");break;case"file.edited":Console.WriteLine("File was edited");break;}}Get todos for a session:
vartodos=awaitclient.GetTodosAsync(session.Id);foreach(vartodointodos){Console.WriteLine($"[{todo.Status}] {todo.Content} (Priority: {todo.Priority})");}// Create client with custom configurationvaroptions=newOpenCodeClientOptions{BaseUrl="http://localhost:4096",Timeout=TimeSpan.FromMinutes(10),DefaultProviderId="anthropic",DefaultModelId="claude-3-5-sonnet-20241022",ThrowOnError=true};usingvarclient=newOpenCodeClient(options);Recommended approach for production applications to avoid socket exhaustion:
// In Program.cs or Startup.csbuilder.Services.AddHttpClient<OpenCodeClient>((serviceProvider,httpClient)=>{httpClient.BaseAddress=newUri("http://localhost:4096");httpClient.Timeout=TimeSpan.FromMinutes(5);});// Or with IHttpClientFactorybuilder.Services.AddHttpClient("OpenCodeApi", client =>{client.BaseAddress=newUri("http://localhost:4096");client.Timeout=TimeSpan.FromMinutes(5);});builder.Services.AddScoped<OpenCodeClient>(sp =>{varhttpClientFactory=sp.GetRequiredService<IHttpClientFactory>();varhttpClient=httpClientFactory.CreateClient("OpenCodeApi");returnnewOpenCodeClient(httpClient);});// Usage in controllerspublicclassMyController:ControllerBase{privatereadonlyOpenCodeClient_openCodeClient;publicMyController(OpenCodeClientopenCodeClient){_openCodeClient=openCodeClient;}}For console applications or simple scenarios:
varhttpClient=newHttpClient{BaseAddress=newUri("http://localhost:4096"),Timeout=TimeSpan.FromMinutes(5)};usingvarclient=newOpenCodeClient(httpClient);The client includes custom exception types for better error handling:
try{varsession=awaitclient.CreateSessionAsync("My Session");varresponse=awaitclient.SendPromptAsync(session.Id,"Write a hello world function");}catch(OpenCodeConnectionExceptionex){// Connection failures (server unreachable, timeout)Console.WriteLine($"Connection failed: {ex.Message}");}catch(OpenCodeApiExceptionex){// API errors (4xx, 5xx responses)Console.WriteLine($"API error {ex.StatusCode}: {ex.Message}");Console.WriteLine($"Response: {ex.ResponseContent}");}catch(OpenCodeExceptionex){// Other OpenCode errorsConsole.WriteLine($"OpenCode error: {ex.Message}");}All async methods support cancellation tokens:
usingvarcts=newCancellationTokenSource(TimeSpan.FromSeconds(30));try{varsessions=awaitclient.GetSessionsAsync(cancellationToken:cts.Token);varresponse=awaitclient.SendPromptAsync(sessionId:"ses_abc123",prompt:"Long-running task...",cancellationToken:cts.Token);}catch(OperationCanceledException){Console.WriteLine("Operation was cancelled");}// List all sessionsvarsessions=awaitclient.GetSessionsAsync();// Get specific sessionvarsession=awaitclient.GetSessionAsync("ses_abc123");// Create session with title and parentvarchildSession=awaitclient.CreateSessionAsync(title:"Feature Development",parentId:session.Id);// Delete sessionawaitclient.DeleteSessionAsync(session.Id);// Simple text promptvarresponse=awaitclient.SendPromptAsync(sessionId:"ses_abc123",prompt:"Explain SOLID principles",providerId:"anthropic",modelId:"claude-3-5-sonnet-20241022");// Async prompt (fire and forget)awaitclient.SendPromptAsyncAsync(sessionId:"ses_abc123",prompt:"Generate documentation");Control the OpenCode terminal interface programmatically:
// Append text to the prompt input (without submitting)awaitclient.AppendPromptAsync(sessionId:"ses_abc123",text:"Write a function to ");// Submit the current prompt (triggers AI processing)awaitclient.SubmitPromptAsync("ses_abc123");// Clear the prompt inputawaitclient.ClearPromptAsync("ses_abc123");// Execute a command in the sessionawaitclient.ExecuteCommandAsync(sessionId:"ses_abc123",command:"/help");// Show a toast notification in the UIawaitclient.ShowToastAsync(sessionId:"ses_abc123",message:"Operation completed successfully",type:"success"// "success", "error", "info", "warning");TUI API Use Cases:
- AppendPrompt: Build prompts incrementally from multiple sources
- SubmitPrompt: Trigger AI processing after composing a prompt
- ClearPrompt: Reset the input for a new interaction
- ExecuteCommand: Run slash commands programmatically (
/help,/clear, etc.) - ShowToast: Provide user feedback for background operations
OpenCode.DotnetClient/
├── src/
│ └── OpenCode.DotnetClient/
│ ├── Models/ # DTOs for API requests/responses
│ │ ├── Session.cs
│ │ ├── Message.cs
│ │ ├── PromptRequest.cs
│ │ ├── PromptResponse.cs
│ │ ├── Todo.cs
│ │ └── OpenCodeEvent.cs # Event models
│ ├── IOpenCodeApi.cs # Refit API interface
│ ├── OpenCodeClient.cs # Main client wrapper
│ ├── OpenCodeClientOptions.cs # Configuration options
│ ├── OpenCodeException.cs # Custom exception types
│ └── OpenCodeEventStream.cs # SSE event streaming
├── tests/
│ └── OpenCode.DotnetClient.Tests/
│ ├── OpenCodeClientTests.cs # Integration tests
│ └── OpenCodeClientUnitTests.cs # Unit tests
└── examples/
└── OpenCode.DotnetClient.Example/ # Example console app
└── Program.cs # Interactive example
OpenCodeClient(string baseUrl = "http://localhost:4096")OpenCodeClient(HttpClient httpClient)
Session Management
Task<List<Session>> GetSessionsAsync(string? directory = null)Task<Session> CreateSessionAsync(string? title = null, string? parentId = null, string? directory = null)Task<Session> GetSessionAsync(string sessionId, string? directory = null)Task<bool> DeleteSessionAsync(string sessionId, string? directory = null)
Messaging
Task<PromptResponse> SendPromptAsync(string sessionId, string prompt, string providerId = "anthropic", string modelId = "claude-3-5-sonnet-20241022", string? directory = null)Task SendPromptAsyncAsync(string sessionId, string prompt, string providerId = "anthropic", string modelId = "claude-3-5-sonnet-20241022", string? directory = null)Task<List<MessageWithParts>> GetMessagesAsync(string sessionId, int? limit = null, string? directory = null)
Session Control
Task<bool> AbortSessionAsync(string sessionId, string? directory = null)
Todos
Task<List<Todo>> GetTodosAsync(string sessionId, string? directory = null)
Terminal User Interface (TUI) API
Task AppendPromptAsync(string sessionId, string text, string? directory = null)Task SubmitPromptAsync(string sessionId, string? directory = null)Task ClearPromptAsync(string sessionId, string? directory = null)Task ExecuteCommandAsync(string sessionId, string command, string? directory = null)Task ShowToastAsync(string sessionId, string message, string type = "info", string? directory = null)
Event Streaming
OpenCodeEventStream CreateEventStream()
The project includes comprehensive tests:
- Unit Tests (4): Basic client functionality, constructors, disposal
- Integration Tests (6): Real API calls requiring running OpenCode server
Run tests:
# All tests
dotnet test# Only unit tests (no server required)
dotnet test --filter "FullyQualifiedName~UnitTests"# Only integration tests
dotnet test --filter "FullyQualifiedName~OpenCodeClientTests"This is a .NET solution with two projects:
- OpenCode.DotnetClient: Class library (.NET 10)
- OpenCode.DotnetClient.Tests: Test project with xUnit
dotnet buildBefore running integration tests, start the OpenCode server:
opencode serve --port 4096An interactive example application is included in the examples directory:
cd examples/OpenCode.DotnetClient.Example
dotnet runThe example demonstrates:
- Event Streaming: Real-time monitoring of all OpenCode events
- Interactive Sessions: Chat with AI through the console
- Session Management: List and manage active sessions
- Color-coded Output: Beautiful terminal UI with ANSI colors
See examples/README.md for detailed usage instructions.
- .NET 10.0 or later
- OpenCode Server running on http://localhost:4096 (for integration tests)
The client supports real-time streaming of these OpenCode events:
session.status- Session status changes (running, idle, etc.)session.idle- Session becomes idle
message.updated- Message content updatedmessage.removed- Message deleted
todo.updated- Todo list changes (new todos, status updates)
file.edited- File was edited by AIfile.watcher.updated- File system watcher detected changes
server.instance.disposed- Server instance cleanuplsp.client.diagnostics- LSP diagnostics from language serverscommand.executed- Command execution notificationsinstallation.updated- Installation updatesinstallation.update-available- New version available
Possible improvements for future versions:
- ✅
Error Handling: Implemented - Custom exception types with detailed error information - ✅
Configuration: Implemented - Strongly-typed OpenCodeClientOptions - Retry Policies: Add automatic retry with exponential backoff using Polly
- NuGet Package: Publish as reusable package to nuget.org
- Additional Endpoints: Support for more OpenCode API features (file operations, providers, models list, etc.)
- CLI Tool: Command-line interface for quick operations
- Logging: Integrate with ILogger for production-grade logging
- Metrics: Add telemetry and metrics collection
- Connection Pooling: Advanced HttpClient configuration for high-throughput scenarios
This project is licensed under the MIT License - see the LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.
For questions or issues, please open an issue on GitHub.
Note: This is a Proof of Concept (POC) implementation. For production use, additional error handling, logging, and configuration options should be added.