From 1a0a690b807866804a6c4b3d9b1a469a2f8644b2 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 1 Jul 2026 11:40:25 -0700 Subject: [PATCH] Add subscription delivery latency preference Add delivery.maxLatencyMs to subscribe params, regenerate schema and client wire types, and expose convenience APIs for clients that want to request coalesced delivery latency. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 2 + clients/go/CHANGELOG.md | 3 ++ clients/go/ahp/client.go | 9 ++++- clients/go/ahptypes/commands.generated.go | 15 ++++++++ clients/kotlin/CHANGELOG.md | 2 + .../generated/Commands.generated.kt | 21 ++++++++++- clients/rust/CHANGELOG.md | 9 +++++ clients/rust/crates/ahp-types/src/commands.rs | 37 +++++++++++++++++++ clients/rust/crates/ahp/src/client.rs | 20 +++++++++- clients/rust/crates/ahp/src/hosts/runtime.rs | 1 + .../Generated/Commands.generated.swift | 24 +++++++++++- .../AgentHostProtocolClient/AHPClient.swift | 7 +++- clients/swift/CHANGELOG.md | 3 ++ clients/typescript/CHANGELOG.md | 3 ++ clients/typescript/src/client/client.ts | 17 ++++++++- clients/typescript/src/client/index.ts | 2 +- clients/typescript/test/client.test.ts | 3 +- docs/specification/subscriptions.md | 14 ++++++- schema/commands.schema.json | 14 +++++++ schema/errors.schema.json | 14 +++++++ scripts/generate-go.ts | 2 +- scripts/generate-kotlin.ts | 2 +- scripts/generate-rust.ts | 30 ++++++++++++++- scripts/generate-swift.ts | 2 +- types/common/commands.ts | 26 ++++++++++++- types/index.ts | 1 + 26 files changed, 266 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54453b63..beffa93b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,8 @@ Spec version: `0.5.1` ### Added +- `SubscribeParams.delivery.maxLatencyMs` for clients to request a maximum + subscription delivery latency, including `0` for no intentional coalescing. - `SessionState.inputNeeded` — a session-level aggregate of outstanding input requests across all chats, so a client can discover and answer elicitations, tool confirmations, and client-tool execution requests from the session diff --git a/clients/go/CHANGELOG.md b/clients/go/CHANGELOG.md index 92fc9df7..f0e7f23f 100644 --- a/clients/go/CHANGELOG.md +++ b/clients/go/CHANGELOG.md @@ -16,6 +16,9 @@ tag whose matching `## [X.Y.Z]` heading is missing from this file. ### Added +- `SubscribeParams.Delivery.MaxLatencyMs` and `Client.SubscribeWithDelivery` + for clients to request a maximum subscription delivery latency, including + `0` for no intentional coalescing. - `SessionState.InputNeeded` — a session-level aggregate of outstanding input requests across all chats (`SessionInputRequest` union with `SessionChatInputRequest`, `SessionToolConfirmationRequest`, and diff --git a/clients/go/ahp/client.go b/clients/go/ahp/client.go index 5663a3ad..6da8197b 100644 --- a/clients/go/ahp/client.go +++ b/clients/go/ahp/client.go @@ -658,9 +658,16 @@ func (c *Client) Reconnect(ctx context.Context, clientID string, lastSeenServerS // Subscribe sends a `subscribe` request and returns the initial snapshot // together with a per-URI [Subscription] handle. func (c *Client) Subscribe(ctx context.Context, uri string) (*ahptypes.SubscribeResult, *Subscription, error) { + return c.SubscribeWithDelivery(ctx, uri, nil) +} + +// SubscribeWithDelivery sends a `subscribe` request with advisory delivery +// preferences and returns the initial snapshot together with a per-URI +// [Subscription] handle. +func (c *Client) SubscribeWithDelivery(ctx context.Context, uri string, delivery *ahptypes.SubscriptionDeliveryOptions) (*ahptypes.SubscribeResult, *Subscription, error) { sub := c.AttachSubscription(uri) var out ahptypes.SubscribeResult - if err := c.Request(ctx, "subscribe", ahptypes.SubscribeParams{Channel: uri}, &out); err != nil { + if err := c.Request(ctx, "subscribe", ahptypes.SubscribeParams{Channel: uri, Delivery: delivery}, &out); err != nil { sub.Close() return nil, nil, err } diff --git a/clients/go/ahptypes/commands.generated.go b/clients/go/ahptypes/commands.generated.go index f903b1ac..70be4320 100644 --- a/clients/go/ahptypes/commands.generated.go +++ b/clients/go/ahptypes/commands.generated.go @@ -200,6 +200,21 @@ type ReconnectSnapshotResult struct { type SubscribeParams struct { // Channel URI this command targets. Channel URI `json:"channel"` + // Optional delivery preferences for this subscription. + // + // Servers MAY use these preferences to buffer and coalesce high-frequency + // updates while preserving the same reduced state. Omit this field for the + // server's default delivery behavior. + Delivery *SubscriptionDeliveryOptions `json:"delivery,omitempty"` +} + +// Advisory delivery preferences for a single subscription. +type SubscriptionDeliveryOptions struct { + // Maximum time, in milliseconds, that the server may intentionally delay + // delivery while buffering/coalescing updates for this subscription. + // + // A value of `0` requests immediate delivery with no intentional coalescing. + MaxLatencyMs *int64 `json:"maxLatencyMs,omitempty"` } // Result of the `subscribe` command. diff --git a/clients/kotlin/CHANGELOG.md b/clients/kotlin/CHANGELOG.md index 2b2903b4..4f47545d 100644 --- a/clients/kotlin/CHANGELOG.md +++ b/clients/kotlin/CHANGELOG.md @@ -17,6 +17,8 @@ versions (`*-SNAPSHOT`) are explicitly rejected by the publish pipeline; bump ### Added +- `SubscribeParams.delivery.maxLatencyMs` for clients to request a maximum + subscription delivery latency, including `0` for no intentional coalescing. - `SessionState.inputNeeded` — a session-level aggregate of outstanding input requests across all chats (`SessionInputRequest` sealed interface with `SessionChatInputRequest`, `SessionToolConfirmationRequest`, and diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt index 6478384f..75d26873 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt @@ -254,7 +254,26 @@ data class SubscribeParams( /** * Channel URI this command targets. */ - val channel: String + val channel: String, + /** + * Optional delivery preferences for this subscription. + * + * Servers MAY use these preferences to buffer and coalesce high-frequency + * updates while preserving the same reduced state. Omit this field for the + * server's default delivery behavior. + */ + val delivery: SubscriptionDeliveryOptions? = null +) + +@Serializable +data class SubscriptionDeliveryOptions( + /** + * Maximum time, in milliseconds, that the server may intentionally delay + * delivery while buffering/coalescing updates for this subscription. + * + * A value of `0` requests immediate delivery with no intentional coalescing. + */ + val maxLatencyMs: Long? = null ) @Serializable diff --git a/clients/rust/CHANGELOG.md b/clients/rust/CHANGELOG.md index 27aae203..794a1f9b 100644 --- a/clients/rust/CHANGELOG.md +++ b/clients/rust/CHANGELOG.md @@ -17,6 +17,9 @@ matching `## [X.Y.Z]` heading is missing from this file. ### Added +- `SubscribeParams.delivery.max_latency_ms` and + `Client::subscribe_with_delivery` for clients to request a maximum + subscription delivery latency, including `0` for no intentional coalescing. - `SessionState.input_needed` — a session-level aggregate of outstanding input requests across all chats (`SessionInputRequest` enum with `SessionChatInputRequest`, `SessionToolConfirmationRequest`, and @@ -30,6 +33,12 @@ matching `## [X.Y.Z]` heading is missing from this file. - Optional `model` and `tools` fields on `AgentCustomization` for a custom agent's pinned model and tool allowlist. +### Changed + +- Direct Rust struct literals for `SubscribeParams` must now include + `delivery: None`; use `SubscribeParams::new(channel)` or + `Client::subscribe` to keep the default delivery behavior. + ## [0.5.0] — 2026-06-26 Implements AHP 0.5.0. diff --git a/clients/rust/crates/ahp-types/src/commands.rs b/clients/rust/crates/ahp-types/src/commands.rs index b7f310ac..7ce8f58a 100644 --- a/clients/rust/crates/ahp-types/src/commands.rs +++ b/clients/rust/crates/ahp-types/src/commands.rs @@ -234,6 +234,43 @@ pub struct ReconnectSnapshotResult { pub struct SubscribeParams { /// Channel URI this command targets. pub channel: Uri, + /// Optional delivery preferences for this subscription. + /// + /// Servers MAY use these preferences to buffer and coalesce high-frequency + /// updates while preserving the same reduced state. Omit this field for the + /// server's default delivery behavior. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub delivery: Option, +} + +impl SubscribeParams { + /// Create subscribe params with default delivery behavior. + pub fn new(channel: impl Into) -> Self { + Self { + channel: channel.into(), + delivery: None, + } + } + + /// Create subscribe params with advisory delivery preferences. + pub fn with_delivery(channel: impl Into, delivery: SubscriptionDeliveryOptions) -> Self { + Self { + channel: channel.into(), + delivery: Some(delivery), + } + } +} + +/// Advisory delivery preferences for a single subscription. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct SubscriptionDeliveryOptions { + /// Maximum time, in milliseconds, that the server may intentionally delay + /// delivery while buffering/coalescing updates for this subscription. + /// + /// A value of `0` requests immediate delivery with no intentional coalescing. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub max_latency_ms: Option, } /// Result of the `subscribe` command. diff --git a/clients/rust/crates/ahp/src/client.rs b/clients/rust/crates/ahp/src/client.rs index cddd0044..50ca34cd 100644 --- a/clients/rust/crates/ahp/src/client.rs +++ b/clients/rust/crates/ahp/src/client.rs @@ -31,7 +31,7 @@ use std::time::Duration; use ahp_types::actions::{ActionEnvelope, StateAction}; use ahp_types::commands::{ DispatchActionParams, InitializeParams, InitializeResult, ReconnectParams, ReconnectResult, - SubscribeParams, SubscribeResult, UnsubscribeParams, + SubscribeParams, SubscribeResult, SubscriptionDeliveryOptions, UnsubscribeParams, }; use ahp_types::common::{Uri, ROOT_RESOURCE_URI}; use ahp_types::messages::{ @@ -390,10 +390,26 @@ impl Client { pub async fn subscribe( &self, uri: String, + ) -> Result<(SubscribeResult, SessionSubscription), ClientError> { + self.subscribe_with_delivery(uri, None).await + } + + /// Subscribe to a URI with advisory delivery preferences and obtain a + /// handle that streams [`SubscriptionEvent`]s for that channel. + pub async fn subscribe_with_delivery( + &self, + uri: String, + delivery: Option, ) -> Result<(SubscribeResult, SessionSubscription), ClientError> { let sub = self.attach_subscription(&uri).await; let result: SubscribeResult = self - .request("subscribe", SubscribeParams { channel: uri }) + .request( + "subscribe", + SubscribeParams { + channel: uri, + delivery, + }, + ) .await?; Ok((result, sub)) } diff --git a/clients/rust/crates/ahp/src/hosts/runtime.rs b/clients/rust/crates/ahp/src/hosts/runtime.rs index a12866f9..b1912525 100644 --- a/clients/rust/crates/ahp/src/hosts/runtime.rs +++ b/clients/rust/crates/ahp/src/hosts/runtime.rs @@ -622,6 +622,7 @@ impl HostRuntime { "subscribe", SubscribeParams { channel: uri.clone(), + delivery: None, }, ) .await diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift index 1f72572e..417cece4 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift @@ -225,11 +225,33 @@ public struct ReconnectSnapshotResult: Codable, Sendable { public struct SubscribeParams: Codable, Sendable { /// Channel URI this command targets. public var channel: String + /// Optional delivery preferences for this subscription. + /// + /// Servers MAY use these preferences to buffer and coalesce high-frequency + /// updates while preserving the same reduced state. Omit this field for the + /// server's default delivery behavior. + public var delivery: SubscriptionDeliveryOptions? public init( - channel: String + channel: String, + delivery: SubscriptionDeliveryOptions? = nil ) { self.channel = channel + self.delivery = delivery + } +} + +public struct SubscriptionDeliveryOptions: Codable, Sendable { + /// Maximum time, in milliseconds, that the server may intentionally delay + /// delivery while buffering/coalescing updates for this subscription. + /// + /// A value of `0` requests immediate delivery with no intentional coalescing. + public var maxLatencyMs: Int? + + public init( + maxLatencyMs: Int? = nil + ) { + self.maxLatencyMs = maxLatencyMs } } diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/AHPClient.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/AHPClient.swift index f0cd233f..e7f59570 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/AHPClient.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/AHPClient.swift @@ -308,12 +308,15 @@ public actor AHPClient { /// If the request fails (RPC error, timeout, transport drop), the local /// listener is removed and its stream finished — callers don't need to /// clean up the partially-attached subscription. - public func subscribe(_ uri: String) async throws -> (SubscribeResult, AsyncStream) { + public func subscribe( + _ uri: String, + delivery: SubscriptionDeliveryOptions? = nil + ) async throws -> (SubscribeResult, AsyncStream) { let (stream, listenerId) = attachSubscriptionInternal(uri) do { let result: SubscribeResult = try await request( method: "subscribe", - params: SubscribeParams(channel: uri) + params: SubscribeParams(channel: uri, delivery: delivery) ) return (result, stream) } catch { diff --git a/clients/swift/CHANGELOG.md b/clients/swift/CHANGELOG.md index 669fc2f6..c60de20e 100644 --- a/clients/swift/CHANGELOG.md +++ b/clients/swift/CHANGELOG.md @@ -19,6 +19,9 @@ the tag matches the version pinned in [`VERSION`](VERSION). ### Added +- `SubscribeParams.delivery.maxLatencyMs` and + `AHPClient.subscribe(_:delivery:)` for clients to request a maximum + subscription delivery latency, including `0` for no intentional coalescing. - `SessionState.inputNeeded` — a session-level aggregate of outstanding input requests across all chats (`SessionInputRequest` enum with `SessionChatInputRequest`, `SessionToolConfirmationRequest`, and diff --git a/clients/typescript/CHANGELOG.md b/clients/typescript/CHANGELOG.md index 32b73ddb..1f8970d6 100644 --- a/clients/typescript/CHANGELOG.md +++ b/clients/typescript/CHANGELOG.md @@ -22,6 +22,9 @@ hotfix escape hatch. ### Added +- `SubscribeParams.delivery.maxLatencyMs` and `AhpClient.subscribe` delivery + options for clients to request a maximum subscription delivery latency, + including `0` for no intentional coalescing. - `SessionState.inputNeeded` — a session-level aggregate of outstanding input requests across all chats (`SessionInputRequest` union with `SessionChatInputRequest`, `SessionToolConfirmationRequest`, and diff --git a/clients/typescript/src/client/client.ts b/clients/typescript/src/client/client.ts index 82ac4a3f..20bcec22 100644 --- a/clients/typescript/src/client/client.ts +++ b/clients/typescript/src/client/client.ts @@ -20,6 +20,7 @@ import type { ReconnectParams, ReconnectResult, SubscribeParams, + SubscriptionDeliveryOptions, SubscribeResult, UnsubscribeParams, } from '../types/common/commands.js'; @@ -75,6 +76,12 @@ export interface AhpClientConfig { subscriptionBuffer?: number; } +/** Optional preferences for a `subscribe` request. */ +export interface SubscribeOptions { + /** Advisory delivery preferences for this subscription. */ + delivery?: SubscriptionDeliveryOptions; +} + const DEFAULT_REQUEST_TIMEOUT_MS = 30_000; const DEFAULT_SUBSCRIPTION_BUFFER = 4096; @@ -291,10 +298,16 @@ export class AhpClient { * before the `subscribe` request is sent, so no events delivered during * the round-trip are missed. */ - async subscribe(uri: URI): Promise<{ result: SubscribeResult; subscription: Subscription }> { + async subscribe( + uri: URI, + options: SubscribeOptions = {}, + ): Promise<{ result: SubscribeResult; subscription: Subscription }> { const subscription = this.attachSubscription(uri); try { - const params: SubscribeParams = { channel: uri }; + const params: SubscribeParams = { + channel: uri, + ...(options.delivery ? { delivery: options.delivery } : {}), + }; const result = await this.request('subscribe', params); return { result, subscription }; } catch (err) { diff --git a/clients/typescript/src/client/index.ts b/clients/typescript/src/client/index.ts index 978e0d03..b5d6a124 100644 --- a/clients/typescript/src/client/index.ts +++ b/clients/typescript/src/client/index.ts @@ -5,7 +5,7 @@ */ export { AhpClient, Subscription } from './client.js'; -export type { AhpClientConfig, DispatchHandle, ServerRequestHandler } from './client.js'; +export type { AhpClientConfig, DispatchHandle, ServerRequestHandler, SubscribeOptions } from './client.js'; export type { ClientEvent, ClosedReason, ConnectionState, SubscriptionEvent } from './events.js'; export { AhpClientError, diff --git a/clients/typescript/test/client.test.ts b/clients/typescript/test/client.test.ts index 37cb724f..3a0219af 100644 --- a/clients/typescript/test/client.test.ts +++ b/clients/typescript/test/client.test.ts @@ -107,10 +107,11 @@ test('subscribe attaches before sending the request and fans out an action', asy const client = new AhpClient(c); client.connect(); - const subPromise = client.subscribe('ahp-session:/s1'); + const subPromise = client.subscribe('ahp-session:/s1', { delivery: { maxLatencyMs: 100 } }); const req = await readRequest(s); assert.equal(req.method, 'subscribe'); assert.equal((req.params as SubscribeParams).channel, 'ahp-session:/s1'); + assert.deepEqual((req.params as SubscribeParams).delivery, { maxLatencyMs: 100 }); const result: SubscribeResult = { channel: 'ahp-session:/s1', diff --git a/docs/specification/subscriptions.md b/docs/specification/subscriptions.md index b6d89fd8..50d1fe70 100644 --- a/docs/specification/subscriptions.md +++ b/docs/specification/subscriptions.md @@ -42,7 +42,10 @@ Future channel types (LSP relay, MCP relay, …) introduce their own URI schemes "jsonrpc": "2.0", "id": 1, "method": "subscribe", - "params": { "channel": "ahp-session:/" } + "params": { + "channel": "ahp-session:/", + "delivery": { "maxLatencyMs": 100 } + } } // Server → Client (state-bearing channel) @@ -73,6 +76,15 @@ Future channel types (LSP relay, MCP relay, …) introduce their own URI schemes After subscribing, the client receives all messages scoped to that channel — both action envelopes (for state channels) and any channel-specific notifications. +### Delivery preferences + +Clients MAY include `delivery.maxLatencyMs` on `subscribe` to request an upper +bound, in milliseconds, on intentional server-side buffering for that +subscription. Servers MAY use that budget to coalesce high-frequency updates +while preserving the same reduced state a client would observe from immediate +delivery. A value of `0` requests immediate delivery with no intentional +coalescing. Omitting `delivery` uses the server's default delivery behavior. + ## Unsubscribe (Notification) `unsubscribe` is a fire-and-forget client → server notification. Like every wire message, its params carry the channel URI being released. diff --git a/schema/commands.schema.json b/schema/commands.schema.json index 2befba80..008f993d 100644 --- a/schema/commands.schema.json +++ b/schema/commands.schema.json @@ -218,12 +218,26 @@ "channel": { "$ref": "#/$defs/URI", "description": "Channel URI this command targets." + }, + "delivery": { + "$ref": "#/$defs/SubscriptionDeliveryOptions", + "description": "Optional delivery preferences for this subscription.\n\nServers MAY use these preferences to buffer and coalesce high-frequency\nupdates while preserving the same reduced state. Omit this field for the\nserver's default delivery behavior." } }, "required": [ "channel" ] }, + "SubscriptionDeliveryOptions": { + "type": "object", + "description": "Advisory delivery preferences for a single subscription.", + "properties": { + "maxLatencyMs": { + "type": "number", + "description": "Maximum time, in milliseconds, that the server may intentionally delay\ndelivery while buffering/coalescing updates for this subscription.\n\nA value of `0` requests immediate delivery with no intentional coalescing." + } + } + }, "SubscribeResult": { "type": "object", "description": "Result of the `subscribe` command.\n\n`snapshot` is present when the subscribed channel has associated state, and\nabsent for stateless channels.", diff --git a/schema/errors.schema.json b/schema/errors.schema.json index 45247620..69ea4afa 100644 --- a/schema/errors.schema.json +++ b/schema/errors.schema.json @@ -4601,12 +4601,26 @@ "channel": { "$ref": "#/$defs/URI", "description": "Channel URI this command targets." + }, + "delivery": { + "$ref": "#/$defs/SubscriptionDeliveryOptions", + "description": "Optional delivery preferences for this subscription.\n\nServers MAY use these preferences to buffer and coalesce high-frequency\nupdates while preserving the same reduced state. Omit this field for the\nserver's default delivery behavior." } }, "required": [ "channel" ] }, + "SubscriptionDeliveryOptions": { + "type": "object", + "description": "Advisory delivery preferences for a single subscription.", + "properties": { + "maxLatencyMs": { + "type": "number", + "description": "Maximum time, in milliseconds, that the server may intentionally delay\ndelivery while buffering/coalescing updates for this subscription.\n\nA value of `0` requests immediate delivery with no intentional coalescing." + } + } + }, "SubscribeResult": { "type": "object", "description": "Result of the `subscribe` command.\n\n`snapshot` is present when the subscribed channel has associated state, and\nabsent for stateless channels.", diff --git a/scripts/generate-go.ts b/scripts/generate-go.ts index c160af0c..cf538796 100644 --- a/scripts/generate-go.ts +++ b/scripts/generate-go.ts @@ -1421,7 +1421,7 @@ const COMMAND_STRUCTS: { name: string; omitDiscriminants?: boolean; goName?: str { name: 'ReconnectParams' }, { name: 'ReconnectReplayResult', omitDiscriminants: true }, { name: 'ReconnectSnapshotResult', omitDiscriminants: true }, - { name: 'SubscribeParams' }, { name: 'SubscribeResult' }, + { name: 'SubscribeParams' }, { name: 'SubscriptionDeliveryOptions' }, { name: 'SubscribeResult' }, { name: 'SessionForkSource' }, { name: 'CreateSessionParams' }, { name: 'DisposeSessionParams' }, { name: 'ChatForkSource' }, { name: 'CreateChatParams' }, { name: 'DisposeChatParams' }, diff --git a/scripts/generate-kotlin.ts b/scripts/generate-kotlin.ts index e492cec0..c01ace39 100644 --- a/scripts/generate-kotlin.ts +++ b/scripts/generate-kotlin.ts @@ -1396,7 +1396,7 @@ const COMMAND_STRUCTS = [ 'InitializeParams', 'InitializeResult', 'ClientCapabilities', 'ReconnectParams', 'ReconnectReplayResult', 'ReconnectSnapshotResult', - 'SubscribeParams', 'SubscribeResult', + 'SubscribeParams', 'SubscriptionDeliveryOptions', 'SubscribeResult', 'SessionForkSource', 'CreateSessionParams', 'DisposeSessionParams', 'ChatForkSource', 'CreateChatParams', 'DisposeChatParams', 'ListSessionsParams', 'ListSessionsResult', diff --git a/scripts/generate-rust.ts b/scripts/generate-rust.ts index 60fbbced..4e8d0287 100644 --- a/scripts/generate-rust.ts +++ b/scripts/generate-rust.ts @@ -1088,6 +1088,10 @@ function generateStateFile(project: Project): string { lines.push(generateStructFromInterface(project, entry.name, entry.rustName, { omitDiscriminants: entry.omitDiscriminants, })); + if (entry.name === 'SubscribeParams') { + lines.push(''); + lines.push(generateSubscribeParamsImplRust()); + } lines.push(''); } catch (e) { lines.push(`// TODO: could not generate ${entry.name}: ${e}`); @@ -1366,7 +1370,7 @@ const COMMAND_STRUCTS: { name: string; omitDiscriminants?: boolean; rustName?: s { name: 'ReconnectParams' }, { name: 'ReconnectReplayResult', omitDiscriminants: true }, { name: 'ReconnectSnapshotResult', omitDiscriminants: true }, - { name: 'SubscribeParams' }, { name: 'SubscribeResult' }, + { name: 'SubscribeParams' }, { name: 'SubscriptionDeliveryOptions' }, { name: 'SubscribeResult' }, { name: 'SessionForkSource' }, { name: 'CreateSessionParams' }, { name: 'DisposeSessionParams' }, { name: 'ChatForkSource' }, { name: 'CreateChatParams' }, @@ -1431,6 +1435,10 @@ function generateCommandsFile(project: Project): string { lines.push(generateStructFromInterface(project, entry.name, entry.rustName, { omitDiscriminants: entry.omitDiscriminants, })); + if (entry.name === 'SubscribeParams') { + lines.push(''); + lines.push(generateSubscribeParamsImplRust()); + } lines.push(''); } catch (e) { lines.push(`// TODO: could not generate ${entry.name}: ${e}`); @@ -1449,6 +1457,26 @@ function generateCommandsFile(project: Project): string { return lines.join('\n'); } +function generateSubscribeParamsImplRust(): string { + return `impl SubscribeParams { + /// Create subscribe params with default delivery behavior. + pub fn new(channel: impl Into) -> Self { + Self { + channel: channel.into(), + delivery: None, + } + } + + /// Create subscribe params with advisory delivery preferences. + pub fn with_delivery(channel: impl Into, delivery: SubscriptionDeliveryOptions) -> Self { + Self { + channel: channel.into(), + delivery: Some(delivery), + } + } +}`; +} + function generateChangesetOperationTargetRust(): string { return `/// Identifies the file or range a \`ChangesetOperation\` should act on. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/scripts/generate-swift.ts b/scripts/generate-swift.ts index 594c1a2a..4b73ddb4 100644 --- a/scripts/generate-swift.ts +++ b/scripts/generate-swift.ts @@ -1313,7 +1313,7 @@ const COMMAND_ENUMS = ['ReconnectResultType', 'ContentEncoding', 'CompletionItem const COMMAND_STRUCTS = [ 'InitializeParams', 'InitializeResult', 'ClientCapabilities', 'ReconnectParams', 'ReconnectReplayResult', 'ReconnectSnapshotResult', - 'SubscribeParams', 'SubscribeResult', + 'SubscribeParams', 'SubscriptionDeliveryOptions', 'SubscribeResult', 'SessionForkSource', 'CreateSessionParams', 'DisposeSessionParams', 'ChatForkSource', 'CreateChatParams', 'DisposeChatParams', 'ListSessionsParams', 'ListSessionsResult', diff --git a/types/common/commands.ts b/types/common/commands.ts index 35e75256..b02a8716 100644 --- a/types/common/commands.ts +++ b/types/common/commands.ts @@ -251,7 +251,31 @@ export type ReconnectResult = ReconnectReplayResult | ReconnectSnapshotResult; * @version 1 * @see {@link /specification/subscriptions | Subscriptions} */ -export interface SubscribeParams extends BaseParams {} +export interface SubscribeParams extends BaseParams { + /** + * Optional delivery preferences for this subscription. + * + * Servers MAY use these preferences to buffer and coalesce high-frequency + * updates while preserving the same reduced state. Omit this field for the + * server's default delivery behavior. + */ + delivery?: SubscriptionDeliveryOptions; +} + +/** + * Advisory delivery preferences for a single subscription. + * + * @category Commands + */ +export interface SubscriptionDeliveryOptions { + /** + * Maximum time, in milliseconds, that the server may intentionally delay + * delivery while buffering/coalescing updates for this subscription. + * + * A value of `0` requests immediate delivery with no intentional coalescing. + */ + maxLatencyMs?: number; +} /** * Result of the `subscribe` command. diff --git a/types/index.ts b/types/index.ts index 13f1985c..bcb11e73 100644 --- a/types/index.ts +++ b/types/index.ts @@ -265,6 +265,7 @@ export type { ReconnectSnapshotResult, ReconnectResult, SubscribeParams, + SubscriptionDeliveryOptions, SubscribeResult, CreateSessionParams, SessionForkSource,