Skip to content

Add workspace/agent management APIs and WS events#20

Merged
willwashburn merged 16 commits into
mainfrom
ts-sdk-improvments
Feb 16, 2026
Merged

Add workspace/agent management APIs and WS events#20
willwashburn merged 16 commits into
mainfrom
ts-sdk-improvments

Conversation

@willwashburn
Copy link
Copy Markdown
Member

@willwashburn willwashburn commented Feb 16, 2026

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.


Open with Devin

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.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 + registerOrGet conflict handling.
  • Extended SDK internals with HttpClient.put() and new WsClient events (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.

Comment thread packages/sdk/src/ws.ts
Comment on lines 127 to 129
this.reconnectAttempt++;
this.emit('reconnecting', { type: 'reconnecting', attempt: this.reconnectAttempt } as unknown as ServerEvent);
this.reconnectTimer = setTimeout(() => {
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment thread packages/sdk/src/relay.ts Outdated
Comment on lines 32 to 33
import { SDK_VERSION } from './index.js';

Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
import { SDK_VERSION } from './index.js';
const SDK_VERSION = 'unknown';

Copilot uses AI. Check for mistakes.
Comment thread packages/sdk/src/relay.ts
Comment on lines +53 to +66
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,
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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', ...).

Copilot uses AI. Check for mistakes.
Comment thread packages/sdk/src/ws.ts
Comment on lines 59 to 61
this.ws.onerror = () => {
// onclose will fire after this
this.emit('error', { type: 'error' } as unknown as ServerEvent);
};
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 uses AI. Check for mistakes.
@willwashburn
Copy link
Copy Markdown
Member Author

@copilot open a new pull request to apply changes based on the comments in this thread

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 16, 2026

@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.

Copilot AI and others added 6 commits February 16, 2026 17:57
- 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.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Suggested change
const uris = eventToResourceUris(event);
const uris = eventToResourceUris(event as ServerEvent);

Copilot uses AI. Check for mistakes.
devin-ai-integration[bot]

This comment was marked as resolved.

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.
@willwashburn willwashburn requested a review from Copilot February 16, 2026 19:18
devin-ai-integration[bot]

This comment was marked as resolved.

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.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread packages/sdk/src/agent.ts
Comment on lines +101 to +136
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);
},
};
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 uses AI. Check for mistakes.
@willwashburn
Copy link
Copy Markdown
Member Author

@copilot open a new pull request to apply changes based on the comments in this thread

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 16, 2026

@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.

Copilot AI and others added 4 commits February 16, 2026 19:40
Co-authored-by: willwashburn <957608+willwashburn@users.noreply.github.com>
Add on.error handler to AgentClient lifecycle events
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 17 additional findings in Devin Review.

Open in Devin Review

@@ -0,0 +1 @@
export const SDK_VERSION = '0.2.3' as const;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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.

Suggested change
export const SDK_VERSION = '0.2.3' as const;
export const SDK_VERSION = '0.2.4' as const;
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@willwashburn willwashburn merged commit e2cb8c2 into main Feb 16, 2026
1 check passed
@willwashburn willwashburn deleted the ts-sdk-improvments branch February 16, 2026 20:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants