OpenCodeDotNet.Sdk
1.0.0
Prefix Reserved
dotnet add package OpenCodeDotNet.Sdk --version 1.0.0
NuGet\Install-Package OpenCodeDotNet.Sdk -Version 1.0.0
<PackageReference Include="OpenCodeDotNet.Sdk" Version="1.0.0" />
<PackageVersion Include="OpenCodeDotNet.Sdk" Version="1.0.0" />
<PackageReference Include="OpenCodeDotNet.Sdk" />
paket add OpenCodeDotNet.Sdk --version 1.0.0
#r "nuget: OpenCodeDotNet.Sdk, 1.0.0"
#:package OpenCodeDotNet.Sdk@1.0.0
#addin nuget:?package=OpenCodeDotNet.Sdk&version=1.0.0
#tool nuget:?package=OpenCodeDotNet.Sdk&version=1.0.0
opencode SDK for .NET
π Quick Start: Install | Quick start | Playground | Guide | API coverage
Unofficial. This project is not affiliated with or endorsed by the opencode team.
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.
using OpenCode.Sdk;
using OpenCode.Sdk.Models;
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}");
Prefer learning by example? QuickStart starts a private server and streams a model's reply, no API key needed. The playground has three more apps.
π‘ 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 declareIsAotCompatibleon every modern target. - 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
Every operation in the pinned OpenAPI snapshot is callable except the three declined below; the two terminal connections go through hand-written WebSocket 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.
OpenCodeClient exposes one sub-client per area of the API. Every sub-client is also injectable on
its own when you use dependency injection.
The HTTP API has no file-content search: FileSystemClient.FindEntriesAsync ranks file and directory paths by fuzzy match; it does not search contents.
| Area | Entry point | Guide |
|---|---|---|
| Sessions, messages, prompts | client.Sessions |
Requests, Pagination |
| Durable session event streams | client.Experimental.ReadSessionLogAsync |
Streaming |
| The live event stream | 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 |
Experimental APIs |
The two terminal connections (pty.connect, experimental.persistentPty.connect) are WebSocket
upgrades that the HTTP pipeline cannot carry. They are fully usable through the hand-written
PtySession and PersistentPtySession types. Three are declined by decision: the client-hosted MCP
attach and its token, deferred to a hand-written API in 1.1 (#529), and the legacy text generation
endpoint, which upstream's own clients omit.
src/OpenCode.Sdk/.generation-incomplete
is the machine-readable map that the build reads.
π¦ Installation
dotnet add package OpenCodeDotNet.Sdk
dotnet add package OpenCodeDotNet.Sdk.Extensions # 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.
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.2.5
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 a nightly of the next version, {version}-nightly.{yyyyMMdd}.g{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
To keep the token out of shell history, put the source and its credentials in a nuget.config instead:
<?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>
π 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));
try
{
await foreach (var @event in client.Events.SubscribeAsync(cancellationToken: window.Token))
{
Console.WriteLine($"{@event.GetType().Name} ({@event.Type})");
}
}
catch (OperationCanceledException failure) when (failure.CancellationToken == window.Token)
{
Console.WriteLine("the 30-second window ended");
}
Send a prompt and stream the reply
This is QuickStart's core, shortened. It picks a free model from the live catalog, opens the event stream before it prompts, prints the reply's text as it arrives, and ends on the turn's execution event. QuickStart is the full version: it waits out the server's startup and reports retries and failures.
using OpenCode.Sdk;
using OpenCode.Sdk.Models;
await using var server = await OpenCodeServer.StartAsync();
using var client = server.CreateClient();
// A free model from the live catalog; no API key needed.
await client.LanguageModels.WaitForDefaultAsync();
var catalog = await client.LanguageModels.ListModelsAsync();
var model = catalog.Models.First(static m => m.ProviderId == "opencode" && m.Cost.All(static c => c.Input <= 0));
var created = await client.Sessions.CreateSessionAsync(new SessionCreateRequest
{
Model = new ModelRef { Id = model.Id, ProviderId = model.ProviderId },
});
// Open the event stream before prompting: an event published before you listen is gone.
await using var events = client.Events.SubscribeAsync().GetAsyncEnumerator();
await events.MoveNextAsync();
await client.Sessions.GetSessionClient(created.Session.Id)
.PromptAsync(new SessionPromptRequest { Text = "In one sentence, what is a typed SDK?" });
while (await events.MoveNextAsync())
{
switch (events.Current)
{
case SessionTextDelta delta when delta.Data.SessionId == created.Session.Id:
Console.Write(delta.Data.Delta);
break;
case SessionExecutionSucceeded done when done.Data.SessionId == created.Session.Id:
Console.WriteLine();
return;
case SessionExecutionFailed failed when failed.Data.SessionId == created.Session.Id:
Console.WriteLine($"the turn failed: {failed.Data.Error.Message}");
return;
case SessionExecutionInterrupted stopped when stopped.Data.SessionId == created.Session.Id:
Console.WriteLine($"the turn was interrupted: {stopped.Data.Reason}");
return;
}
}
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
The OpenCodeDotNet.Sdk.Extensions package provides AddOpenCode in the
Microsoft.Extensions.DependencyInjection namespace. It registers one OpenCodeClient singleton
and every sub-client, each only when that service type is not already registered. A consumer's earlier root-client or sub-client
registration wins, and repeated AddOpenCode calls add no duplicate client registrations. The
default root is thread-safe and owns its transport for the container's lifetime; default
sub-clients resolve from the registered root, so you can inject SessionsClient, EventsClient,
or PtysClient directly. See the DI registration guide.
For a non-web host, Host and BackgroundService come from the Microsoft.Extensions.Hosting package:
dotnet add package Microsoft.Extensions.Hosting
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");
});
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.
π Playground
Four runnable apps show the SDK in real workflows:
- QuickStart: start in five minutes without an API key.
- HQ: see live sessions and answer permission asks in Blazor Server with Aspire.
- Worker: run the SDK in a hosted service.
- AotCli: ship a Native AOT tool.
They need the .NET SDK band that global.json pins and the opencode CLI, installed as Installation describes.
π 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 live event stream and durable session 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 |
| Experimental APIs | What upstream serves as experimental, the OPENCODE001 and OPENCODE002 diagnostics, and how to opt in |
Architecture, decision records, and engineering policy live under
docs/.
CONTRIBUTING.md is where contributors start.
π Versions and support
- From
1.0.0, a version's parts say where a change comes from: a patch fixes or adds, a minor moves the pinned opencode release and may change the generated protocol models, and only a major breaks the hand-written surface. 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.
π Platform Compatibility & Quality Status
Supported Platforms
Both packages target netstandard2.0;net472;net10.0;net11.0. The whole suite runs on net472 on
Windows, real-process launcher tests included, and on net10.0 and net11.0 on Windows, Linux,
and macOS. A project whose target framework falls back to the netstandard2.0 asset on a runtime
the packages do not target, such as net8.0 or net9.0, gets a build warning;
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings> silences it. 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 durable session event stream holds only the sync marker. It 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).ExperimentalClient.ReadSessionLogAsyncdoes not fail: with or withoutFollow, it delivers onelog.syncedframe, normally materialized asEventLogSynced, and no durable events. For live session activity, subscribe to the live event stream and filter by session id. Persisted replay needs a host that embeds the opencode server library with persistence enabled.On
net472andnetstandard2.0, some costs are larger than on modern targets. A response body of 64 MiB or more gets a newly allocated buffer on every call. Smaller bodies over 1 MiB come from a pool the SDK keeps for the life of the process, which holds at most about 250 MiB after large responses or requests. A body with no declared length uses every smaller size on the way up, so one 33 MiB body can leave about 126 MiB held. On .NET Framework, a request with more than about 21 MiB of text still allocates its buffer on every call. A request on thenetstandard2.0asset isn't pooled at all. On .NET Framework, reading a JSON string over 1 MiB that contains an escape, such as a newline, allocates one extra array of its length each time. On Unix (Mono), owner-only files that discovery andEnsureAsyncwrite get mode0600through achmodchild process rather than at creation, and a failedchmodfails the write. No CI leg runs .NET Framework or Mono on Unix.A terminal's receiver 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.
Community
Got questions or wild feature ideas?
π Blank issues are disabled. Use the missing-operation form for a missing operation; use the bug form for anything else, including questions and ideas, and say when it is an idea. A vulnerability goes through a private advisory instead, as SECURITY.md says.
Related Projects
- opencode-mcp: a Model Context Protocol (MCP) server project for opencode.
- opencode-agents-ai: a Microsoft Agent Framework provider project for opencode.
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 was computed. 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 was computed. 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. net11.0 is compatible. |
| .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.13)
- Microsoft.Bcl.Memory (>= 10.0.13)
- Microsoft.Bcl.TimeProvider (>= 10.0.13)
- System.Buffers (>= 4.6.1)
- System.Collections.Immutable (>= 10.0.13)
- System.Memory (>= 4.6.3)
- System.Text.Json (>= 10.0.13)
- System.Threading.Channels (>= 10.0.13)
-
.NETStandard 2.0
- Microsoft.Bcl.AsyncInterfaces (>= 10.0.13)
- Microsoft.Bcl.Memory (>= 10.0.13)
- Microsoft.Bcl.TimeProvider (>= 10.0.13)
- System.Buffers (>= 4.6.1)
- System.Collections.Immutable (>= 10.0.13)
- System.Memory (>= 4.6.3)
- System.Text.Json (>= 10.0.13)
- System.Threading.Channels (>= 10.0.13)
-
net10.0
- No dependencies.
-
net11.0
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on OpenCodeDotNet.Sdk:
| Package | Downloads |
|---|---|
|
OpenCodeDotNet.Sdk.Extensions
Dependency injection for the unofficial OpenCode .NET SDK: AddOpenCode registers one client for the OpenCode 2.x server HTTP API and every sub-client resolved from it. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 110 | 11/6/2026 |
| 0.9.0-preview.15 | 195 | 11/1/2026 |
| 0.9.0-preview.14 | 364 | 10/23/2026 |
| 0.9.0-preview.13 | 180 | 10/18/2026 |
| 0.9.0-preview.12 | 130 | 10/17/2026 |
| 0.9.0-preview.11 | 308 | 10/15/2026 |
| 0.9.0-preview.10 | 96 | 10/14/2026 |
| 0.9.0-preview.9 | 70 | 10/13/2026 |
| 0.9.0-preview.8 | 78 | 10/9/2026 |
| 0.9.0-preview.7 | 68 | 10/6/2026 |
| 0.9.0-preview.6 | 70 | 10/4/2026 |
| 0.9.0-preview.5 | 92 | 9/29/2026 |
| 0.9.0-preview.4 | 69 | 9/27/2026 |
| 0.9.0-preview.3 | 77 | 9/24/2026 |