feat(sdk): add setup client#125
Conversation
Adds docs/sdk-setup-client.md — a detailed spec for a new RelaycastSetup class that enables standalone usage of Relaycast in cloud and local environments. Covers: - RelaycastSetup entry point for workspace creation/joining - WorkspaceHandle with relayCast(), as(), relay(), registerAgent() - Full error type hierarchy - Usage examples for common patterns - Local dev mode and staging support - Future work items
|
|
||
| 1. `POST {baseUrl}/v1/workspaces` with body `{ name? }` | ||
| — Auth header: `Authorization: Bearer {apiKey}` if provided, else anonymous | ||
| — Returns: `{ workspaceId, apiKey, createdAt, name? }` |
There was a problem hiding this comment.
🔴 API wire format documented with camelCase instead of snake_case
The spec describes the POST /v1/workspaces API response as { workspaceId, apiKey, createdAt, name? } using camelCase field names. This violates the AGENTS.md rule: "HTTP JSON wire fields are snake_case (for example agent_name, created_at)." The actual wire format confirmed by both openapi.yaml:56-67 and packages/types/src/workspace.ts:18-22 (CreateWorkspaceResponseSchema) uses workspace_id, api_key, created_at. The SDK then camelizes keys internally (packages/sdk-typescript/src/casing.ts:29). If this spec is implemented as written, the new setup client would expect camelCase from the API and break on the actual snake_case response.
| — Returns: `{ workspaceId, apiKey, createdAt, name? }` | |
| — Returns: `{ ok: true, data: { workspace_id, api_key, created_at, name? } }` |
Was this helpful? React with 👍 or 👎 to provide feedback.
|
|
||
| 1. `POST {baseUrl}/v1/workspaces` with body `{ name? }` | ||
| — Auth header: `Authorization: Bearer {apiKey}` if provided, else anonymous | ||
| — Returns: `{ workspaceId, apiKey, createdAt, name? }` |
There was a problem hiding this comment.
🔴 API response missing required { ok: true, data: ... } envelope
The spec documents the POST /v1/workspaces response as a flat object { workspaceId, apiKey, createdAt, name? }, omitting the mandatory response envelope. AGENTS.md specifies: "Success response envelope: { ok: true, data: ... }." This is confirmed by openapi.yaml:11 and the existing ApiSuccessSchema at packages/types/src/api.ts:3-11. The existing RelayCast.createWorkspaceWithStatus() at packages/sdk-typescript/src/relay.ts:165 parses the response through ApiResponseSchema(CreateWorkspaceResponseSchema) which expects the { ok: true, data: ... } wrapper. If implemented as specced, the new setup client would fail to interop with the actual API.
Was this helpful? React with 👍 or 👎 to provide feedback.
| - `createWorkspace()` — malformed response (missing workspaceId): throws `MalformedApiResponseError` | ||
| - `joinWorkspace()` — happy path: uses provided workspaceId and apiKey | ||
| - `lookupWorkspace()` — found: returns WorkspaceHandle | ||
| - `lookupWorkspace()` — not found: throws `WorkspaceNotFoundError` |
There was a problem hiding this comment.
🟡 lookupWorkspace spec contradicts itself: return type says null but test says throws
The method signature at line 171 declares lookupWorkspace(name: string): Promise<WorkspaceHandle | null> with docstring "Returns null if the workspace does not exist." However, the testing section at line 608 specifies the test case as "lookupWorkspace() — not found: throws WorkspaceNotFoundError". These are mutually exclusive behaviors. The existing RelayCast.lookupWorkspace at packages/sdk-typescript/src/relay.ts:220-222 returns null on 404, so the method signature is consistent with existing behavior, but the test spec would fail.
| - `lookupWorkspace()` — not found: throws `WorkspaceNotFoundError` | |
| - `lookupWorkspace()` — not found: returns `null` |
Was this helpful? React with 👍 or 👎 to provide feedback.
| name: string | ||
| type: 'agent' | 'human' | 'system' | ||
| token: string | ||
| status: 'online' | 'offline' |
There was a problem hiding this comment.
🟡 AgentRecord.status is missing the 'away' variant from the existing schema
The proposed AgentRecord interface at line 304 defines status: 'online' | 'offline', but the existing AgentStatusSchema at packages/types/src/agent.ts:6 is z.enum(['online', 'offline', 'away']), and CreateAgentResponseSchema at packages/types/src/agent.ts:39 uses this full schema. If an agent has 'away' status, the new AgentRecord type would not accept it, causing a type mismatch at runtime.
| status: 'online' | 'offline' | |
| status: 'online' | 'offline' | 'away' |
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2090ebacda
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| * If omitted, the server assigns a generated name. | ||
| */ | ||
| name?: string |
There was a problem hiding this comment.
Require workspace name in createWorkspace contract
The spec marks CreateWorkspaceOptions.name as optional, but the current API route still validates name as required (packages/server/src/routes/workspace.ts uses z.object({ name: z.string().min(1) })) and this PR explicitly says no cloud API changes are needed. If implementers follow this contract and allow createWorkspace() without a name, those calls will consistently fail with 400 invalid_request, so the primary setup flow would be broken for the documented input shape.
Useful? React with 👍 / 👎.
| await aliceClient.post('#general', 'Hey team, standup in 5 minutes') | ||
| await bobClient.post('#general', 'On my way!') |
There was a problem hiding this comment.
Use AgentClient.send in setup example
workspace.as(...) is documented to return an AgentClient, but this example calls .post(...) on that client. In the SDK, AgentClient exposes send(channel, text, ...) and does not provide a post method (packages/sdk-typescript/src/agent.ts), so this copy-paste path fails immediately for users and gives the wrong API contract for the proposed setup handle.
Useful? React with 👍 / 👎.
| const aliceClient = workspace.as(alice.token) | ||
| await aliceClient.channels.create({ name: 'general', topic: 'Team chat' }) | ||
| await aliceClient.channels.join('general') | ||
| await bobClient = workspace.as(bob.token) |
There was a problem hiding this comment.
Fix invalid TypeScript in basic usage snippet
The line await bobClient = workspace.as(bob.token) is invalid TypeScript because it assigns to an undeclared identifier and incorrectly prefixes the assignment with await. Anyone following this example will hit a syntax/runtime failure before reaching messaging behavior, so the onboarding sample is not executable as written.
Useful? React with 👍 / 👎.
SDK setup client usage guideThe review feedback has been addressed in
Basic full-SDK flowimport { RelaycastSetup } from '@relaycast/sdk'
const setup = new RelaycastSetup()
const workspace = await setup.createWorkspace({ name: 'my-agent-workspace' })
const alice = await workspace.registerAgent({ name: 'Alice', type: 'agent' })
const bob = await workspace.registerAgent({ name: 'Bob', type: 'agent' })
const aliceClient = workspace.as(alice.token)
const bobClient = workspace.as(bob.token)
await aliceClient.channels.create({ name: 'general' })
await aliceClient.channels.join('general')
await bobClient.channels.join('general')
await aliceClient.send('#general', 'Hey team, standup in 5 minutes')
const visibleToBob = await bobClient.messages('#general')Communicate-style flowconst aliceRelay = workspace.relay('Alice')
const bobRelay = workspace.relay('Bob')
await aliceRelay.post('#general', 'Standup in 5 minutes')
await bobRelay.send('Alice', 'On my way')
const stop = bobRelay.onMessage((message) => {
console.log(`${message.sender}: ${message.text}`)
})
// Later, when the listener should stop receiving events:
stop()Rejoin and lookup flowconst lookup = await setup.lookupWorkspace('my-agent-workspace')
if (lookup) {
const workspace = await setup.joinWorkspace(lookup.id, process.env.RELAYCAST_API_KEY!)
console.log(workspace.workspaceId)
}Local developmentconst setup = new RelaycastSetup({ local: true })
const workspace = await setup.createWorkspace({ name: `local-${Date.now()}` })To validate the setup client against a real local server from this PR: npm --workspace @relaycast/server run dev -- --port 8799
npx tsx scripts/e2e-sdk-setup-client.ts http://127.0.0.1:8799Focused verification run for the review fixes: npm --workspace @relaycast/sdk run test -- src/__tests__/setup.test.ts src/__tests__/communicate.test.ts
npm --workspace @relaycast/sdk run build |
|
Updated this PR with the relayfile agent-invite consumption coverage for the agent workspace golden path. Added commit: ee2e1fd feat: support relayfile agent invites Validation run locally:
Related PRs: |
Summary
Verification
Follow-up