Skip to content

Releases: opencode-dotnet/opencode-sdk-dotnet

0.9.0-preview.13

0.9.0-preview.13 Pre-release
Pre-release

Choose a tag to compare

@Blind-Striker Blind-Striker released this 18 Oct 16:10
a024408

The SDK speaks opencode 2.0.32. The pinned server moves from 2.0.29 to 2.0.32. The upgrade
brings an experimental voice session, typed reasoning and temperature capabilities on models, and
a background service whose saved env wins after startup. On Linux and macOS, StopAsync now
returns only once the stopped daemon has been reaped. IntelliSense reaches the members that
implement a documented interface. The guides gain a recipe for answering permission asks and
question forms, plus the notes that isolating and cleaning up a background service needs.

✨ Added

  • The SDK speaks opencode 2.0.32. ExperimentalClient.CreateVoiceSessionAsync opens an
    experimental voice session over WebRTC or WebSocket. ConfigModelCapabilities.Reasoning tells a
    model that does not reason (ConfigModelReasoningUnsupported) from one that does
    (ConfigModelReasoningSupported, with its toggle, efforts and ConfigModelReasoningBudget);
    ConfigModelCapabilities.Temperature and ModelCapabilities.Temperature say whether a model
    takes a temperature, and ConfigModel.ReleaseDate carries its release date.

🔧 Changes

  • A spawned background service's saved env wins after startup.
    OpenCodeServerEnsureOptions.Environment entries still win over the channel's service-config
    env at spawn, but a 2.0.32 service re-applies its saved env when it starts, so a key set in
    both takes the saved value for everything the service reads after startup.

🐛 Fixes

  • Stopping a background service waits for its child process to be reaped. On Linux and macOS,
    OpenCodeServer.StopAsync no longer returns while the daemon it stopped is still an unreaped
    zombie of the calling process.
  • IntelliSense reaches implementing members and restores declared halves. Properties that
    implement a documented interface member (durable envelopes' Seq and Version, the MCP
    timeouts and Codemode, inbox and compaction Created times) show that member's documentation,
    and 26 summaries that preview.12 dropped as partly implementation-only return with the part
    upstream declares. SDK types and runtime behavior are unchanged.

📚 Documentation

  • Isolating a background service needs separate state and config roots. The connection-modes
    guide now says to give XDG_STATE_HOME and XDG_CONFIG_HOME different directories: the
    registration and the CLI's service configuration are both named service.json, so one folder for
    both makes them the same file, and EnsureAsync then fails now and then on Windows with EPERM.
  • A stopped service's files unlock a moment after StopAsync on Windows. The connection-modes
    guide now says that the daemon's opencode.db-shm and -wal stay locked for a few tens of
    milliseconds after the call returns, so deleting an isolated data directory right away can fail.
  • The streaming guide answers permission asks and question forms. One recipe covers explicit
    tool-action rules, one-time approval or rejection, and field-keyed text answers with nullable
    union-member handling; the requests guide links to it from session permissions.

0.9.0-preview.12

0.9.0-preview.12 Pre-release
Pre-release

Choose a tag to compare

@Blind-Striker Blind-Striker released this 17 Oct 16:36
7bb2fae

Streams and automatic traversals take per-call options, and the public surface sheds its last
loose names before 1.0.
SubscribeAsync, ReadLogAsync (formerly GetLogAsync),
EnumerateMessagesAsync and EnumerateSessionsAsync accept OpenCodeRequestOptions, so a
per-call Location reaches every connection attempt and page request, and
EventStreamIdleTimeout sets the event bus's idle window. Query parameters take the types the
server reads (bool?, int?, long?), generated enums work in switch and patterns through
their Values constants, assistant content has one union type, PTY sizes share PtySize, and
OpenCodeRoutes is internal. Cancellation reports your own token at every public async door,
disposing the root client interrupts calls and streams with ObjectDisposedException, readiness
timeouts keep an outcome recorded before them, IntelliSense states only what the API declares, and
on .NET Framework and netstandard2.0 response bodies up to 16 MiB are pooled.

💥 Breaking changes

  • Streams and automatic cursor traversals take per-call options before cancellation.
    These four virtual method signatures change; recompile consumers and update overrides.
    Name a token as cancellationToken: token when omitting the new options argument.
    Calls that omit options keep their behavior. Streams and traversals refuse NoThrow;
    the event bus also refuses a per-call NetworkTimeout, including Timeout.InfiniteTimeSpan.

    Door Was Now
    EventsClient.SubscribeAsync (CancellationToken cancellationToken = default) (OpenCodeRequestOptions? requestOptions = null, CancellationToken cancellationToken = default)
    SessionClient.ReadLogAsync (SessionLogRequest? request = null, CancellationToken cancellationToken = default) (SessionLogRequest? request = null, OpenCodeRequestOptions? requestOptions = null, CancellationToken cancellationToken = default)
    SessionClient.EnumerateMessagesAsync (SessionMessageListRequest? request = null, CancellationToken cancellationToken = default) (SessionMessageListRequest? request = null, OpenCodeRequestOptions? requestOptions = null, CancellationToken cancellationToken = default)
    SessionsClient.EnumerateSessionsAsync (SessionListRequest? request = null, CancellationToken cancellationToken = default) (SessionListRequest? request = null, OpenCodeRequestOptions? requestOptions = null, CancellationToken cancellationToken = default)
  • Invalid per-call options name requestOptions in exceptions. An undefined ErrorBehavior
    on a one-shot call now reports ArgumentOutOfRangeException.ParamName as "requestOptions"
    rather than "options", and the new stream and traversal refusals name "requestOptions" too.

  • IOpenCodeStreamFailureCause exposes Tag. Every cause in
    OpenCodeStreamFailureException.Causes exposes its wire tag without a cast, including an
    unknown cause. Custom test implementations must supply the property. IStreamFailureCause
    inherits it rather than redeclaring it; concrete causes and their wire representation are unchanged.

  • Instruction-entry DTO names retain Put.
    ExperimentalSessionInstructionsEntryRequest is now
    ExperimentalSessionInstructionsEntryPutRequest, and
    ExperimentalSessionInstructionsEntryResponse is now
    ExperimentalSessionInstructionsEntryPutResponse.
    ExperimentalClient.SetSessionInstructionsEntryAsync keeps its method name.

  • The instruction-entry list method names its subject in the plural.
    ExperimentalClient.ListSessionInstructionsEntryAsync is now
    ListSessionInstructionsEntriesAsync, and
    ExperimentalSessionInstructionsEntryListResponse.SessionInstructionsEntry is now
    SessionInstructionsEntries.

  • The session log uses stream-reading terminology.
    SessionClient.GetLogAsync is now ReadLogAsync; its request and stream item types and
    its behavior are unchanged.

  • OpenCodeRoutes is no longer public. Call the typed clients to build and send request
    paths instead of using route constants or builders directly. Request paths on the wire are
    unchanged.

  • Assistant content has one union type. Message content and content-updated events use
    ISessionMessageAssistantContent; the encoded duplicate and its unknown carrier are removed.
    Existing text, reasoning, and tool records implement the surviving interface. The wire is unchanged.

    Surface Was Now
    SessionMessageContentUpdatedData.Content item ISessionMessageAssistantContentEncoded ISessionMessageAssistantContent
    Unknown assistant-content record UnknownSessionMessageAssistantContentEncoded UnknownSessionMessageAssistantContent
  • PTY requests share PtySize. Replace the three old size names with PtySize.
    Its Cols and Rows members remain required long values. The persistent requests remain experimental.
    The pty.update body now writes cols before rows, which the server does not observe.

    Property Was Now
    PtyUpdateRequest.Size PtyUpdateSize? PtySize?
    PersistentPtyCreateRequest.Size PersistentPtyCreateInputSize? PtySize?
    PersistentPtyUpdateRequest.Size PersistentPtyUpdateInputSize PtySize
  • Boolean query parameters use bool?. QueryBoolean is removed. Pass true or false,
    not QueryBoolean.True or QueryBoolean.False; leave the member null to omit it.
    The query text remains lowercase true or false.

    Property Was Now
    SessionLogRequest.Follow QueryBoolean? bool?
    SessionInterruptRequest.Resume QueryBoolean? bool?
    ProjectListRequest.Archived QueryBoolean? bool?
    ExperimentalSessionExportRequest.Sanitize QueryBoolean? bool?
  • Numeric query parameters are numbers. The API description declares them as strings, but the
    server reads each one as a number and upstream's own client takes a number, so the request
    records now do too. Pass Limit = 50, not Limit = "50". The value still goes out as query text,
    and the server still enforces its range.

    Property Was Now
    ListRequest.Limit (SessionListRequest, SessionMessageListRequest) string? int?
    FsFindRequest.Limit string? int?
    VcsBranchListRequest.Limit string? int?
    ShellOutputRequest.Limit string? int?
    ShellOutputRequest.Cursor string? long?
    PersistentPtyReadRequest.Lines string? int?
    SessionLogRequest.After string? long?
    SessionDiffRequest.Context string? int?
    VcsDiffRequest.Context string? int?
    ExperimentalSessionStatsRequest.From, .To (milliseconds since the Unix epoch) string? long?
  • OpenCodeApiException and OpenCodeServerException are sealed. They can no longer be used
    as base classes; their constructors and error data are unchanged.

  • SessionParentFilter is a record with value equality. Filters for the same parent compare
    equal and have the same hash code, so SessionListRequest records built with equal filters
    compare equal too. ToString() identifies RootOnly or Of and includes the parent id for Of.

