OpenCodeAI.Sdk.Extensions
0.8.0-preview.2
dotnet add package OpenCodeAI.Sdk.Extensions --version 0.8.0-preview.2
NuGet\Install-Package OpenCodeAI.Sdk.Extensions -Version 0.8.0-preview.2
<PackageReference Include="OpenCodeAI.Sdk.Extensions" Version="0.8.0-preview.2" />
<PackageVersion Include="OpenCodeAI.Sdk.Extensions" Version="0.8.0-preview.2" />
<PackageReference Include="OpenCodeAI.Sdk.Extensions" />
paket add OpenCodeAI.Sdk.Extensions --version 0.8.0-preview.2
#r "nuget: OpenCodeAI.Sdk.Extensions, 0.8.0-preview.2"
#:package OpenCodeAI.Sdk.Extensions@0.8.0-preview.2
#addin nuget:?package=OpenCodeAI.Sdk.Extensions&version=0.8.0-preview.2&prerelease
#tool nuget:?package=OpenCodeAI.Sdk.Extensions&version=0.8.0-preview.2&prerelease
opencode SDK for .NET
π 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
net472assemblies. Linux and macOS run the same suite onnet8.0,net9.0, andnet10.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 privateopencode servechild for you - β
Source-generated
System.Text.Jsonwith no reflection fallback; both packages declareIsAotCompatibleonnet10.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 before1.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
π¦ Package Status
| Package | NuGet.org | GitHub Packages |
|---|---|---|
| OpenCodeAI.Sdk | ||
| OpenCodeAI.Sdk.Extensions |
Table of Contents
- Supported Platforms
- Why this SDK?
- Prerequisites
- Installation
- Quick Start
- API Coverage
- Documentation
- Known Issues
- Developing
- Changelog
- 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.NoThrowwhen a 404 is a normal answer. - Broad .NET reach.
netstandard2.0andnet472are first-class, so this works inside .NET Framework hosts, not just modern console apps. - No reflection serialization.
System.Text.Jsonsource 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 writeusing OpenCode.Sdk;β the assemblies and every public namespace areOpenCode.Sdk. nuget.org reserves theOpenCode.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:packagesscope, even for public packages (fine-grained tokens are not supported by the NuGet registry).--store-password-in-clear-textis required on Linux and macOS, where NuGet cannot encrypt stored credentials. Never commit a real token β thenuget.configabove reads it from the environment. Inside GitHub Actions you need no PAT at all: the workflow's ownGITHUB_TOKENworks 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 andreferences' 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 asv2.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 asOpenCodeAI.SdkandOpenCodeAI.Sdk.Extensionswhile the assemblies and every public namespace stayOpenCode.Sdk. Nothing about your code changes: only thePackageReferencecarries theOpenCodeAIname.Response bodies larger than 1 MB allocate an extra copy on
net472andnetstandard2.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 letOpenCodeServer.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.SubscribeAsyncis 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
opencodeCLI 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.GetLogAsyncwithoutFollowanswers with a singleEventLogSyncedmarker 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. LiveFollow = Truedelivery is unaffected. See the streaming guide.Running the in-repo sandbox against a different server needs
--no-launch-profile. The checked-inlaunchSettings.jsonprefillsOPENCODE_SANDBOX_ENDPOINTat port 4096, anddotnet runapplies 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. Seetests/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 | 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.Extensions.DependencyInjection.Abstractions (>= 10.0.10)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.10)
- OpenCodeAI.Sdk (>= 0.8.0-preview.2)
-
.NETStandard 2.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.10)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.10)
- OpenCodeAI.Sdk (>= 0.8.0-preview.2)
-
net10.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.10)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.10)
- OpenCodeAI.Sdk (>= 0.8.0-preview.2)
-
net8.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.10)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.10)
- OpenCodeAI.Sdk (>= 0.8.0-preview.2)
-
net9.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.10)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.10)
- OpenCodeAI.Sdk (>= 0.8.0-preview.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.8.0-preview.2 | 29 | 9/12/2026 |
| 0.8.0-preview.1 | 77 | 9/8/2026 |