OpenCodeDotNet.Sdk
0.9.0-preview.6
Prefix Reserved
dotnet add package OpenCodeDotNet.Sdk --version 0.9.0-preview.6
NuGet\Install-Package OpenCodeDotNet.Sdk -Version 0.9.0-preview.6
<PackageReference Include="OpenCodeDotNet.Sdk" Version="0.9.0-preview.6" />
<PackageVersion Include="OpenCodeDotNet.Sdk" Version="0.9.0-preview.6" />
<PackageReference Include="OpenCodeDotNet.Sdk" />
paket add OpenCodeDotNet.Sdk --version 0.9.0-preview.6
#r "nuget: OpenCodeDotNet.Sdk, 0.9.0-preview.6"
#:package OpenCodeDotNet.Sdk@0.9.0-preview.6
#addin nuget:?package=OpenCodeDotNet.Sdk&version=0.9.0-preview.6&prerelease
#tool nuget:?package=OpenCodeDotNet.Sdk&version=0.9.0-preview.6&prerelease
opencode SDK for .NET
π Quick Start: Install | Quick start | Guide | API coverage
Unofficial. This project is not affiliated with or endorsed by the opencode team.
π¦ New package ids and a new home (
0.9.0-preview.5). The packages are nowOpenCodeDotNet.SdkandOpenCodeDotNet.Sdk.Extensions;OpenCodeAI.SdkandOpenCodeAI.Sdk.Extensionsare deprecated. Namespaces are unchanged, so only thePackageReferencechanges. The repository moved to theopencode-dotnetorganization, and old links redirect.
A strongly typed .NET client for the opencode server β the HTTP API that every opencode front-end (TUI, desktop, web UI, plugins) uses. The SDK speaks the OpenCode 2.x API; the 1.x server API is not supported.
await using var server = await OpenCodeServer.StartAsync();
using var client = server.CreateClient();
var created = await client.Sessions.CreateSessionAsync(new SessionCreateRequest { Title = "hello from .NET" });
Console.WriteLine($"session {created.Session.Id}");
π Project Status
Pre-1.0, and the protocol surface is complete. All 140 operations in the pinned OpenAPI snapshot are callable β 138 as generated HTTP calls, and the two terminal WebSocket connections through hand-written transports. All three connection modes work: a private server the SDK starts, a server you already run, and the background service that the opencode CLI registers.
- π§ Releases are
0.9.0-preview.N. A reviewed baseline locks the public surface, but it can still change before1.0.0. See CHANGELOG.md. - π The SDK builds against a snapshot of an upstream release tag, never a live branch.
spec/SNAPSHOT.mdowns the exact pin and the refresh procedure. - π An MCP server over this SDK is planned, not started. It will live in its own repository in the opencode-dotnet organization.
π‘ Why this SDK?
- Typed all the way down. Every operation has a generated request type, a generated response envelope, and typed error models. That includes opencode's unions without a discriminator, which off-the-shelf .NET OpenAPI generators did not represent correctly.
- One transport for every connection mode. The same pipeline owns endpoint authority, authentication, buffering, and failure mapping, whether you start the server, point at one, or discover the CLI's background service.
- Errors you can branch on. A call throws a typed exception by default. With
OpenCodeRequestOptions.NoThrow, it returns the failure as data instead β useful when a 404 is a normal answer. - Broad .NET reach.
net472is a first-class target, so the SDK works in .NET Framework hosts, not only in modern apps;netstandard2.0is a compatibility asset for other runtimes. - No reflection serialization.
System.Text.Jsonsource generation throughout. Both packages declareIsAotCompatibleonnet10.0. - A pinned protocol. Each refresh to a new upstream release comes with a receipt, so a regeneration is a diff that you can review.
π§ API Coverage
OpenCodeClient exposes one sub-client per area of the API. Every sub-client is also injectable on
its own when you use dependency injection.
| Area | Entry point | Guide |
|---|---|---|
| Sessions, messages, prompts, session logs | client.Sessions |
Requests, Pagination |
| The global event bus | client.Events |
Streaming |
| Terminals and persistent terminals | client.Ptys, client.PersistentPtys |
Terminals |
| Shell commands | client.Shells |
Requests |
| Files and version control | client.FileSystem, client.Vcs, client.Worktrees |
Requests |
| Permissions and forms | client.Permissions, client.Forms |
Requests |
| Models and providers | client.Providers, client.LanguageModels, client.Credentials |
β |
| Agents, commands, skills, plugins | client.Agents, client.Commands, client.Skills, client.Plugins |
β |
| MCP servers and integrations | client.McpServers, client.Integrations |
β |
| Configuration, projects, references | client.Config, client.Projects, client.References |
β |
| Server info and pairing | client.Server |
Connection modes |
| Locations | client.GetLocationAsync, client.ReloadLocationsAsync, client.Debug |
Requests |
| Web search and RPC | client.Websearch, client.Rpc |
β |
| Operations upstream marks experimental | client.Experimental |
β |
The two terminal connections (pty.connect, persistentPty.connect) are WebSocket upgrades that
the HTTP pipeline cannot carry. They are fully usable through the hand-written PtySession and
PersistentPtySession types. Nothing is declined:
src/OpenCode.Sdk/.generation-incomplete
is the machine-readable map that the build reads.
π¦ Installation
dotnet add package OpenCodeDotNet.Sdk --prerelease
dotnet add package OpenCodeDotNet.Sdk.Extensions --prerelease # dependency injection, optional
The package id is not the namespace. You install
OpenCodeDotNet.Sdk, and you writeusing OpenCode.Sdk;. nuget.org reserves theOpenCode.id prefix for an unrelated owner, so only thePackageReferencecarries theOpenCodeDotNetname. The earlier ids,OpenCodeAI.SdkandOpenCodeAI.Sdk.Extensions, receive no further versions.
You also need the opencode CLI, from the @opencode/cli npm scope. Install the release that this
repository pins β later releases usually work, but they are not what the tests run against:
npm install -g @opencode/cli@2.0.22
You do not have to start it. OpenCodeServer.StartAsync() in the quick start
starts a private server for you.
Nightly builds (GitHub Packages)
Every code push to master publishes 0.9.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/opencode-dotnet/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 OpenCodeDotNet.Sdk --prerelease --source github-opencode-sdk
dotnet add package OpenCodeDotNet.Sdk.Extensions --prerelease --source github-opencode-sdk
<details> <summary>Keep the token out of shell history with a <code>nuget.config</code></summary>
<?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/opencode-dotnet/index.json" />
</packageSources>
<packageSourceCredentials>
<github-opencode-sdk>
<add key="Username" value="%GITHUB_USERNAME%" />
<add key="ClearTextPassword" value="%GITHUB_PAT%" />
</github-opencode-sdk>
</packageSourceCredentials>
</configuration>
</details>
π GitHub Packages authentication: GitHub Packages requires a classic Personal Access Token with the
read:packagesscope, even for public packages. The NuGet registry does not accept fine-grained tokens. Linux and macOS need--store-password-in-clear-text, because NuGet cannot encrypt stored credentials there. Never commit a real token. Inside GitHub Actions you need no PAT: the workflow's ownGITHUB_TOKENworks as the password.
π Quick Start
Pick the connection mode that matches where your server lives:
| You want⦠| Use | Who stops the server |
|---|---|---|
| A private server for this app only | OpenCodeServer.StartAsync() |
The SDK, when you dispose it |
| A server you already run | new OpenCodeClient(options) |
You |
| The background service the CLI shares with other clients | OpenCodeServer.DiscoverAsync() or EnsureAsync() |
Nobody, unless you call StopAsync() |
The SDK starts the server
The launcher starts a private opencode serve child, creates its password, and gives you a client
bound to it. It resolves opencode from PATH as a shell does, PATHEXT included, so the npm
.cmd shim works on Windows.
using OpenCode.Sdk;
using OpenCode.Sdk.Models;
await using var server = await OpenCodeServer.StartAsync();
using var client = server.CreateClient();
var info = await client.Server.GetInfoAsync();
Console.WriteLine($"opencode {info.ServerInfo.Version} (pid {info.ServerInfo.Pid})");
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"),
});
The client reads no environment variables of its own. The caller decides where the password comes from, as opencode's own CLI does.
The background service the CLI runs
DiscoverAsync reads the CLI's registration file with the CLI's own rules and checks the daemon's
authenticated status. It returns a handle that owns nothing: disposing it never stops the service
that other clients share. Null means no ready service. EnsureAsync starts one through the CLI's
own election when none is running.
var server = await OpenCodeServer.DiscoverAsync();
if (server is null)
{
return; // no ready registered service
}
using var client = server.CreateClient();
The connection guide
has every option, EnsureAsync, StopAsync, launcher output capture, and pairing another client.
Dependency injection
OpenCode.Sdk.Extensions registers one singleton client that owns its transport for the
container's lifetime. The client is thread-safe. Every sub-client resolves from that same instance,
so you can inject SessionsClient, EventsClient, or PtysClient directly.
builder.Services.AddOpenCode(options =>
{
options.Endpoint = new Uri("http://127.0.0.1:4096");
options.Password = Environment.GetEnvironmentVariable("OPENCODE_PASSWORD");
});
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. Configuration
binding uses reflection, so that overload is annotated [RequiresDynamicCode] and
[RequiresUnreferencedCode]. Under trimming or native AOT, use the configure-action overload.
π 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, the background service, pairing, 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 |
| Requests | Request records, the absent/null/set states of Optional<T>, query members, per-call location, and the permission, worktree, and shell-timeout members |
Architecture, decision records, and engineering policy live under
docs/. Start at
AGENTS.md for the
internals.
π Platform Compatibility & Quality Status
Supported Platforms
Both packages target netstandard2.0;net472;net8.0;net9.0;net10.0. The whole suite runs on
net472 on Windows, real-process launcher tests included, and on net8.0, net9.0, and net10.0
on Windows, Linux, and macOS. A .NET Framework project needs a newer C# version than its default;
the getting-started guide
has the two properties to set.
Build & Test Matrix
π¦ Package Status
| Package | NuGet.org | GitHub Packages |
|---|---|---|
| OpenCodeDotNet.Sdk | ||
| OpenCodeDotNet.Sdk.Extensions |
Known Issues
The event bus has no replay.
EventsClient.SubscribeAsyncis a live stream: events published while you are disconnected are lost, and a consumer slower than the producer can overflow and fail the stream. This is the server's contract, not an SDK limitation. See the streaming guide.A CLI-started server's session log holds only the sync marker. The per-session log reads from the server's event store, and the distributed
opencodeCLI starts its server without event persistence. No flag, environment variable, or configuration key turns it on (confirmed at the pin; observed on@opencode/cli@2.0.15).SessionClient.GetLogAsyncdoes not fail: with or withoutFollow, it delivers oneEventLogSyncedmarker and no durable events. For live session activity, subscribe to the global event bus and filter by session id. Persisted replay needs a host that embeds the opencode server library with persistence enabled.On
net472andnetstandard2.0, two costs are larger than on modern targets. A response body over 1 MB is copied once at wire size, because the downlevel array pool caps its buckets at 1 MB. On Unix (Mono), owner-only files that discovery andEnsureAsyncwrite get mode0600through achmodchild process, which does not quote the path or check the exit code. No CI leg runs .NET Framework or Mono on Unix.Each terminal connection allocates a 16 KiB receive buffer, reused across reads. The receiver also queues frames that nobody reads, so a slow or absent consumer can grow memory. See the terminal lifetime contract.
Developing
We appreciate contributions in the form of feedback, bug reports, and pull requests. Read CONTRIBUTING.md first β it has the full gate and the commit convention.
git clone --recurse-submodules https://github.com/opencode-dotnet/opencode-sdk-dotnet.git
cd opencode-sdk-dotnet
dotnet build --configuration Release
external/ holds read-only upstream submodules: protocol evidence, and the pinned server that the
fixture-backed tests run. Those tests also need Bun, the pinned server's dependencies, and
ripgrep; docs/engineering/quality-gates.md
has the versions and the full completion gate.
tests/OpenCode.Sdk.Sandbox
is a committed playground that drives the SDK against a real opencode serve under a debugger.
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 | Versions 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. |
-
.NETFramework 4.7.2
- Microsoft.Bcl.AsyncInterfaces (>= 10.0.11)
- Microsoft.Bcl.Memory (>= 10.0.11)
- System.Buffers (>= 4.6.1)
- System.Collections.Immutable (>= 10.0.11)
- System.Memory (>= 4.6.3)
- System.Text.Json (>= 10.0.11)
- System.Threading.Channels (>= 10.0.11)
-
.NETStandard 2.0
- Microsoft.Bcl.AsyncInterfaces (>= 10.0.11)
- Microsoft.Bcl.Memory (>= 10.0.11)
- System.Buffers (>= 4.6.1)
- System.Collections.Immutable (>= 10.0.11)
- System.Memory (>= 4.6.3)
- System.Text.Json (>= 10.0.11)
- System.Threading.Channels (>= 10.0.11)
-
net10.0
- No dependencies.
-
net8.0
- System.Text.Json (>= 10.0.11)
-
net9.0
- System.Text.Json (>= 10.0.11)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on OpenCodeDotNet.Sdk:
| Package | Downloads |
|---|---|
|
OpenCodeDotNet.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 OpenCodeDotNet.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.9.0-preview.6 | 0 | 10/4/2026 |
| 0.9.0-preview.5 | 56 | 9/29/2026 |
| 0.9.0-preview.4 | 51 | 9/27/2026 |
| 0.9.0-preview.3 | 59 | 9/24/2026 |