✨ Added

  • Per-call Location applies to streams and automatic cursor traversals. Pass
    OpenCodeRequestOptions to override the ambient directory on every stream connection attempt
    or every page request. The server still ignores the header on routes that resolve a session's
    location or need no location; it does not filter the global event bus. A per-call
    NetworkTimeout bounds only opening the session log, and bounds each page request separately
    on a traversal, not the whole walk.

  • OpenCodeClientOptions.EventStreamIdleTimeout sets the event bus's idle window, as
    upstream's own client allows. A connection that receives nothing for that long is replaced; the
    default stays 45 seconds. Keep it above the server's 15-second keepalive. Any window above zero
    and at most int.MaxValue milliseconds is accepted, but not Timeout.InfiniteTimeSpan: a bus
    that never times out would hang on a half-open connection. The 2-second connect bound and the
    1-second delay before a new attempt stay fixed.

  • Enum values work in switch and in patterns. Every generated enum carries its pinned wire
    spellings as const string members of a nested Values class, and a Value property that
    returns a value's spelling. Match one against the other in a case label or a property pattern;
    a value a newer server sends matches none of them:

    switch (mode.Value) { case VcsMode.Values.Committed: /* … */ break; }
    if (rule is { Effect.Value: PermissionEffect.Values.Allow }) { /* … */ }

    Nothing else changes: the static properties, ==, IsKnown and the wire format stay as they
    were.

🔧 Changes

  • On .NET Framework and netstandard2.0, response bodies between 1 and 16 MiB are pooled instead
    of allocated on every call. The SDK then typically keeps one buffer per size class it has used,
    at most about 60 MiB after large responses; bodies above 16 MiB still allocate.

🐛 Fixes

  • IntelliSense documents declared units, defaults and family roles. Documentation inferred only
    from server implementation is removed; SDK types and runtime behavior are unchanged.
  • Generated XML summaries use grammatical prose. Articles and brand spellings are corrected,
    digits stay attached to their words, and members named Value read "Gets the value."
  • A background-service start failure no longer carries part of the PTY handoff ticket. The
    contender's stderr tail redacts the ticket even where the 8 KiB cut or the end of the pipe splits
    it, so no fragment of it reaches OpenCodeServerException messages.
  • **Caller cancellation pres...
Read more

0.9.0-preview.11

0.9.0-preview.11 Pre-release
Pre-release

Choose a tag to compare

@Blind-Striker Blind-Striker released this 15 Oct 13:59

The event bus reconnects the way upstream's own client does, and the calls that answer only when
their work ends now wait for it.
EventsClient.SubscribeAsync replaces a dropped, failed or
silent connection one second later, starts every connection with EventServerConnected, and gives
up after one minute in which every attempt failed, so a wrong address or password surfaces after a
minute rather than at once. NetworkTimeout sets how long a call may go without network progress,
and WaitForSessionAsync, RunShellCommandAsync, RunCommandAsync and GenerateTextAsync wait
with no window. The other breaking changes: generated string enums are extensible value types, the
upstream-experimental surface is [Experimental] and needs an opt-in, and OAuth, shellId and
savedPermissionId are spelled as they should be. Events that belong to a session implement
ISessionScopedEvent, the generated XML documentation says what each record is, the guides carry
the fixes from the preview.10 evaluation, and on Windows the background service's sidecar
publishes even while another process reads it.

