@craftedxp/sdk-node 0.9.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -23,10 +23,11 @@ interface HttpRequest {
23
23
  }
24
24
  declare const createHttpClient: (opts: HttpClientOptions) => {
25
25
  request: <T>(req: HttpRequest) => Promise<T>;
26
+ stream: <T = unknown>(req: HttpRequest) => AsyncIterable<T>;
26
27
  };
27
28
  type HttpClient = ReturnType<typeof createHttpClient>;
28
29
 
29
- type TtsProvider = 'pockettts' | 'cartesia' | 'fish-audio' | 'gemini';
30
+ type TtsProvider = 'pockettts' | 'cartesia' | 'fish-audio' | 'gemini' | 'kokoro' | 'hume' | 'omnivoice' | 'chatterbox' | 'tada';
30
31
  type SttProvider = 'deepgram';
31
32
  type LlmProvider = 'gemini' | 'openai' | 'anthropic';
32
33
  type EndpointingMode = 'smart' | 'simple';
@@ -34,6 +35,20 @@ interface AgentVoice {
34
35
  provider: TtsProvider;
35
36
  voiceId?: string;
36
37
  language?: string;
38
+ /**
39
+ * Secondary language for bilingual agents. When set, the agent supports
40
+ * mid-utterance code-switching via the `[lang=xx]...[/lang]` tag contract.
41
+ * Requires a multilingual-capable TTS provider (cartesia, kokoro,
42
+ * fish-audio, hume, omnivoice, gemini). See docs/sdks.md "Bilingual
43
+ * agents" section.
44
+ */
45
+ secondaryLanguage?: string;
46
+ /**
47
+ * Second-language voice ID for voice-pinned providers (kokoro). Stays
48
+ * unset for providers whose voices handle multiple languages natively
49
+ * (cartesia, hume, omnivoice).
50
+ */
51
+ secondaryVoiceId?: string;
37
52
  /**
38
53
  * Per-utterance variation, 0.0–1.0. Honoured by Fish Audio and
39
54
  * Chatterbox (passed through to each provider's synthesis API).
@@ -54,6 +69,13 @@ interface AgentTranscriber {
54
69
  provider: SttProvider;
55
70
  model: string;
56
71
  language?: string;
72
+ /**
73
+ * Secondary language for bilingual agents. When set, the STT provider
74
+ * auto-flips to multi-language mode (Deepgram `multi`, Whisper
75
+ * auto-detect). Other STT providers reject bilingual config at agent
76
+ * create time.
77
+ */
78
+ secondaryLanguage?: string;
57
79
  }
58
80
  interface AgentModel {
59
81
  provider: LlmProvider;
@@ -122,6 +144,19 @@ interface Agent {
122
144
  * cache-buster so browser/CDN caches refresh on replace.
123
145
  */
124
146
  avatarUrl?: string;
147
+ /**
148
+ * Transport the agent's live calls use. `'webrtc'` (the platform default
149
+ * for new agents) or `'ws'` (legacy). Forwarded on the mint result and
150
+ * needed by the agent-initiated push payload so the app dispatches to the
151
+ * right gateway.
152
+ */
153
+ transport?: 'ws' | 'webrtc';
154
+ /** Whether this agent may host multi-party video rooms. */
155
+ canHostRooms?: boolean;
156
+ /** Room operating mode (`'notes-only'` today). */
157
+ roomMode?: 'notes-only';
158
+ /** Transcribe-only mode — the agent listens but never speaks. */
159
+ transcribeOnly?: boolean;
125
160
  createdAt: number;
126
161
  updatedAt: number;
127
162
  }
@@ -140,6 +175,8 @@ interface AgentCreateInput {
140
175
  recording?: AgentRecording;
141
176
  structuredDataSchema?: Record<string, unknown>;
142
177
  allowedUserTags?: string[];
178
+ /** See Agent.transport. Defaults to the platform default when omitted. */
179
+ transport?: 'ws' | 'webrtc';
143
180
  /**
144
181
  * Server-managed. Don't set this directly via create/update — use
145
182
  * `agents.uploadAvatar(agentId, file)` instead. Sending it raw is
@@ -147,6 +184,25 @@ interface AgentCreateInput {
147
184
  * actually changes.
148
185
  */
149
186
  avatarUrl?: string;
187
+ /**
188
+ * Let this agent host multi-party video rooms. Off by default. Required
189
+ * (`true`) before `client.rooms.create({ agentId })` will accept the agent.
190
+ */
191
+ canHostRooms?: boolean;
192
+ /**
193
+ * Room operating mode. Today only `'notes-only'` exists — the agent listens
194
+ * and takes structured notes without speaking. A literal union so future
195
+ * modes can be added without breaking the wire.
196
+ */
197
+ roomMode?: 'notes-only';
198
+ /**
199
+ * Transcribe-only mode. When `true`, the agent listens but never speaks: no
200
+ * TTS/LLM, no greeting, idle nudges suppressed, user turns are not routed
201
+ * through the LLM — an STT-only loop that streams `transcript` data-messages
202
+ * back to the caller. `voice` stays required by the schema (defaults apply)
203
+ * but is never read at runtime.
204
+ */
205
+ transcribeOnly?: boolean;
150
206
  }
151
207
  type AgentUpdateInput = Partial<AgentCreateInput>;
152
208
  /**
@@ -228,6 +284,8 @@ interface CallRecord {
228
284
  costBreakdown?: CostBreakdown;
229
285
  errorMessage?: string;
230
286
  metadata?: Record<string, string>;
287
+ /** Transport channel the call was carried over (`'voice'` = WebRTC/WS audio, `'text'` = text-only). */
288
+ channel?: 'voice' | 'text';
231
289
  }
