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.
using OpenCode.DotnetClient;
// Create client
using var client = new OpenCodeClient("http://localhost:4096");
// Create a new session
var session = await client.CreateSessionAsync("My AI Session");
Console.WriteLine($"Session created: {session.Id}");
// Send a prompt
var response = await client.SendPromptAsync(
session.Id,
"Write a hello world function in C#",
providerId: "anthropic",
modelId: "claude-3-5-sonnet-20241022"
);
// Get the response
foreach (var part in response.Parts)
{
if (part.Type == "text")
{
Console.WriteLine(part.Text);
}
}
// List messages in session
var messages = await client.GetMessagesAsync(session.Id);
Console.WriteLine($"Total messages: {messages.Count}");
// Cleanup
await client.DeleteSessionAsync(session.Id);Listen to real-time events from OpenCode server:
using var eventStream = client.CreateEventStream();
await foreach (var globalEvent in eventStream.StreamGlobalEventsAsync())
{
Console.WriteLine($"[{globalEvent.Payload.Type}] in {globalEvent.Directory}");
// Handle specific event types
switch (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:
var todos = await client.GetTodosAsync(session.Id);
foreach (var todo in todos)
{
Console.WriteLine($"[{todo.Status}] {todo.Content} (Priority: {todo.Priority})");
}// Create client with custom configuration
var options = new OpenCodeClientOptions
{
BaseUrl = "http://localhost:4096",
Timeout = TimeSpan.FromMinutes(10),
DefaultProviderId = "anthropic",
DefaultModelId = "claude-3-5-sonnet-20241022",
ThrowOnError = true
};
using var client = new OpenCodeClient(options);Recommended approach for production applications to avoid socket exhaustion:
// In Program.cs or Startup.cs
builder.Services.AddHttpClient<OpenCodeClient>((serviceProvider, httpClient) =>
{
httpClient.BaseAddress = new Uri("http://localhost:4096");
httpClient.Timeout = TimeSpan.FromMinutes(5);
});
// Or with IHttpClientFactory
builder.Services.AddHttpClient("OpenCodeApi", client =>
{
client.BaseAddress = new Uri("http://localhost:4096");
client.Timeout = TimeSpan.FromMinutes(5);
});
builder.Services.AddScoped<OpenCodeClient>(sp =>
{
var httpClientFactory = sp.GetRequiredService<IHttpClientFactory>();
var httpClient = httpClientFactory.CreateClient("OpenCodeApi");
return new OpenCodeClient(httpClient);
});
// Usage in controllers
public class MyController : ControllerBase
{
private readonly OpenCodeClient _openCodeClient;
public MyController(OpenCodeClient openCodeClient)
{
_openCodeClient = openCodeClient;
}
}For console applications or simple scenarios:
var httpClient = new HttpClient
{
BaseAddress = new Uri("http://localhost:4096"),
Timeout = TimeSpan.FromMinutes(5)
};
using var client = new OpenCodeClient(httpClient);The client includes custom exception types for better error handling:
try
{
var session = await client.CreateSessionAsync("My Session");
var response = await client.SendPromptAsync(
session.Id,
"Write a hello world function"
);
}
catch (OpenCodeConnectionException ex)
{
// Connection failures (server unreachable, timeout)
Console.WriteLine($"Connection failed: {ex.Message}");
}
catch (OpenCodeApiException ex)
{
// API errors (4xx, 5xx responses)
Console.WriteLine($"API error {ex.StatusCode}: {ex.Message}");
Console.WriteLine($"Response: {ex.ResponseContent}");
}
catch (OpenCodeException ex)
{
// Other OpenCode errors
Console.WriteLine($"OpenCode error: {ex.Message}");
}All async methods support cancellation tokens:
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
try
{
var sessions = await client.GetSessionsAsync(
cancellationToken: cts.Token
);
var response = await client.SendPromptAsync(
sessionId: "ses_abc123",
prompt: "Long-running task...",
cancellationToken: cts.Token
);
}
catch (OperationCanceledException)
{
Console.WriteLine("Operation was cancelled");
}// List all sessions
var sessions = await client.GetSessionsAsync();
// Get specific session
var session = await client.GetSessionAsync("ses_abc123");
// Create session with title and parent
var childSession = await client.CreateSessionAsync(
title: "Feature Development",
parentId: session.Id
);
// Delete session
await client.DeleteSessionAsync(session.Id);// Simple text prompt
var response = await client.SendPromptAsync(
sessionId: "ses_abc123",
prompt: "Explain SOLID principles",
providerId: "anthropic",
modelId: "claude-3-5-sonnet-20241022"
);
// Async prompt (fire and forget)
await client.SendPromptAsyncAsync(
sessionId: "ses_abc123",
prompt: "Generate documentation"
);Control the OpenCode terminal interface programmatically:
// Append text to the prompt input (without submitting)
await client.AppendPromptAsync(
sessionId: "ses_abc123",
text: "Write a function to "
);
// Submit the current prompt (triggers AI processing)
await client.SubmitPromptAsync("ses_abc123");
// Clear the prompt input
await client.ClearPromptAsync("ses_abc123");
// Execute a command in the session
await client.ExecuteCommandAsync(
sessionId: "ses_abc123",
command: "/help"
);
// Show a toast notification in the UI
await client.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.