💥 Breaking changes

  • EventsClient.SubscribeAsync reconnects, as upstream's client does.

    • A connection that drops, fails, or receives nothing for 45 seconds is replaced one second
      later. Every failure counts: an HTTP error status, a malformed frame, a failure frame and the
      server ending the stream included.
    • Each connection has 2 seconds to answer and deliver its first event, so a server consistently
      slower than that fails every attempt.
    • Each connection starts with EventServerConnected, and the SDK fails one that does not. A
      second one means the events in between were missed.
    • The stream no longer ends on its own. Your token, disposing the client, or one minute in which
      every attempt failed ends it; the minute throws the last failure.
    • What changes for you: a wrong address or a refused password surfaces after one minute, not at
      once, and code that expected the stream to end when the server went away now sees a new
      EventServerConnected instead. OpenCodeClientOptions.NetworkTimeout no longer reaches the
      bus.
  • The upstream-experimental surface is marked [Experimental]. Every member that calls an
    operation upstream serves under /api/experimental/, and every type only those operations use,
    now reports a diagnostic when you use it. It is a compile error until you opt in with
    #pragma warning disable or <NoWarn> for its id:

    • OPENCODE001: client.Experimental (ExperimentalClient);
    • OPENCODE002: the persistent-PTY family (client.PersistentPtys, its handle, connect door,
      session, options and frames);
    • OPENCODE003: the Session family's experimental operations, today SessionClient.GetLogAsync.

    Nothing on the wire changes. See Experimental APIs. Waiting for a
    turn on the event bus, as the getting-started guide shows, needs no opt-in.

  • Every generated name spells OAuth as the brand does. Names built from operation ids and wire
    members said Oauth, beside the schema-derived CredentialOAuth and McpOAuthConfig. They are now
    BeginOAuthConnectionAsync, CancelOAuthConnectionAsync, CompleteOAuthConnectionAsync,
    GetOAuthStatusAsync, the IntegrationOAuth* request and response types with their
    OAuthConnect and OAuthStatus members, McpRemoteConfigOAuth (with its Kind), the
    OpenCodeRoutes builders and templates, and the OAuth value of
    ConnectionCredentialInfoMethod. Rename the uses; nothing on the wire changes.

  • The shell and saved-permission ids have descriptive parameter names.

    • ShellsClient.GetShellClient, and the OpenCodeRoutes.Shells builders, take shellId.
    • PermissionsClient.RemoveSavedAsync and OpenCodeRoutes.Permissions.RemoveSaved take
      savedPermissionId.
    • Before, both were a bare id. Only a call that named the argument (id: …) changes.
  • Generated string enums are extensible value types (ADR-0035).

    • Every enum the API defines, such as VcsMode, PermissionEffect, SessionInboxDelivery and
      AgentInfoMode, is now a readonly struct, not a C# enum.

    • Previously a server newer than the SDK broke reading whenever it sent a value the SDK did not
      know, on a response or on an event stream frame. That value now reads as it was sent, and it is
      written back unchanged.

    • The pinned values are static properties with the same names, so VcsMode.Committed still
      compiles.

    • IsKnown reports whether this SDK's pin defines a value. new VcsMode("…"), the implicit
      conversion from string, and FromString create a value from any spelling.

    • Equality is ordinal.

    • ToString() now returns the wire spelling (running), not the C# member name (Running).
      Logs and string comparisons built on it change.

    • A default value has no spelling. A request body or query refuses it.

    • What breaks: enum values used as constant patterns, and code that relied on enum behaviour
      (numeric casts, Enum.GetValues, [Flags]). Compare with == instead:

      // Before
      switch (mode) { case VcsMode.Committed: /* … */ break; }
      if (rule is { Effect: PermissionEffect.Allow }) { /* … */ }
      
      // After
      if (mode == VcsMode.Committed) { /* … */ }
      if (rule.Effect == PermissionEffect.Allow) { /* … */ }

✨ Added

  • ISessionScopedEvent routes an event by its session. Every event whose payload names the
    session it happened in (58 at the pin: the session.* events, permission.asked/replied,
    persistent-pty.added/removed and tui.session.select) implements it, so
    e is ISessionScopedEvent { SessionId: var id } replaces a switch over every type. The member is
    implemented explicitly, so the events gain no public member and serialize as before. The form
    events are not session-scoped: a form's session id can be "global".

  • NetworkTimeout sets how long a call may go without network progress.
    OpenCodeClientOptions.NetworkTimeout sets it for the client and
    OpenCodeRequestOptions.NetworkTimeout for one call; the default stays 100 seconds.
    WaitForSessionAsync, RunShellCommandAsync, RunCommandAsync and both GenerateTextAsync
    methods (on SessionClient and ExperimentalClient) answer only when their work ends, so they
    now wait with no window and the call's cancellation token is their deadline; before, each failed
    after 100 seconds without headers, and a shell command kept running on the server. A client-wide
    value does not reach them; a per-call value does. OpenCodeServer.CreateClient carries the
    client value over. See Requests.

🔧 Changes

  • Generated XML documentation says what each record is. The pinned API describes almost none of
    its schemas, so summaries now come from the wire: an event record names its event type, its
    payload names the event it belongs to, an operation's body, response and query records name the
    operation with its method and route, and an error record names its tag. The types builders found
    hardest carry hand-written summaries from upstream's behaviour: assistant messages and their
    finish, error and retry; text deltas and their end; the prompt and create requests; the execution
    events; and a PTY's command and arguments, including which shell starts when the command is
    absent.
  • The package README links open the docs of the release they came with. On NuGet.org, the
    links to the guides point at the commit the package was built from, not at master.
  • The hosting internals join the flat OpenCode.Sdk.Internal namespace. The public API is
    unchanged, byte for byte; only internal type names differ, as a stack trace or reflection shows
    them.

🐛 Fixes

  • An event stream no longer ends on an enum value the SDK does not know. Before, a frame with
    such a value, such as a new reason on session.execution.interrupted, failed to materialize and
    ended the subscription with OpenCodeTransportException. The frame now reads as its typed event,
    with the value kept (IsKnown is false). One-shot responses read the same way. See the Breaking
    entry on extensible enums.
  • On Windows, publishing the background service's sidecar no longer fails while another process
    reads it.
    The sidecar was replaced with a rename that Windows refuses with access denied while a
    reader holds the file open. It is now replaced with a POSIX-semantics rename, which succeeds even
    then, and falls back to the previous rename where the file system does not support it.

📚 Documentation

  • The streaming guide streams an answer as it arrives. The recipe follows upstream's own client:
    • wait for EventServerConnected before you prompt;
    • prompt with your own msg_ id, and match it to SessionInboxDelivered's InboxId;
    • append SessionTextDelta text, and replace it with SessionTextEnded's final Text;
    • stop on the session's execution event.
  • The requests guide names the session-create location. SessionCreateRequest.Location is a
    body LocationPublicRef, not the LocationSelector that headers and queries use. The guide also
    shows that a collection expression cannot fill an Optional collection member (CS9174).
  • The terminals guide documents the two normal-PTY gaps. It says which shell a PTY gets without a
    Command: the configured shell, then $SHELL, then a per-OS fallback. It also shows how to
    resize a normal PTY, through PtyClient.UpdateAsync with Size.
  • The connection guide keeps a launched server around a host, and fixes the 401.
    • A launched server stays in await using around the host's RunAsync. The co...
Read more

0.9.0-preview.10

0.9.0-preview.10 Pre-release
Pre-release

Choose a tag to compare

@Blind-Striker Blind-Striker released this 14 Oct 18:01

The packages target netstandard2.0, net472, net10.0, and net11.0: net8.0 and net9.0
leave ahead of their end of support, and net11.0 joins on its go-live release candidate.
A
project on .NET 8 or 9 still installs the packages through the netstandard2.0 asset, now with a
build warning. The pin moves to upstream release tag v2.0.29, which adds provider compatibility
defaults, request-body encoding and top-level config policies. OpenCodeServer.Exited reports how
a started server ended, and on Windows a server's exit is observed on the operating system's own
wait thread, so a busy host no longer reports a crashed start as a closed stdout. The breaking
changes come first: besides the target frameworks, OpenCodeServerOptions refuses an out-of-range
timeout when it is set, IOpenCodeClientOptions is removed,
OpenCodeStreamFailureException.Cause is now Causes, and PersistentPtyReplayBounds takes
camelCase constructor parameters.

