OpenCodeAI.Sdk 0.8.0-preview.2

This is a prerelease version of OpenCodeAI.Sdk.
dotnet add package OpenCodeAI.Sdk --version 0.8.0-preview.2
                    
NuGet\Install-Package OpenCodeAI.Sdk -Version 0.8.0-preview.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="OpenCodeAI.Sdk" Version="0.8.0-preview.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="OpenCodeAI.Sdk" Version="0.8.0-preview.2" />
                    
Directory.Packages.props
<PackageReference Include="OpenCodeAI.Sdk" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add OpenCodeAI.Sdk --version 0.8.0-preview.2
                    
#r "nuget: OpenCodeAI.Sdk, 0.8.0-preview.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package OpenCodeAI.Sdk@0.8.0-preview.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=OpenCodeAI.Sdk&version=0.8.0-preview.2&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=OpenCodeAI.Sdk&version=0.8.0-preview.2&prerelease
                    
Install as a Cake Tool

opencode SDK for .NET

License: MIT NuGet CI Linux Tests

πŸš€ Quick Start: Install | Quick start | Guide

Unofficial. This project is not affiliated with or endorsed by the opencode team.

A strongly typed .NET client for opencode β€” the protocol surface every opencode front-end (TUI, desktop, web UI, plugins) goes through. This SDK speaks the OpenCode 2.x server HTTP API; OpenCode 1.x's server API is not supported. The callable surface is generated from a pinned OpenAPI snapshot and rides one hand-written transport runtime, so what you call is exactly what the server declares.


πŸŽ‰ Project Status

Published and pre-1.0; the protocol surface is complete. Everything below is landed and covered. What is still outstanding is opencode's third connection mode β€” attaching to a registered background service.

  • βœ… 138 of 143 operations callable, across 29 client families β€” sessions, PTYs, persistent PTYs, shells, events, MCP servers, integrations, providers, permissions, credentials, config, VCS, worktrees, websearch, RPC, and more
  • βœ… 5,532 tests green on Windows β€” the fullest leg, the only one that adds the net472 assemblies. Linux and macOS run the same suite on net8.0, net9.0, and net10.0
  • βœ… Server-sent event streams, global and per-session, over the same transport as one-shot calls
  • βœ… PTY and persistent-PTY terminal sessions through hand-written WebSocket doors
  • βœ… A launcher β€” OpenCodeServer.StartAsync() starts, monitors, and stops a private opencode serve child for you
  • βœ… Source-generated System.Text.Json with no reflection fallback; both packages declare IsAotCompatible on net10.0
  • 🚧 Pre-1.0 and iterating β€” released as 0.8.0-preview.N; the public surface is locked by a reviewed baseline but may still move before 1.0.0. See CHANGELOG.md
  • πŸ”œ Background-service attachment and an MCP server over this SDK β€” both planned, neither started

Versioning: the SDK builds against an accepted OpenAPI snapshot taken at an upstream release tag, never a live branch. The exact commit and the refresh procedure live in spec/SNAPSHOT.md, which owns that pin.

πŸš€ Platform Compatibility & Quality Status

Supported Platforms

Both packages target the same set: netstandard2.0;net472;net8.0;net9.0;net10.0. The downlevel targets are not a compatibility shim β€” the whole suite runs on net472 on Windows, real-process launcher acceptance included, and on net8.0/net9.0/net10.0 on all three OSes. netstandard2.0 is a consumption target rather than a test target: it has no runtime to execute on, and the net472 leg is what exercises its compile surface.

Build & Test Matrix

Category Platform/Type Status Description
πŸ”§ Build Cross-Platform CI Matrix: Windows, Linux, macOS
πŸ§ͺ Tests Linux Linux Tests net8.0, net9.0, net10.0
πŸ§ͺ Tests Windows Windows Tests net472 plus every modern target
πŸ§ͺ Tests macOS macOS Tests net8.0, net9.0, net10.0

πŸ“¦ Package Status

Package NuGet.org GitHub Packages
OpenCodeAI.Sdk NuGet GitHub Packages
OpenCodeAI.Sdk.Extensions NuGet GitHub Packages

Table of Contents

  1. Supported Platforms
  2. Why this SDK?
  3. Prerequisites
  4. Installation
  5. Quick Start
  6. API Coverage
  7. Documentation
  8. Known Issues
  9. Developing
  10. Changelog
  11. License

