Skip to content

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 container does not
      dispose a handle you register.
    • A passwordless 401 carries the typed UnauthorizedError, not an empty body.
  • The guides wait for a turn on the execution events, not on SessionIdle. The pinned server
    never publishes session.idle or session.status, so a loop that waited for SessionIdle never
    ended. A turn ends with exactly one of SessionExecutionSucceeded, SessionExecutionFailed or
    SessionExecutionInterrupted.

📋 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.