Skip to content

feat(sdk): add setup client#125

Merged
khaliqgant merged 7 commits intomainfrom
docs/sdk-setup-client-spec
May 1, 2026
Merged

feat(sdk): add setup client#125
khaliqgant merged 7 commits intomainfrom
docs/sdk-setup-client-spec

Conversation

@khaliqgant
Copy link
Copy Markdown
Member

@khaliqgant khaliqgant commented Apr 30, 2026

Summary

  • Add the SDK setup client implementation with RelaycastSetup, WorkspaceHandle, setup errors, setup types, communicate Relay wrapper, and SDK exports.
  • Add setup and communicate unit coverage plus a local server-backed E2E script for the setup flow.
  • Add and harden the 80-to-100 agent-relay workflow so final spec coverage is deterministic and emits exact CHANGES_REQUIRED or APPROVED output.

Verification

  • Workflow run reached the final spec coverage review after setup tests, SDK build/regression, local E2E, focused server regressions, and monorepo regression gates.
  • Locally verified the workflow patch with: npx tsc --noEmit --target ES2022 --module NodeNext --moduleResolution NodeNext --skipLibCheck workflows/sdk-setup-client-80-100.ts
  • Locally verified: git diff --check -- workflows/sdk-setup-client-80-100.ts
  • Locally verified workflow dry run: node --experimental-strip-types workflows/sdk-setup-client-80-100.ts --dry-run

Follow-up

  • Rerun from audit-spec-coverage-initial to let the new deterministic audit/fix/review tail close any remaining SDK coverage gaps before merge.

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
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 4 potential issues.

View 5 additional findings in Devin Review.

Open in Devin Review

Comment thread docs/sdk-setup-client.md Outdated

1. `POST {baseUrl}/v1/workspaces` with body `{ name? }`
— Auth header: `Authorization: Bearer {apiKey}` if provided, else anonymous
— Returns: `{ workspaceId, apiKey, createdAt, name? }`
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Suggested change
— Returns: `{ workspaceId, apiKey, createdAt, name? }`
— Returns: `{ ok: true, data: { workspace_id, api_key, created_at, name? } }`
Open in Devin Review

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

Comment thread docs/sdk-setup-client.md Outdated

1. `POST {baseUrl}/v1/workspaces` with body `{ name? }`
— Auth header: `Authorization: Bearer {apiKey}` if provided, else anonymous
— Returns: `{ workspaceId, apiKey, createdAt, name? }`
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Open in Devin Review

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

Comment thread docs/sdk-setup-client.md Outdated
- `createWorkspace()` — malformed response (missing workspaceId): throws `MalformedApiResponseError`
- `joinWorkspace()` — happy path: uses provided workspaceId and apiKey
- `lookupWorkspace()` — found: returns WorkspaceHandle
- `lookupWorkspace()` — not found: throws `WorkspaceNotFoundError`
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Suggested change
- `lookupWorkspace()` — not found: throws `WorkspaceNotFoundError`
- `lookupWorkspace()` — not found: returns `null`
Open in Devin Review

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

Comment thread docs/sdk-setup-client.md Outdated
name: string
type: 'agent' | 'human' | 'system'
token: string
status: 'online' | 'offline'
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Suggested change
status: 'online' | 'offline'
status: 'online' | 'offline' | 'away'
Open in Devin Review

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

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 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".

Comment thread docs/sdk-setup-client.md Outdated
Comment on lines +183 to +185
* If omitted, the server assigns a generated name.
*/
name?: string
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Comment thread docs/sdk-setup-client.md Outdated
Comment on lines +43 to +44
await aliceClient.post('#general', 'Hey team, standup in 5 minutes')
await bobClient.post('#general', 'On my way!')
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment thread docs/sdk-setup-client.md Outdated
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)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@khaliqgant khaliqgant changed the title docs: add SDK setup client spec feat(sdk): add setup client May 1, 2026
@khaliqgant
Copy link
Copy Markdown
Member Author

SDK setup client usage guide

The review feedback has been addressed in fac5a0e:

  • AgentClient examples now use send(channel, text) instead of post(...).
  • The basic usage snippet now declares const bobClient = ... instead of using invalid assignment syntax.
  • POST /v1/workspaces is documented as an enveloped snake_case wire response: { ok: true, data: { workspace_id, api_key, created_at } }.
  • CreateWorkspaceOptions.name is required because the current server route requires a non-empty workspace name.
  • lookupWorkspace() is documented as returning WorkspaceLookup | null, with null on 404.
  • AgentRecord.status now preserves the existing SDK status union: 'online' | 'offline' | 'away'.

Basic full-SDK flow

import { 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 flow

const 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 flow

const 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 development

const 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:8799

Focused 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

@khaliqgant
Copy link
Copy Markdown
Member Author

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:

  • npm run test --workspace=packages/sdk-typescript -- src/tests/invite-consumption.test.ts

Related PRs:

@khaliqgant khaliqgant merged commit c040c1a into main May 1, 2026
3 of 4 checks passed
@khaliqgant khaliqgant deleted the docs/sdk-setup-client-spec branch May 1, 2026 17:54
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.

1 participant