πŸ’‘ Why this SDK?

  • Typed all the way down. Every operation has a generated request type, a generated response envelope, and typed error models β€” including opencode's discriminator-free unions, which the repository's own generator represents faithfully because off-the-shelf .NET OpenAPI generators did not.
  • One transport, either way in. Point the client at a server you already run, or let OpenCodeServer.StartAsync() start a private one for you β€” the same pipeline owns endpoint authority, authentication, buffering, and failure mapping in both. (opencode's third connection mode, attaching to a registered background service, is not implemented yet.)
  • Errors you can branch on. Every call throws typed exceptions by default, or returns the failure as data with OpenCodeRequestOptions.NoThrow when a 404 is a normal answer.
  • Broad .NET reach. netstandard2.0 and net472 are first-class, so this works inside .NET Framework hosts, not just modern console apps.
  • No reflection serialization. System.Text.Json source generation throughout, with no reflection fallback to surprise a trimmed or AOT-published app.
  • A pinned protocol, not a moving target. Upstream ships releases on its own cadence; this SDK builds against a reviewed snapshot of a release tag, with a receipt, so a regeneration is a reviewable diff.

Prerequisites

You need an opencode server. It ships from the @opencode/cli npm scope and installs the opencode command (opencode.ai). This SDK is generated from the upstream release this repository pins, so install that version; later releases usually work, but they are not what this repository tests. The pinned release tag and its npm version are owned by spec/SNAPSHOT.md.

npm install -g @opencode/cli@2.0.2

Then either run it yourself:

OPENCODE_PASSWORD=your-password opencode serve --hostname 127.0.0.1 --port 4096

…or let the SDK start one for you β€” that is what OpenCodeServer.StartAsync() in the quick start does, and it needs nothing running in advance.

πŸ“¦ Installation

Stable (NuGet.org)

dotnet add package OpenCodeAI.Sdk --prerelease
dotnet add package OpenCodeAI.Sdk.Extensions --prerelease   # dependency injection, optional

The package id is not the namespace. You install OpenCodeAI.Sdk, and you write using OpenCode.Sdk; β€” the assemblies and every public namespace are OpenCode.Sdk. nuget.org reserves the OpenCode. id prefix for an unrelated owner, so the artifact carries a different name than the code inside it.

The version badge above shows the latest published prerelease. Nightly builds of master are on the GitHub Packages feed below.

Nightly builds (GitHub Packages)

Every code push to master publishes 0.8.0-nightly.{yyyyMMdd}.{shortSha} to GitHub Packages:

# Add the GitHub Packages source (PAT: classic token with the read:packages scope)
dotnet nuget add source https://nuget.pkg.github.com/Blind-Striker/index.json \
  --name github-opencode-sdk \
  --username YOUR_GITHUB_USERNAME \
  --password YOUR_GITHUB_PAT \
  --store-password-in-clear-text

# Install the nightly packages
dotnet add package OpenCodeAI.Sdk --prerelease --source github-opencode-sdk
dotnet add package OpenCodeAI.Sdk.Extensions --prerelease --source github-opencode-sdk

Prefer keeping the token out of shell history? Commit a nuget.config next to your solution and keep the credentials in environment variables:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
    <add key="github-opencode-sdk" value="https://nuget.pkg.github.com/Blind-Striker/index.json" />
  </packageSources>
  <packageSourceCredentials>
    <github-opencode-sdk>
      <add key="Username" value="%GITHUB_USERNAME%" />
      <add key="ClearTextPassword" value="%GITHUB_PAT%" />
    </github-opencode-sdk>
  </packageSourceCredentials>
</configuration>

πŸ”‘ GitHub Packages Authentication: GitHub Packages requires a classic Personal Access Token with the read:packages scope, even for public packages (fine-grained tokens are not supported by the NuGet registry). --store-password-in-clear-text is required on Linux and macOS, where NuGet cannot encrypt stored credentials. Never commit a real token β€” the nuget.config above reads it from the environment. Inside GitHub Actions you need no PAT at all: the workflow's own GITHUB_TOKEN works as the password.

πŸš€ Quick Start

The SDK starts the server

No ambient process, no endpoint to configure β€” the launcher starts a private opencode serve child, mints its credential, and hands you a client bound to it. opencode is resolved from PATH the way a shell resolves it, PATHEXT included, so an npm-installed CLI's .cmd shim starts on Windows too. Disposing the server stops the child. To keep the child's output for diagnostics, pass an OpenCodeServerOutput collector in OpenCodeServerOptions.Output and read its snapshot whenever you like β€” bounded, and still readable when the start failed.

using OpenCode.Sdk;
using OpenCode.Sdk.Models;

await using var server = await OpenCodeServer.StartAsync();
using var client = server.CreateClient();

var health = await client.GetHealthAsync();
Console.WriteLine($"opencode {health.Health.Version} is healthy: {health.Health.Healthy}");

var created = await client.Sessions.CreateSessionAsync(new SessionCreateRequest { Title = "hello from .NET" });
Console.WriteLine($"session {created.Session.Id}: {created.Session.Title}");

using var window = new CancellationTokenSource(TimeSpan.FromSeconds(30));
await foreach (var @event in client.Events.SubscribeAsync(window.Token))
{
    Console.WriteLine($"{@event.GetType().Name} ({@event.Type})");
}

A server you already run

using var client = new OpenCodeClient(new OpenCodeClientOptions
{
    Endpoint = new Uri("http://127.0.0.1:4096"),
    Password = Environment.GetEnvironmentVariable("OPENCODE_PASSWORD"),
});

var health = await client.GetHealthAsync();
Console.WriteLine(health.Health.Version);

The SDK reads no environment variables of its own β€” resolving a password from the environment is the caller's decision, exactly as opencode's own CLI layers it.

Dependency injection

OpenCode.Sdk.Extensions registers one singleton client owning its transport for the container's lifetime, plus every sub-client resolved from that same instance β€” so inject SessionsClient, EventsClient, or PtysClient directly.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using OpenCode.Sdk;
using OpenCode.Sdk.Models;

var builder = Host.CreateApplicationBuilder(args);

builder.Services.AddOpenCode(options =>
{
    options.Endpoint = new Uri("http://127.0.0.1:4096");
    options.Password = Environment.GetEnvironmentVariable("OPENCODE_PASSWORD");
});

builder.Services.AddHostedService<SessionWorker>();

await builder.Build().RunAsync();

internal sealed class SessionWorker(SessionsClient sessions) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        var page = await sessions.ListSessionsAsync(
            new SessionListRequest { Limit = "10", Order = ListOrder.Descending },
            cancellationToken: stoppingToken);

        Console.WriteLine($"{page.Sessions.Count} sessions, next cursor {page.Cursor.Next ?? "<none>"}");
    }
}