232
290
  interface CallListFilters {
233
291
  agentId?: string;
@@ -323,6 +381,25 @@ interface CallTokenMintInput {
323
381
  * if you blindly accept client-asserted tier the gate is theatre.
324
382
  */
325
383
  userTags?: string[];
384
+ /**
385
+ * Mark this token as agent-initiated. When 'agent', the tenant's server
386
+ * is ringing the end-user (push → app connects) and the resulting
387
+ * CallRecord is tagged `direction: 'outbound'`. Omit / 'user' for the
388
+ * default user-initiated connect. See docs/sdks.md "Agent-initiated calls".
389
+ */
390
+ initiatedBy?: 'user' | 'agent';
391
+ /**
392
+ * Session channel selector. `voice` (default) routes to WS/WebRTC audio;
393
+ * `text` routes to HTTP+SSE chat via the `chats` resource. Rejected at
394
+ * mint when the agent has `transcribeOnly: true` or when `mode: 'learning'`
395
+ * is supplied.
396
+ */
397
+ channel?: 'voice' | 'text';
398
+ /**
399
+ * Session purpose. `standard` (default) is a regular call. `learning`
400
+ * (admin-bypass only) flags a self-learning session — see Phase 19.
401
+ */
402
+ mode?: 'standard' | 'learning';
326
403
  }
327
404
  interface CallTokenMintResult {
328
405
  tokenId: string;
@@ -344,6 +421,12 @@ interface CallTokenMintResult {
344
421
  * of falling back to the Phase-1 routes on the API base (local dev).
345
422
  */
346
423
  webrtcGatewayBase?: string;
424
+ /**
425
+ * Session channel echoed back from the mint. Useful for the client SDK
426
+ * to dispatch correctly — text-mode tokens go to `chatsFor(token)`,
427
+ * voice-mode tokens go to `voice-js`/`voice-rn`.
428
+ */
429
+ channel?: 'voice' | 'text';
347
430
  }
348
431
  interface CallTokenSummary {
349
432
  tokenId: string;
@@ -400,6 +483,112 @@ interface MeResponse {
400
483
  plan: 'free' | 'paid';
401
484
  creditBalance: number;
402
485
  }
486
+ type RoomStatus = 'provisioning' | 'active' | 'ended';
487
+ type RoomEndReason = 'duration_reached' | 'empty' | 'manual' | 'expired';
488
+ type GuestRole = 'host' | 'participant';
489
+ type WorkerStatus = 'pending' | 'up' | 'down' | 'exited';
490
+ interface CreateRoomInput {
491
+ agentId: string;
492
+ /** 1..240 — enforced by the server. */
493
+ durationMin: number;
494
+ }
495
+ interface CreateRoomResponse {
496
+ roomId: string;
497
+ status: 'provisioning';
498
+ expiresAt: string;
499
+ /** The shareable room-level secret — returned once, never persisted raw.
500
+ * Anyone with this token + a display name joins as a fresh participant. */
501
+ joinToken: string;
502
+ /** `${APP_ORIGIN}/rooms/${roomId}/join?t=${joinToken}` — the single link to
503
+ * share with everyone you want in the room. */
504
+ joinUrl: string;
505
+ }
506
+ interface RoomMetricsWire {
507
+ droppedUtterances: number;
508
+ deepgramReconnects: number;
509
+ }
510
+ interface RoomDoc {
511
+ roomId: string;
512
+ orgId: string;
513
+ agentId: string;
514
+ status: RoomStatus;
515
+ livekitSid: string | null;
516
+ durationMin: number;
517
+ /** sha256 of the shared room join token. The raw token is never persisted. */
518
+ joinTokenJti?: string;
519
+ createdByApiKeyId?: string;
520
+ createdAt?: string;
521
+ startedAt: string | null;
522
+ endedAt: string | null;
523
+ expiresAt: string;
524
+ endReason: RoomEndReason | null;
525
+ workerStatus?: WorkerStatus;
526
+ metrics?: RoomMetricsWire | null;
527
+ }
528
+ interface RoomListFilters {
529
+ status?: RoomStatus;
530
+ /** Server clamps to 100 max; default 20. */
531
+ limit?: number;
532
+ cursor?: string;
533
+ }
534
+ interface ListRoomsResponse {
535
+ rooms: RoomDoc[];
536
+ nextCursor: string | null;
537
+ }
538
+ interface RoomTranscriptOptions {
539
+ cursor?: string;
540
+ /** Server clamps to 1000 max; default 200. */
541
+ limit?: number;
542
+ }
543
+ interface UtteranceWire {
544
+ utteranceId: string;
545
+ participantId: string;
546
+ speakerName: string;
547
+ text: string;
548
+ startedAt: string;
549
+ endedAt: string;
550
+ sttConfidence: number;
551
+ }
552
+ interface ListUtterancesResponse {
553
+ utterances: UtteranceWire[];
554
+ nextCursor: string | null;
555
+ }
556
+ /** Returned by host-action endpoints (`/end` and friends) — 202 + eventId so
557
+ * callers can correlate the async system-message broadcast that follows. */
558
+ interface RoomEventAck {
559
+ eventId: string;
560
+ }
561
+ /**
562
+ * SSE events emitted by the text-channel chat endpoints. Concatenate
563
+ * `token.text` chunks to build up the agent's reply; render `tool.call`
564
+ * and `tool.result` as "doing-something" UI hints; treat `turn.end` as
565
+ * the signal that the current POST has finished streaming.
566
+ */
567
+ type ChatEvent = {
568
+ type: 'chat.started';
569
+ chatId: string;
570
+ callId: string;
571
+ } | {
572
+ type: 'token';
573
+ text: string;
574
+ } | {
575
+ type: 'tool.call';
576
+ name: string;
577
+ args: unknown;
578
+ } | {
579
+ type: 'tool.result';
580
+ name: string;
581
+ ok?: boolean;
582
+ [key: string]: unknown;
583
+ } | {
584
+ type: 'turn.end';
585
+ finishReason: 'stop' | 'aborted' | 'length' | 'tool_error';
586
+ committedText?: string;
587
+ } | {
588
+ type: 'error';
589
+ code: string;
590
+ message: string;
591
+ };
403
592
 
404
593
  declare const createMeResource: (http: HttpClient) => {
405
594
  get: () => Promise<MeResponse>;
@@ -536,6 +725,40 @@ declare const createOrgsResource: (http: HttpClient) => {
536
725
  };
537
726
  type OrgsResource = ReturnType<typeof createOrgsResource>;
538
727
 
728
+ declare const createRoomsResource: (http: HttpClient) => {
729
+ create: (input: CreateRoomInput) => Promise<CreateRoomResponse>;
730
+ list: (filters?: RoomListFilters) => Promise<ListRoomsResponse>;
731
+ get: (roomId: string) => Promise<RoomDoc>;
732
+ transcript: (roomId: string, opts?: RoomTranscriptOptions) => Promise<ListUtterancesResponse>;
733
+ end: (roomId: string) => Promise<RoomEventAck>;
734
+ };
735
+ type RoomsResource = ReturnType<typeof createRoomsResource>;
736
+
737
+ interface StartChatInput {
738
+ agentId: string;
739
+ text?: string;
740
+ }
741
+ interface Chat {
742
+ id: string;
743
+ callId: string;
744
+ greeting: AsyncIterable<ChatEvent>;
745
+ send(text: string): AsyncIterable<ChatEvent>;
746
+ end(): Promise<void>;
747
+ }
748
+ /**
749
+ * Per-token chat resource. Construct via `client.chatsFor(callToken)` —
750
+ * `callToken` is a raw `ct_…` minted with `channel: 'text'`.
751
+ *
752
+ * The async iterables stream SSE events: `chat.started` (start only),
753
+ * then a sequence of `token` / `tool.call` / `tool.result` / `error`,
754
+ * then `turn.end` which closes the stream. The client must NOT keep
755
+ * a connection open between turns — each `send` is a fresh POST.
756
+ */
757
+ declare const createChatsResource: (http: HttpClient, callToken: string) => {
758
+ start(input: StartChatInput): Promise<Chat>;
759
+ };
760
+ type ChatsResource = ReturnType<typeof createChatsResource>;
761
+
539
762
  interface PlatformClientOptions {
540
763
  apiKey: string;
541
764
  baseUrl?: string;
@@ -553,7 +776,19 @@ declare class PlatformClient {
553
776
  readonly callTokens: CallTokensResource;
554
777
  readonly webhooks: WebhooksResource;
555
778
  readonly orgs: OrgsResource;
779
+ readonly rooms: RoomsResource;
780
+ private readonly _http;
556
781
  constructor(options: PlatformClientOptions);
782
+ /**
783
+ * Returns a per-token chat resource bound to the given `ct_…` call token.
784
+ * The token must have been minted with `channel: 'text'`.
785
+ *
786
+ * Note: `chatsFor` is a factory method (not a fixed property) because the
787
+ * underlying resource is scoped to a single call token, whereas this client
788
+ * was constructed with an `sk_` admin key. Each end-user session needs its
789
+ * own `chatsFor(token)` handle.
790
+ */
791
+ chatsFor(callToken: string): ChatsResource;
557
792
  }
558
793
 
559
794
  type ApiErrorCode = 'unauthorized' | 'forbidden' | 'not_found' | 'bad_request' | 'conflict' | 'rate_limited' | 'payment_required' | 'internal_error' | 'unknown';
@@ -576,4 +811,4 @@ declare class PlatformError extends Error {
576
811
 
577
812
  declare const verifyWebhookSignature: (rawBody: Buffer | string, signatureHeader: string, secret: string) => boolean;
578
813
 
579
- export { type Agent, type AgentCreateInput, type AgentEndpointing, type AgentHttpTool, type AgentModel, type AgentRecording, type AgentTool, type AgentTranscriber, type AgentUpdateInput, type AgentVoice, type AgentWebhooksResource, type AgentsResource, type ApiErrorCode, type CallListFilters, type CallRecord, type CallRecordingUrlResponse, type CallStatus, type CallSummary, type CallTokenMintInput, type CallTokenMintResult, type CallTokenSummary, type CallTokensResource, type CallsResource, type CatalogAgent, type CatalogListInput, type CostBreakdown, type CreditsResource, type EndReason, type EndpointingMode, type KnowledgeBase, type KnowledgeBaseFile, type KnowledgeBasesResource, type LedgerEntry, type LlmProvider, type MeResource, type MeResponse, type OrgsResource, PlatformClient, type PlatformClientOptions, PlatformError, type PlatformEventName, type RecordingMeta, type SttProvider, type TranscriptTurn, type TtsProvider, type WebhookConfig, type WebhookCreateInput, type WebhookDelivery, type WebhookUpdateInput, type WebhooksResource, verifyWebhookSignature };
814
+ export { type Agent, type AgentCreateInput, type AgentEndpointing, type AgentHttpTool, type AgentModel, type AgentRecording, type AgentTool, type AgentTranscriber, type AgentUpdateInput, type AgentVoice, type AgentWebhooksResource, type AgentsResource, type ApiErrorCode, type CallListFilters, type CallRecord, type CallRecordingUrlResponse, type CallStatus, type CallSummary, type CallTokenMintInput, type CallTokenMintResult, type CallTokenSummary, type CallTokensResource, type CallsResource, type CatalogAgent, type CatalogListInput, type Chat, type ChatEvent, type ChatsResource, type CostBreakdown, type CreateRoomInput, type CreateRoomResponse, type CreditsResource, type EndReason, type EndpointingMode, type GuestRole, type KnowledgeBase, type KnowledgeBaseFile, type KnowledgeBasesResource, type LedgerEntry, type ListRoomsResponse, type ListUtterancesResponse, type LlmProvider, type MeResource, type MeResponse, type OrgsResource, PlatformClient, type PlatformClientOptions, PlatformError, type PlatformEventName, type RecordingMeta, type RoomDoc, type RoomEndReason, type RoomEventAck, type RoomListFilters, type RoomMetricsWire, type RoomStatus, type RoomTranscriptOptions, type RoomsResource, type StartChatInput, type SttProvider, type TranscriptTurn, type TtsProvider, type UtteranceWire, type WebhookConfig, type WebhookCreateInput, type WebhookDelivery, type WebhookUpdateInput, type WebhooksResource, type WorkerStatus, verifyWebhookSignature };
package/dist/index.js CHANGED
@@ -179,7 +179,82 @@ var createHttpClient = (opts) => {
179
179
  }
180
180
  throw lastErr instanceof Error ? lastErr : new Error("request exhausted retries");
181
181
  };
182
- return { request };
182
+ async function* stream(req) {
183
+ const url = buildUrl(opts.baseUrl, req.path, req.query);
184
+ const headers = {
185
+ Authorization: `Bearer ${opts.apiKey}`,
186
+ "Content-Type": "application/json",
187
+ Accept: "text/event-stream",
188
+ ...req.headers ?? {}
189
+ };
190
+ const started = Date.now();
191
+ let res;
192
+ try {
193
+ res = await fetchImpl(url, {
194
+ method: req.method,
195
+ headers,
196
+ body: req.body !== void 0 ? JSON.stringify(req.body) : void 0
197
+ });
198
+ } catch (err) {
199
+ const msg = err instanceof Error ? err.message : "network error";
200
+ throw new PlatformError({
201
+ code: "unknown",
202
+ message: `Network error: ${msg}`,
203
+ status: 0
204
+ });
205
+ }
206
+ opts.onRequest?.({
207
+ method: req.method,
208
+ url,
209
+ status: res.status,
210
+ durationMs: Date.now() - started,
211
+ attempt: 1
212
+ });
213
+ if (!res.ok || !res.body) {
214
+ const text = await res.text().catch(() => "");
215
+ let parsed = void 0;
216
+ try {
217
+ parsed = text ? JSON.parse(text) : void 0;
218
+ } catch {
219
+ }
220
+ const errObj = parsed?.error;
221
+ throw new PlatformError({
222
+ code: errObj?.code ?? "unknown",
223
+ message: errObj?.message ?? res.statusText ?? `HTTP ${res.status}`,
224
+ status: res.status,
225
+ field: errObj?.field,
226
+ docsUrl: errObj?.docs_url,
227
+ body: parsed ?? text
228
+ });
229
+ }
230
+ const reader = res.body.getReader();
231
+ const decoder = new TextDecoder();
232
+ let buf = "";
233
+ while (true) {
234
+ const { value, done } = await reader.read();
235
+ if (done) return;
236
+ buf += decoder.decode(value, { stream: true });
237
+ let idx;
238
+ while ((idx = buf.indexOf("\n\n")) >= 0) {
239
+ const block = buf.slice(0, idx);
240
+ buf = buf.slice(idx + 2);
241
+ let event = "message";
242
+ let data = "";
243
+ for (const line of block.split("\n")) {
244
+ if (line.startsWith(":")) continue;
245
+ if (line.startsWith("event:")) event = line.slice(6).trim();
246
+ else if (line.startsWith("data:")) data += line.slice(5).trim();
247
+ }
248
+ if (!data) continue;
249
+ try {
250
+ const parsed = JSON.parse(data);
251
+ yield { type: event, ...parsed };
252
+ } catch {
253
+ }
254
+ }
255
+ }
256
+ }
257
+ return { request, stream };
183
258
  };
184
259
 
185
260
  // src/resources/me.ts
@@ -482,6 +557,102 @@ var createOrgsResource = (http) => ({
482
557
  }
483
558
  });
484
559
 
560
+ // src/resources/rooms.ts
561
+ var createRoomsResource = (http) => ({
562
+ // Provision a new room. Server returns 201 with ONE shared room-level
563
+ // `joinToken` + `joinUrl`. Share the single link with everyone you want in
564
+ // the room — each visitor supplies their own display name at join time and
565
+ // becomes a fresh, distinct participant. The server persists only the
566
+ // token's sha256 hash; the raw token is returned here once and never stored.
567
+ create: async (input) => http.request({
568
+ method: "POST",
569
+ path: "/v1/rooms",
570
+ body: input
571
+ }),
572
+ // Listing — opaque cursor pagination (`nextCursor` returned by the server
573
+ // is whatever startAfter() needs, don't parse client-side).
574
+ list: async (filters = {}) => {
575
+ const query = {};
576
+ if (filters.status) query.status = filters.status;
577
+ if (filters.limit !== void 0) query.limit = filters.limit;
578
+ if (filters.cursor) query.cursor = filters.cursor;
579
+ return http.request({
580
+ method: "GET",
581
+ path: "/v1/rooms",
582
+ query
583
+ });
584
+ },
585
+ // Fetch a single room. 404s are surfaced as PlatformError('not_found').
586
+ get: async (roomId) => http.request({
587
+ method: "GET",
588
+ path: `/v1/rooms/${roomId}`
589
+ }),
590
+ // Transcript pages — utterances are ordered by `startedAt asc`. Cursor is
591
+ // an ISO timestamp (server enforces, don't construct yourself).
592
+ transcript: async (roomId, opts = {}) => {
593
+ const query = {};
594
+ if (opts.cursor) query.cursor = opts.cursor;
595
+ if (opts.limit !== void 0) query.limit = opts.limit;
596
+ return http.request({
597
+ method: "GET",
598
+ path: `/v1/rooms/${roomId}/transcript`,
599
+ query
600
+ });
601
+ },
602
+ // End a room — async on the server: returns 202 + eventId once the
603
+ // controlEvents entry is written. The room-worker observes the entry,
604
+ // broadcasts the system message, and tears down LiveKit shortly after.
605
+ end: async (roomId) => http.request({
606
+ method: "POST",
607
+ path: `/v1/rooms/${roomId}/end`
608
+ })
609
+ });
610
+
611
+ // src/resources/chats.ts
612
+ var createChatsResource = (http, callToken) => ({
613
+ async start(input) {
614
+ const tokenQs = `?token=${encodeURIComponent(callToken)}`;
615
+ const streamIterable = http.stream({
616
+ method: "POST",
617
+ path: `/v1/agents/${input.agentId}/chat${tokenQs}`,
618
+ body: input.text ? { text: input.text } : {}
619
+ });
620
+ let chatId = "";
621
+ let callId = "";
622
+ const buffered = [];
623
+ const iter = streamIterable[Symbol.asyncIterator]();
624
+ while (true) {
625
+ const { value, done } = await iter.next();
626
+ if (done) break;
627
+ if (value.type === "chat.started") {
628
+ chatId = value.chatId;
629
+ callId = value.callId;
630
+ break;
631
+ }
632
+ buffered.push(value);
633
+ }
634
+ return {
635
+ id: chatId,
636
+ callId,
637
+ greeting: replayThen(buffered, { [Symbol.asyncIterator]: () => iter }),
638
+ send(text) {
639
+ return http.stream({
640
+ method: "POST",
641
+ path: `/v1/chats/${chatId}/messages${tokenQs}`,
642
+ body: { text }
643
+ });
644
+ },
645
+ async end() {
646
+ await http.request({ method: "DELETE", path: `/v1/calls/${callId}` });
647
+ }
648
+ };
649
+ }
650
+ });
651
+ async function* replayThen(buffered, rest) {
652
+ for (const x of buffered) yield x;
653
+ for await (const x of rest) yield x;
654
+ }
655
+
485
656
  // src/PlatformClient.ts
486
657
  var PlatformClient = class {
487
658
  me;
@@ -492,6 +663,8 @@ var PlatformClient = class {
492
663
  callTokens;
493
664
  webhooks;
494
665
  orgs;
666
+ rooms;
667
+ _http;
495
668
  constructor(options) {
496
669
  if (!options.apiKey) {
497
670
  throw new Error("PlatformClient: `apiKey` is required");
@@ -512,6 +685,20 @@ var PlatformClient = class {
512
685
  this.callTokens = createCallTokensResource(http);
513
686
  this.webhooks = createWebhooksResource(http);
514
687
  this.orgs = createOrgsResource(http);
688
+ this.rooms = createRoomsResource(http);
689
+ this._http = http;
690
+ }
691
+ /**
692
+ * Returns a per-token chat resource bound to the given `ct_…` call token.
693
+ * The token must have been minted with `channel: 'text'`.
694
+ *
695
+ * Note: `chatsFor` is a factory method (not a fixed property) because the
696
+ * underlying resource is scoped to a single call token, whereas this client
697
+ * was constructed with an `sk_` admin key. Each end-user session needs its
698
+ * own `chatsFor(token)` handle.
699
+ */
700
+ chatsFor(callToken) {
701
+ return createChatsResource(this._http, callToken);
515
702
  }
516
703
  };
517
704