From 18ef16824edabc45915edf1acd2e0f98ad74ad19 Mon Sep 17 00:00:00 2001 From: "denizirgin@gmail.com" Date: Tue, 8 Sep 2026 16:42:24 +0300 Subject: [PATCH 1/6] docs: refresh status claims and document terminal lifetime [skip ci] Three consumer-facing documents had drifted from the tree they describe. The test count in the README and the changelog predates the terminal work by 124 tests, and the package table still rendered dynamic NuGet badges for packages that are not published, so both resolved to an error rather than a status. The project status also claimed the first published package was the only thing missing, which its own next bullet and the known issues already contradict. The terminals guide never received the lifetime contract that shipped with the terminal work. A caller reading it could not learn that canceling a read leaves the connection usable, that undelivered frames queue and the queue is not history, that termination delivers what it already holds before reporting its outcome, that disposal abandons unread frames, or that a send carries a thirty-second budget it can configure. That is now one shared section for both families, relaying the exact contract to the runtime architecture that owns it, and the changelog records the same addition with its SendTimeout option. Every sample now leads with OPENCODE_PASSWORD. Upstream reads that name first and calls OPENCODE_SERVER_PASSWORD its legacy name, still honored, so the fallback survives as a single sentence on the page that owns the explanation rather than as the name every example teaches. The launcher has landed, so the packaging canon states where it ships instead of where it will ship, and every guide page now carries the Date line the documentation rules require. --- CHANGELOG.md | 11 ++- README.md | 20 ++--- docs/architecture/platform-and-packaging.md | 4 +- docs/guide/README.md | 2 + docs/guide/connection-modes.md | 18 +++-- docs/guide/errors-and-responses.md | 2 + docs/guide/getting-started.md | 6 +- docs/guide/pagination.md | 2 + docs/guide/terminals.md | 81 ++++++++++++++++++- .../Properties/launchSettings.json | 4 +- tests/OpenCode.Sdk.Sandbox/README.md | 4 +- 11 files changed, 127 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 358d70237..1b82d3dcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,15 @@ Each released version links straight to its GitHub Release tag. hand-written WebSocket doors over a shared, family-neutral socket core: read frames, write input, dispose to close. The persistent family adds attach/handoff/snapshot semantics, byte-typed output and checkpoints, and the framed input protocol with viewport tracking. +- **Terminal connection lifetime a caller can drive.** A connection-owned receiver assembles + frames into an internal queue independently of the caller's enumeration, so canceling a read ends + only that wait: the same healthy connection can be read again, or written to, afterwards. Both + connect options gained an init-only `SendTimeout` (default 30 seconds) bounding each input or + resize send end to end, including the wait for the send gate. Connection termination delivers the + frames it already holds before reporting its outcome, while disposal abandons unread frames and + joins the connection's own work. A persistent PTY keeps its locally requested viewport coherent + with the input it labels, rather than letting an inbound resize report overwrite it. See + [the terminals guide](docs/guide/terminals.md#-cancellation-deadlines-and-disposal). - **Dependency injection through `OpenCode.Sdk.Extensions`.** `AddOpenCode(Action<…>)` or `AddOpenCode(IConfiguration)` registers one singleton client owning its transport for the container's lifetime, plus every sub-client resolved from that same instance β€” inject @@ -90,7 +99,7 @@ Each released version links straight to its GitHub Release tag. [`spec/SNAPSHOT.md`](spec/SNAPSHOT.md). - **Generated output is committed and reviewed as source**, locked by a public-API baseline and verified by regeneration, so a protocol refresh arrives as a readable diff. -- **Test suite:** 5,010 tests green on Windows β€” the fullest leg, and the only one that adds the +- **Test suite:** 5,134 tests green on Windows β€” the fullest leg, and the only one that adds the `net472` assemblies. Linux and macOS run the same suite on the three modern targets. ### πŸ“‹ Important Notes diff --git a/README.md b/README.md index f45bbffff..36fab53a8 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,14 @@ so what you call is exactly what the server declares. ## πŸŽ‰ Project Status -**Pre-release, and the surface is complete.** Everything below is landed and covered; the only -thing missing is the first published package. +**Pre-release, and the protocol surface is complete.** Everything below is landed and covered. +What is still outstanding is the first published package and opencode's third connection mode β€” +attaching to a registered background service. - βœ… **135 of 140 operations** callable, across 28 client families β€” sessions, PTYs, persistent PTYs, shells, events, MCP servers, integrations, providers, permissions, credentials, VCS, worktrees, websearch, RPC, and more -- βœ… **5,010 tests** green on Windows β€” the fullest leg, the only one that adds the `net472` +- βœ… **5,134 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 @@ -30,7 +31,8 @@ thing missing is the first published package. `opencode serve` child for you - βœ… **Source-generated `System.Text.Json`** with no reflection fallback; both packages declare `IsAotCompatible` on `net10.0` -- πŸ”œ **First release (0.1.0)** β€” packaging is the current work; see [CHANGELOG.md](CHANGELOG.md) +- πŸ”œ **First release (0.1.0)** β€” packed and verified; publication waits on the NuGet.org prefix + reservation dispute in [Known Issues](#known-issues). See [CHANGELOG.md](CHANGELOG.md) - πŸ”œ **Background-service attachment** and an **MCP server** over this SDK β€” both planned, neither started @@ -65,8 +67,8 @@ on, and the `net472` leg is what exercises its compile surface. | Package | NuGet.org | GitHub Packages | |---------|-----------|-----------------| -| **OpenCode.Sdk** | [![NuGet](https://img.shields.io/endpoint?url=https%3A%2F%2Fapi.localstackfor.net%2Fbadges%2Fpackages%2Fnuget%2FOpenCode.Sdk%3Fprerelease%3Dtrue)](https://www.nuget.org/packages/OpenCode.Sdk) | [![GitHub Packages](https://img.shields.io/badge/GitHub%20Packages-nightly-blue)](https://github.com/Blind-Striker/opencode-sdk-dotnet/pkgs/nuget/OpenCode.Sdk) | -| **OpenCode.Sdk.Extensions** | [![NuGet](https://img.shields.io/endpoint?url=https%3A%2F%2Fapi.localstackfor.net%2Fbadges%2Fpackages%2Fnuget%2FOpenCode.Sdk.Extensions%3Fprerelease%3Dtrue)](https://www.nuget.org/packages/OpenCode.Sdk.Extensions) | [![GitHub Packages](https://img.shields.io/badge/GitHub%20Packages-nightly-blue)](https://github.com/Blind-Striker/opencode-sdk-dotnet/pkgs/nuget/OpenCode.Sdk.Extensions) | +| **OpenCode.Sdk** | [![NuGet](https://img.shields.io/badge/NuGet-coming%20soon-lightgrey)](https://www.nuget.org/packages/OpenCode.Sdk) | [![GitHub Packages](https://img.shields.io/badge/GitHub%20Packages-nightly-blue)](https://github.com/Blind-Striker/opencode-sdk-dotnet/pkgs/nuget/OpenCode.Sdk) | +| **OpenCode.Sdk.Extensions** | [![NuGet](https://img.shields.io/badge/NuGet-coming%20soon-lightgrey)](https://www.nuget.org/packages/OpenCode.Sdk.Extensions) | [![GitHub Packages](https://img.shields.io/badge/GitHub%20Packages-nightly-blue)](https://github.com/Blind-Striker/opencode-sdk-dotnet/pkgs/nuget/OpenCode.Sdk.Extensions) | ## Table of Contents @@ -108,7 +110,7 @@ You need an `opencode` server. Install the CLI `opencode2` command), then either run it yourself: ```sh -OPENCODE_SERVER_PASSWORD=your-password opencode2 serve --hostname 127.0.0.1 --port 4096 +OPENCODE_PASSWORD=your-password opencode2 serve --hostname 127.0.0.1 --port 4096 ``` …or let the SDK start one for you β€” that is what `OpenCodeServer.StartAsync()` in the @@ -208,7 +210,7 @@ await foreach (var @event in client.Events.SubscribeAsync(window.Token)) using var client = new OpenCodeClient(new OpenCodeClientOptions { Endpoint = new Uri("http://127.0.0.1:4096"), - Password = Environment.GetEnvironmentVariable("OPENCODE_SERVER_PASSWORD"), + Password = Environment.GetEnvironmentVariable("OPENCODE_PASSWORD"), }); var health = await client.GetHealthAsync(); @@ -235,7 +237,7 @@ var builder = Host.CreateApplicationBuilder(args); builder.Services.AddOpenCode(options => { options.Endpoint = new Uri("http://127.0.0.1:4096"); - options.Password = Environment.GetEnvironmentVariable("OPENCODE_SERVER_PASSWORD"); + options.Password = Environment.GetEnvironmentVariable("OPENCODE_PASSWORD"); }); builder.Services.AddHostedService(); diff --git a/docs/architecture/platform-and-packaging.md b/docs/architecture/platform-and-packaging.md index 5fb896189..bf0b2ac64 100644 --- a/docs/architecture/platform-and-packaging.md +++ b/docs/architecture/platform-and-packaging.md @@ -43,8 +43,8 @@ Two separate API vintages shape the downlevel legs, and conflating them would mi ## Packages -- `OpenCode.Sdk` is the core typed client. The local server launcher belongs in this package when - its milestone lands (ADR-0001); `docs/ROADMAP.md` owns delivery status. +- `OpenCode.Sdk` is the core typed client, and the local server launcher ships inside it + (ADR-0001). - `OpenCode.Sdk.Extensions` owns dependency-injection registration. DI dependencies do not enter the core package. - Exact package references and dependency versions are read from project files and diff --git a/docs/guide/README.md b/docs/guide/README.md index 69f573808..529adebff 100644 --- a/docs/guide/README.md +++ b/docs/guide/README.md @@ -1,5 +1,7 @@ # πŸ“š The opencode SDK guide +Date: 2026-08-31 + Six pages for people **calling** the SDK. Read them in order the first time: install the package and make a call, decide how you reach a server, then the three subsystems that behave differently from ordinary request/response calls β€” event streams, terminals, and cursor lists β€” plus the error diff --git a/docs/guide/connection-modes.md b/docs/guide/connection-modes.md index 1f07f8689..051ecc94b 100644 --- a/docs/guide/connection-modes.md +++ b/docs/guide/connection-modes.md @@ -1,5 +1,7 @@ # πŸ”Œ Connection modes +Date: 2026-09-08 + There are two ways to get a client bound to a running opencode server: **let the SDK start one**, or **point it at one you already have**. Dependency injection is not a third way in β€” it is how you register either of them with a container. @@ -99,7 +101,7 @@ var endpoint = new Uri("http://127.0.0.1:4096"); using var client = new OpenCodeClient(new OpenCodeClientOptions { Endpoint = endpoint, - Password = Environment.GetEnvironmentVariable("OPENCODE_SERVER_PASSWORD"), + Password = Environment.GetEnvironmentVariable("OPENCODE_PASSWORD"), }); using var probe = new CancellationTokenSource(TimeSpan.FromSeconds(5)); @@ -116,17 +118,19 @@ the SDK carries no version comparand of its own and no network-timeout knob yet, `CancellationTokenSource` is the honest timeout and your own expectation is the honest version check. -**About `OPENCODE_SERVER_PASSWORD`**: that is the variable *the opencode CLI* reads when you start +**About `OPENCODE_PASSWORD`**: that is the variable *the opencode CLI* reads when you start a server with authentication β€” ```sh -OPENCODE_SERVER_PASSWORD=your-password opencode2 serve --hostname 127.0.0.1 --port 4096 +OPENCODE_PASSWORD=your-password opencode2 serve --hostname 127.0.0.1 --port 4096 ``` -β€” and the client must present the same value as its Basic password. The SDK never reads it, or any -other environment variable, for you. Reading it in the snippet above is your application's choice; -a configuration section or a secret store works exactly as well. A server started **without** a -password expects anonymous requests, so leave `Password` as `null` for one. +β€” and the client must present the same value as its Basic password. `OPENCODE_SERVER_PASSWORD` is +the CLI's legacy name for the same value, still honored as a fallback, so an older setup keeps +working. The SDK never reads either one, or any other environment variable, for you. Reading it in +the snippet above is your application's choice; a configuration section or a secret store works +exactly as well. A server started **without** a password expects anonymous requests, so leave +`Password` as `null` for one. ## 🧩 Registering with dependency injection diff --git a/docs/guide/errors-and-responses.md b/docs/guide/errors-and-responses.md index ae4ee8f79..b75fcd369 100644 --- a/docs/guide/errors-and-responses.md +++ b/docs/guide/errors-and-responses.md @@ -1,5 +1,7 @@ # 🧱 Errors and responses +Date: 2026-08-31 + Every one-shot call answers with a **response envelope** that carries the same four members no matter which operation produced it, and every failure is either a typed API error you can branch on or a transport failure you cannot. Nothing is stringly typed, and nothing is swallowed. diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md index bbd2e4b96..b0aa4375f 100644 --- a/docs/guide/getting-started.md +++ b/docs/guide/getting-started.md @@ -1,6 +1,6 @@ # πŸš€ Getting started -Date: 2026-09-06 +Date: 2026-09-08 Install the package, point a client at a server, and make three calls. Ten minutes, and the last one talks to a model. @@ -36,7 +36,7 @@ using OpenCode.Sdk.Models; using var client = new OpenCodeClient(new OpenCodeClientOptions { Endpoint = new Uri("http://127.0.0.1:4096"), - Password = Environment.GetEnvironmentVariable("OPENCODE_SERVER_PASSWORD"), + Password = Environment.GetEnvironmentVariable("OPENCODE_PASSWORD"), }); ``` @@ -54,7 +54,7 @@ the server's working directory. Session listing filters with `SessionListRequest `Project`, independently of these headers. See the [location contract](../architecture/client-runtime.md#location) for ambient and per-call header behavior. -> **πŸ”‘ The SDK reads no environment variables of its own.** `OPENCODE_SERVER_PASSWORD` above is +> **πŸ”‘ The SDK reads no environment variables of its own.** `OPENCODE_PASSWORD` above is > *your* code reading *your* environment β€” exactly how opencode's own CLI layers it. Options are > snapshotted at construction, so changing the environment later never reaches a live client. diff --git a/docs/guide/pagination.md b/docs/guide/pagination.md index 0d1918ba6..ad5ff4350 100644 --- a/docs/guide/pagination.md +++ b/docs/guide/pagination.md @@ -1,5 +1,7 @@ # πŸ“‘ Pagination +Date: 2026-08-31 + Two operations in the API return more than fits in one response, and both use the same opaque cursor envelope: **listing a session's messages** and **listing sessions**. Everything else returns its whole answer at once. diff --git a/docs/guide/terminals.md b/docs/guide/terminals.md index 43e401ad1..9b975e381 100644 --- a/docs/guide/terminals.md +++ b/docs/guide/terminals.md @@ -1,5 +1,7 @@ # πŸ–₯️ Terminals +Date: 2026-09-08 + opencode has two terminal families, and they are genuinely different animals: | | **PTY** | **Persistent PTY** | @@ -18,6 +20,7 @@ generated from an OpenAPI document. - [🧡 A normal PTY, end to end](#-a-normal-pty-end-to-end) - [♻️ Resuming with a cursor](#️-resuming-with-a-cursor) - [🧷 Persistent PTYs](#-persistent-ptys) +- [πŸ›‘ Cancellation, deadlines, and disposal](#-cancellation-deadlines-and-disposal) - [πŸͺŸ The Windows platform note](#-the-windows-platform-note) - [🎫 Connect tokens](#-connect-tokens) @@ -64,7 +67,9 @@ Three rules worth knowing before your first surprise: 1. **Enter is `\r`.** `WriteAsync` sends exactly the bytes you give it. A command ending in `\n` renders on the terminal and then sits there forever, unsubmitted. 2. **One read at a time.** A session carries one active read enumeration; starting a second - concurrently throws `InvalidOperationException`. Writes are serialized for you. + concurrently throws `InvalidOperationException`. Writes are serialized for you. Canceling a read + does not end the connection β€” see + [cancellation, deadlines, and disposal](#-cancellation-deadlines-and-disposal). 3. **There is no end-of-command marker on this wire.** A terminal just goes quiet. If you need to know a command finished, either wait for the stream to settle or ask `GetPtyAsync` for the PTY's status and exit code β€” the exit code is *not* on the socket. @@ -184,6 +189,80 @@ The collection client also carries the daemon's lifecycle doors: `HandoffAsync` to outlive this server until a replacement claims it, and `ShutdownAsync` stops the daemon **and every terminal it owns** β€” not just one. +## πŸ›‘ Cancellation, deadlines, and disposal + +Both terminal families share one lifetime model. This is what it means for a caller; the exact +contract is owned by [the client runtime architecture](../architecture/client-runtime.md). + +**Canceling a read ends the read, not the connection.** A connection-owned receiver assembles +frames independently of your enumeration, so the token you hand `ReadAsync` bounds *your* wait and +nothing else. Stop reading, send input, start a new enumeration on the same session β€” it still +works: + +```csharp +using (var window = new CancellationTokenSource(TimeSpan.FromSeconds(5))) +{ + try + { + await foreach (var frame in terminal.ReadAsync(window.Token)) + { + // …stop once you have seen enough + } + } + catch (OperationCanceledException) + { + // the wait ended; the connection did not + } +} + +await terminal.WriteAsync("echo again\r"); + +await foreach (var frame in terminal.ReadAsync(cancellationToken)) +{ + // same connection, still healthy +} +``` + +This is an SDK contract, not something `IAsyncEnumerable` gives you for free, and not an upstream +cancel/resume API. "One read at a time" still holds β€” it is a *concurrent* second enumeration that +throws, not a later one. + +**Frames you have not read are queued, and the queue is not history.** The receiver holds only what +it has not handed you yet, so a connection nobody reads grows in memory; the queue is deliberately +unbounded until a real workload argues for a limit. It is not a replay buffer either β€” to go back, +reconnect with a [cursor](#️-resuming-with-a-cursor). + +**When the connection ends, you still get what it already had.** A remote close or a transport +failure hands you the queued frames in order first, then completes normally or throws. That ending +is stable: a later read reports the same outcome rather than a fresh error. + +**Disposal is the other path, and it does not wait for you.** `DisposeAsync` closes the socket, +abandons unread frames, and joins the connection's own work. Reads after it are empty, writes throw +`ObjectDisposedException`, and two callers disposing at once await the same cleanup. + +**Sends carry their own deadline.** `PtyConnectOptions.SendTimeout` and +`PersistentPtyConnectOptions.SendTimeout` default to 30 seconds and bound each input or resize send +end to end, the wait for the send gate included: + +```csharp +await using var bounded = await pty.ConnectAsync(new PtyConnectOptions +{ + SendTimeout = TimeSpan.FromSeconds(5), +}); +``` + +It is a fixed total per call β€” progress does not restart it β€” and it is not a connect timeout, an +idle timeout, or a command-execution timeout. Expiry while the call is still queued fails that call +alone; expiry once the bytes are going out ends the attachment, because whether the server saw them +is no longer knowable. Nothing is retried. Your own cancellation stays +`OperationCanceledException`; the SDK's deadline is an `OpenCodeTransportException` naming a +timeout. + +**A persistent PTY keeps your viewport intent.** The outbound size starts at the attachment's size +and moves when a `ResizeAsync` send succeeds. A `PersistentPtyResizedFrame` tells you how the +server is rendering β€” it does not overwrite what you asked for, and every input you send carries a +size coherent with its place in the send order. + ## πŸͺŸ The Windows platform note **Persistent PTYs do not work on Windows hosts.** At the pinned snapshot, upstream's `opencode-pty` diff --git a/tests/OpenCode.Sdk.Sandbox/Properties/launchSettings.json b/tests/OpenCode.Sdk.Sandbox/Properties/launchSettings.json index c43446ffd..b80759020 100644 --- a/tests/OpenCode.Sdk.Sandbox/Properties/launchSettings.json +++ b/tests/OpenCode.Sdk.Sandbox/Properties/launchSettings.json @@ -5,7 +5,7 @@ "commandLineArgs": "--stream", "environmentVariables": { "OPENCODE_SANDBOX_ENDPOINT": "http://127.0.0.1:4096", - "OPENCODE_SERVER_PASSWORD": "123456" + "OPENCODE_PASSWORD": "123456" } }, "sandbox-events": { @@ -13,7 +13,7 @@ "commandLineArgs": "--events", "environmentVariables": { "OPENCODE_SANDBOX_ENDPOINT": "http://127.0.0.1:4096", - "OPENCODE_SERVER_PASSWORD": "123456" + "OPENCODE_PASSWORD": "123456" } } } diff --git a/tests/OpenCode.Sdk.Sandbox/README.md b/tests/OpenCode.Sdk.Sandbox/README.md index 5f7ca3674..5d8eae803 100644 --- a/tests/OpenCode.Sdk.Sandbox/README.md +++ b/tests/OpenCode.Sdk.Sandbox/README.md @@ -16,11 +16,11 @@ Configuration comes from environment variables, prefilled for the IDE by ## Running Start a server with a fixed password so the checked-in profile matches (`serve` adopts the -same `OPENCODE_SERVER_PASSWORD` variable; without it the server generates and prints a +same `OPENCODE_PASSWORD` variable; without it the server generates and prints a random one): ```sh -OPENCODE_SERVER_PASSWORD=123456 opencode2 serve --hostname 127.0.0.1 --port 4096 +OPENCODE_PASSWORD=123456 opencode2 serve --hostname 127.0.0.1 --port 4096 ``` Then F5 with one of the sandbox profiles, or run either stream mode directly: From 0989abc313f55a0e0be64a674e24264249776176 Mon Sep 17 00:00:00 2001 From: "denizirgin@gmail.com" Date: Tue, 8 Sep 2026 18:18:20 +0300 Subject: [PATCH 2/6] build: move the version line to 0.8.0 The first NuGet.org release will be a 0.8.0-preview.N prerelease rather than 0.1.0, iterating the preview and patch numbers toward 1.0.0. Nothing is published yet, so nothing migrates: the single-sourced VersionPrefix moves and every place that quoted the old number follows it. Prose that named an exact release number now names the release instead, because that number changes with each preview while the version prefix and the nightly format do not. The roadmap's open question narrows accordingly: the prerelease-versus-stable decision is made, and only the release-notes flow stays open. --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- .github/SECURITY.md | 2 +- .github/workflows/publish-nuget.yml | 2 +- CHANGELOG.md | 7 ++++--- Directory.Build.props | 2 +- README.md | 10 +++++----- docs/ROADMAP.md | 7 ++++--- docs/guide/getting-started.md | 2 +- 8 files changed, 18 insertions(+), 16 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index d542e5bdb..47f95c53d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -40,7 +40,7 @@ using var client = new OpenCodeClient(new OpenCodeClientOptions **OpenCode.Sdk Version:** -- Version: (e.g., `0.1.0-nightly.20260831.abc1234`) +- Version: (e.g., `0.8.0-nightly.20260831.abc1234`) - Package source: (NuGet.org / GitHub Packages nightly / built from source at commit …) - Also using `OpenCode.Sdk.Extensions`? (yes / no) diff --git a/.github/SECURITY.md b/.github/SECURITY.md index 0556ec71a..a1102bb86 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -2,7 +2,7 @@ ## Supported Versions -This project is pre-1.0 and has not published a stable release yet. Until `0.1.0` ships, the +This project is pre-1.0 and has not published a stable release yet. Until the first release ships, the supported version is whatever is current on `master` (and the nightly packages built from it). Security patches are prioritized by CVSS v3.0 rating: diff --git a/.github/workflows/publish-nuget.yml b/.github/workflows/publish-nuget.yml index 1df99eba9..6d0ff651e 100644 --- a/.github/workflows/publish-nuget.yml +++ b/.github/workflows/publish-nuget.yml @@ -24,7 +24,7 @@ on: workflow_dispatch: inputs: version: - description: "Exact version to stamp on the packages (e.g. 0.1.0-preview.1 or 0.1.0)" + description: "Exact version to stamp on the packages (e.g. 0.8.0-preview.1 or 0.8.0)" required: true target: type: choice diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b82d3dcc..6a84ba712 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,10 @@ Each released version links straight to its GitHub Release tag. ## [Unreleased] -> **The first release.** `0.1.0` has not shipped to NuGet.org yet β€” nightly builds of everything -> below are on [GitHub Packages](README.md#nightly-builds-github-packages) today, versioned -> `0.1.0-nightly.{yyyyMMdd}.{shortSha}` from every code push to `master`. +> **The first release.** Nothing has shipped to NuGet.org yet, and it will arrive as a +> `0.8.0-preview.N` prerelease. Nightly builds of everything below are on +> [GitHub Packages](README.md#nightly-builds-github-packages) today, versioned +> `0.8.0-nightly.{yyyyMMdd}.{shortSha}` from every code push to `master`. ### ✨ New features diff --git a/Directory.Build.props b/Directory.Build.props index e0e1d491e..429822b2d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -10,7 +10,7 @@ enable - 0.1.0 + 0.8.0 Deniz Δ°rgin Deniz Δ°rgin Deniz Δ°rgin diff --git a/README.md b/README.md index 36fab53a8..5f5afa4b6 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ attaching to a registered background service. `opencode serve` child for you - βœ… **Source-generated `System.Text.Json`** with no reflection fallback; both packages declare `IsAotCompatible` on `net10.0` -- πŸ”œ **First release (0.1.0)** β€” packed and verified; publication waits on the NuGet.org prefix +- πŸ”œ **First release** β€” packed and verified; publication waits on the NuGet.org prefix reservation dispute in [Known Issues](#known-issues). See [CHANGELOG.md](CHANGELOG.md) - πŸ”œ **Background-service attachment** and an **MCP server** over this SDK β€” both planned, neither started @@ -120,8 +120,8 @@ OPENCODE_PASSWORD=your-password opencode2 serve --hostname 127.0.0.1 --port 4096 ### Stable (NuGet.org) -**The first release is upcoming.** Nothing is on NuGet.org yet; when `0.1.0` ships, installation -will be the usual: +**The first release is upcoming.** Nothing is on NuGet.org yet; when it ships, installation will +be the usual: ```bash dotnet add package OpenCode.Sdk @@ -132,7 +132,7 @@ Until then, use the nightly feed below. ### Nightly builds (GitHub Packages) -Every code push to `master` publishes `0.1.0-nightly.{yyyyMMdd}.{shortSha}` to GitHub Packages: +Every code push to `master` publishes `0.8.0-nightly.{yyyyMMdd}.{shortSha}` to GitHub Packages: ```bash # Add the GitHub Packages source (PAT: classic token with the read:packages scope) @@ -303,7 +303,7 @@ Architecture, decision records, and engineering policy live under [`docs/`](docs ## Known Issues -- **Nothing is on NuGet.org yet.** The `0.1.0` package is prepared but unpublished β€” +- **Nothing is on NuGet.org yet.** The packages are prepared but unpublished β€” publication is currently blocked by an upstream package-ID prefix reservation dispute β€” so `dotnet add package OpenCode.Sdk` will not resolve. Use the [GitHub Packages nightly feed](#nightly-builds-github-packages) until the first stable release; diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 4184e097e..549ba7e43 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -29,7 +29,7 @@ accepted OpenAPI snapshot and rides one hand-written transport runtime. round trip and normal PTY reuse after read cancellation. `architecture/client-runtime.md` and ADR-0023 own the contract. - **Packages** β€” `OpenCode.Sdk` and `OpenCode.Sdk.Extensions` pack at the single-sourced - `VersionPrefix 0.1.0`, and every `master` push publishes a `0.1.0-nightly.*` build to GitHub + `VersionPrefix 0.8.0`, and every `master` push publishes a `0.8.0-nightly.*` build to GitHub Packages. NuGet.org publication is currently blocked by an upstream prefix reservation dispute; the manual publish lane is wired and waits on it. @@ -89,8 +89,9 @@ is revisited at each boundary. value, a numeric range and a file path both invisible behind bare strings, a WebSocket close code overloaded across two causes, and two declared arms the handler cannot produce. Findings stay diagnostic and never feed generation or curation (ADR-0013). -- **Release mechanics** β€” ADR-0006's shape is wired. Open: the release-notes flow and the - prerelease-versus-stable wording for the first tagged release. +- **Release mechanics** β€” ADR-0006's shape is wired, and the first tagged release ships as a + `0.8.0-preview.N` prerelease, iterating the preview and patch numbers toward `1.0.0`. Open: + the release-notes flow. - **Deferred design questions, each parked behind a named trigger** β€” splitting validated client configuration from the transport factory (reopens when M6 attaches telemetry or hooks, or when Extensions gains a concrete named-client need); a parent-mediated handle door for flat diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md index b0aa4375f..8bf2a71b1 100644 --- a/docs/guide/getting-started.md +++ b/docs/guide/getting-started.md @@ -13,7 +13,7 @@ one talks to a model. ## πŸ“¦ Install -**Nothing is on NuGet.org yet** β€” `0.1.0` is prepared but unpublished. Where the packages can be +**Nothing is on NuGet.org yet** β€” the packages are prepared but unpublished. Where they can be had in the meantime, the exact `dotnet nuget add source` command, the two package names, and the `read:packages` token requirement all live in one place so they never drift: [**Installation** in the root README](../../README.md#-installation). From f0efbd5fd077d067a4778950eac0225811578e28 Mon Sep 17 00:00:00 2001 From: "denizirgin@gmail.com" Date: Tue, 8 Sep 2026 19:30:58 +0300 Subject: [PATCH 3/6] build: publish under the OpenCodeAI package ids nuget.org reserves the OpenCode. prefix for an unrelated owner, which is why every push of OpenCode.Sdk was refused with a 409 the publish lane reported as success. An upload probe confirmed the reservation is dot-terminated, so OpenCodeAI.Sdk is accepted, and the first release ships under that identity while the dispute runs its course. Only the package id moves. Neither project declared a PackageId before, so both inherited their assembly name; each now sets its own and nothing else changes. The shipped assemblies stay OpenCode.Sdk.dll and OpenCode.Sdk.Extensions.dll, every public namespace stays OpenCode.Sdk, the public API baseline is untouched, and consumer source needs no edit at all: a project reference packs as a dependency on OpenCodeAI.Sdk, so the two artifacts still resolve each other. The install commands, package table, badge targets, and GitHub Packages links follow the ids, the publish workflow's header names the ids the Trusted Publishing policy has to cover, and the packaging canon records that the ids and the assembly names are deliberately different things. --- .github/workflows/publish-nuget.yml | 2 +- README.md | 16 ++++++++-------- docs/architecture/platform-and-packaging.md | 5 +++++ .../OpenCode.Sdk.Extensions.csproj | 2 ++ src/OpenCode.Sdk/OpenCode.Sdk.csproj | 4 ++++ 5 files changed, 20 insertions(+), 9 deletions(-) diff --git a/.github/workflows/publish-nuget.yml b/.github/workflows/publish-nuget.yml index 6d0ff651e..81396d8f6 100644 --- a/.github/workflows/publish-nuget.yml +++ b/.github/workflows/publish-nuget.yml @@ -11,7 +11,7 @@ # target: nuget publishes to NuGet.org via Trusted Publishing (OIDC through NuGet/login@v1, no # API key β€” mirrors localstack-dotnet-client's publish-nuget.yml, PR #54). This requires a Trusted # Publishing policy configured on the NuGet.org account that will own this package (package IDs -# OpenCode.Sdk and OpenCode.Sdk.Extensions, repository Blind-Striker/opencode-sdk-dotnet, workflow +# OpenCodeAI.Sdk and OpenCodeAI.Sdk.Extensions, repository Blind-Striker/opencode-sdk-dotnet, workflow # file publish-nuget.yml, no environment) β€” the maintainer must create that policy on nuget.org # BEFORE the first nuget run; until then only target: github succeeds. The NuGet.org profile (or # organization) name the policy is created under must also be set as the repository variable diff --git a/README.md b/README.md index 5f5afa4b6..8b83323f5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # opencode SDK for .NET -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![NuGet](https://img.shields.io/badge/NuGet-coming%20soon-lightgrey)](https://www.nuget.org/packages/OpenCode.Sdk) [![CI](https://github.com/Blind-Striker/opencode-sdk-dotnet/actions/workflows/ci.yml/badge.svg)](https://github.com/Blind-Striker/opencode-sdk-dotnet/actions/workflows/ci.yml) [![Linux Tests](https://img.shields.io/endpoint?url=https%3A%2F%2Fapi.localstackfor.net%2Fbadges%2Ftests%2Flinux%2Fblind-striker%2Fopencode-sdk-dotnet%2Fmaster)](https://api.localstackfor.net/redirect/test-results/linux/blind-striker/opencode-sdk-dotnet/master) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![NuGet](https://img.shields.io/badge/NuGet-coming%20soon-lightgrey)](https://www.nuget.org/packages/OpenCodeAI.Sdk) [![CI](https://github.com/Blind-Striker/opencode-sdk-dotnet/actions/workflows/ci.yml/badge.svg)](https://github.com/Blind-Striker/opencode-sdk-dotnet/actions/workflows/ci.yml) [![Linux Tests](https://img.shields.io/endpoint?url=https%3A%2F%2Fapi.localstackfor.net%2Fbadges%2Ftests%2Flinux%2Fblind-striker%2Fopencode-sdk-dotnet%2Fmaster)](https://api.localstackfor.net/redirect/test-results/linux/blind-striker/opencode-sdk-dotnet/master) > **πŸš€ Quick Start**: the first release is upcoming β€” [nightly builds](#-installation) are on GitHub Packages today | [Quick start](#-quick-start) | [Guide](docs/guide/getting-started.md) @@ -67,8 +67,8 @@ on, and the `net472` leg is what exercises its compile surface. | Package | NuGet.org | GitHub Packages | |---------|-----------|-----------------| -| **OpenCode.Sdk** | [![NuGet](https://img.shields.io/badge/NuGet-coming%20soon-lightgrey)](https://www.nuget.org/packages/OpenCode.Sdk) | [![GitHub Packages](https://img.shields.io/badge/GitHub%20Packages-nightly-blue)](https://github.com/Blind-Striker/opencode-sdk-dotnet/pkgs/nuget/OpenCode.Sdk) | -| **OpenCode.Sdk.Extensions** | [![NuGet](https://img.shields.io/badge/NuGet-coming%20soon-lightgrey)](https://www.nuget.org/packages/OpenCode.Sdk.Extensions) | [![GitHub Packages](https://img.shields.io/badge/GitHub%20Packages-nightly-blue)](https://github.com/Blind-Striker/opencode-sdk-dotnet/pkgs/nuget/OpenCode.Sdk.Extensions) | +| **OpenCodeAI.Sdk** | [![NuGet](https://img.shields.io/badge/NuGet-coming%20soon-lightgrey)](https://www.nuget.org/packages/OpenCodeAI.Sdk) | [![GitHub Packages](https://img.shields.io/badge/GitHub%20Packages-nightly-blue)](https://github.com/Blind-Striker/opencode-sdk-dotnet/pkgs/nuget/OpenCodeAI.Sdk) | +| **OpenCodeAI.Sdk.Extensions** | [![NuGet](https://img.shields.io/badge/NuGet-coming%20soon-lightgrey)](https://www.nuget.org/packages/OpenCodeAI.Sdk.Extensions) | [![GitHub Packages](https://img.shields.io/badge/GitHub%20Packages-nightly-blue)](https://github.com/Blind-Striker/opencode-sdk-dotnet/pkgs/nuget/OpenCodeAI.Sdk.Extensions) | ## Table of Contents @@ -124,8 +124,8 @@ OPENCODE_PASSWORD=your-password opencode2 serve --hostname 127.0.0.1 --port 4096 be the usual: ```bash -dotnet add package OpenCode.Sdk -dotnet add package OpenCode.Sdk.Extensions # dependency injection, optional +dotnet add package OpenCodeAI.Sdk +dotnet add package OpenCodeAI.Sdk.Extensions # dependency injection, optional ``` Until then, use the nightly feed below. @@ -143,8 +143,8 @@ dotnet nuget add source https://nuget.pkg.github.com/Blind-Striker/index.json \ --store-password-in-clear-text # Install the nightly packages -dotnet add package OpenCode.Sdk --prerelease --source github-opencode-sdk -dotnet add package OpenCode.Sdk.Extensions --prerelease --source github-opencode-sdk +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 @@ -305,7 +305,7 @@ Architecture, decision records, and engineering policy live under [`docs/`](docs - **Nothing is on NuGet.org yet.** The packages are prepared but unpublished β€” publication is currently blocked by an upstream package-ID prefix reservation dispute β€” so - `dotnet add package OpenCode.Sdk` will not resolve. Use the + `dotnet add package OpenCodeAI.Sdk` will not resolve. Use the [GitHub Packages nightly feed](#nightly-builds-github-packages) until the first stable release; the nightly and stable packages are built from the same sources by two workflows that share the same verify-and-pack steps. diff --git a/docs/architecture/platform-and-packaging.md b/docs/architecture/platform-and-packaging.md index bf0b2ac64..810b0678e 100644 --- a/docs/architecture/platform-and-packaging.md +++ b/docs/architecture/platform-and-packaging.md @@ -47,6 +47,11 @@ Two separate API vintages shape the downlevel legs, and conflating them would mi (ADR-0001). - `OpenCode.Sdk.Extensions` owns dependency-injection registration. DI dependencies do not enter the core package. +- The two names above are assembly and namespace names. The published package ids are + `OpenCodeAI.Sdk` and `OpenCodeAI.Sdk.Extensions`, because nuget.org reserves the `OpenCode.` + prefix for an unrelated owner. Each packable project sets its own `PackageId`; the assemblies, + the public namespaces, and therefore consumer source are unaffected, and a project reference + packs as a dependency on the corresponding package id. - Exact package references and dependency versions are read from project files and `Directory.Packages.props`. Documentation records policy, not a second version inventory. - A future package is added only for a real distribution boundary; repository layout alone does diff --git a/src/OpenCode.Sdk.Extensions/OpenCode.Sdk.Extensions.csproj b/src/OpenCode.Sdk.Extensions/OpenCode.Sdk.Extensions.csproj index 048339c86..11e821d5d 100644 --- a/src/OpenCode.Sdk.Extensions/OpenCode.Sdk.Extensions.csproj +++ b/src/OpenCode.Sdk.Extensions/OpenCode.Sdk.Extensions.csproj @@ -2,6 +2,8 @@ $(AllTargetFrameworks) + + OpenCodeAI.Sdk.Extensions Dependency injection integration for the unofficial opencode .NET SDK. opencode;sdk;dependency-injection;options true diff --git a/src/OpenCode.Sdk/OpenCode.Sdk.csproj b/src/OpenCode.Sdk/OpenCode.Sdk.csproj index 83b32a04b..020874148 100644 --- a/src/OpenCode.Sdk/OpenCode.Sdk.csproj +++ b/src/OpenCode.Sdk/OpenCode.Sdk.csproj @@ -2,6 +2,10 @@ $(AllTargetFrameworks) + + OpenCodeAI.Sdk Unofficial .NET SDK for opencode: a typed client for the opencode HTTP API. opencode;sdk;client;ai;agent true From 5a341b9ab821ed0aca71cc8c3bc90ea5139105b9 Mon Sep 17 00:00:00 2001 From: "denizirgin@gmail.com" Date: Tue, 8 Sep 2026 22:00:39 +0300 Subject: [PATCH 4/6] docs: match the documented test count to the merged guard test The fixture contract guard landed with the snapshot refresh and runs on every target framework, so the suite is four tests larger than the number the readme and the changelog carry. --- CHANGELOG.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a84ba712..da295ec91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -100,7 +100,7 @@ Each released version links straight to its GitHub Release tag. [`spec/SNAPSHOT.md`](spec/SNAPSHOT.md). - **Generated output is committed and reviewed as source**, locked by a public-API baseline and verified by regeneration, so a protocol refresh arrives as a readable diff. -- **Test suite:** 5,134 tests green on Windows β€” the fullest leg, and the only one that adds the +- **Test suite:** 5,138 tests green on Windows β€” the fullest leg, and the only one that adds the `net472` assemblies. Linux and macOS run the same suite on the three modern targets. ### πŸ“‹ Important Notes diff --git a/README.md b/README.md index 8b83323f5..78aff60f4 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ attaching to a registered background service. - βœ… **135 of 140 operations** callable, across 28 client families β€” sessions, PTYs, persistent PTYs, shells, events, MCP servers, integrations, providers, permissions, credentials, VCS, worktrees, websearch, RPC, and more -- βœ… **5,134 tests** green on Windows β€” the fullest leg, the only one that adds the `net472` +- βœ… **5,138 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 From 42b2808f1142384dd844c7a03aae86514ecac479 Mon Sep 17 00:00:00 2001 From: "denizirgin@gmail.com" Date: Tue, 8 Sep 2026 22:48:40 +0300 Subject: [PATCH 5/6] fix: default the launcher to the executable the v2 CLI installs The standalone door defaulted to `opencode serve`, described in its own summary as upstream's default command shape. Upstream has no such default: its standalone mode re-invokes the executable it is already running, because the CLI is opencode. An SDK cannot do that, so it has to name a binary, and the one it should name is the one the line this SDK speaks to installs. That CLI package declares exactly one executable and calls it `opencode2`. Nothing exercised the old value. Every test supplies its own command, pointing at the pinned submodule through bun, so the default was never spawned by anything and the mismatch survived: the readme tells a reader to install `opencode2`, and the quick start then called `StartAsync()` with a default that looked for `opencode`. That is the first sample on the package page, sold as needing nothing running in advance. A caller who wants a different binary still passes `Command`; a caller whose executable is missing gets a spawn failure naming it. --- docs/architecture/client-runtime.md | 3 ++- src/OpenCode.Sdk/OpenCodeServerOptions.cs | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/architecture/client-runtime.md b/docs/architecture/client-runtime.md index 2aa3a4ec8..0824de1d9 100644 --- a/docs/architecture/client-runtime.md +++ b/docs/architecture/client-runtime.md @@ -340,7 +340,8 @@ local server launcher. Protocol and generated-model rules live in `OpenCodeServer.StartAsync(OpenCodeServerOptions?, CancellationToken)` is the standalone door (upstream `Standalone.start` parity), hand-written over `System.Diagnostics.Process` with no process-management dependency (ADR-0001). Every call is always a fresh private server on port -zero: the caller's `Command` plus `--stdio --port 0` is the argv, and a freshly generated lease +zero: the caller's `Command` β€” `opencode2 serve` by default, the single executable the line +this SDK speaks to installs β€” plus `--stdio --port 0` is the argv, and a freshly generated lease credential is injected into the child environment as `OPENCODE_PASSWORD`, after any caller-supplied `Environment` entries so it can never be shadowed. Readiness is the single JSON stdout line the child prints once fully booted; stdin stays open as the ownership lease for as long as the server diff --git a/src/OpenCode.Sdk/OpenCodeServerOptions.cs b/src/OpenCode.Sdk/OpenCodeServerOptions.cs index 92d63a192..bfe712f59 100644 --- a/src/OpenCode.Sdk/OpenCodeServerOptions.cs +++ b/src/OpenCode.Sdk/OpenCodeServerOptions.cs @@ -10,10 +10,12 @@ public sealed class OpenCodeServerOptions /// /// Gets or sets the server command: the executable followed by its leading arguments. The /// launcher appends --stdio --port 0 β€” the reference client's exact standalone argv. - /// The default runs opencode serve from the PATH, upstream's own default command - /// shape; tests and tools point this at a source-run instead. + /// The default runs opencode2 serve from the PATH: the CLI package of the line this + /// SDK speaks to declares exactly one executable and names it opencode2. Upstream's + /// own standalone mode re-invokes the executable it is already running rather than naming + /// one, which an SDK cannot do. Tests and tools point this at a source run instead. /// - public IReadOnlyList Command { get; set; } = ["opencode", "serve"]; + public IReadOnlyList Command { get; set; } = ["opencode2", "serve"]; /// Gets or sets the child's working directory; null inherits the caller's. public string? WorkingDirectory { get; set; } From e07bb91f3056f11f6c3587879af0255d347aa665 Mon Sep 17 00:00:00 2001 From: "denizirgin@gmail.com" Date: Tue, 8 Sep 2026 22:48:55 +0300 Subject: [PATCH 6/6] docs: make the package page and the guide true at publication MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The root readme is the package page, and it announced that the package does not exist: "Nothing is on NuGet.org yet", an install command that "will not resolve", and three grey coming-soon badges. That text is embedded at pack time and a published package's readme cannot be edited afterwards, so it had to go before the first push rather than after it. The wording is now evergreen β€” the commands say what to run, and a live version badge says whether a version exists β€” so it is correct both before and after a publish, and does not need editing again at the next one. Twenty-three relative links are now absolute. NuGet.org renders the embedded readme with a restricted subset that blanks any non-absolute link, so the whole documentation table, the licence, the changelog and every guide link resolved to nothing on the package page. They still work on GitHub. The package id differs from the assembly name, and nothing a consumer reads said so. Installing `OpenCodeAI.Sdk` and writing `using OpenCode.Sdk;` now has an explanation where it is needed: a note under both install blocks, the known-issue entry, and the package description itself, which also gives the search index the name people will look for. Corrections found alongside it: the readme stated a pin two refreshes stale, so it now relays to the file that owns the pin instead of repeating a value that rots; the changelog gained the `f9bc223` entry it never received and lost the migration notes written for nightly consumers of an API no one has published; the guide counted 27 client families where the baseline has 28, missing `Rpc`; the guide's install section installed nothing; the roadmap and the security policy still named the old package ids; and a workflow rule pointed at a path that moved in the public/private split. Upstream's MIT notice now travels with the snapshot derived from it, in THIRD-PARTY-NOTICES.md, packed beside the licence. --- .github/ISSUE_TEMPLATE/bug_report.md | 4 +- .github/SECURITY.md | 4 +- CHANGELOG.md | 32 +++--- Directory.Build.props | 1 + README.md | 100 ++++++++++-------- THIRD-PARTY-NOTICES.md | 46 ++++++++ docs/ROADMAP.md | 3 +- docs/engineering/workflow.md | 2 +- docs/guide/connection-modes.md | 8 +- docs/guide/getting-started.md | 21 ++-- spec/SNAPSHOT.md | 3 + .../OpenCode.Sdk.Extensions.csproj | 4 +- src/OpenCode.Sdk/OpenCode.Sdk.csproj | 4 +- 13 files changed, 151 insertions(+), 81 deletions(-) create mode 100644 THIRD-PARTY-NOTICES.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 47f95c53d..e67d449bb 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -38,11 +38,11 @@ using var client = new OpenCodeClient(new OpenCodeClientOptions ## πŸ“‹ Environment Information -**OpenCode.Sdk Version:** +**OpenCodeAI.Sdk Version:** - Version: (e.g., `0.8.0-nightly.20260831.abc1234`) - Package source: (NuGet.org / GitHub Packages nightly / built from source at commit …) -- Also using `OpenCode.Sdk.Extensions`? (yes / no) +- Also using `OpenCodeAI.Sdk.Extensions`? (yes / no) **opencode Server:** diff --git a/.github/SECURITY.md b/.github/SECURITY.md index a1102bb86..44a439600 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -41,8 +41,8 @@ suspected vulnerability as a public issue. ## Scope -This policy covers the SDK packages in this repository β€” `OpenCode.Sdk` and -`OpenCode.Sdk.Extensions` β€” and the repository's own tooling and workflows. +This policy covers the SDK packages in this repository β€” `OpenCodeAI.Sdk` and +`OpenCodeAI.Sdk.Extensions` β€” and the repository's own tooling and workflows. Two things are explicitly **out of scope**: diff --git a/CHANGELOG.md b/CHANGELOG.md index da295ec91..75d45e056 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,29 +5,36 @@ Each released version links straight to its GitHub Release tag. ## [Unreleased] -> **The first release.** Nothing has shipped to NuGet.org yet, and it will arrive as a -> `0.8.0-preview.N` prerelease. Nightly builds of everything below are on -> [GitHub Packages](README.md#nightly-builds-github-packages) today, versioned -> `0.8.0-nightly.{yyyyMMdd}.{shortSha}` from every code push to `master`. +> **The first release.** Everything below is the surface of the first published package, +> `0.8.0-preview.1`. There is no migration to perform: no earlier version was ever published, and +> the pin moved several times on the way here. Nightly builds of `master` are on +> [GitHub Packages](README.md#nightly-builds-github-packages) as +> `0.8.0-nightly.{yyyyMMdd}.{shortSha}`. ### ✨ New features - **Refreshed to upstream `89f1943`.** Worktree operations now resolve their project through an optional location query. Call create, list, remove, and refresh directly on `WorktreesClient`; - the project-bound `ProjectWorktreesClient` and its factory are gone, which nightly consumers will - meet as a compile break. Create's strategy and destination directory are both optional now, as + the project-bound `ProjectWorktreesClient` and its factory are gone. Create's strategy and + destination directory are both optional now, as upstream declares them; remove retains its required JSON body. The list operation also exposes its declared `WorktreeError` failure arm. Compaction completion payloads now expose the optional model and provider state supplied by the upstream schema. Project updates accept an optional canonical directory; execution interruption events include the upstream inactivity reason. -- **`OpenCode.Sdk` β€” the typed client.** **135 of the 140 operations** in the pinned OpenAPI +- **`OpenCodeAI.Sdk` β€” the typed client.** **135 of the 140 operations** in the pinned OpenAPI snapshot are callable across **28 client families**: sessions, PTYs, persistent PTYs, shells, events, MCP servers, integrations, projects, worktrees, workspaces, providers, language models, agents, skills, commands, forms, permissions, credentials, plugins, RPC, references, VCS, websearch, file system, generation, server, debug, and experimental. Every operation carries a generated request type and a generated response envelope; bound handles (`SessionClient`, `PtyClient`, `PersistentPtyClient`) partially apply a resource id over the shared pipeline. +- **Refreshed to upstream `f9bc223`.** The compaction surface grew: `IProviderCompaction` is a + `mode`-tagged union (`ProviderCompactionLocal`, `ProviderCompactionProvider`, and the usual + `UnknownProviderCompaction` carrier) and hangs off `ModelInfo.Compaction` and + `ProviderInfo.Compaction`. A completed compaction and the `session.compaction.ended` event both + carry an optional `SessionProviderContext`, with its `SessionProviderContextProvenance` record. + Every one of those properties is optional, so nothing that compiled before stops compiling. - **Refreshed to upstream `48f2466`.** The pinned snapshot moved, and the surface moved with it. The live event stream gained its first prefix-tagged arm: every `rpc.*` event dispatches to `EventRpc`, tried after the declared literal tags and before the unknown carrier, and @@ -35,7 +42,7 @@ Each released version links straight to its GitHub Release tag. `v2.plugin.awaitActivation`, `v2.plugin.check`, and `v2.plugin.update` as `PluginsClient.AwaitPluginActivationAsync`, `CheckPluginUpdatesAsync`, and `UpdatePluginsAsync`, and `v2.rpc.call` as `CallAsync` on the new `RpcClient` family. Upstream also removed and - reshaped the plugin types, which nightly consumers will meet as compile breaks: the + reshaped the plugin types: the `plugin.added` event arm is gone, and `PluginAdded` and `PluginAddedData` with it; `IPluginInfo` and its `PluginInfoActive`, `PluginInfoFailed`, and `UnknownPluginInfo` variants are replaced by the `PluginInfo` record, whose `State` is the new `IPluginState` union (`PluginStateActive`, @@ -43,7 +50,7 @@ Each released version links straight to its GitHub Release tag. variant; `PluginSourcePackage.Package` is now `Target`, joined by `Version`, `Outdated`, and `Updating`; and `PluginListResponse.Plugins` is an `IReadOnlyList`. - **A standalone server launcher.** `OpenCodeServer.StartAsync()` starts, monitors, and stops a - private `opencode serve` child β€” generated lease credential, stdin-EOF ownership, bounded tree + private `opencode2 serve` child β€” generated lease credential, stdin-EOF ownership, bounded tree termination β€” and `CreateClient()` hands back a client already bound to it. An optional `OpenCodeServerOutput` collector, supplied through `OpenCodeServerOptions.Output`, retains a bounded tail of the child's stdout and stderr for pull snapshots that report truncation, and it @@ -67,7 +74,7 @@ Each released version links straight to its GitHub Release tag. joins the connection's own work. A persistent PTY keeps its locally requested viewport coherent with the input it labels, rather than letting an inbound resize report overwrite it. See [the terminals guide](docs/guide/terminals.md#-cancellation-deadlines-and-disposal). -- **Dependency injection through `OpenCode.Sdk.Extensions`.** `AddOpenCode(Action<…>)` or +- **Dependency injection through `OpenCodeAI.Sdk.Extensions`.** `AddOpenCode(Action<…>)` or `AddOpenCode(IConfiguration)` registers one singleton client owning its transport for the container's lifetime, plus every sub-client resolved from that same instance β€” inject `SessionsClient` or `EventsClient` directly. @@ -85,11 +92,6 @@ Each released version links straight to its GitHub Release tag. ### πŸ› οΈ General -- **Breaking (nightly API):** `RpcClient.PostCallAsync` is now `CallAsync`; - `PluginsClient.PostAwaitActivationAsync`, `PostCheckAsync`, and `PostUpdateAsync` are now - `AwaitPluginActivationAsync`, `CheckPluginUpdatesAsync`, and `UpdatePluginsAsync`. Their - `OpenCodeRoutes` members use the corresponding names without `Async`; request/response types and - wire behavior are unchanged. - **Target frameworks:** `netstandard2.0`, `net472`, `net8.0`, `net9.0`, `net10.0` β€” for both packages. The suite runs on `net472` on Windows, real-process launcher tests included, and on `net8.0`/`net9.0`/`net10.0` on all three OSes. `netstandard2.0` is a consumption target rather diff --git a/Directory.Build.props b/Directory.Build.props index 429822b2d..cd1428f0e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -51,6 +51,7 @@ + diff --git a/README.md b/README.md index 78aff60f4..621f972e9 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # opencode SDK for .NET -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![NuGet](https://img.shields.io/badge/NuGet-coming%20soon-lightgrey)](https://www.nuget.org/packages/OpenCodeAI.Sdk) [![CI](https://github.com/Blind-Striker/opencode-sdk-dotnet/actions/workflows/ci.yml/badge.svg)](https://github.com/Blind-Striker/opencode-sdk-dotnet/actions/workflows/ci.yml) [![Linux Tests](https://img.shields.io/endpoint?url=https%3A%2F%2Fapi.localstackfor.net%2Fbadges%2Ftests%2Flinux%2Fblind-striker%2Fopencode-sdk-dotnet%2Fmaster)](https://api.localstackfor.net/redirect/test-results/linux/blind-striker/opencode-sdk-dotnet/master) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![NuGet](https://img.shields.io/nuget/vpre/OpenCodeAI.Sdk)](https://www.nuget.org/packages/OpenCodeAI.Sdk) [![CI](https://github.com/Blind-Striker/opencode-sdk-dotnet/actions/workflows/ci.yml/badge.svg)](https://github.com/Blind-Striker/opencode-sdk-dotnet/actions/workflows/ci.yml) [![Linux Tests](https://img.shields.io/endpoint?url=https%3A%2F%2Fapi.localstackfor.net%2Fbadges%2Ftests%2Flinux%2Fblind-striker%2Fopencode-sdk-dotnet%2Fmaster)](https://api.localstackfor.net/redirect/test-results/linux/blind-striker/opencode-sdk-dotnet/master) -> **πŸš€ Quick Start**: the first release is upcoming β€” [nightly builds](#-installation) are on GitHub Packages today | [Quick start](#-quick-start) | [Guide](docs/guide/getting-started.md) +> **πŸš€ Quick Start**: [Install](#-installation) | [Quick start](#-quick-start) | [Guide](https://github.com/Blind-Striker/opencode-sdk-dotnet/blob/master/docs/guide/getting-started.md) > **Unofficial.** This project is not affiliated with or endorsed by the > [opencode](https://opencode.ai) team. @@ -28,17 +28,17 @@ attaching to a registered background service. - βœ… **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 + `opencode2 serve` child for you - βœ… **Source-generated `System.Text.Json`** with no reflection fallback; both packages declare `IsAotCompatible` on `net10.0` -- πŸ”œ **First release** β€” packed and verified; publication waits on the NuGet.org prefix - reservation dispute in [Known Issues](#known-issues). See [CHANGELOG.md](CHANGELOG.md) +- 🚧 **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](https://github.com/Blind-Striker/opencode-sdk-dotnet/blob/master/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, never a live branch. The exact -upstream commit and the refresh procedure live in [`spec/SNAPSHOT.md`](spec/SNAPSHOT.md); today's -pin is `89f1943d` on upstream's `v2` branch. +**Versioning**: the SDK builds against an accepted OpenAPI snapshot of upstream's `v2` branch, +never a live branch. The exact commit and the refresh procedure live in +[`spec/SNAPSHOT.md`](https://github.com/Blind-Striker/opencode-sdk-dotnet/blob/master/spec/SNAPSHOT.md), which owns that pin. ## πŸš€ Platform Compatibility & Quality Status @@ -67,8 +67,8 @@ on, and the `net472` leg is what exercises its compile surface. | Package | NuGet.org | GitHub Packages | |---------|-----------|-----------------| -| **OpenCodeAI.Sdk** | [![NuGet](https://img.shields.io/badge/NuGet-coming%20soon-lightgrey)](https://www.nuget.org/packages/OpenCodeAI.Sdk) | [![GitHub Packages](https://img.shields.io/badge/GitHub%20Packages-nightly-blue)](https://github.com/Blind-Striker/opencode-sdk-dotnet/pkgs/nuget/OpenCodeAI.Sdk) | -| **OpenCodeAI.Sdk.Extensions** | [![NuGet](https://img.shields.io/badge/NuGet-coming%20soon-lightgrey)](https://www.nuget.org/packages/OpenCodeAI.Sdk.Extensions) | [![GitHub Packages](https://img.shields.io/badge/GitHub%20Packages-nightly-blue)](https://github.com/Blind-Striker/opencode-sdk-dotnet/pkgs/nuget/OpenCodeAI.Sdk.Extensions) | +| **OpenCodeAI.Sdk** | [![NuGet](https://img.shields.io/nuget/vpre/OpenCodeAI.Sdk)](https://www.nuget.org/packages/OpenCodeAI.Sdk) | [![GitHub Packages](https://img.shields.io/badge/GitHub%20Packages-nightly-blue)](https://github.com/Blind-Striker/opencode-sdk-dotnet/pkgs/nuget/OpenCodeAI.Sdk) | +| **OpenCodeAI.Sdk.Extensions** | [![NuGet](https://img.shields.io/nuget/vpre/OpenCodeAI.Sdk.Extensions)](https://www.nuget.org/packages/OpenCodeAI.Sdk.Extensions) | [![GitHub Packages](https://img.shields.io/badge/GitHub%20Packages-nightly-blue)](https://github.com/Blind-Striker/opencode-sdk-dotnet/pkgs/nuget/OpenCodeAI.Sdk.Extensions) | ## Table of Contents @@ -105,9 +105,14 @@ on, and the `net472` leg is what exercises its compile surface. ## Prerequisites -You need an `opencode` server. Install the CLI -([opencode.ai](https://opencode.ai) β€” the v2 line ships as `@opencode-ai/cli@next` and installs the -`opencode2` command), then either run it yourself: +You need an `opencode` server. The v2 line ships as `@opencode-ai/cli@next` and installs a single +command, `opencode2` ([opencode.ai](https://opencode.ai)): + +```sh +npm install -g @opencode-ai/cli@next +``` + +Then either run it yourself: ```sh OPENCODE_PASSWORD=your-password opencode2 serve --hostname 127.0.0.1 --port 4096 @@ -120,15 +125,18 @@ OPENCODE_PASSWORD=your-password opencode2 serve --hostname 127.0.0.1 --port 4096 ### Stable (NuGet.org) -**The first release is upcoming.** Nothing is on NuGet.org yet; when it ships, installation will -be the usual: - ```bash -dotnet add package OpenCodeAI.Sdk -dotnet add package OpenCodeAI.Sdk.Extensions # dependency injection, optional +dotnet add package OpenCodeAI.Sdk --prerelease +dotnet add package OpenCodeAI.Sdk.Extensions --prerelease # dependency injection, optional ``` -Until then, use the nightly feed below. +> **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) @@ -178,7 +186,7 @@ keep the credentials in environment variables: ### The SDK starts the server -No ambient process, no endpoint to configure β€” the launcher starts a private `opencode serve` +No ambient process, no endpoint to configure β€” the launcher starts a private `opencode2 serve` child, mints its credential, and hands you a client bound to it. 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 @@ -266,7 +274,7 @@ overload shown above. **135 of the 140 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`](src/OpenCode.Sdk/.generation-incomplete) is the +[`src/OpenCode.Sdk/.generation-incomplete`](https://github.com/Blind-Striker/opencode-sdk-dotnet/blob/master/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 @@ -284,31 +292,29 @@ does not declare: **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](docs/guide/terminals.md) β€” they simply are not generated. +[the terminals guide](https://github.com/Blind-Striker/opencode-sdk-dotnet/blob/master/docs/guide/terminals.md) β€” they simply are not generated. ## πŸ“š Documentation | Guide | What it covers | |---|---| -| [**The guide**](docs/guide/README.md) | Index of every page below, in reading order | -| [Getting started](docs/guide/getting-started.md) | Install, first call, and the shape of the client family | -| [Connection modes](docs/guide/connection-modes.md) | The standalone launcher, an external server, and DI registration | -| [Streaming](docs/guide/streaming.md) | The global event bus and per-session server-sent event streams | -| [Terminals](docs/guide/terminals.md) | PTY and persistent-PTY sessions over the WebSocket doors | -| [Errors and responses](docs/guide/errors-and-responses.md) | Throwing versus `NoThrow`, and the typed error model | -| [Pagination](docs/guide/pagination.md) | Cursor-carrying list envelopes and `EnumerateMessagesAsync` | +| [**The guide**](https://github.com/Blind-Striker/opencode-sdk-dotnet/blob/master/docs/guide/README.md) | Index of every page below, in reading order | +| [Getting started](https://github.com/Blind-Striker/opencode-sdk-dotnet/blob/master/docs/guide/getting-started.md) | Install, first call, and the shape of the client family | +| [Connection modes](https://github.com/Blind-Striker/opencode-sdk-dotnet/blob/master/docs/guide/connection-modes.md) | The standalone launcher, an external server, and DI registration | +| [Streaming](https://github.com/Blind-Striker/opencode-sdk-dotnet/blob/master/docs/guide/streaming.md) | The global event bus and per-session server-sent event streams | +| [Terminals](https://github.com/Blind-Striker/opencode-sdk-dotnet/blob/master/docs/guide/terminals.md) | PTY and persistent-PTY sessions over the WebSocket doors | +| [Errors and responses](https://github.com/Blind-Striker/opencode-sdk-dotnet/blob/master/docs/guide/errors-and-responses.md) | Throwing versus `NoThrow`, and the typed error model | +| [Pagination](https://github.com/Blind-Striker/opencode-sdk-dotnet/blob/master/docs/guide/pagination.md) | Cursor-carrying list envelopes and `EnumerateMessagesAsync` | -Architecture, decision records, and engineering policy live under [`docs/`](docs) β€” start at -[`AGENTS.md`](AGENTS.md) if you want the internals rather than the API. +Architecture, decision records, and engineering policy live under [`docs/`](https://github.com/Blind-Striker/opencode-sdk-dotnet/tree/master/docs) β€” start at +[`AGENTS.md`](https://github.com/Blind-Striker/opencode-sdk-dotnet/blob/master/AGENTS.md) if you want the internals rather than the API. ## Known Issues -- **Nothing is on NuGet.org yet.** The packages are prepared but unpublished β€” - publication is currently blocked by an upstream package-ID prefix reservation dispute β€” so - `dotnet add package OpenCodeAI.Sdk` will not resolve. Use the - [GitHub Packages nightly feed](#nightly-builds-github-packages) until the first stable release; - the nightly and stable packages are built from the same sources by two workflows that share the - same verify-and-pack steps. +- **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 @@ -321,7 +327,7 @@ Architecture, decision records, and engineering policy live under [`docs/`](docs 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](docs/architecture/client-runtime.md). + [terminal lifetime contract](https://github.com/Blind-Striker/opencode-sdk-dotnet/blob/master/docs/architecture/client-runtime.md). - **Attaching to an existing background service is not implemented.** opencode's third connection mode β€” discovering a registered daemon through its registration file (`Service.discover` / @@ -333,19 +339,19 @@ Architecture, decision records, and engineering policy live under [`docs/`](docs 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](docs/guide/streaming.md)). + ([streaming guide](https://github.com/Blind-Striker/opencode-sdk-dotnet/blob/master/docs/guide/streaming.md)). - **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`](tests/OpenCode.Sdk.Sandbox/README.md). + [`tests/OpenCode.Sdk.Sandbox/README.md`](https://github.com/Blind-Striker/opencode-sdk-dotnet/blob/master/tests/OpenCode.Sdk.Sandbox/README.md). ## Developing We appreciate contributions in the form of feedback, bug reports, and pull requests. Read -[CONTRIBUTING.md](.github/CONTRIBUTING.md) first β€” it carries the full gate and the commit +[CONTRIBUTING.md](https://github.com/Blind-Striker/opencode-sdk-dotnet/blob/master/.github/CONTRIBUTING.md) first β€” it carries the full gate and the commit convention. ### Building the Project @@ -367,10 +373,10 @@ provisions the matching official ripgrep archive and verifies its SHA-256 digest ### Sandbox Application -[`tests/OpenCode.Sdk.Sandbox`](tests/OpenCode.Sdk.Sandbox) is a committed playground that drives +[`tests/OpenCode.Sdk.Sandbox`](https://github.com/Blind-Striker/opencode-sdk-dotnet/tree/master/tests/OpenCode.Sdk.Sandbox) is a committed playground that drives the SDK against a real `opencode2 serve` under a debugger β€” the standing breadth walkthrough, the SSE stream modes, the PTY legs, and the standalone-launcher demo. Its -[README](tests/OpenCode.Sdk.Sandbox/README.md) documents every mode. +[README](https://github.com/Blind-Striker/opencode-sdk-dotnet/blob/master/tests/OpenCode.Sdk.Sandbox/README.md) documents every mode. ```bash dotnet run --project tests/OpenCode.Sdk.Sandbox -- --standalone @@ -383,7 +389,7 @@ dotnet test --configuration Release --no-build ``` The full completion gate β€” analyzers, formatting, and the suite β€” is -[`docs/engineering/quality-gates.md`](docs/engineering/quality-gates.md). +[`docs/engineering/quality-gates.md`](https://github.com/Blind-Striker/opencode-sdk-dotnet/blob/master/docs/engineering/quality-gates.md). ## Community @@ -394,8 +400,10 @@ questions, and proposals all land there for now. ## Changelog -Please refer to [`CHANGELOG.md`](CHANGELOG.md) to see the complete list of changes for each release. +Please refer to [`CHANGELOG.md`](https://github.com/Blind-Striker/opencode-sdk-dotnet/blob/master/CHANGELOG.md) to see the complete list of changes for each release. ## License -Licensed under MIT, see [LICENSE](LICENSE) for the full text. +Licensed under MIT, see [LICENSE](https://github.com/Blind-Striker/opencode-sdk-dotnet/blob/master/LICENSE) for the full text. Content derived from +upstream opencode carries its own notice in +[THIRD-PARTY-NOTICES.md](https://github.com/Blind-Striker/opencode-sdk-dotnet/blob/master/THIRD-PARTY-NOTICES.md). diff --git a/THIRD-PARTY-NOTICES.md b/THIRD-PARTY-NOTICES.md new file mode 100644 index 000000000..97ac065a8 --- /dev/null +++ b/THIRD-PARTY-NOTICES.md @@ -0,0 +1,46 @@ +# Third-Party Notices + +This repository's own code is licensed under the MIT License in [`LICENSE`](LICENSE). It also +carries content derived from a third-party project, reproduced here with that project's notice as +its license requires. + +## opencode + +Upstream project: [anomalyco/opencode](https://github.com/anomalyco/opencode) + +Derived content in this repository: + +- `spec/openapi.json` β€” the accepted snapshot of upstream's `packages/protocol/openapi.json`, at + the exact commit [`spec/SNAPSHOT.md`](spec/SNAPSHOT.md) pins. It is produced from upstream's own + generator, so it is a derived copy rather than an independent description. +- `spec/patches/` β€” snapshot-production patches whose context lines quote upstream TypeScript + source. The patches themselves are this repository's work; the quoted context is upstream's. +- `external/opencode` β€” a submodule reference. Nothing of it is redistributed by this repository; + a clone fetches it from upstream directly. + +Generated SDK source under `src/OpenCode.Sdk/` is emitted by this repository's own generator from +the accepted snapshot. Upstream's notice follows. + +``` +MIT License + +Copyright (c) 2025 opencode + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 549ba7e43..58430bfc7 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -28,7 +28,8 @@ accepted OpenAPI snapshot and rides one hand-written transport runtime. targets. Linux and macOS live verification also passed on net8/net9/net10, including the persistent daemon round trip and normal PTY reuse after read cancellation. `architecture/client-runtime.md` and ADR-0023 own the contract. -- **Packages** β€” `OpenCode.Sdk` and `OpenCode.Sdk.Extensions` pack at the single-sourced +- **Packages** β€” the two packages publish as `OpenCodeAI.Sdk` and `OpenCodeAI.Sdk.Extensions` + (the assemblies stay `OpenCode.Sdk`) and pack at the single-sourced `VersionPrefix 0.8.0`, and every `master` push publishes a `0.8.0-nightly.*` build to GitHub Packages. NuGet.org publication is currently blocked by an upstream prefix reservation dispute; the manual publish lane is wired and waits on it. diff --git a/docs/engineering/workflow.md b/docs/engineering/workflow.md index cf284dd14..3722e3a1c 100644 --- a/docs/engineering/workflow.md +++ b/docs/engineering/workflow.md @@ -14,7 +14,7 @@ history. - Align on structure and direction before writing. Prefer the smallest correct change over a broad refactor. - When implementation contradicts current canon, stop the affected work and follow - `../agents/deviation-protocol.md`; never silently code around the contradiction. + `deviation-protocol.md`; never silently code around the contradiction. ## Repository artifacts and temporary work diff --git a/docs/guide/connection-modes.md b/docs/guide/connection-modes.md index 051ecc94b..4af29f9c2 100644 --- a/docs/guide/connection-modes.md +++ b/docs/guide/connection-modes.md @@ -13,7 +13,7 @@ register either of them with a container. ## πŸš€ The SDK starts the server -`OpenCodeServer.StartAsync()` launches a private `opencode serve` child, waits for it to report +`OpenCodeServer.StartAsync()` launches a private `opencode2 serve` child, waits for it to report readiness, mints its credential, and hands you an owner object. No ambient process, no endpoint to configure, no port to pick. @@ -47,7 +47,7 @@ it started: ```csharp await using var server = await OpenCodeServer.StartAsync(new OpenCodeServerOptions { - Command = ["opencode2", "serve"], + Command = ["/opt/opencode/bin/opencode2", "serve"], WorkingDirectory = "/srv/my-project", Environment = new Dictionary(StringComparer.Ordinal) { ["OPENCODE_LOG_LEVEL"] = "debug" }, ReadinessTimeout = TimeSpan.FromSeconds(90), @@ -57,7 +57,7 @@ await using var server = await OpenCodeServer.StartAsync(new OpenCodeServerOptio | Option | Default | What it does | |---|---|---| -| `Command` | `["opencode", "serve"]` | The executable plus its leading arguments. The launcher appends `--stdio --port 0` itself. | +| `Command` | `["opencode2", "serve"]` | The executable plus its leading arguments. The launcher appends `--stdio --port 0` itself. | | `WorkingDirectory` | `null` | The child's working directory; `null` inherits yours. | | `Environment` | `null` | Extra environment entries for the child. | | `ReadinessTimeout` | 60 s | How long to wait for the readiness line before failing and ending the child. | @@ -136,7 +136,7 @@ exactly as well. A server started **without** a password expects anonymous reque `OpenCode.Sdk.Extensions` adds `AddOpenCode` to `IServiceCollection`. What lands in the container is deliberately small: **one `OpenCodeClient` singleton** holding the transport open for the -container's lifetime, and **each of the 27 families registered as its own singleton resolved from +container's lifetime, and **each of the 28 families registered as its own singleton resolved from that one client**. A service therefore asks for the family it actually uses β€” `EventsClient`, `PtysClient`, `WorktreesClient` β€” and all of them share a single pipeline and a single disposal at shutdown. diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md index 8bf2a71b1..01d3cc5b0 100644 --- a/docs/guide/getting-started.md +++ b/docs/guide/getting-started.md @@ -13,9 +13,18 @@ one talks to a model. ## πŸ“¦ Install -**Nothing is on NuGet.org yet** β€” the packages are prepared but unpublished. Where they can be -had in the meantime, the exact `dotnet nuget add source` command, the two package names, and the -`read:packages` token requirement all live in one place so they never drift: +```bash +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;` β€” nuget.org reserves the `OpenCode.` id prefix for an unrelated owner, so +> the artifact carries a different name than the code inside it. + +The packages target `netstandard2.0`, `net472`, `net8.0`, `net9.0`, and `net10.0`, so any project +on one of those works. Nightly builds of `master` live on a GitHub Packages feed; the source +command and its `read:packages` token requirement live in one place so they never drift: [**Installation** in the root README](../../README.md#-installation). You also need an `opencode` server. Either install the CLI and run one yourself, or let the SDK @@ -99,11 +108,11 @@ Console.WriteLine(generated.Generate.Text); ## 🧭 How the client is organised -The root client exposes **27 families** as properties β€” `Sessions`, `Events`, `Ptys`, +The root client exposes **28 families** as properties β€” `Sessions`, `Events`, `Ptys`, `PersistentPtys`, `Shells`, `Providers`, `LanguageModels`, `Agents`, `Skills`, `Commands`, `Permissions`, `Credentials`, `Projects`, `Workspaces`, `Worktrees`, `Vcs`, `FileSystem`, `Forms`, -`Generation`, `Integrations`, `McpServers`, `Plugins`, `References`, `Server`, `Websearch`, -`Debug`, and `Experimental`: +`Generation`, `Integrations`, `McpServers`, `Plugins`, `References`, `Rpc`, `Server`, +`Websearch`, `Debug`, and `Experimental`: ```csharp var providers = await client.Providers.ListProvidersAsync(); diff --git a/spec/SNAPSHOT.md b/spec/SNAPSHOT.md index 1f52d760f..e2fe0fd86 100644 --- a/spec/SNAPSHOT.md +++ b/spec/SNAPSHOT.md @@ -22,6 +22,9 @@ scope from `@opencode-ai`. The published channel has not moved with it: npm's `@ carries a reserved placeholder on `latest`, so `@opencode-ai/cli@next` stays the installable pre-release and is what the row above names. +`openapi.json` is derived from upstream's own generator output, so upstream's MIT notice +travels with it in [`THIRD-PARTY-NOTICES.md`](../THIRD-PARTY-NOTICES.md). + Platform evidence for the v2 line: internal research, 2026-08-16, "The opencode v2 platform: branch, surface, architecture, distribution". diff --git a/src/OpenCode.Sdk.Extensions/OpenCode.Sdk.Extensions.csproj b/src/OpenCode.Sdk.Extensions/OpenCode.Sdk.Extensions.csproj index 11e821d5d..77e5fbc4e 100644 --- a/src/OpenCode.Sdk.Extensions/OpenCode.Sdk.Extensions.csproj +++ b/src/OpenCode.Sdk.Extensions/OpenCode.Sdk.Extensions.csproj @@ -4,8 +4,8 @@ $(AllTargetFrameworks) OpenCodeAI.Sdk.Extensions - Dependency injection integration for the unofficial opencode .NET SDK. - opencode;sdk;dependency-injection;options + Dependency injection integration for the unofficial opencode .NET SDK: 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. + opencode;opencode2;sdk;dependency-injection;di;options;hosting;dotnet;csharp true diff --git a/src/OpenCode.Sdk/OpenCode.Sdk.csproj b/src/OpenCode.Sdk/OpenCode.Sdk.csproj index 020874148..be3af7a4a 100644 --- a/src/OpenCode.Sdk/OpenCode.Sdk.csproj +++ b/src/OpenCode.Sdk/OpenCode.Sdk.csproj @@ -6,8 +6,8 @@ prefix for another owner, so the shipped assembly and every public namespace stay OpenCode.Sdk while the artifact is published under OpenCodeAI. --> OpenCodeAI.Sdk - Unofficial .NET SDK for opencode: a typed client for the opencode HTTP API. - opencode;sdk;client;ai;agent + Unofficial .NET SDK for opencode: a typed client for the opencode HTTP API, with server-sent event streams, PTY and persistent-PTY terminals over WebSockets, cursor pagination, typed errors, and a local server launcher. The package id is OpenCodeAI.Sdk; the assembly and every namespace are OpenCode.Sdk. + opencode;opencode2;sdk;client;http-client;openapi;ai;agent;llm;sse;streaming;pty;terminal;dotnet;csharp true true