AddOpenCode(IConfiguration) binds the same options from a configuration section instead. That overload is annotated [RequiresDynamicCode] / [RequiresUnreferencedCode] because configuration binding reflects over the options type β€” under trimming or native AOT, prefer the configure-action overload shown above.

🧭 API Coverage

138 of the 143 operations in the pinned snapshot are callable. The remaining five are not omissions β€” each one is a recorded decision with a named cause, and src/OpenCode.Sdk/.generation-incomplete is the machine-readable map that the build itself reads.

Three operations are declined, because admitting them would mean inventing a contract upstream does not declare:

  • v2.config.get β€” the config schema nests unions whose branches are all JSON objects with no discriminator (lsp's and references' map values), so a decoder cannot tell one branch from another without guessing. Moving this needs a new union mechanism, not a mapping row.
  • v2.fs.read β€” the route is /api/fs/read/*, a framework wildcard rather than an OpenAPI path template. There is no declared path parameter to bind, and inventing one would put a fabricated contract in a generated client. An upstream report is drafted.
  • v2.experimental.migration.v1.status β€” the same undiscriminated-object-union wall as v2.config.get, on an operation upstream itself marks experimental.

Two operations are transport-owned: v2.pty.connect and v2.persistentPty.connect are WebSocket upgrades that the HTTP pipeline cannot carry. They are fully usable β€” through the hand-written PtySession and PersistentPtySession doors described in the terminals guide β€” they simply are not generated.

πŸ“š Documentation

Guide What it covers
The guide Index of every page below, in reading order
Getting started Install, first call, and the shape of the client family
Connection modes The standalone launcher, an external server, and DI registration
Streaming The global event bus and per-session server-sent event streams
Terminals PTY and persistent-PTY sessions over the WebSocket doors
Errors and responses Throwing versus NoThrow, and the typed error model
Pagination Cursor-carrying list envelopes, Enumerate*Async, and its Pages

Architecture, decision records, and engineering policy live under docs/ β€” start at AGENTS.md if you want the internals rather than the API.

Known Issues

  • The package id differs from the assembly name. nuget.org reserves the OpenCode. id prefix for an unrelated owner, so the packages publish as OpenCodeAI.Sdk and OpenCodeAI.Sdk.Extensions while the assemblies and every public namespace stay OpenCode.Sdk. Nothing about your code changes: only the PackageReference carries the OpenCodeAI name.

  • Response bodies larger than 1 MB allocate an extra copy on net472 and netstandard2.0. The downlevel array pool caps its buckets at 1 MB, so a rent above that cap falls through to a fresh allocation and the body is copied once at wire size. Modern targets are unaffected β€” their pool has no such cap. This only shows up on genuinely large payloads (a long session export, for example); a larger-capacity pool is a measured, benchmark-gated follow-up rather than a speculative change.

  • Each terminal connection allocates a 16 KiB receive buffer. The connection-owned receiver reuses it across messages and consumer read enumerations. Pooling remains a benchmark-gated follow-up; repeated reads on the same connection do not allocate another receive buffer. The receiver also queues undelivered frames, so slow or absent consumers can grow memory; see the terminal lifetime contract.

  • Attaching to an existing background service is not implemented. opencode's third connection mode β€” discovering a registered daemon through its registration file (Service.discover / ensure / stop) β€” has no SDK parity yet. You can point the client at an endpoint you already know, or let OpenCodeServer.StartAsync() start a private server; what you cannot do is find a daemon someone else started. That parity is a queued follow-up arc, not a defect in what ships.

  • The event bus has no replay contract. EventsClient.SubscribeAsync is a live, volatile stream: events published while you are disconnected are gone, and a consumer slower than the producer can overflow and fail the stream. This is the server's contract, not an SDK limitation β€” if you need durable history for one session, use the per-session log stream instead (streaming guide).

  • A replay from a CLI-started server is the marker alone. Durable session-log replay needs a server started with event persistence, and the distributed opencode CLI starts its server without it and exposes no switch to turn it on β€” no serve flag, no environment variable, no configuration key (confirmed at the pin; observed on @opencode/cli@2.0.2). A replay against such a server does not fail: SessionClient.GetLogAsync without Follow answers with a single EventLogSynced marker whose sequence has advanced and no durable events before it. Persisted replay needs a host that embeds the opencode server library with persistence enabled β€” this repository's own simulation host does that for its tests. Live Follow = True delivery is unaffected. See the streaming guide.

  • Running the in-repo sandbox against a different server needs --no-launch-profile. The checked-in launchSettings.json prefills OPENCODE_SANDBOX_ENDPOINT at port 4096, and dotnet run applies the default profile unless told otherwise β€” so without the flag the sandbox silently addresses 4096 whatever your environment says. The prefill stays deliberately: it is what makes zero-argument F5 work against a local server. See tests/OpenCode.Sdk.Sandbox/README.md.

Developing

We appreciate contributions in the form of feedback, bug reports, and pull requests. Read CONTRIBUTING.md first β€” it carries the full gate and the commit convention.

Building the Project

git clone --recurse-submodules https://github.com/Blind-Striker/opencode-sdk-dotnet.git
cd opencode-sdk-dotnet
dotnet build --configuration Release

external/ holds read-only upstream submodules used as protocol evidence and as the pinned-server test fixture; --recurse-submodules is what makes the fixture-backed tests runnable.

Fixture-backed tests also require the Bun version named by external/opencode/package.json, the pinned server dependencies (bun install --frozen-lockfile --ignore-scripts from external/opencode), and ripgrep 15.1.0 on PATH. The simulated server deliberately denies unregistered network access, so its fs.find test cannot use upstream's fallback download. CI provisions the matching official ripgrep archive and verifies its SHA-256 digest before use.

Sandbox Application

tests/OpenCode.Sdk.Sandbox is a committed playground that drives the SDK against a real opencode serve under a debugger β€” the standing breadth walkthrough, the SSE stream modes, the PTY legs, and the standalone-launcher demo. Its README documents every mode.

dotnet run --project tests/OpenCode.Sdk.Sandbox -- --standalone

Running Tests

dotnet test --configuration Release --no-build

The full completion gate β€” analyzers, formatting, and the suite β€” is docs/engineering/quality-gates.md.

Community

Got questions or wild feature ideas?

πŸ‘‰ Open an issue β€” bug reports, questions, and proposals all land there for now.

Changelog

Please refer to CHANGELOG.md to see the complete list of changes for each release.

License

Licensed under MIT, see LICENSE for the full text. Content derived from upstream opencode carries its own notice in THIRD-PARTY-NOTICES.md.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 is compatible.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on OpenCodeAI.Sdk:

Package Downloads
OpenCodeAI.Sdk.Extensions

Dependency injection integration for the unofficial OpenCode .NET SDK, a typed client for the OpenCode 2.x server HTTP API (OpenCode 1.x servers are not supported): registers one singleton client and every sub-client from it. The package id is OpenCodeAI.Sdk.Extensions; the assembly and every namespace are OpenCode.Sdk.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.8.0-preview.2 32 9/12/2026
0.8.0-preview.1 87 9/8/2026