diff --git a/fern/products/api-def/ferndef/endpoints/sse.mdx b/fern/products/api-def/ferndef/endpoints/sse.mdx index 48d553ee4..75d2c1583 100644 --- a/fern/products/api-def/ferndef/endpoints/sse.mdx +++ b/fern/products/api-def/ferndef/endpoints/sse.mdx @@ -69,6 +69,28 @@ types: Generated SDKs expose each event's [metadata — event ID, event type, and retry interval](/learn/sdks/deep-dives/sse-metadata) to your end users. +### Resumable streams + +Set `resumable: true` on `response-stream` to opt an SSE endpoint into [automatic reconnection](/learn/sdks/deep-dives/sse-metadata#automatic-reconnection). When the connection drops mid-stream, the generated SDK reconnects and resends the last event ID in the `Last-Event-ID` header, so a server that supports that header resumes where the stream left off. + +```yaml title="chat.yml" {10} +service: + base-path: /chat + endpoints: + stream: + method: POST + path: "" + response-stream: + type: Chat + format: sse + resumable: true + +types: + Chat: + properties: + text: string +``` + ## `Stream` parameter It has become common practice for endpoints to have a `stream` parameter that diff --git a/fern/products/api-def/openapi/endpoints/sse.mdx b/fern/products/api-def/openapi/endpoints/sse.mdx index 0f853f2e5..53aed0df2 100644 --- a/fern/products/api-def/openapi/endpoints/sse.mdx +++ b/fern/products/api-def/openapi/endpoints/sse.mdx @@ -90,6 +90,38 @@ paths: # ... responses and schemas ``` +### Resumable streams + +Set `resumable: true` to opt an SSE endpoint into [automatic reconnection](/learn/sdks/deep-dives/sse-metadata#automatic-reconnection). When the connection drops mid-stream, the generated SDK reconnects and resends the last event ID in the `Last-Event-ID` header, so a server that supports that header resumes where the stream left off. + +```yaml title="openapi.yml" {4-7} +paths: + /logs: + post: + x-fern-streaming: + format: sse + terminator: "[DONE]" + resumable: true +# ... responses and schemas +``` + +Configure a [`terminator`](#terminator-message) alongside `resumable`. The terminator marks a stream as complete, letting the SDK distinguish a finished stream from a dropped connection so it reconnects only on genuine drops. + +`resumable` is inheritable. Set it at the document level to apply to every SSE endpoint, and override it on individual operations: + +```yaml title="openapi.yml" +x-fern-streaming: + resumable: true # applies to all SSE endpoints + +paths: + /logs: + post: + x-fern-streaming: + format: sse + resumable: false # overrides the document default +# ... responses and schemas +``` + ## `Stream` parameter It has become common practice for endpoints to have a `stream` parameter that diff --git a/fern/products/sdks/capabilities.mdx b/fern/products/sdks/capabilities.mdx index 98322abca..ffb52c397 100644 --- a/fern/products/sdks/capabilities.mdx +++ b/fern/products/sdks/capabilities.mdx @@ -257,7 +257,7 @@ Fern SDKs include the following capabilities: - **Retries with backoff**: Automatically retry failed requests with exponential backoff. [Learn more](/sdks/deep-dives/retries-with-backoff) - **Webhook signature verification**: Verify the signature of incoming webhook requests. [Learn more](/learn/sdks/deep-dives/webhook-signature-verification) - **Idempotency headers**: Built-in protection against duplicate submissions. [Learn more](/sdks/deep-dives/idempotency) -- **Server-sent events**: Stream JSON data from your server to your client, with opt-in access to [SSE metadata](/learn/sdks/deep-dives/sse-metadata) (event ID, event type, retry). [Learn more](/learn/sdks/deep-dives/sse-metadata) +- **Server-sent events**: Stream JSON data from your server to your client, with opt-in access to [SSE metadata](/learn/sdks/deep-dives/sse-metadata) (event ID, event type, retry) and [automatic reconnection](/learn/sdks/deep-dives/sse-metadata#automatic-reconnection) for resumable endpoints. [Learn more](/learn/sdks/deep-dives/sse-metadata) - **Testing**: Auto-generated and handwritten tests for your SDK. [Learn more](/sdks/deep-dives/testing) - **Code snippets**: No longer depend on manually written code snippets. [Learn more](/docs/api-references/sdk-snippets) - **Augment with custom code**: Extend the generated SDK with additional functionality. [Learn more](/sdks/overview/custom-code) diff --git a/fern/products/sdks/deep-dives/sse-metadata.mdx b/fern/products/sdks/deep-dives/sse-metadata.mdx index 480e12584..07007044e 100644 --- a/fern/products/sdks/deep-dives/sse-metadata.mdx +++ b/fern/products/sdks/deep-dives/sse-metadata.mdx @@ -51,3 +51,50 @@ Each event exposes the parsed data alongside its protocol fields. Default iterat ## Stream resumption The event ID is useful for resuming a stream via the standard `Last-Event-ID` header: store the last received ID as you iterate, then pass it back to the server on reconnection. This requires server-side support for the `Last-Event-ID` header. + +## Automatic reconnection + +Mark an SSE endpoint [`resumable`](/learn/api-definitions/openapi/endpoints/sse#resumable-streams) in your API definition (`x-fern-streaming.resumable: true` in OpenAPI, or `response-stream.resumable: true` in a Fern Definition) to have the SDK handle resumption for you. On a mid-stream drop, the SDK reconnects transparently, resending the last dispatched event ID in the `Last-Event-ID` header so iteration continues without gaps. No manual event-ID tracking is required. + +Reconnection honors the server's `retry:` directive for the reconnect delay, falling back to a 1-second default and capped at 30 seconds. The SDK retries up to 5 consecutive times by default; the counter resets whenever an event is received. Configure a [terminator](/learn/api-definitions/openapi/endpoints/sse#terminator-message) on the endpoint so the SDK can tell a completed stream from a dropped connection. + +Both the attempt cap and an on/off toggle are configurable per client and per request: + + + + ```ts {2} + const stream = await client.plants.stream({ query: "fern" }, { + stream: { reconnectionEnabled: true, maxReconnectionAttempts: 3 } + }); + ``` + + + ```python {3-4} + stream = client.plants.stream( + query="fern", + stream_reconnection_enabled=True, + max_stream_reconnection_attempts=3, + ) + ``` + + + ```go {4} + stream := client.Plants.Stream( + ctx, + &PlantRequest{Query: "fern"}, + option.WithMaxStreamReconnectAttempts(3), // or option.WithoutStreamReconnection() + ) + ``` + + + ```csharp {2} + var stream = await client.Plants.StreamAsync(new PlantRequest { Query = "fern" }, new RequestOptions { + MaxStreamReconnectAttempts = 3 // set DisableStreamReconnection = true to turn off + }); + ``` + + + + +Automatic reconnection requires TypeScript SDK generator version 3.77.0+, Python 5.15.0+, Go 1.42.0+, or C# 2.71.0+. +