💥 Breaking changes

  • The packages target netstandard2.0, net472, net10.0, and net11.0. net8.0 and
    net9.0 leave ahead of their end of support on 2026-11-10, and net11.0 joins on its go-live
    release candidate. 0.9.0-preview.9 is the last version with net8.0 and net9.0 assets. A
    net8.0 or net9.0 project still installs the packages through the netstandard2.0 asset, now
    with a build warning, because that runtime is not one the packages are tested on; set
    <SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings> to silence it, or move
    the project to net10.0 or later. A project that treats warnings as errors fails its build on it
    until it does one or the other.
  • OpenCodeServerOptions refuses an out-of-range timeout when it is set. ReadinessTimeout
    takes a value above zero and at most int.MaxValue milliseconds, or
    Timeout.InfiniteTimeSpan to wait until the start's cancellation token fires;
    GracefulShutdownTimeout takes zero to int.MaxValue milliseconds. A value outside that range
    throws ArgumentOutOfRangeException from the setter. A zero or negative readiness timeout and a
    negative grace were refused by OpenCodeServer.StartAsync before; a value past the ceiling was
    accepted and failed only after the server had been spawned.
  • IOpenCodeClientOptions is removed. No public API accepted it, and every option added
    later would have broken its implementers, because net472 and netstandard2.0 have no default
    interface members. Configure clients through OpenCodeClientOptions, which a client still
    snapshots at construction. Code that declared a variable or parameter as
    IOpenCodeClientOptions uses OpenCodeClientOptions instead.
  • OpenCodeStreamFailureException.Cause is now Causes. It is a list of causes, so the name
    is now plural. The constructor parameter is now causes as well. Read failure.Causes where
    you read failure.Cause, and pass causes: where you named the argument.
  • PersistentPtyReplayBounds takes camelCase constructor parameters. They are
    requestedOffset, availableOffset, endOffset and truncated, like the other frame types'
    constructors. The properties keep their names, and positional construction, with and
    deconstruction are unchanged. Only a call that named an argument (RequestedOffset: 0) changes.

✨ Added

  • OpenCodeServer.Exited reports how a started server ended. The task completes with an
    OpenCodeServerExit. It holds an exit code (on Windows the full 32-bit code), a terminating
    signal by name and number, or Unknown when the exit status could not be read, for example
    because something else reaped the server. It completes whether the server ended on its own or
    disposal ended it, once its output has ended or one second after the exit, as upstream's
    exited does. It never faults. On a handle from
    DiscoverAsync or EnsureAsync it throws InvalidOperationException, because that handle owns
    no process.
  • The accepted snapshot moved to upstream release tag v2.0.29
    (042d0832f5f3a73eaa039a58a7d565776f6d4290), published as @opencode/cli@2.0.29; install it
    with npm install -g @opencode/cli@2.0.29. It adds and changes no operation, so all 142 remain
    callable.
  • Provider compatibility defaults. ProviderInfo.Compatibility and
    ConfigProvider.Compatibility carry a ProviderCompatibility, the defaults a provider's models
    inherit: ReasoningField, RequireReasoning, MaxTokensField, RequireFinishReason,
    RequireAssistantAfterTool, SupportsPromptCacheKey, and CacheFormat.
  • Prompt-cache marker format. ModelCompatibility.CacheFormat and
    ProviderCompatibility.CacheFormat name the marker syntax the endpoint accepts, as the new
    ModelCacheFormat enum (Anthropic, Alibaba).
  • Request-body encoding. ProviderSettings.Encoding, ConfigProviderSettings.Encoding,
    ModelSettings.Encoding, and ConfigModelSettings.Encoding name the compression upstream applies
    to the provider request body, as the new ProviderEncoding enum (Zstd, Gzip, None).
  • Top-level config policies. ConfigInfo.Policies carries the ordered policies that control
    access to providers, tools, and integrations, as the new ConfigInfoPolicies (Action,
    Resource, Effect) with the new ConfigInfoPoliciesAction (ProviderUse, ToolUse,
    IntegrationUse) and ConfigInfoPoliciesEffect (Allow, Deny) enums. Upstream deprecates
    experimental.policies: a 2.0.29 server reports those policies here, and
    ConfigInfoExperimental.Policies stays null.

🔧 Changes

  • The packages depend on the .NET 10.0.13 servicing builds of their dependencies.
    OpenCodeDotNet.Sdk requires System.Text.Json, Microsoft.Bcl.AsyncInterfaces,
    Microsoft.Bcl.Memory, Microsoft.Bcl.TimeProvider, System.Collections.Immutable, and
    System.Threading.Channels 10.0.13 on netstandard2.0 and net472, up from 10.0.11; its
    net10.0 and net11.0 assets declare no dependency. OpenCodeDotNet.Sdk.Extensions requires
    Microsoft.Extensions.DependencyInjection.Abstractions and
    Microsoft.Extensions.Options.ConfigurationExtensions 10.0.13 on every target, up from 10.0.10.
    No security advisory covers the versions they replace. A project that references one of these
    packages directly at a lower version must raise it, or restore reports a package downgrade
    (NU1605, an error by default).

  • A server that does not report readiness in time fails with the timeout as the inner
    exception.
    The OpenCodeServerException that OpenCodeServer.StartAsync throws then carries
    the readiness bound's OperationCanceledException as its InnerException, which was null.

  • A settings encoding member is read as the typed Encoding. On ProviderSettings,
    ConfigProviderSettings, ModelSettings, and ConfigModelSettings it is no longer in
    AdditionalProperties, where 0.9.0-preview.9 kept it as an undeclared member.

  • The source is laid out in feature folders, and the internal namespaces are flattened into
    OpenCode.Sdk.Internal.
    The public API is unchanged, byte for byte. Only the names of internal
    types differ, as a stack trace or reflection shows them.

🐛 Fixes

  • On a busy Windows host, a server's exit is no longer reported late. The launcher learned of a
    Windows server's exit through a callback queued to the .NET thread pool, so under load it could
    arrive seconds after the server had exited. A server that exited before readiness then failed
    its start as one that closed its stdout, without its exit code; OpenCodeServer.Exited
    completed late; and ending a server that had already exited could still end the descendants it
    left running. The exit is now observed on the operating system's own wait thread, as libuv
    observes it, so each of these reads the exit as soon as the kernel reports it.
  • OpenCodeServer.EnsureAsync started through a .cmd or .bat shim on Windows opens no
    console window.
    With a batch shim such as npm's opencode.cmd as the command, the background
    service took a new console of its own, with a visible terminal window that took the focus and
    stayed open for the service's life, and its startup output went to that window instead of to
    the SDK. The shim now starts with a console that has no window, which the service inherits, so
    its window stays hidden and its startup output reaches the SDK again. A service started from an
    .exe still starts detached, with no console.
  • On Unix under Mono, a failed chmod of an owner-only file fails its write. The
    netstandard2.0 and net472 assets set mode 0600 on the files discovery and EnsureAsync
    write through a chmod child process after the create. That process now receives a quoted path,
    and a nonzero exit removes the file and fails the create, as the modern targets' atomic
    owner-only create does. Behaviour change: such a file was kept at the process umask before.

📋 Important Notes

  • This release is built on the .NET 11 release-candidate SDK. A preview package may build on a
    go-live release candidate (ADR-0002); the first non-preview package waits for the GA SDK. The
    net11.0 asset needs the .NET 11 runtime, and a net10.0 app keeps using the net10.0 asset.
  • From 1.0.0, a minor version may move the protocol pin and change the generated surface the
    way upstream changed it, each change listed under protocol changes; only a major version breaks
    the hand-written design (ADR-0034).

0.9.0-preview.9

