@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/README.md CHANGED
@@ -24,16 +24,24 @@ const client = new PlatformClient({
24
24
  baseUrl: 'https://api.your-server.com', // or http://localhost:8080 for dev
25
25
  })
26
26
 
27
- // In your "/voice-token" route handler — called by the RN SDK's fetchToken:
27
+ // In your "/voice-token" route handler — called by the client SDK's fetchToken:
28
28
  async function mintForCall({ agentId, userId, context, metadata }) {
29
- const { token } = await client.callTokens.mint({
29
+ const result = await client.callTokens.mint({
30
30
  agentId,
31
31
  ttlSeconds: 600, // 10 min default; up to 3600 (60 min)
32
32
  contactId: userId, // optional → cross-call memory
33
33
  context, // optional arbitrary JSON, lowered into agent's system prompt
34
34
  metadata, // optional opaque keys round-tripped on call.ended webhook (≤1 KB)
35
35
  })
36
- return { token } // hand to the RN SDK
36
+ // Forward `transport` + `webrtcGatewayBase` so the client SDK can
37
+ // dispatch WS vs WebRTC based on the agent's configuration. New agents
38
+ // default to `transport: 'webrtc'` (2026-05-16); WS stays as the
39
+ // back-compat fallback for legacy agents and older client builds.
40
+ return {
41
+ token: result.token,
42
+ transport: result.transport,
43
+ webrtcGatewayBase: result.webrtcGatewayBase,
44
+ }
37
45
  }
38
46
  ```
39
47
 
@@ -72,6 +80,57 @@ client.credits.getBalance() // billing
72
80
  client.credits.getLedger({ limit, cursor })
73
81
 
74
82
  client.webhooks.deliveries({ agentId, callId, webhookId }) // org-wide delivery log
83
+
84
+ client.rooms.create({ agentId, durationMin }) // multi-party video rooms
85
+ client.rooms.list({ status, limit, cursor })
86
+ client.rooms.get(roomId)
87
+ client.rooms.transcript(roomId, { cursor, limit })
88
+ client.rooms.end(roomId)
89
+ ```
90
+
91
+ ## Multi-party video rooms
92
+
93
+ Provision a hosted room from your backend; share the single returned link;
94
+ participants join with **video** in the browser via `@craftedxp/voice-js`
95
+ (`joinRoom`). The room always includes a silent AI notetaker that transcribes
96
+ per speaker — the human-to-human video call works regardless.
97
+
98
+ ```ts
99
+ // Backend — the agent must be allowed to host rooms. Enable it from code
100
+ // (since 0.10.1) instead of the dashboard:
101
+ await client.agents.update(agentId, { canHostRooms: true, roomMode: 'notes-only' })
102
+ // (you can also pass canHostRooms/roomMode to `agents.create`.)
103
+
104
+ // Mint a room. Returns ONE shared joinToken + a joinUrl; the raw token is
105
+ // returned once and never stored.
106
+ const room = await client.rooms.create({ agentId, durationMin: 30 })
107
+ // → { roomId, status: 'provisioning', joinToken, joinUrl, expiresAt }
108
+
109
+ // Hand `roomId` + `joinToken` to the browser. There, with @craftedxp/voice-js:
110
+ // const session = await configureVoiceClient({ apiBase }).joinRoom({
111
+ // roomId, joinCode: joinToken, name,
112
+ // })
113
+ // await session.publishMic(); await session.publishCamera()
114
+ // session.on('track.subscribed', ({ kind, track }) => kind === 'video' && track.attach(el))
115
+
116
+ // Read the transcript (cursor-paginated, oldest first) or end the room early:
117
+ const page = await client.rooms.transcript(room.roomId, { limit: 100 })
118
+ await client.rooms.end(room.roomId)
119
+ ```
120
+
121
+ A complete, runnable consumer app (backend + browser video UI) lives in
122
+ [`examples/video-call`](../../examples/video-call).
123
+
124
+ ### Transcribe-only agents
125
+
126
+ Set `transcribeOnly: true` (on `agents.create` or `agents.update`, since 0.10.1)
127
+ for an agent that **listens but never speaks** — no TTS/LLM, no greeting; user
128
+ turns stream back as `transcript` data-messages over the existing call/WebRTC
129
+ transport. Useful for note-taking / dictation surfaces that reuse the agent +
130
+ call-mint plumbing but want the agent silent.
131
+
132
+ ```ts
133
+ await client.agents.create({ name: 'Scribe', systemPrompt: 'n/a', transcribeOnly: true })
75
134
  ```
76
135
 
77
136
  ## Webhook signature verification
package/dist/index.d.mts 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 };