sdk-rust: add reconnect lifecycle and DM parity APIs#50
Conversation
|
Preview deployed!
This preview shares the staging database and will be cleaned up when the PR is merged or closed. Run E2E testsnpm run e2e -- https://pr50-api.relaycast.dev --ciOpen observer dashboard |
There was a problem hiding this comment.
Pull request overview
This PR adds WebSocket reconnection lifecycle support, runtime token updates, and typed DM helper APIs to the Rust SDK. The changes enable long-lived agents to handle connection failures gracefully with automatic reconnection and channel re-subscription, while also improving the ergonomics of DM-related operations.
Changes:
- Added WebSocket lifecycle events (
Open,Close,Error,Reconnecting) with automatic reconnection using exponential backoff - Added runtime token update support via
AgentClient::set_token()andWsClient::set_token()for long-lived clients - Added typed DM helper methods (
dm_typed,create_group_dm_typed,send_dm_message_typed,add_dm_participant_typed) with corresponding response structs, fixing theagent_namepayload compatibility issue
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/sdk-rust/src/ws.rs | Implements WebSocket reconnection logic with exponential backoff, lifecycle events, token updates, and automatic channel re-subscription |
| packages/sdk-rust/src/types.rs | Adds typed DM response structs and custom deserializers for flexible participant/last_message parsing |
| packages/sdk-rust/src/agent.rs | Adds typed DM helper methods, token update propagation, and fixes add_dm_participant payload to use agent_name |
| packages/sdk-rust/src/lib.rs | Exports new lifecycle types and DM response structs |
| packages/sdk-rust/tests/parity.rs | Adds tests for DM deserialization parity and agent_name payload compatibility |
| packages/sdk-rust/CHANGELOG.md | Documents 0.2.5 release with all new features and changes |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| *is_connected.lock().await = true; | ||
|
|
||
| // Spawn the WebSocket handler task |
There was a problem hiding this comment.
The initial WebSocket connection does not emit a WsLifecycleEvent::Open event. The Open event is only sent during reconnections (line 301), not during the initial connection. This creates inconsistent behavior where lifecycle subscribers won't receive an Open event for the first connection. Consider sending an Open event after setting is_connected to true and before spawning the task, or ensuring the spawned task sends it for both initial and reconnect scenarios.
| /// Maximum reconnect delay in milliseconds (default: 30000). | ||
| pub max_reconnect_delay_ms: Option<u64>, |
There was a problem hiding this comment.
The reconnect_delay_ms function enforces a minimum delay of 1000ms even if max_reconnect_delay_ms is set lower. This behavior is not documented in the field's doc comment (line 40) or the setter method (line 90-93). Consider documenting this minimum to avoid user confusion, for example: "Maximum reconnect delay in milliseconds (default: 30000, minimum: 1000)".
| reconnect_attempt += 1; | ||
| let _ = lifecycle_tx.send(WsLifecycleEvent::Reconnecting { | ||
| attempt: reconnect_attempt, | ||
| }); |
There was a problem hiding this comment.
The reconnect attempt counter is incremented in two places: once after a connection drops (line 401) and again if the subsequent reconnect fails (line 262). This can lead to double-counting reconnect attempts. For example, if a connection drops and the first reconnect attempt fails, the counter goes from 0→1→2, emitting two Reconnecting lifecycle events (attempt 1 and attempt 2) for what is logically a single reconnect cycle. This also means max_reconnect_attempts is effectively halved in scenarios with repeated connection failures.
Consider restructuring the logic so that the attempt counter is only incremented once per reconnect cycle, not both when entering the reconnect loop AND when a connection attempt fails.
| reconnect_attempt += 1; | |
| let _ = lifecycle_tx.send(WsLifecycleEvent::Reconnecting { | |
| attempt: reconnect_attempt, | |
| }); |
Summary
Testing