Skip to content

Latest commit

Β 

History

History
544 lines (415 loc) Β· 16 KB

File metadata and controls

544 lines (415 loc) Β· 16 KB

OpenCode.DotnetClient

Build Publish NuGet NuGet NuGet Downloads .NET License

A simple .NET client library for the OpenCode API, enabling easy integration of OpenCode's AI coding capabilities into C# applications.

πŸ“‹ Overview

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

πŸš€ Features

  • βœ… 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

πŸ“¦ Installation

NuGet Package

dotnet add package Olbrasoft.OpenCode.DotnetClient

Or via Package Manager Console:

Install-Package Olbrasoft.OpenCode.DotnetClient

Prerequisites

  • .NET 10.0 SDK or later
  • Running OpenCode server (default: http://localhost:4096)

Building from Source

git clone https://github.com/Olbrasoft/OpenCode.DotnetClient.git
cd OpenCode.DotnetClient
dotnet build

Running Tests

dotnet test

Note: Integration tests require a running OpenCode server at http://localhost:4096.

πŸ“– Usage

Basic Example

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);

Event Streaming

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;
    }
}

Todos

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})");
}

Advanced Usage

Configuration Options

// 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);

Dependency Injection (ASP.NET Core)

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;
    }
}

Custom HttpClient (Simple Usage)

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);

Error Handling

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}");
}

Cancellation Support

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");
}

Working with Sessions

// 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);

Sending Prompts

// 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"
);

Terminal User Interface (TUI) API

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

πŸ—οΈ Architecture

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

πŸ”§ API Reference

OpenCodeClient

Constructor

  • OpenCodeClient(string baseUrl = "http://localhost:4096")
  • OpenCodeClient(HttpClient httpClient)

Methods

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()

πŸ§ͺ Testing

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"

πŸ› οΈ Development

Project Structure

This is a .NET solution with two projects:

  • OpenCode.DotnetClient: Class library (.NET 10)
  • OpenCode.DotnetClient.Tests: Test project with xUnit

Dependencies

  • Refit - Type-safe REST client
  • xUnit - Testing framework
  • Moq - Mocking framework

Building

dotnet build

Running OpenCode Server

Before running integration tests, start the OpenCode server:

opencode serve --port 4096

Running the Example

An interactive example application is included in the examples directory:

cd examples/OpenCode.DotnetClient.Example
dotnet run

The 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.

πŸ“ Requirements

🎯 Supported Events

The client supports real-time streaming of these OpenCode events:

Session Events

  • session.status - Session status changes (running, idle, etc.)
  • session.idle - Session becomes idle

Message Events

  • message.updated - Message content updated
  • message.removed - Message deleted

Todo Events

  • todo.updated - Todo list changes (new todos, status updates)

File Events

  • file.edited - File was edited by AI
  • file.watcher.updated - File system watcher detected changes

Other Events

  • server.instance.disposed - Server instance cleanup
  • lsp.client.diagnostics - LSP diagnostics from language servers
  • command.executed - Command execution notifications
  • installation.updated - Installation updates
  • installation.update-available - New version available

🎯 Future Enhancements

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

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

🀝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

πŸ“§ Contact

For questions or issues, please open an issue on GitHub.

πŸ™ Acknowledgments

  • OpenCode - The AI coding assistant
  • Refit - For the excellent HTTP client library

Note: This is a Proof of Concept (POC) implementation. For production use, additional error handling, logging, and configuration options should be added.