0.9.0-preview.9 Pre-release
Pre-release

Choose a tag to compare

@Blind-Striker Blind-Striker released this 13 Oct 02:19

The pin moves to upstream release tag v2.0.27, and all 142 operations it exposes are
callable — 140 as generated HTTP calls, and the two terminal WebSocket connections through
hand-written transports.
Upstream 2.0.27 moves to Effect 4.0.1, which renames the stream failure
frame, and project.list takes an archived filter, which makes its route helper a method; the
two breaking changes come first. A project records when it was archived, an idle message carries
the error of a failed turn, fs.list answers a missing directory with a typed
FileNotFoundError, and the background-service config reader validates remoteAccepted as the
CLI does. Discovery reads a refused connection as no service even when its bound expires at the
same moment. net8.0 and net9.0 still leave in an upcoming preview; see the important notes.

💥 Breaking changes

  • A mid-stream failure is recognized under upstream 2.0.27's frame name. Upstream 2.0.27 moves
    to Effect 4.0.1, which renames the reserved failure frame from effect/httpapi/stream/failure to
    effect/http-api/stream/failure. Against a 2.0.27 server a failure on EventsClient or the
    session log surfaces as OpenCodeStreamFailureException with its typed causes; against an older
    server it surfaces as OpenCodeTransportException naming the undeclared frame. On
    0.9.0-preview.8 it was the other way round.
  • OpenCodeRoutes.Projects.ListProjects is now a method. project.list takes an optional
    archived query, so the route helper is ListProjects(ProjectListRequest? request = null) and
    the template constant is ListProjectsTemplate. ProjectsClient.ListProjectsAsync gains a
    leading optional ProjectListRequest? request parameter: a call that passes requestOptions
    positionally moves it one place, and the change needs a recompile.

✨ Added

  • The accepted snapshot moved to upstream release tag v2.0.27
    (1f4d07a4a519185955ae8ab2ea3607805509e386), published as @opencode/cli@2.0.27; install it
    with npm install -g @opencode/cli@2.0.27.
  • Archived projects. Upstream 2.0.27 archives a project whose known directories are all gone
    and unarchives it when it is resolved again. ProjectTime.Archived carries when it was
    archived, and project.list leaves archived projects out unless
    ProjectListRequest.Archived is QueryBoolean.True.
  • SessionMessageIdle.Error carries the SessionStructuredError of a turn that went idle with
    a failed outcome.

🔧 Changes

  • The netstandard2.0 and net472 assets depend on Microsoft.Bcl.TimeProvider. The request
    progress window and the standalone launcher's output drain now run on TimeProvider, which
    net8.0 and later carry inbox. Behaviour does not change: both still run on the system clock.
  • The background-service config reader validates remoteAccepted as upstream 2.0.27 decodes
    it.
    The CLI records an accepted remote access as the literal true; a document whose
    remoteAccepted is anything else is no config at all, so its env overlay and the legacy
    migration it gates do not apply, as in the CLI.
  • A 2.0.27 server that stops or fails while a terminal is connected closes it with 1001 or
    1011, which reads as an abnormal close.
    When the server stops, or a handler fails, before it
    queues its own close on a pty.connect or persistent-terminal connection, it now closes with
    1001 or 1011, which PtySession and PersistentPtySession report as an abnormal close
    (OpenCodeTransportException), as upstream's own terminal clients do. A 2.0.26 server sent a
    close frame with no status there, which read as a normal end.
  • FileSystemClient.ListEntriesAsync reports a missing directory as the typed FileNotFoundError,
    which upstream 2.0.27 now answers, and declares on fs.list's 404 beside LocationNotFoundError;
    an older server answered an undeclared server error.

🐛 Fixes

  • OpenCodeServer.DiscoverAsync and EnsureAsync read a refused connection as no service even
    when the probe's two-second bound expires at the same moment.
    On a loaded machine
    the bound could expire after the runtime had already seen the daemon's port refuse the connect,
    and the probe then reported a timeout for a daemon that was gone. A refused connection now decides
    whatever the bound says; any other failure still counts as a timeout exactly when the bound
    expired. Behaviour change: fewer spurious timeouts under load, so Ensure less often counts a dead
    daemon toward the three timeouts that end a registered service, and starts a contender after the
    spawn delay instead.

📋 Important Notes

  • net8.0 and net9.0 leave the packages, and net11.0 joins, in an upcoming preview, ahead
    of their end of support on 2026-11-10; this release still ships them. The packages will then
    target netstandard2.0;net472;net10.0;net11.0. An app on .NET 8 or 9 still installs through the
    netstandard2.0 asset, but that asset is a compatibility asset, not a supported runtime: support
    and testing cover the targeted runtimes only, and .NET 5–7 are not supported. Plan the move to
    .NET 10.
  • Upstream's committed OpenAPI document matches its own generator at v2.0.27. The stale
    copy that 0.9.0-preview.8 noted is current again, so a diff against it no longer shows
    operations or errors this SDK has and that document lacks.

0.9.0-preview.8

0.9.0-preview.8 Pre-release
Pre-release

Choose a tag to compare

@Blind-Striker Blind-Striker released this 13 Oct 01:28

The pin moves to upstream release tag v2.0.26, and all 142 operations it exposes are
callable — 140 as generated HTTP calls, and the two terminal WebSocket connections through
hand-written transports.
An integration can connect through an external credential source, and
a 2.0.26 server's external credentials now decode; the experimental policy action permission is
renamed tool.use, the one breaking change, which comes first. On Windows a standalone server now
runs as upstream's launcher runs it: created suspended in a kill-on-close job, so it ends with its
owner however the owner ends; given its three standard handles and nothing else; read through
overlapped pipes; and ended at once with taskkill /T /F. net8.0 and net9.0 still leave in an
upcoming preview; see the important notes.

💥 Breaking changes

  • ConfigInfoExperimentalPoliciesAction.Permission is now ToolUse. Upstream 2.0.26 renames
    the experimental policy action permission to tool.use and adds integration.use, which gates
    MCP servers and skills, so the enum's members are ProviderUse, ToolUse, and IntegrationUse.
    A 2.0.26 server drops a permission policy entry when it loads its config; against an older
    server whose config still has one, reading the config fails to decode.

✨ Added

  • The accepted snapshot moved to upstream release tag v2.0.26
    (9b4ec5714d481559990db0a816d5dec19541a814), published as @opencode/cli@2.0.26; install it
    with npm install -g @opencode/cli@2.0.26.
  • An integration can be connected through an external credential source.
    IntegrationClient.ConnectWithExternalCredentialsAsync(request) (integration.connect.external)
    runs an external authentication method, named by MethodId with an optional form Answer and
    Label, and stores a reference to its credential source. The integration lists such a method
    as IntegrationExternalMethod, and the stored credential reads as CredentialExternal
    (MethodId, Metadata). The route helper is
    OpenCodeRoutes.Integrations.ConnectWithExternalCredentials. Behaviour change: on
    0.9.0-preview.7 a 2.0.26 server's external method surfaced as UnknownIntegrationMethod, and
    its external credential value as UnknownCredentialValue.
  • ConnectionCredentialInfoMethod.External names a credential stored through an external
    method. Before, a server that returned one failed the decode of its connection.
  • ModelCompatibility.SupportsThinkingBlockBinding and SupportsEffortUpdates carry the two
    compatibility flags upstream 2.0.26 adds.

