2.52.0
(feat): Conditionally emit System.Text.Json as a PackageReference only when
targeting pre-net8.0 TFMs (netstandard2.0, net462). The package is
wrapped in an ItemGroup with an MSBuild IsTargetFrameworkCompatible
condition so it is skipped for net8.0+ where it ships in-box.
2.51.1
(chore): Remove dead #elif NET7_0_OR_GREATER and #else branches inside
QueryStringBuilder that could only execute on .NET 6 or .NET 7,
which are no longer targeted.
2.51.0
(feat): Add net9.0 target framework to all generated C# SDK projects. The library
now targets net462;net8.0;net9.0;netstandard2.0, enabling #if NET9_0_OR_GREATER
code paths such as ClientWebSocketOptions.KeepAliveTimeout for transport-level
dead connection detection. Test projects now target net9.0 instead of net8.0.
(feat): Add comprehensive WebSocket unit and end-to-end test templates. All generated
WebSocket SDKs now ship with 349 tests covering AsyncLock, Event<T>,
ReconnectStrategy, DisconnectionInfo, RequestMessage, Query,
WebSocketConnection, WebSocketClient, ReconnectionInfo, and
WebsocketException. End-to-end tests run against an in-process
TestWebSocketServer and cover echo, rapid-fire, reconnection, server close,
abort recovery, concurrent clients, large messages, and full lifecycle.
Templates are conditionally emitted when the Fern definition includes WebSocket
endpoints.
(feat): Expose StateCheckInterval property on WebSocketClient. The property
(default: 5 seconds) forwards to WebSocketConnection in ConnectAsync,
allowing callers to control how often the background state monitor polls
for silent disconnections.
(fix): Fix server-initiated close skipping reconnection. When the server sent a
WebSocketMessageType.Close frame, the Listen() method used return
which exited entirely — bypassing the ReconnectSynchronized call. Changed
to break with a closedByServer flag so reconnection executes with
ReconnectionType.ByServer.
(fix): Fix CancelReconnection flag being ignored for server-initiated close.
The OnDisconnectionHappened callback fires in the Close handler and the
user can set info.CancelReconnection = true, but this flag was never
checked before calling ReconnectSynchronized. Now captured into a local
cancelReconnect variable and checked before reconnecting.
2.50.0
(feat): Add exponential backoff reconnection strategy for WebSocket connections.
A new ReconnectStrategy class provides configurable exponential backoff
(doubling intervals from 1s to 60s), optional random jitter (0-25%) to
prevent thundering herd, and configurable max attempts. The strategy is
exposed as a ReconnectBackoff property on the generated Options class
and wired through WebSocketClient to WebSocketConnection.Reconnect().
When max attempts are exhausted, reconnection stops and a disconnection
event is raised. The backoff resets on successful reconnection.
2.49.0
(feat): Add per-send timeout to prevent SendAsync hangs. A new SendTimeout property
(default: 30 seconds) on WebSocketConnection wraps every SendAsync call with a
linked CancellationTokenSource that fires CancelAfter(SendTimeout). When the
timeout elapses the connection is aborted and a WebsocketException is thrown,
preventing a single dead connection from blocking the send lock for up to 30 minutes.
See: https://github.com/dotnet/runtime/issues/125257
2.48.0
(feat): Add bounded send queue with message expiration to WebSocketConnection. The
unbounded Channel<T> queues are replaced with configurable bounded channels
(SendQueueLimit, default: 10,000) that use BoundedChannelFullMode.DropWrite
to reject new messages when the queue is full. A SendCacheItemTimeout property
(default: 30 minutes) causes stale messages to be silently dropped during drain,
preventing memory growth during network outages and avoiding sending outdated
messages after reconnection. Set SendQueueLimit to 0 for unbounded or
SendCacheItemTimeout to null to disable expiration.
2.47.0
(feat): Add receive hang detection via a background state monitor task. A new
StateCheckInterval property (default: 5 seconds) controls how often the
monitor polls WebSocket.State. When the state transitions to Closed,
Aborted, or CloseReceived — indicating the TCP connection died without
notifying the receive loop — the monitor cancels the pending ReceiveAsync
via a linked CancellationTokenSource. This works around known .NET runtime
issues where ReceiveAsync hangs indefinitely on silent TCP disconnections
(dotnet/runtime#110496) and Abort() fails to cancel a pending receive
(dotnet/runtime#44272). Set StateCheckInterval to null to disable.
2.46.0
(feat): Wire up WebSocket reconnection logic. The WebSocketConnection template now
contains a complete reconnection implementation ported from the Marfusios library:
heartbeat timer (ReconnectTimeout), error-retry timer (ErrorReconnectTimeout),
lost-connection delay (LostReconnectTimeout), and Reconnect()/ReconnectOrFail()
public methods. The generated WebSocket API classes expose a Reconnecting event
and an IsReconnectionEnabled option (default: false) so callers can opt in to
automatic reconnection. All timeout properties are configurable with sensible defaults.
2.45.1
(fix): Fix sticky compression on reconnect. DeflateOptions is now explicitly reset to
null when EnableCompression is false, preventing a previous non-null value
from persisting across connection cycles.
2.45.0
(feat): Add InjectTestMessage internal method to generated WebSocket client classes.
This allows unit tests to simulate incoming WebSocket messages by injecting raw
JSON strings through the normal OnTextMessage pipeline without requiring a real
WebSocket connection. The method is marked internal so it is only accessible
via [InternalsVisibleTo] in test projects, keeping the public API surface clean.
2.44.0
(feat): Add queued sending via Channel<T> to the WebSocket connection layer. Two
unbounded channels (text and binary) are drained by dedicated background tasks
started alongside the connection. The non-blocking Send(string) and Send(byte[])
methods return bool indicating whether the message was queued, while the existing
SendInstant async-per-send path remains unchanged. Queues are completed in Dispose
before cancellation tokens are signalled. Drain loop errors are surfaced through the
ExceptionOccurred event.
2.43.0
(feat): Add per-message deflate compression support (RFC 7692) for WebSocket connections.
A new EnableCompression option on the generated WebSocket Options class enables
WebSocketDeflateOptions via ClientWebSocketOptions.DangerousDeflateOptions.
This can significantly reduce bandwidth for text-heavy (JSON) WebSocket APIs.
Warning: Do not use compression when sending data containing secrets
(CRIME/BREACH vulnerability).
See https://learn.microsoft.com/dotnet/api/system.net.websockets.clientwebsocketoptions.dangerousdeflateoptions
for details.
2.42.0
(feat): Add support for HTTP/2 WebSockets via SocketsHttpHandler. A new HttpInvoker
property on the WebSocket Options class allows passing an HttpMessageInvoker to
enable multiplexing multiple WebSocket streams over a single TCP connection. When
set, the connection factory configures HttpVersion.Version20 and uses the
ConnectAsync(Uri, HttpMessageInvoker, CancellationToken) overload on .NET 7+.
On older targets the invoker is ignored and the standard connect path is used.
2.41.0
(feat): Add ConnectTimeout property to WebSocketConnection and WebSocketClient with a
default of 5 seconds. The connection factory call in StartClient now uses a linked
CancellationTokenSource with CancelAfter(ConnectTimeout), preventing indefinite
hangs when the server is unresponsive.
2.40.0
(feat): Configure PING/PONG keep-alive on ClientWebSocket. The default connection factory
now sets KeepAliveInterval (default: 30 s) and, on .NET 9+, KeepAliveTimeout
(default: 20 s) to enable the PING/PONG keep-alive strategy that detects unresponsive
servers at the transport level. Both values are exposed as public properties on
WebSocketConnection for caller customization.
2.38.2
(fix): Add using declaration to JsonDocument in WebSocket OnTextMessage handler.
JsonDocument implements IDisposable and holds pooled memory buffers; without
disposal, high message throughput causes memory pressure. The generated code now
emits using var json = await JsonSerializer.DeserializeAsync<JsonDocument>(stream);
so the document is disposed when the method returns.
2.39.0
(feat): Generate interfaces for WebSocket API classes (e.g., IStreamApi, ITranscribeApi)
to improve testability. Each WebSocket client now implements a corresponding interface
that declares all public event properties, Status, ConnectAsync, Send, and
CloseAsync. The root client interface (ICortiClient) also includes factory method
signatures (CreateStreamApi, CreateTranscribeApi) when WebSockets are enabled.
2.38.1
(fix): Fix Event<T> thread-safety issue. RaiseEvent now snapshots the subscriber
collections under a lock before iterating, and Subscribe, Unsubscribe, and
UnsubscribeAll are synchronized to prevent InvalidOperationException when
handlers are added or removed concurrently from different threads.
2.38.0
(feat): Add optional CancellationToken parameter to all public async WebSocket methods
(ConnectAsync, CloseAsync, and Send overloads). The token is threaded through
the internal WebSocketClient, WebSocketConnection, and down to the underlying
ClientWebSocket.SendAsync/ConnectAsync/CloseAsync calls, enabling callers to
cancel long-running WebSocket operations.
2.37.2
(fix): Fix WebSocket binary messages being sent with WebSocketMessageType.Text instead of
WebSocketMessageType.Binary. RequestBinaryMessage and RequestBinarySegmentMessage
now correctly use WebSocketMessageType.Binary when calling SendAsync, ensuring
audio data (e.g. webm/opus chunks) is sent as binary frames.
2.37.1
(fix): Add ReadAsPropertyName and WriteAsPropertyName overrides to all remaining
custom JsonConverter types: EnumSerializer<TEnum> (native C# enums),
DateTimeSerializer, DateAsDateTimeConverter, discriminated union converters,
and literal type converters. Without these overrides, System.Text.Json throws
NotSupportedException when any of these types is used as a Dictionary<TKey, TValue>
key. This complements the IStringEnum fix in 2.35.1.
2.37.0
(feat): Replace separate deserialization and serialization tests with a single round-trip
serialization test using JsonAssert.Roundtrips<T>(inputJson). The helper deserializes
JSON to T, re-serializes, and compares to the original JSON via JsonElement deep
equality, producing diff-reportable failures.
2.36.0
(feat): Add UsingObjectDictionaryComparer to test comparers for handling
Dictionary<string, object?> fields. System.Text.Json deserializes
object? values as JsonElement, so structural equality comparisons
need to serialize both sides before comparing. The UsingDefaults()
chain now includes this comparer automatically.
2.35.1
(fix): Add ReadAsPropertyName and WriteAsPropertyName overrides to the generated
IStringEnum JsonConverter. Without these overrides, System.Text.Json throws
NotSupportedException at runtime when serializing or deserializing a
Dictionary<TStringEnum, TValue>. This is applied to all generated IStringEnum
types so dictionary-key usage works without requiring regeneration.
2.35.0
(fix): Fix WebSocket binary message sending. Binary messages (e.g. audio bytes) are now
sent as raw bytes instead of being JSON-serialized, which caused servers to reject
them with CONFIG_DENIED.
(fix): Fix WebSocket incoming message deserialization. TryDeserialize now passes
JsonSerializerOptions so the generated literal property setters correctly
validate const discriminator fields (e.g. type: "transcript" vs type: "flushed")
during deserialization.
(feat): Add forward-compatible UnknownMessage event to WebSocket clients. When the server
sends an event type not recognized by the SDK, it is now dispatched to the
UnknownMessage event handler (as a JsonElement) instead of raising an exception.
This allows SDKs to gracefully handle new server event types without requiring
regeneration.
2.34.0
(feat): Add unified-client-options configuration flag. When enabled, all auth parameters,
global headers, and BaseUrl are consolidated into ClientOptions so the client
constructor takes a single argument with no positional parameters:
Before (default):
After (with unified-client-options: true):
Supported auth schemes: bearer token, basic auth (username/password), header auth, OAuth client credentials (clientId/clientSecret + custom params), and inferred auth. Global headers (non-literal) are also included.
Properties on ClientOptions use the appropriate C# semantics based on the
parameter’s characteristics:
- Has environment variable fallback —
public string? ApiKey { get; set; }. Nullable and mutable so the constructor can apply the fallback viaclientOptions.ApiKey ??= GetFromEnvironmentOrThrow(...). - Optional (no environment variable) —
public string? ApiKey { get; init; }. Nullable, immutable after construction. - Required (no environment variable, not optional) —
public required string ApiKey { get; init; }. Non-nullable, enforced at the call site by the compiler.BaseUrlis alsorequiredwhen no default environment is configured, replacing the previous behavior of silently defaulting to"".
When any ClientOptions field is required, the constructor parameter becomes
non-nullable (ClientOptions clientOptions instead of ClientOptions? clientOptions = null),
and Clone() uses an internal copy constructor annotated with [SetsRequiredMembers].
2.33.1
(fix): Fix README generation to use the package-name field from generator.yml for the
NuGet package name in installation instructions and shields. Previously, the README
always used the namespace (or package-id custom config) instead of the configured
package-name, which is the actual NuGet package identifier.
2.33.0
(feat): WebSocket factory methods now resolve the base URL from the main client’s
multi-URL environment when the channel specifies a baseUrl. This allows
WebSocket clients to use environments defined on the root client (e.g.,
Environment.Wss) instead of requiring separate per-WebSocket environment
classes.
2.32.2
(fix): Fix CS1501 build error for literal struct GetHashCode on netstandard2.0 and
net462 targets. string.GetHashCode(StringComparison) is only available in
.NET Core 2.1+ / .NET Standard 2.1+. The generated code now uses
StringComparer.Ordinal.GetHashCode(Value) which is available on all frameworks.
2.32.1
(fix): Fix referenced request bodies with application/x-www-form-urlencoded content type
to generate FormRequest instead of JsonRequest. Previously, endpoints using a
$ref schema with form-urlencoded content type would incorrectly send JSON-serialized
bodies, causing auth token requests (e.g., OAuth) to fail.
2.32.0
(feat): Add support for the google.protobuf.Empty well-known type for gRPC endpoints.
(fix): Add more permissive handling for optional google.protobuf.Timestamp response properties.
2.31.1
(fix): Fix CS0426 compilation error when the client class name matches a namespace
root segment (e.g., class Candid in namespace Candid.Net). The C# compiler
previously resolved Candid.Net as looking for a Net member on the Candid
type instead of the Candid.Net namespace. The generator now uses global::
prefixes in both inline references and using directives to disambiguate.
2.31.0
(feat): Add support for base64 primitive type in the IR to represent Protobuf byte[].
(fix): Fix the .ToProto and .FromProto mapper methods to support for enums nested within
message descriptors.
2.30.3
(fix): Add better support for the google.rpc.Status type, and stop
generating redundant wrapper types for google.api and
google.rpc types.
2.30.2
(fix): Fix gRPC code generation to support enums with numeric identifiers.
2.30.1
(fix): Fix gRPC code generation to support enums with numeric identifiers.
2.30.0
(feat): Add sln-format configuration option. Set to "sln" to generate both a legacy
.sln solution file and the modern .slnx file. The default value "slnx" generates
only the .slnx file. This is useful for teams that need compatibility with older
.NET tooling or CI systems that do not yet support the .slnx format.
2.29.0
(feat): Rework enum JSON serialization to eliminate reflection. Each enum now gets
a generated serializer with static dictionary lookups instead of the generic
EnumSerializer<T> / StringEnumSerializer<T> that used Enum.GetValues,
GetField, GetCustomAttributes, and Activator.CreateInstance at runtime.
Serializer initialization is ~1,000x faster (18 ns vs 18 us) and allocates
160x less memory; steady-state throughput is unchanged. The generated code is
also NativeAOT and IL-trimming compatible.
2.28.0
(feat): Generate a separate WireMock server for each test fixture instead of sharing
a single global server across all mock server tests. Each test class now starts
and stops its own WireMockServer in [OneTimeSetUp]/[OneTimeTearDown],
and test fixtures are annotated with [Parallelizable(ParallelScope.Self)]
so NUnit can run them in parallel.
2.27.1
(fix): Fix retry test assertions to match the SDK’s indented JSON serialization.
2.27.0
(feat): Improve gRPC code generation with the following:
- Updated enum representation and .ToProto + .FromProto mappers.
- Fix an issue with rendering the gRPC client reference.
- Update protoc-gen-openapi to unwrap synthetic oneof types (re: https://github.com/fern-api/protoc-gen-openapi/releases/tag/v0.1.7)
2.26.0
(feat): Add omit-fern-headers configuration option. When enabled, Fern platform headers
(X-Fern-Language, X-Fern-SDK-Name, X-Fern-SDK-Version, User-Agent) are omitted
from generated SDK requests.
2.25.0
(feat): Add generate-literals configuration option. When enabled, string and boolean
literal properties emit a self-contained readonly struct with a built-in
JsonConverter instead of using a string property with an Assert setter.
The struct type validates the literal value at the JSON deserialization boundary
and supports implicit conversion to string or bool.
The previous experimental-readonly-constants option is now deprecated and
will log a warning when used. Use generate-literals instead.
2.24.5
(chore): Add concurrency configuration to generated CI workflow with cancel-in-progress: false
to prevent stacked runs from being cancelled when a newer run starts.
2.24.4
(fix): Fix dynamic snippet generation for undiscriminated unions when use-undiscriminated-unions: true
is set. Previously, snippets always emitted OneOf<A, B, C> regardless of the config setting.
Now, when the setting is enabled, snippets use the SDK’s generated union wrapper type (e.g.
MixedType) instead, matching the actual SDK types and producing compilable code.
2.24.3
(fix): Re-throw errors after logging for README.md and reference.md generation failures.
Documentation generation errors now break the generation flow so they are treated
as important to fix.
2.24.2
(fix): Fix dynamic snippets to include global headers in generated code examples.
Previously, required global headers (e.g., X-Organization-ID) configured via
x-fern-global-headers were missing from dynamic snippet output. Also fixes
header value lookup to use wire values instead of a non-existent property.
2.24.1
(chore): Improve error logging for README.md and reference.md generation failures.
The generator now logs the actual error message and stack trace instead
of a generic “this is OK” message, making it easier to diagnose generation issues.