Add workspace/agent management APIs and WS events#20
Conversation
Introduce new Relay APIs and related client/type updates: add Relay.createWorkspace, workspace.delete, systemPrompt.get/set, agents.update/delete/presence, and agents.registerOrGet (with conflict handling). Add AgentClient.channels.update and HttpClient.put. Improve WsClient to emit 'error' and 'reconnecting' events. Add new types (AgentPresenceInfo, SystemPrompt, SetSystemPromptRequest) and bump SDK_VERSION to 0.2.3. Tests updated/added to cover the new behaviors.
There was a problem hiding this comment.
Pull request overview
Adds new workspace/system-prompt/agent management APIs to the SDK and types package, plus new WS client events and supporting HTTP/client updates.
Changes:
- Added Relay APIs for workspace creation/deletion, system prompt get/set, and agent update/delete/presence +
registerOrGetconflict handling. - Extended SDK internals with
HttpClient.put()and newWsClientevents (error,reconnecting), plus new exported types. - Updated/added tests covering the new endpoints, WS events, and version bump.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/types/src/workspace.ts | Adds SystemPrompt and SetSystemPromptRequest types for new system-prompt endpoints. |
| packages/types/src/agent.ts | Adds AgentPresenceInfo type for presence endpoint responses. |
| packages/sdk/src/ws.ts | Emits new WS lifecycle events (error, reconnecting). |
| packages/sdk/src/relay.ts | Adds new Relay APIs (workspace/systemPrompt/agents) and static createWorkspace(). |
| packages/sdk/src/index.ts | Bumps SDK_VERSION to 0.2.3. |
| packages/sdk/src/client.ts | Adds HttpClient.put() helper. |
| packages/sdk/src/agent.ts | Adds AgentClient.channels.update(). |
| packages/sdk/src/tests/ws.test.ts | Tests for WS error + reconnecting events. |
| packages/sdk/src/tests/relay.test.ts | Tests for new Relay endpoints and registerOrGet() behavior. |
| packages/sdk/src/tests/index.test.ts | Updates expected SDK_VERSION. |
| packages/sdk/src/tests/agent-features.test.ts | Tests channels.update() PATCH behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| this.reconnectAttempt++; | ||
| this.emit('reconnecting', { type: 'reconnecting', attempt: this.reconnectAttempt } as unknown as ServerEvent); | ||
| this.reconnectTimer = setTimeout(() => { |
There was a problem hiding this comment.
Same type-safety issue as the error event: reconnecting is emitted with a payload that is not part of the ServerEvent union, and is forced via as unknown as ServerEvent. Define/export an explicit event type for reconnecting (including the attempt field) and use that in EventHandler/emit typing so this is checked by TypeScript.
| import { SDK_VERSION } from './index.js'; | ||
|
|
There was a problem hiding this comment.
Relay now imports SDK_VERSION from ./index.js, but index.ts re-exports Relay from ./relay.js. This creates a circular module dependency (index -> relay -> index) which can lead to partially-initialized exports in some bundlers/runtime environments. Move SDK_VERSION into a leaf module (e.g. version.ts) and have both index.ts and relay.ts import from there (and avoid importing index from internal modules).
| import { SDK_VERSION } from './index.js'; | |
| const SDK_VERSION = 'unknown'; |
| const res = await fetch(url.toString(), { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| 'X-SDK-Version': SDK_VERSION, | ||
| }, | ||
| body: JSON.stringify({ name }), | ||
| }); | ||
| const parsed = await res.json(); | ||
| if (!parsed.ok) { | ||
| throw new RelayError( | ||
| parsed.error?.code ?? 'unknown_error', | ||
| parsed.error?.message ?? 'Unknown error', | ||
| res.status, |
There was a problem hiding this comment.
createWorkspace() assumes the response body is valid JSON with an { ok: boolean } shape and immediately accesses parsed.ok. If the API returns non-JSON (e.g. an HTML error page/proxy error) or an unexpected schema, this will throw a low-signal JSON parse/type error instead of a consistent RelayError. Consider mirroring HttpClient.request()'s validation: wrap res.json() in try/catch and verify the parsed value is an object with an ok boolean, otherwise throw RelayError('invalid_response', ...).
| this.ws.onerror = () => { | ||
| // onclose will fire after this | ||
| this.emit('error', { type: 'error' } as unknown as ServerEvent); | ||
| }; |
There was a problem hiding this comment.
WsClient emits error and reconnecting events by casting to ServerEvent, but @relaycast/types' ServerEvent union does not include these event shapes/types. This makes the public WS event surface type-unsafe (handlers are typed as ServerEvent but will receive { type: 'error' } / { type: 'reconnecting', attempt: number }). Add explicit event types for these and include them in a dedicated WsClientEvent union (or extend ServerEvent if appropriate) so consumers can type-check these events without as unknown as casts.
|
@copilot open a new pull request to apply changes based on the comments in this thread |
|
@willwashburn I've opened a new pull request, #21, to work on those changes. Once the pull request is ready, I'll request review from you. |
- Move SDK_VERSION to separate version.ts file to break circular dependency - Add WsClientEvent union type with proper event types (open, close, error, reconnecting) - Remove type casts in WsClient, use proper typed events instead - Add response validation to Relay.createWorkspace for non-JSON responses - All tests passing (151 total) Co-authored-by: willwashburn <957608+willwashburn@users.noreply.github.com>
Update client.ts to import SDK_VERSION from version.ts instead of index.ts Co-authored-by: willwashburn <957608+willwashburn@users.noreply.github.com>
Filter out client-only events (open, close, error, reconnecting) in ws-bridge since they don't affect MCP resources. Only process server events. Co-authored-by: willwashburn <957608+willwashburn@users.noreply.github.com>
Update useEvent hook and provider to use WsClientEvent. Filter out client-only events (open, close, error, reconnecting) before passing to handleServerEvent. Co-authored-by: willwashburn <957608+willwashburn@users.noreply.github.com>
[WIP] Add workspace and agent management APIs and WS events
Introduce zod-based runtime schemas across the codebase and add validation for API/WS payloads. Many @relaycast/types interfaces were converted to zod schemas and types; zod was added as a dependency in types and the SDK. HttpClient now parses an API envelope, uses ApiErrorSchema for errors and supports an optional schema option to validate response data. WsClient validates incoming server events with ServerEventSchema before emitting. Relay parsing/error handling was hardened to use schemas, and AgentClient.leave was made async (awaits post). Tests updated to include message attachments.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 30 out of 31 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (event.type === 'open' || event.type === 'close' || event.type === 'error' || event.type === 'reconnecting') { | ||
| return; | ||
| } | ||
| const uris = eventToResourceUris(event); |
There was a problem hiding this comment.
The event parameter should be cast to ServerEvent when calling eventToResourceUris. After filtering out client-only events (open, close, error, reconnecting), the remaining events are guaranteed to be ServerEvent types, but TypeScript still sees the type as WsClientEvent. Consider adding a type assertion: const uris = eventToResourceUris(event as ServerEvent);
| const uris = eventToResourceUris(event); | |
| const uris = eventToResourceUris(event as ServerEvent); |
Update the GitHub Actions publish workflow to stage packages/sdk/src/version.ts when committing release changes so SDK version updates are included in the release commit. Also apply a minor formatting tweak in the release script.
Replace the Relay class/type with RelayCast across the repo: update imports, types, and constructors in CLI commands, MCP server/tools, OpenClaw bridge, React provider/hooks/context, and SDK tests. Expand README example to the new RelayCast API and add details about event handlers and workspace/admin flows. Add comprehensive agent WebSocket unit tests (packages/sdk/src/__tests__/agent-ws.test.ts) and adjust numerous tests to use RelayCast.
Add a debug flag to WsClient and improve message handling: forward objects with an unrecognized but string "type" to emit(parsed.type, parsed) so wildcard and typed listeners receive them; log dropped messages (missing/invalid "type") and malformed JSON via console.warn when debug is enabled. Update constructor to accept debug option and add tests covering forwarding, ignored messages without type, and debug logging behavior.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 61 out of 62 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| on = { | ||
| messageCreated: (handler: (e: MessageCreatedEvent) => void): (() => void) => this.onEvent('message.created', handler), | ||
| messageUpdated: (handler: (e: MessageUpdatedEvent) => void): (() => void) => this.onEvent('message.updated', handler), | ||
| threadReply: (handler: (e: ThreadReplyEvent) => void): (() => void) => this.onEvent('thread.reply', handler), | ||
| messageRead: (handler: (e: MessageReadEvent) => void): (() => void) => this.onEvent('message.read', handler), | ||
| reactionAdded: (handler: (e: ReactionAddedEvent) => void): (() => void) => this.onEvent('reaction.added', handler), | ||
| reactionRemoved: (handler: (e: ReactionRemovedEvent) => void): (() => void) => this.onEvent('reaction.removed', handler), | ||
| dmReceived: (handler: (e: DmReceivedEvent) => void): (() => void) => this.onEvent('dm.received', handler), | ||
| groupDmReceived: (handler: (e: GroupDmReceivedEvent) => void): (() => void) => this.onEvent('group_dm.received', handler), | ||
| agentOnline: (handler: (e: AgentOnlineEvent) => void): (() => void) => this.onEvent('agent.online', handler), | ||
| agentOffline: (handler: (e: AgentOfflineEvent) => void): (() => void) => this.onEvent('agent.offline', handler), | ||
| channelCreated: (handler: (e: ChannelCreatedEvent) => void): (() => void) => this.onEvent('channel.created', handler), | ||
| channelUpdated: (handler: (e: ChannelUpdatedEvent) => void): (() => void) => this.onEvent('channel.updated', handler), | ||
| channelArchived: (handler: (e: ChannelArchivedEvent) => void): (() => void) => this.onEvent('channel.archived', handler), | ||
| memberJoined: (handler: (e: MemberJoinedEvent) => void): (() => void) => this.onEvent('member.joined', handler), | ||
| memberLeft: (handler: (e: MemberLeftEvent) => void): (() => void) => this.onEvent('member.left', handler), | ||
| fileUploaded: (handler: (e: FileUploadedEvent) => void): (() => void) => this.onEvent('file.uploaded', handler), | ||
| webhookReceived: (handler: (e: WebhookReceivedEvent) => void): (() => void) => this.onEvent('webhook.received', handler), | ||
| commandInvoked: (handler: (e: CommandInvokedEvent) => void): (() => void) => this.onEvent('command.invoked', handler), | ||
| // Lifecycle | ||
| connected: (handler: () => void): (() => void) => this.onEvent('open', handler as (e: never) => void), | ||
| disconnected: (handler: () => void): (() => void) => this.onEvent('close', handler as (e: never) => void), | ||
| reconnecting: (handler: (attempt: number) => void): (() => void) => { | ||
| if (!this.ws) { | ||
| throw new Error('WebSocket not connected. Call connect() first.'); | ||
| } | ||
| return this.ws.on('reconnecting', (e: WsClientEvent) => handler((e as WsReconnectingEvent).attempt)); | ||
| }, | ||
| // Wildcard | ||
| any: (handler: (e: WsClientEvent) => void): (() => void) => { | ||
| if (!this.ws) { | ||
| throw new Error('WebSocket not connected. Call connect() first.'); | ||
| } | ||
| return this.ws.on('*', handler); | ||
| }, | ||
| }; |
There was a problem hiding this comment.
The WsClient now emits 'error' events (line 65-66 in ws.ts), but the AgentClient's on object doesn't provide a typed handler for it (e.g., on.error). While users can access error events via on.any, providing a dedicated on.error handler would improve developer experience and consistency with other lifecycle events like on.connected and on.disconnected.
|
@copilot open a new pull request to apply changes based on the comments in this thread |
|
@willwashburn I've opened a new pull request, #22, to work on those changes. Once the pull request is ready, I'll request review from you. |
Co-authored-by: willwashburn <957608+willwashburn@users.noreply.github.com>
Add on.error handler to AgentClient lifecycle events
| @@ -0,0 +1 @@ | |||
| export const SDK_VERSION = '0.2.3' as const; | |||
There was a problem hiding this comment.
🟡 SDK_VERSION constant (0.2.3) does not match package.json version (0.2.4)
The SDK_VERSION exported from packages/sdk/src/version.ts is '0.2.3', but the canonical version in packages/sdk/package.json is "0.2.4". Every HTTP request made through HttpClient.request sends 'X-SDK-Version': SDK_VERSION (see packages/sdk/src/client.ts:77), meaning the server sees version 0.2.3 even though the actual published package is 0.2.4.
Root Cause and Impact
The PR description says "bump SDK_VERSION to 0.2.3", but the package.json was already at 0.2.4. The publish workflow (packages/sdk/src/version.ts is synced at publish time via .github/workflows/publish.yml:142-144), so a CI-driven publish would overwrite this. However, any local builds, tests, or manual publishes will ship with the wrong version constant.
Impact: The X-SDK-Version header will report 0.2.3 instead of 0.2.4, making server-side version tracking, debugging, and compatibility checks unreliable.
| export const SDK_VERSION = '0.2.3' as const; | |
| export const SDK_VERSION = '0.2.4' as const; | |
Was this helpful? React with 👍 or 👎 to provide feedback.
Introduce new Relay APIs and related client/type updates: add Relay.createWorkspace, workspace.delete, systemPrompt.get/set, agents.update/delete/presence, and agents.registerOrGet (with conflict handling). Add AgentClient.channels.update and HttpClient.put. Improve WsClient to emit 'error' and 'reconnecting' events. Add new types (AgentPresenceInfo, SystemPrompt, SetSystemPromptRequest) and bump SDK_VERSION to 0.2.3. Tests updated/added to cover the new behaviors.