🔧 Changes

  • On Windows a standalone server is placed in a job that ends it with its owner, however the owner
    ends.
    StartAsync creates the server suspended, places it in a process-wide job that ends it
    when the host process exits, and only then lets it run, as upstream's launcher places its server
    in a job. Behaviour change: a server that ignores the end-of-stream of its stdin no longer
    outlives an owner that crashed or was killed before disposing it. Descendants the server starts
    detached stay outside the job and keep running; the other children of a server built on Bun or
    Node end with it, because its runtime places them in a job of its own under the launcher's. The
    job also ends a server that crashes on an unhandled exception at once, without a Windows Error
    Reporting dialog, as upstream's job does, and any window the server opens starts hidden. A server
    the kernel refuses to place in the job with ERROR_ACCESS_DENIED, as under a host job that cannot
    nest, runs outside it, as upstream's does, and then ends with a crashed owner only through its
    stdin lease. Any other refusal, a job that cannot be created, and a server that cannot be resumed
    fail the start with OpenCodeServerException and leave nothing running.
  • With the default npm install, pass the path of opencode.exe in Command for that guarantee.
    The bare opencode resolves to npm's opencode.cmd shim, which runs through cmd.exe: the job
    then holds cmd.exe, and the real server, cmd.exe's child, ends with a crashed owner only
    through its stdin lease, as upstream's own launcher of a .cmd file arranges it.
  • Disposing a standalone server on Windows ends the server and its whole process tree at once
    with taskkill /T /F, as upstream does, instead of closing its stdin and waiting.
    Behaviour
    changes:
    • The server no longer runs its own graceful shutdown on disposal: it exits with code 1, where
      the stdin end-of-stream used to end it with code 0. Stdin now closes last, as on Linux and
      macOS, so an Output collector no longer receives the lines a server writes when its stdin
      ends.
    • Every live descendant of the server is ended at every close, detached ones included; the
      descendants used to survive a normal close.
    • Disposal takes the taskkill run, about 300 ms, where a server that left on its stdin's
      end-of-stream used to take about 10 ms.
    • GracefulShutdownTimeout now starts with that first kill and bounds it: when the server is
      still running at its end, the same kill runs once more, and TerminateProcess ends the server
      with code 1 when a kill fails. A disposal takes at most the grace plus 26 seconds, the output
      drain and the release of the output readers included.
    • On net8.0 and later the forced end is taskkill, exit code 1, where it was the runtime's tree
      kill, exit code -1.
    • A server that already exited is not tree-killed, and a failed start ends the server the same
      way, with the configured grace, so a canceled or timed-out StartAsync can take up to the
      grace plus 26 seconds to throw.
    • On net8.0 and later the descendants of a server that exits before readiness are no longer
      ended: the runtime's tree kill used to end them, but taskkill reaches no descendant of an
      exited root, so it does not run.
  • On Windows the server no longer inherits the host's own inheritable handles. It receives its
    three standard handles and nothing else, so a parent process that captures the host's output
    sees it end when the host exits, even while a server it started still runs. Behaviour change:
    the server also no longer receives the handles the host inherited from its own ancestors.
  • On Windows the server's output is read without a dedicated thread, on every target. Its
    stdout and stderr are overlapped pipes whose reads complete on the I/O completion port, on .NET
    Framework too; the two reader threads each server used to hold are gone.
  • StartAsync on Windows fails at once when the server closes its stdout before readiness and
    keeps running
    , as on Linux and macOS, where it used to wait for the readiness timeout. A
    readiness line that arrives together with the exit now always starts the server, where the exit
    could win before. A server that exits before readiness while a process it started holds its
    stdout fails the start after a drain of at most one second instead of two.
  • A negative Windows exit code is reported with its hexadecimal form. A server that crashes
    before readiness is reported as exited with code -1073741819 (0xC0000005), where the message
    used to give only the decimal value.
  • A whitespace-only WorkingDirectory counts as none on Windows too. The server then starts in
    the host's directory, as on Linux and macOS, where the blank value used to be passed to the
    process start as the server's directory.
  • OPENCODE_PRINT_LOGS=1 in the host's environment hands the server the host's own stderr on
    Windows too
    , as upstream's launcher does; no stderr is then collected or quoted in a startup
    failure. Diagnostics-mode behaviour, as upstream's: the server then also shares the host's
    console, so a Ctrl+C in that console reaches the server, even when the host handles it.
  • A NUL in a command entry or an environment entry is refused with ArgumentException on Windows
    too
    , before anything is spawned, and a spawn that fails in a WorkingDirectory names the
    directory.
  • StopAsync and the Ensure replacement act on the service registered when they stop it. Like
    upstream's client, they read the registration again before preparing or clearing the terminal
    handoff, and end the service that file names then. Behaviour change: a service that registered
    while StopAsync was asking the old one to shut its terminals down is the one stopped, where it
    used to be left running.
  • The service config's remote key is read as upstream reads it. Remote access is set only
    through the CLI (opencode service set remote true, or opencode pair --remote); the SDK reads
    the key and never writes it. While the service's tunnel is attached, its remote URL is listed in
    ServerInfo.Urls. Behaviour change: a remote that is neither an object holding a string
    route nor the legacy boolean makes the whole document no config, as upstream decides, so its
    env overlay no longer applies, and a document with a legacy boolean still reads but is no
    longer migrated to a named channel's file; 0.9.0-preview.7 ignored remote in both cases.
  • A PTY connect refused with HTTP 401 or 403 names the credential or the origin. Upstream
    2.0.26 checks the request's origin on every connect, before it looks the PTY up. Behaviour
    change: the OpenCodeTransportException message now ends "the request's credential or origin
    was rejected", as the persistent PTY's message already did, and a refused origin is reported as
    such even for a PTY that does not exist.
  • FileSystem.ReadFileAsync with a path that leaves the location answers the declared 404 with a
    typed FileNotFoundError.
    Upstream 2.0.26 refuses such a path, and one it cannot decode, as a
    missing file. Behaviour change: the status is 404, not an undeclared 5xx, and Error is a
    FileNotFoundError.
  • **A server bound to every interface lists it...
Read more

0.9.0-preview.7

0.9.0-preview.7 Pre-release
Pre-release

Choose a tag to compare

@Blind-Striker Blind-Striker released this 06 Oct 13:25

The pin moves to upstream release tag v2.0.23, and all 141 operations it exposes are
callable — 139 as generated HTTP calls, and the two terminal WebSocket connections through
hand-written transports.
A repository can now be initialized, a location whose directory does
not exist answers a typed 404, and the server says whether it can run persistent terminals.
2.0.23 tags every error, so WorktreeError changes shape and an error body marked only by name
no longer decodes, and EnsureAsync no longer replaces a service whose health protocol is
incompatible; the breaking changes come first. On Linux and macOS a standalone server now runs in
a session of its own and is ended as upstream's launcher ends it, a SIGTERM to its process group
before the SIGKILL; Windows keeps its earlier path. The background-service doors follow the
2.0.23 client, the generated ToString() masks every configured header and environment map, the
open provider options, and unrecognized union values that can carry a secret, and both packages
declare IsAotCompatible on net8.0 and net9.0 too. net8.0 and net9.0 still leave in an
upcoming preview; see the important notes.

