Skip to content

0.9.0-preview.4

Pre-release
Pre-release

Choose a tag to compare

@Blind-Striker Blind-Striker released this 27 Sep 11:57

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
    with npm 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 with Password = token without ever holding the server password. The
    token is masked in PairingSession.ToString(), and so is the code in PairingCode.ToString().
  • ShellInfo.Signal names 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: a ConfigDocument carries its ConfigInfo, a ConfigDirectory the directory it came
    from. Experimental.UpdateConfigAsync writes the global file and the server reloads it
    asynchronously, publishing config.updated once the loaded configuration changes, so read after
    that event.
  • client.Experimental.GetMigrationV1StatusAsync() (experimental.migration.v1.status)
    returns the V1 session-history migration status: ExperimentalMigrationV1StatusIdle
    (Required or Completed), …Running with 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
    as File.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 relative Path resolves 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 as Content (ReadOnlyMemory<byte>) and the MIME type the
    server declared as ContentType. Path is 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 404 FileNotFoundError; 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 raw Unknown. 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*Async companion, 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, so Error is UnauthorizedError instead of null. 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() printed RawBody, so logging a failed response wrote whatever the server echoed into
    its error body, request data included. The printed form now keeps Status, IsError, and the
    typed Error; RawBody itself is unchanged and still carries the body.
  • OpenCodeServer.EnsureAsync no 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's Process and 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 its client.* 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, then Password = token), and it lists the launcher's Output option.
  • 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 live Follow = True delivery 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 distributed opencode CLI started, GetLogAsync delivers one
    EventLogSynced and no durable event either way. Both now say so and point live session activity
    to client.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 WorktreeError removed
    nothing. With ForceRequired false, 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 unavailable with ForceRequired null. 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.EvictLocationAsync before
    the removal) and how to recover (evict, delete the directory, RefreshWorktreesAsync).
  • An empty VCS summary is explained. Getting started says that Vcs.GetVcsAsync for a
    directory the server has not served before can answer with a null Provider and null branch
    values until that location's plugins settle, that the caller waits for a non-null Provider,
    and that a null Branch.Current with a provider set is a detached HEAD.
  • Getting started shows a prompt answered in a console app: PromptAsync, then
    Experimental.WaitForSessionAsync under a caller-owned deadline, then EnumerateMessagesAsync.
  • Getting started has a .NET Framework section. A net472 project 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's Directory as the parent and Name as the child, and a shell Timeout in
    milliseconds with 0 or 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 under docs/guide differ.