Repository navigation
0.9.0-preview.4
Pre-release
Pre-release
Every operation upstream exposes is now callable: 100% coverage of the protocol surface. At
upstream release tag v2.0.18, all 138 operations in the pinned document are usable — 136
generated and the two terminal WebSocket transports — and none is declined. 0.9.0-preview.3
generated 130 and declined 4. config.get, experimental.migration.v1.status, fs.read, and
experimental.fs.write are now generated, and the refresh to v2.0.18 adds pairing.
✨ Added
- The accepted snapshot moved to upstream release tag
v2.0.18
(cd9a14a6b688d4021bee381dfd39d2cef9c0f862), published as@opencode/cli@2.0.18; install it
withnpm install -g @opencode/cli@2.0.18. It adds two operations and moves none, so all 138
are usable: 136 generated and the two WebSocket transports. - Pairing.
client.Server.CreatePairingCodeAsync()(server.pair) issues a short-lived,
single-use code;client.Server.RedeemPairingCodeAsync(code)(server.connect) exchanges it,
with no other credential, for a session token the server accepts anywhere the password is, so
another client connects withPassword = tokenwithout ever holding the server password. The
token is masked inPairingSession.ToString(), and so is the code inPairingCode.ToString(). ShellInfo.Signalnames the signal that ended a killed command.- The configuration can be read back.
client.Config.GetConfigAsync()(config.get) returns
the configuration documents and discovery sources for the location, from lowest to highest
priority: aConfigDocumentcarries itsConfigInfo, aConfigDirectorythe directory it came
from.Experimental.UpdateConfigAsyncwrites the global file and the server reloads it
asynchronously, publishingconfig.updatedonce the loaded configuration changes, so read after
that event. client.Experimental.GetMigrationV1StatusAsync()(experimental.migration.v1.status)
returns the V1 session-history migration status:ExperimentalMigrationV1StatusIdle
(RequiredorCompleted),…Runningwith its progress, or…Error.- Files can be written.
client.Experimental.WriteFileAsync(new ExperimentalFsWriteRequest { Path = … }, stream)
(experimental.fs.write) sends the stream as the raw body and returns the resolved absolute path
asFile.Path. The SDK reads the stream once from its current position to its end and never
disposes or rewinds it; a slow upload stays alive while it keeps moving. Upstream does not confine
the target to the location, so a relativePathresolves against it and an absolute one does not. - Files can be read.
client.FileSystem.ReadFileAsync(new FsReadRequest { Path = … })
(fs.read) returns the file's bytes asContent(ReadOnlyMemory<byte>) and the MIME type the
server declared asContentType.Pathis relative to the location and may be nested; the SDK
escapes each segment as upstream's own client does and refuses.and..segments. A missing
file is the declared 404FileNotFoundError; a path that leaves the location is refused by the
server. - Unions of objects without a discriminator are typed. A structural union carrier whose object
members share the JSON object token now selects a member the way upstream's decoder does: the
first member in declaration order whose required keys are present and whose literal-constrained
properties hold admitted values, then that member's full parse. A value no member claims is kept
as the carrier's rawUnknown. The configuration's reference entries (ConfigReferenceEntry),
language-server entries (ConfigLspEntry), and the migration status use it. - Operation members name their OpenAPI operation. Each generated operation method, each
Enumerate*Asynccompanion, and each hand-written PTY and persistent-PTY door carries an XML
<remarks>with its operation id, HTTP method, and route, for example
Operation session.permission.create: POST /api/session/{sessionID}/permission.
🔧 Changes
- A 401 now carries the typed
UnauthorizedError. From 2.0.18 the server answers a missing or
refused credential with the error body its document declares, where it used to send an empty
one, soErrorisUnauthorizedErrorinstead ofnull. Nothing changed in the SDK; the errors
guide now says so.
🐛 Fixes
- A logged response no longer prints the raw error body. Every response envelope's
ToString()printedRawBody, so logging a failed response wrote whatever the server echoed into
its error body, request data included. The printed form now keepsStatus,IsError, and the
typedError;RawBodyitself is unchanged and still carries the body. OpenCodeServer.EnsureAsyncno longer holds a thread-pool thread per contender on Windows.
The contender's standard error was an anonymous pipe, which Windows reads synchronously, so every
contender Ensure started kept one pool thread blocked for as long as its standard error stayed
open — for the elected service, its whole life — and ten concurrent callers stalled the pool for
seconds. The pipe's read end is now overlapped, as in .NET 11'sProcessand libuv.
📚 Documentation
- The README is a front page. It opens with the status and a capability table that maps each
area of the API to itsclient.*entry point and guide page. The quick start opens with a table
for choosing a connection mode, and the known issues keep only what a consumer meets. Every link
is absolute, so the page also works on NuGet.org. - The connection guide shows how to pair another client (
CreatePairingCodeAsync, then
RedeemPairingCodeAsync, thenPassword = token), and it lists the launcher'sOutputoption. - A CLI-started server's session log is the marker alone, followed as well as replayed. The
streaming guide and the README's known issues said liveFollow = Truedelivery was unaffected
by persistence; it is not. The server's live tail re-reads the same persisted store as a replay,
so against a server the distributedopencodeCLI started,GetLogAsyncdelivers one
EventLogSyncedand no durable event either way. Both now say so and point live session activity
toclient.Events.SubscribeAsync, filtered by session id. Observed on@opencode/cli@2.0.15. - A refused worktree removal can be partial. The errors guide said a 400
WorktreeErrorremoved
nothing. WithForceRequiredfalse, git can already have deleted the worktree's files and its
git metadata while the directory and the inventory row remain, and a retry then answers
Worktree directory unavailablewithForceRequirednull. The guide names the cause observed on
Windows at the pin — the server keeps the worktree's location alive, and that location's local MCP
servers run inside the worktree — and shows how to prevent it (Debug.EvictLocationAsyncbefore
the removal) and how to recover (evict, delete the directory,RefreshWorktreesAsync). - An empty VCS summary is explained. Getting started says that
Vcs.GetVcsAsyncfor a
directory the server has not served before can answer with anullProviderandnullbranch
values until that location's plugins settle, that the caller waits for a non-nullProvider,
and that anullBranch.Currentwith a provider set is a detached HEAD. - Getting started shows a prompt answered in a console app:
PromptAsync, then
Experimental.WaitForSessionAsyncunder a caller-owned deadline, thenEnumerateMessagesAsync. - Getting started has a .NET Framework section. A
net472project needs
<LangVersion>10.0</LangVersion>— the SDK's public surface needs C# 9 and the console template's
implicit usings C# 10 — and no extra package. - The requests guide explains the members the schema does not: session permission rules
(an update replaces the whole ruleset; create, list, and reply; how upstream evaluates), a
worktree'sDirectoryas the parent andNameas the child, and a shellTimeoutin
milliseconds with0or unset meaning none. - The README no longer states a test count by hand; the test badges carry it. A tools test now
fails when the README's guide table and the pages underdocs/guidediffer.