💥 Breaking changes

  • WorktreeError is a tagged error. Upstream 2.0.23 declares it with _tag beside its name
    and data, so Tag reads _tag and a new required Name carries name. A body without _tag,
    from a server older than 2.0.23, no longer decodes to a typed error: Error is null and
    RawBody keeps the body.
  • _tag is the only error marker. Every error the 2.0.23 contract declares is tagged, so a
    name is no longer read as an error's marker:
    • Any error body whose only marker is name, not just a worktree failure, no longer decodes to
      UnknownOpenCodeError: Error is null and RawBody keeps the body.
    • The public UnknownOpenCodeError(tag, payload) constructor throws ArgumentException for a
      payload whose only marker is name; the payload must carry the tag under _tag.
  • OpenCodeServer.EnsureAsync throws OpenCodeServerException on a service whose health
    protocol is incompatible, where it used to replace it.
    A registered daemon that answers
    /api/info with an authenticated 404 is left running and the call throws, unless the call's
    version requirement is unmet, which still replaces it. Stop it explicitly with StopAsync to
    replace it.
  • OpenCodeServer.StartAsync refuses POSIX systems other than Linux and macOS, FreeBSD among
    them,
    with OpenCodeServerException before anything is spawned; they used to start through
    Process. On Linux and macOS a NUL in a command entry or an environment entry is now refused
    with ArgumentException, because the C string it is passed as would end there.

✨ Added

  • The accepted snapshot moved to upstream release tag v2.0.23
    (0fd7e2829449b052abf0078666669302923d77af), published as @opencode/cli@2.0.23; install it
    with npm install -g @opencode/cli@2.0.23.
  • A repository can be initialized in a project that has none.
    client.Vcs.InitializeRepositoryAsync(request) (vcs.init) takes an optional Provider (git by
    default); a provider without initialization answers 501 VcsInitNotSupportedError. The route
    helper is OpenCodeRoutes.Vcs.InitializeRepository.
  • A missing directory is a typed 404. Every location- and session-scoped route, and
    experimental.generate.text, session.diff, and session.permission.list by name, now declare
    LocationNotFoundError (Location, Message) for a location whose directory does not exist;
    before 2.0.23 the server failed such a request instead of answering it. A PTY connect refused
    with HTTP 404 carries no error body, so its OpenCodeTransportException now names both causes:
    the PTY session or the requested location does not exist.
  • ServerInfo.Capabilities.PersistentPty says whether the server can run the
    persistent-terminal daemon: false on Windows, where opencode-pty ships no binaries. A server
    older than 2.0.23 leaves Capabilities null.

🔧 Changes

  • On Linux and macOS a standalone server runs in a session of its own, so a Ctrl+C or a hangup
    sent to the host's terminal no longer reaches it.
    Behaviour change: the server used to stay in
    the host's process group, so a Ctrl+C the host itself handled still ended the server. It now
    ends through DisposeAsync, or, when the host dies without disposing, on the end-of-stream of
    its stdin lease.
  • Disposing a standalone server on Linux and macOS sends SIGTERM to the server's process group,
    and SIGKILL to whatever is left of the group after GracefulShutdownTimeout
    , as upstream's own
    launcher does. Behaviour changes:
    • The pinned server answers the SIGTERM by exiting with code 130, where the stdin end-of-stream
      used to end it with code 0. Stdin now closes last.
    • Children the server started in its own process group end with it. Descendants that moved into a
      session of their own (the persistent-terminal daemon, PTY shells) are left to the server, as
      upstream leaves them; the forced tree kill used to end them too.
    • A server that already exited on its own with a non-zero code has the survivors of its group
      ended, and so does one whose exit status nobody could read (the host ignores SIGCHLD on
      Linux, or runs as pid 1); one that exited with code 0 or on a signal leaves them alone, as
      before.
  • A failed StartAsync on Linux and macOS ends the server the same way, with the configured
    grace
    , so a canceled or timed-out start can take up to that grace plus 16 seconds to throw: at
    most 10 for the server's exit after SIGKILL, 1 for draining its output, and 5 for releasing the
    output readers. Behaviour change: it used to kill the server at once.
  • StartAsync on Linux and macOS sees the server's own exit without waiting for its output, and
    fails at once when the server closes its stdout before readiness.
    A server that exits before
    readiness while a process it started still holds its stdout now fails the start within about a
    second instead of at the readiness timeout. Behaviour change in the message: a server ended by a
    signal before readiness reads "terminated on signal N", where it read "exited with code 128+N".
  • OpenCodeServerOptions.WorkingDirectory works on Linux with a C library older than glibc 2.29
    (RHEL 8, Debian 10, Ubuntu 18.04): the server starts there through /usr/bin/env -C, which keeps
    it the same process, and a directory that does not exist fails the start as an exit with code
    125 whose stderr names it. A blank WorkingDirectory counts as none.
  • A standalone server's relative command path on Linux and macOS is resolved against the
    caller's current directory
    , never against WorkingDirectory. Behaviour change: Process
    tried it against the application's own directory first, so a relative path that named a file
    there started that file even when the current directory held another.
  • OPENCODE_PRINT_LOGS=1 in the host's environment hands the server the host's own stderr on
    Linux and macOS
    , as upstream's launcher does; no stderr is then collected or quoted in a startup
    failure.
  • EnsureAsync reports the first contender failure without recruiting replacements. While a
    failed contender's error stands, no further contender starts; the call throws it once no contender
    is live, and at the 120-second bound it throws that failure rather than the timeout. A recovery or
    replacement that ends the registered service clears it and releases the contenders it made moot.
    Behaviour change: a call that a recruited replacement contender used to rescue now throws the
    first contender's failure.
  • Persistent-terminal handoff and the sidecar clear are best-effort. Preparing the handoff for
    a replacement never fails it: a failed or unusable ticket shuts the daemon's terminals down and
    publishes no ticket, and a sidecar that cannot be written leaves the replacement without one.
    StopAsync and the Ensure replacement end the service even when the sidecar cannot be removed.
    Behaviour change: StopAsync used to throw OpenCodeServerException when it could not remove
    the sidecar.
  • StopAsync escalates to the hard kill by the signalled process alone. When the process
    survives the request to stop, the hard kill follows it even if its registration disappeared or
    another service registered meanwhile, as upstream's client does; a successor's registration is
    still never removed. Behaviour change: the stop used to end without the hard kill when the
    registration changed between the two signals.
  • Both packages declare IsAotCompatible on every modern target (net8.0 and later), not only
    net10.0, so trimming and native AOT analysis cover the net8.0 and net9.0 assets too.

🔒 Security

  • An unrecognized union value no longer prints a secret. When a union's known shapes carry a
    masked member at any depth, its unknown arm — what a newer server's value decodes to — prints its
    preserved payload as [REDACTED] and keeps its marker: UnknownCredentialValue, UnknownMcp,
    UnknownConfigEntry, and the unknown arm of McpRemoteConfigOauth, ConfigInfoFormatter,
    ConfigInfoLsp, and ConfigLspEntry. Payload itself and serialization are unchanged.
  • Every user-configured header and environment map is masked in ToString(), now including
    ConfigProvider.Headers, ConfigModel.Headers, ConfigModelVariants.Headers,
    ConfigAgentRequest.Headers, ConfigFormatterEntry.Environment, ConfigLspServer.Env, and
    SessionEnvironmentRequest.Variables.
  • The open provider options are masked in ToString(). ProviderSettings,
    ConfigProviderSettings, ModelSettings, and ConfigModelSettings keep upstream's provider
    options, apiKey among them, in AdditionalProperties. A record printed that dictionary's type
    name, ...
Read more

0.9.0-preview.6

0.9.0-preview.6 Pre-release
Pre-release

Choose a tag to compare

@Blind-Striker Blind-Striker released this 04 Oct 12:11

The pin moves to upstream release tag v2.0.22, and all 140 operations it exposes are
callable — 138 as generated HTTP calls, and the two terminal WebSocket connections through
hand-written transports.
Stored credentials can
now be listed and created, a session can be created as a linked child of another, a cancelled form
can tell the asker why, and a connection reports when it needs signing in again. The refresh
changes three shapes, so the breaking changes come first. Discovery, Ensure, and Stop now read the
CLI's service files exactly as the CLI does. net8.0 and net9.0 leave the packages in an
upcoming preview; see the important notes.

💥 Breaking changes

  • CancelFormAsync takes an optional SessionFormCancelRequest before requestOptions. A call
    that passed requestOptions positionally names it now (requestOptions: options). The method is
    virtual, so overrides and mock setups move to the new signature, and an assembly compiled against
    0.9.0-preview.5 must be rebuilt. The route helper OpenCodeRoutes.Sessions.CancelForm gained
    the same optional request.
  • ProviderSettings.ChunkTimeout and ConfigProviderSettings.ChunkTimeout are number-or-false
    unions, not double?.
    Upstream accepts a number of milliseconds or false (no timeout), so a
    provider configured with false now decodes. Read Kind, then Number or Boolean; a value
    neither arm claims is kept as Unknown. ChunkTimeout = 30000 becomes
    ChunkTimeout = ProviderSettingsChunkTimeout.FromNumber(30000) (ConfigProviderSettingsChunkTimeout
    on the config side), and FromBoolean(false) turns the timeout off; upstream refuses true.
  • ConfigModel.Capabilities is a ConfigModelCapabilities, not the catalog's
    ModelCapabilities.
    It is the shape upstream's config accepts, and every member is optional:
    Tools is bool?, and Input and Output may be null. ModelInfo.Capabilities is unchanged.
  • IConnectionInfo has a Status member. A type outside the SDK that implements the interface,
    such as a test fake, adds ConnectionStatus? Status; code that only reads connections is
    unaffected.

✨ Added

  • The accepted snapshot moved to upstream release tag v2.0.22
    (527f0b931d1f9b3ebd34e106c51b31ce5db5b075), published as @opencode/cli@2.0.22; install it
    with npm install -g @opencode/cli@2.0.22.
  • Stored credentials can be listed and created. client.Credentials.ListCredentialsAsync()
    (credential.list) returns every CredentialEntry (Id, IntegrationId, Label, Active,
    Value), and CreateCredentialAsync(request) (credential.create) stores one and returns it;
    an Id that already exists answers with the declared 409. They join activate, update, and
    remove. Value is a CredentialKey or a CredentialOAuth. Upstream returns secrets in clear
    text, so the API key (CredentialKey.Key) and the OAuth tokens (CredentialOAuth.Access,
    Refresh) are masked in ToString().
  • SessionCreateRequest.ParentId creates a linked child session at its parent's location; a
    missing parent answers with the declared 404 SessionNotFoundError.
  • A cancelled form can tell the asker why. CancelFormAsync(formId, new SessionFormCancelRequest { Message = "..." }) sends it, and FormStateCancelled.Message carries it back.
  • HeaderTimeout on ProviderSettings and ConfigProviderSettings bounds the wait for a
    provider's response headers, as the same number-of-milliseconds-or-false union.
  • A connection reports when it needs attention. IConnectionInfo.Status is a
    ConnectionStatus with the Status, a Message, and, when set, the Url to sign in again at.
  • SessionStructuredError.Response carries the failed provider response's Body.

🐛 Fixes

  • OpenCodeServer.DiscoverAsync, EnsureAsync, and StopAsync decode the CLI's service files
    exactly as the CLI does.
    A whole-valued number such as 8080.0 is an integer for the service
    config's port and for the pid in the registration and the info probe. Before, such a config was
    ignored, so EnsureAsync lost its env overlay, and such a registration was not found. A service
    config whose disabled member is not a boolean is no config at all, so its env overlay and the
    legacy config copy do not apply, as in the CLI. disabled: true itself does not stop
    EnsureAsync: only CLI commands read it, never the CLI's service operations.

📋 Important Notes

  • net8.0 and net9.0 leave the packages, and net11.0 joins, in an upcoming preview, ahead of
    their end of support on 2026-11-10. The packages will then target
    netstandard2.0;net472;net10.0;net11.0. An app on .NET 8 or 9 still installs through the
    netstandard2.0 asset, but that asset is a compatibility asset, not a supported runtime: support
    and testing cover the targeted runtimes only, and .NET 5–7 are not supported. Plan the move to
    .NET 10.
  • Upstream's committed OpenAPI document is stale at v2.0.22
    (anomalyco/opencode#53105). This SDK's
    snapshot comes from upstream's own pinned generator, so a diff against that committed file shows
    four more operations here; they are real and the server serves them.

0.9.0-preview.5

0.9.0-preview.5 Pre-release
Pre-release

Choose a tag to compare

@Blind-Striker Blind-Striker released this 29 Sep 08:09

The packages are now OpenCodeDotNet.Sdk and OpenCodeDotNet.Sdk.Extensions, and the
repository lives in the opencode-dotnet organization.
The API is unchanged: only the
PackageReference changes. The release also fixes thread-pool starvation from standalone servers
on Windows.

💥 Breaking changes

  • The packages are now OpenCodeDotNet.Sdk and OpenCodeDotNet.Sdk.Extensions. Change the
    PackageReference from OpenCodeAI.Sdk and OpenCodeAI.Sdk.Extensions; the earlier ids receive
    no further versions. Assemblies and namespaces are still OpenCode.Sdk, so no source changes.
  • The repository moved to
    opencode-dotnet/opencode-sdk-dotnet.

    GitHub redirects the old links. The nightly feed moved with it to
    https://nuget.pkg.github.com/opencode-dotnet/index.json; the earlier feed keeps its existing
    OpenCodeAI.* nightlies and receives no new ones.

🐛 Fixes

  • A standalone server no longer holds two thread-pool threads on Windows. Process creates
    the redirected stdout and stderr pipes synchronous there before .NET 11, and its event readers
    blocked a pool thread on each for the server's whole life; with a few servers in one process the
    pool starved, and unrelated async work stalled for seconds. The launcher now reads both streams
    on dedicated threads on Windows, and disposal ends them: it waits for end-of-stream inside the
    drain bound and cancels a read still blocked, so no reader outlives the server handle. That also
    keeps the last lines of an exited child from missing the OpenCodeServerOutput snapshot under
    load. Linux and macOS are unchanged.

0.9.0-preview.4

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.