@spekoai/sdk 0.4.3 → 0.5.2

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.
Files changed (71) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +88 -0
  3. package/dist/index.d.ts +3 -1
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +1 -0
  6. package/dist/lib/client.d.ts +13 -1
  7. package/dist/lib/client.d.ts.map +1 -1
  8. package/dist/lib/client.js +22 -2
  9. package/dist/lib/http.d.ts +12 -4
  10. package/dist/lib/http.d.ts.map +1 -1
  11. package/dist/lib/http.js +31 -14
  12. package/dist/lib/resources/agents.d.ts +12 -2
  13. package/dist/lib/resources/agents.d.ts.map +1 -1
  14. package/dist/lib/resources/agents.js +12 -4
  15. package/dist/lib/resources/calls.d.ts +19 -1
  16. package/dist/lib/resources/calls.d.ts.map +1 -1
  17. package/dist/lib/resources/calls.js +22 -0
  18. package/dist/lib/resources/knowledge-bases.d.ts +1 -1
  19. package/dist/lib/resources/knowledge-bases.js +1 -1
  20. package/dist/lib/resources/phone-numbers.d.ts +2 -1
  21. package/dist/lib/resources/phone-numbers.d.ts.map +1 -1
  22. package/dist/lib/resources/phone-numbers.js +2 -1
  23. package/dist/lib/resources/realtime.d.ts +3 -5
  24. package/dist/lib/resources/realtime.d.ts.map +1 -1
  25. package/dist/lib/resources/realtime.js +829 -91
  26. package/dist/lib/resources/sessions.d.ts +53 -0
  27. package/dist/lib/resources/sessions.d.ts.map +1 -0
  28. package/dist/lib/resources/sessions.js +166 -0
  29. package/dist/lib/resources/sms.d.ts +80 -0
  30. package/dist/lib/resources/sms.d.ts.map +1 -0
  31. package/dist/lib/resources/sms.js +152 -0
  32. package/dist/lib/resources/synthesize.d.ts.map +1 -1
  33. package/dist/lib/resources/synthesize.js +12 -6
  34. package/dist/lib/resources/transcribe.d.ts.map +1 -1
  35. package/dist/lib/resources/transcribe.js +9 -2
  36. package/dist/lib/resources/voice.d.ts +283 -1
  37. package/dist/lib/resources/voice.d.ts.map +1 -1
  38. package/dist/lib/resources/voice.js +345 -0
  39. package/dist/lib/resources/webhooks.d.ts +25 -0
  40. package/dist/lib/resources/webhooks.d.ts.map +1 -0
  41. package/dist/lib/resources/webhooks.js +46 -0
  42. package/dist/lib/types/index.d.ts +998 -9
  43. package/dist/lib/types/index.d.ts.map +1 -1
  44. package/dist/lib/voice-contract.d.ts +280 -0
  45. package/dist/lib/voice-contract.d.ts.map +1 -0
  46. package/dist/lib/voice-contract.js +115 -0
  47. package/package.json +2 -1
  48. package/src/index.ts +212 -0
  49. package/src/lib/client.ts +169 -0
  50. package/src/lib/errors.ts +28 -0
  51. package/src/lib/http.ts +442 -0
  52. package/src/lib/resources/agents.ts +211 -0
  53. package/src/lib/resources/callbacks.ts +40 -0
  54. package/src/lib/resources/calls.ts +113 -0
  55. package/src/lib/resources/complete.ts +63 -0
  56. package/src/lib/resources/credits.ts +41 -0
  57. package/src/lib/resources/knowledge-bases.ts +199 -0
  58. package/src/lib/resources/phone-numbers.ts +109 -0
  59. package/src/lib/resources/realtime-globals.d.ts +31 -0
  60. package/src/lib/resources/realtime.spec.ts +565 -0
  61. package/src/lib/resources/realtime.ts +1169 -0
  62. package/src/lib/resources/sessions.ts +191 -0
  63. package/src/lib/resources/sms.ts +214 -0
  64. package/src/lib/resources/synthesize.ts +101 -0
  65. package/src/lib/resources/transcribe.ts +91 -0
  66. package/src/lib/resources/usage.ts +24 -0
  67. package/src/lib/resources/voice.ts +426 -0
  68. package/src/lib/resources/voices.ts +32 -0
  69. package/src/lib/resources/webhooks.ts +67 -0
  70. package/src/lib/types/index.ts +2409 -0
  71. package/src/lib/voice-contract.ts +358 -0
@@ -0,0 +1,2409 @@
1
+ import type {
2
+ CallDirection,
3
+ CallJoinCredentials,
4
+ CallResource,
5
+ CallStatus,
6
+ } from '../voice-contract.js';
7
+
8
+ /** Options for creating a Speko client. */
9
+ export interface SpekoClientOptions {
10
+ /** API key for authentication. */
11
+ apiKey: string;
12
+ /** Org-defined broker identity used by human-calling methods. */
13
+ brokerId?: string;
14
+ /** Base URL of the Speko API. Defaults to https://api.speko.dev */
15
+ baseUrl?: string;
16
+ /** Alias for {@link SpekoClientOptions.baseUrl}. If both are set, `baseUrl` wins. */
17
+ baseURL?: string;
18
+ /** Request timeout in milliseconds. Defaults to 30000. */
19
+ timeout?: number;
20
+ }
21
+
22
+ /** BYOK = customer key, no Speko charge. MANAGED = platform key, billed. */
23
+ export type KeySource = 'BYOK' | 'MANAGED';
24
+
25
+ /** Usage record for a workspace. */
26
+ export interface UsageSummary {
27
+ totalSessions: number;
28
+ totalMinutes: number;
29
+ totalCost: number;
30
+ breakdown: UsageByProvider[];
31
+ balanceUsd: number;
32
+ currency: 'USD';
33
+ }
34
+
35
+ export interface UsageByProvider {
36
+ provider: string;
37
+ type: 'stt' | 'llm' | 'tts';
38
+ metric: string;
39
+ keySource: KeySource;
40
+ quantity: number;
41
+ cost: number;
42
+ }
43
+
44
+ /** Parameters for querying usage. */
45
+ export interface UsageQueryParams {
46
+ /** Start date (ISO 8601). */
47
+ from?: string;
48
+ /** End date (ISO 8601). */
49
+ to?: string;
50
+ }
51
+
52
+ /** Current prepaid credit balance. */
53
+ export interface OrganizationBalance {
54
+ balanceUsd: number;
55
+ currency: 'USD';
56
+ updatedAt: string;
57
+ }
58
+
59
+ export type CreditLedgerKind = 'grant' | 'debit' | 'topup' | 'refund' | 'adjustment';
60
+
61
+ export interface CreditLedgerEntry {
62
+ id: string;
63
+ kind: CreditLedgerKind;
64
+ /** Signed. Positive for grants/topups/refunds, negative for debits. */
65
+ amountMicroUsd: string;
66
+ metric: string | null;
67
+ provider: string | null;
68
+ sessionId: string | null;
69
+ createdAt: string;
70
+ }
71
+
72
+ export interface CreditLedgerPage {
73
+ entries: CreditLedgerEntry[];
74
+ /** Pass back as `cursor` for the next page, or null if exhausted. */
75
+ nextCursor: string | null;
76
+ }
77
+
78
+ export interface CreditLedgerQueryParams {
79
+ limit?: number;
80
+ cursor?: string;
81
+ }
82
+
83
+ // --- Routing primitives -----------------------------------------------------
84
+
85
+ /** Optimization preset that biases the router's weighted score. */
86
+ export type OptimizeFor = 'balanced' | 'accuracy' | 'latency' | 'cost';
87
+
88
+ /** Routing intent passed to the proxy primitives. */
89
+ export interface RoutingIntent {
90
+ /** BCP-47 language tag, e.g. "en" or "es-MX". */
91
+ language: string;
92
+ /**
93
+ * Region to rank streaming providers in (e.g. `"us-east4"`, `"eu-west1"`).
94
+ * Defaults to `"global"` on the server, which surfaces region-agnostic
95
+ * (batch) benchmark rows. Set this when latency to a specific
96
+ * geography matters — STT/TTS rankings differ per region.
97
+ */
98
+ region?: string;
99
+ optimizeFor?: OptimizeFor;
100
+ }
101
+
102
+ /**
103
+ * Optional constraints layered on top of `RoutingIntent`. The router still
104
+ * ranks candidates by benchmark score — but if `allowedProviders[modality]`
105
+ * is set and non-empty, it only considers that subset.
106
+ */
107
+ export interface PipelineConstraints {
108
+ allowedProviders?: {
109
+ stt?: string[];
110
+ llm?: string[];
111
+ tts?: string[];
112
+ };
113
+ }
114
+
115
+ // --- Transcribe -------------------------------------------------------------
116
+
117
+ export interface TranscribeOptions extends RoutingIntent {
118
+ /**
119
+ * Optional voice/session identifier forwarded as `x-session-id` for usage
120
+ * attribution. The value is carried out-of-band so request bodies and STT
121
+ * provider options stay provider-shaped.
122
+ */
123
+ sessionId?: string;
124
+ /** MIME type of the audio body. Defaults to "audio/wav". */
125
+ contentType?: string;
126
+ constraints?: PipelineConstraints;
127
+ /**
128
+ * Domain keywords to bias the STT toward. Forwarded to whichever provider
129
+ * the router picks: Deepgram → `keywords`, AssemblyAI → `keyterms_prompt`
130
+ * (or `word_boost` on legacy models), OpenAI Whisper → comma-joined prompt,
131
+ * ElevenLabs Scribe → `biased_keywords`. Casing matters for proper nouns.
132
+ */
133
+ keywords?: readonly string[];
134
+ /** Provider-facing STT overrides. Routing continues to use the inherited language. */
135
+ sttOptions?: { language?: string };
136
+ }
137
+
138
+ export interface TranscribeResult {
139
+ text: string;
140
+ provider: string;
141
+ model: string;
142
+ confidence: number | null;
143
+ failoverCount: number;
144
+ scoresRunId: string | null;
145
+ }
146
+
147
+ export type TranscribeStreamEvent =
148
+ | {
149
+ type: 'meta';
150
+ provider: string;
151
+ model: string;
152
+ failoverCount: number;
153
+ scoresRunId: string | null;
154
+ }
155
+ | {
156
+ type: 'transcript';
157
+ text: string;
158
+ isFinal: boolean;
159
+ confidence: number;
160
+ }
161
+ | (TranscribeResult & { type: 'done' })
162
+ | { type: 'error'; error: string; code: string };
163
+
164
+ // --- Synthesize -------------------------------------------------------------
165
+
166
+ export interface SynthesizeOptions extends RoutingIntent {
167
+ /**
168
+ * Optional voice/session identifier forwarded as `x-session-id` for usage
169
+ * attribution. The value is carried out-of-band so request bodies and TTS
170
+ * provider options stay provider-shaped.
171
+ */
172
+ sessionId?: string;
173
+ /** Optional voice override. Otherwise the SDK uses each provider's default. */
174
+ voice?: string;
175
+ /**
176
+ * Optional upstream model name to use for synthesis (e.g.
177
+ * `eleven_multilingual_v2`, `sonic-2`, `gpt-4o-mini-tts`,
178
+ * `qwen3-tts-flash`). When omitted, the router picks the best-ranked
179
+ * model for the chosen provider. When set, applies to the primary
180
+ * candidate only — failover candidates still use the selector's model
181
+ * so a model intended for provider A isn't sent to provider B.
182
+ */
183
+ model?: string;
184
+ speed?: number;
185
+ /**
186
+ * Free-text speaking-style instruction (tone, pace, emotion) forwarded to the
187
+ * TTS model. Only instruction-capable models honor it (OpenAI
188
+ * `gpt-4o-mini-tts`, Hume Octave, `qwen3-tts-instruct-flash`); the router
189
+ * drops it for any other resolved model, so it's safe to always pass.
190
+ */
191
+ instructions?: string;
192
+ /**
193
+ * Normalize the text into spoken form before TTS — strip markdown/URLs, spell
194
+ * out numbers/currency/abbreviations. A deterministic safety net beneath the
195
+ * voice directive. The voice pipeline sets this; direct TTS callers default
196
+ * off and get literal text.
197
+ */
198
+ spokenForm?: boolean;
199
+ constraints?: PipelineConstraints;
200
+ }
201
+
202
+ export interface SynthesizeResult {
203
+ /** Raw audio bytes. Format depends on the chosen provider — see `contentType`. */
204
+ audio: Uint8Array;
205
+ /** MIME type of the audio (e.g. "audio/mpeg" for ElevenLabs, "audio/pcm;rate=24000" for Cartesia). */
206
+ contentType: string;
207
+ provider: string;
208
+ model: string;
209
+ failoverCount: number;
210
+ scoresRunId: string | null;
211
+ }
212
+
213
+ export interface SynthesizeStreamResult extends AsyncIterable<Uint8Array> {
214
+ contentType: string;
215
+ provider: string;
216
+ model: string;
217
+ failoverCount: number;
218
+ scoresRunId: string | null;
219
+ }
220
+
221
+ // --- Voices (TTS catalog) ---------------------------------------------------
222
+
223
+ export interface VoiceCatalogEntry {
224
+ /** Routing-key vendor (matches `allowedProviders.tts` entries). */
225
+ vendor: string;
226
+ /** Voice id passed through to the provider's TTS API. */
227
+ id: string;
228
+ /** Human-readable label. */
229
+ name: string;
230
+ }
231
+
232
+ export interface VoicesProviderEntry {
233
+ key: string;
234
+ name: string;
235
+ models: readonly string[];
236
+ /**
237
+ * `true` when the provider's voice library is account-scoped and must
238
+ * be fetched live from the provider (currently only ElevenLabs).
239
+ */
240
+ voicesFetchedLive: boolean;
241
+ }
242
+
243
+ export interface VoicesListResult {
244
+ voices: readonly VoiceCatalogEntry[];
245
+ providers: readonly VoicesProviderEntry[];
246
+ }
247
+
248
+ export interface VoicesListParams {
249
+ /**
250
+ * Filter to a single provider's voices. Accepts either the routing key
251
+ * (`cartesia`, `xai`, `alibaba`, `openai`, `inworld`, `elevenlabs`) or the
252
+ * catalog suffix form (`xai-tts`, `alibaba-tts`, `openai-tts`).
253
+ */
254
+ provider?: string;
255
+ }
256
+
257
+ // --- Complete (LLM) ---------------------------------------------------------
258
+
259
+ /**
260
+ * One LLM-emitted tool invocation. `args` is a JSON-encoded string (LLMs may
261
+ * stream partial JSON; the proxy guarantees a complete, parseable string).
262
+ */
263
+ export interface ChatToolCall {
264
+ id: string;
265
+ name: string;
266
+ args: string;
267
+ }
268
+
269
+ export interface ChatMessage {
270
+ role: 'system' | 'user' | 'assistant' | 'tool';
271
+ content: string;
272
+ /** Present on `role: 'assistant'` when the model invoked one or more tools. */
273
+ toolCalls?: ChatToolCall[];
274
+ /** Required on `role: 'tool'` — pairs with the `id` from a prior assistant `toolCalls[]`. */
275
+ toolCallId?: string;
276
+ /**
277
+ * Present on `role: 'tool'` when the customer's tool execute() threw or
278
+ * returned an error. The proxy translates to provider-native error signals
279
+ * (Anthropic `is_error: true`, OpenAI prefixed content) so the LLM sees the
280
+ * failure instead of treating the error message as a normal tool result.
281
+ */
282
+ isError?: boolean;
283
+ }
284
+
285
+ /**
286
+ * Where the tool runs. `inline` (default) preserves the v0.3 behavior —
287
+ * the SDK / customer worker executes the tool. `webhook` opts into
288
+ * Speko's server-side execution: the proxy POSTs a Standard-Webhooks-
289
+ * signed request to your URL, folds the result back into the next
290
+ * provider turn, and only returns to you when the model emits final
291
+ * text or hands back an inline tool call. `builtin` runs Speko-managed
292
+ * primitives (e.g. `search_knowledge_base`, `transfer_call`, `end_call`).
293
+ * `integration` runs an
294
+ * org-installed Speko app action such as Google Calendar or Slack.
295
+ */
296
+ export type ChatToolExecutionMode = 'inline' | 'webhook' | 'builtin' | 'integration';
297
+
298
+ /**
299
+ * Spoken lead-in behavior before a server-executed tool runs. `auto` lets the
300
+ * gateway decide from the tool's recent execution durations; `always` forces a
301
+ * spoken lead-in (the gateway injects one when the model didn't produce any);
302
+ * `never` runs the tool silently.
303
+ */
304
+ export type ChatToolPreToolSpeech = 'auto' | 'always' | 'never';
305
+
306
+ /**
307
+ * Source-of-execution config. Required when `executionMode` is
308
+ * `webhook`, `builtin`, or `integration`. Mirrors the SpekoTool `source` shape inside
309
+ * `@spekoai/tool-execution`.
310
+ */
311
+ export type ChatToolSource =
312
+ | { kind: 'inline' }
313
+ | {
314
+ kind: 'webhook';
315
+ url: string;
316
+ /** Pointer into Speko's secrets store. Created via `POST /v1/agents/:id/tools` (which encrypts and stores the raw secret). */
317
+ secretRef: string;
318
+ headers?: Record<string, string>;
319
+ /**
320
+ * Outbound auth headers whose values are secret-referenced (resolved and
321
+ * injected by Speko at call time). The raw credential never leaves the
322
+ * server — only the `secretRef` pointer is exposed.
323
+ */
324
+ authHeaders?: Array<{ name: string; secretRef: string }>;
325
+ timeoutMs?: number;
326
+ /** `async` returns `asyncAck` immediately while Speko dispatches the webhook in the background. */
327
+ responseMode?: 'sync' | 'async';
328
+ /** LLM-facing acknowledgement used when `responseMode` is `async`. */
329
+ asyncAck?: string;
330
+ }
331
+ | { kind: 'builtin'; name: string; config?: unknown }
332
+ | {
333
+ kind: 'integration';
334
+ installationId: string;
335
+ appKey: string;
336
+ actionKey: string;
337
+ config?: unknown;
338
+ };
339
+
340
+ /**
341
+ * Tool definition exposed to the LLM. `parameters` is a JSON Schema (draft-7)
342
+ * object — typically generated from a Zod schema via
343
+ * `llm.toJsonSchema()` from `@livekit/agents`.
344
+ *
345
+ * `executionMode` and `source` are optional and back-compat: omitting
346
+ * both preserves the v0.3 inline behavior. Set `executionMode: 'webhook'`
347
+ * with a matching `source: { kind: 'webhook', ... }` or
348
+ * `source: { kind: 'integration', ... }` to opt into server-managed execution.
349
+ */
350
+ export interface ChatTool {
351
+ name: string;
352
+ description: string;
353
+ parameters: Record<string, unknown>;
354
+ executionMode?: ChatToolExecutionMode;
355
+ source?: ChatToolSource;
356
+ /** Spoken lead-in behavior before this tool executes. Defaults to `auto` for registered tools. */
357
+ preToolSpeech?: ChatToolPreToolSpeech;
358
+ }
359
+
360
+ /** Mirrors LiveKit's `ToolChoice` for parity with the agents framework. */
361
+ export type ChatToolChoice =
362
+ | 'auto'
363
+ | 'none'
364
+ | 'required'
365
+ | { type: 'function'; function: { name: string } };
366
+
367
+ export type ReasoningEffort = 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
368
+
369
+ export interface CompleteParams {
370
+ messages: ChatMessage[];
371
+ intent: RoutingIntent;
372
+ /**
373
+ * Optional voice/session identifier forwarded as `x-session-id` for
374
+ * server-executed tools. The value is intentionally carried out-of-band
375
+ * so `/v1/complete` request bodies stay provider-shaped.
376
+ */
377
+ sessionId?: string;
378
+ systemPrompt?: string;
379
+ temperature?: number;
380
+ maxTokens?: number;
381
+ reasoningEffort?: ReasoningEffort;
382
+ constraints?: PipelineConstraints;
383
+ tools?: ChatTool[];
384
+ toolChoice?: ChatToolChoice;
385
+ parallelToolCalls?: boolean;
386
+ /**
387
+ * Cap on how many provider hops the proxy may chain when one or more
388
+ * tools have `executionMode: 'webhook' | 'builtin'`. Each hop is one
389
+ * provider call. Default 8 server-side, hard cap 16. Ignored when all
390
+ * tools are inline (the proxy always returns toolCalls verbatim and
391
+ * the caller drives the loop themselves).
392
+ */
393
+ maxToolHops?: number;
394
+ }
395
+
396
+ export interface CompleteResult {
397
+ text: string;
398
+ provider: string;
399
+ model: string;
400
+ usage: {
401
+ promptTokens: number;
402
+ completionTokens: number;
403
+ };
404
+ failoverCount: number;
405
+ scoresRunId: string | null;
406
+ /** Present when the LLM invoked tools instead of (or in addition to) emitting text. */
407
+ toolCalls?: ChatToolCall[];
408
+ }
409
+
410
+ export type CompleteStreamEvent =
411
+ | {
412
+ type: 'meta';
413
+ provider: string;
414
+ model: string;
415
+ failoverCount: number;
416
+ totalFailoverCount: number;
417
+ scoresRunId: string | null;
418
+ hop: number;
419
+ }
420
+ | { type: 'delta'; text: string }
421
+ | (ChatToolCall & { type: 'tool_call' })
422
+ | {
423
+ type: 'server_tool_call';
424
+ id: string;
425
+ name: string;
426
+ status: 'started' | 'completed' | 'failed';
427
+ }
428
+ | (CompleteResult & { type: 'done' })
429
+ | { type: 'error'; error: string; code: string };
430
+
431
+ // --- Realtime (S2S) ---------------------------------------------------------
432
+
433
+ export type RealtimeProvider = 'openai' | 'google' | 'xai';
434
+
435
+ export interface RealtimeToolSpec {
436
+ name: string;
437
+ description: string;
438
+ parameters: Record<string, unknown>;
439
+ }
440
+
441
+ export interface RealtimeConnectParams {
442
+ /** Persisted agent whose workspace webhook routes should receive lifecycle events. */
443
+ agentId?: string;
444
+ provider: RealtimeProvider;
445
+ model: string;
446
+ voice?: string;
447
+ systemPrompt?: string;
448
+ temperature?: number;
449
+ inputSampleRate?: 16000 | 24000;
450
+ outputSampleRate?: 16000 | 24000;
451
+ tools?: RealtimeToolSpec[];
452
+ /** Exact-match attributes used only for workspace webhook routing. Requires agentId. */
453
+ webhookTags?: Record<string, string>;
454
+ metadata?: Record<string, unknown>;
455
+ /** Max session duration in seconds. Server-capped at 1800 (30 min). */
456
+ ttlSeconds?: number;
457
+ /** Reuse when retrying an ambiguous bootstrap timeout. Generated when omitted. */
458
+ idempotencyKey?: string;
459
+ }
460
+
461
+ /**
462
+ * Event shape emitted by a `RealtimeSessionHandle`. Binary audio comes in
463
+ * as `audio` frames; text control messages come through typed variants.
464
+ */
465
+ export type RealtimeFrame =
466
+ | { type: 'ready'; inputSampleRate: 16000 | 24000; outputSampleRate: 16000 | 24000 }
467
+ | { type: 'audio'; pcm: Uint8Array; sampleRate: number }
468
+ | {
469
+ type: 'transcript';
470
+ role: 'user' | 'assistant';
471
+ text: string;
472
+ final: boolean;
473
+ }
474
+ | {
475
+ type: 'tool_call';
476
+ callId: string;
477
+ name: string;
478
+ arguments: string;
479
+ }
480
+ | {
481
+ type: 'usage';
482
+ inputAudioTokens: number;
483
+ outputAudioTokens: number;
484
+ }
485
+ | { type: 'interruption'; at: 'user' | 'assistant' }
486
+ | {
487
+ type: 'server_tool_call';
488
+ id: string;
489
+ name: string;
490
+ status: 'started' | 'completed' | 'failed';
491
+ }
492
+ | { type: 'error'; code: string; message: string }
493
+ | { type: 'close'; code: number; reason: string };
494
+
495
+ export type RealtimeEventHandler = (frame: RealtimeFrame) => void;
496
+
497
+ export interface RealtimeSessionHandle {
498
+ readonly sessionId: string;
499
+ readonly expiresAt: string;
500
+ readonly inputSampleRate: 16000 | 24000;
501
+ readonly outputSampleRate: 16000 | 24000;
502
+
503
+ /** Send a PCM16 audio chunk up to the model. */
504
+ sendAudio(pcm: Uint8Array): void;
505
+
506
+ /** Signal user-turn boundary / commit the input buffer. */
507
+ commit(): void;
508
+
509
+ /** Interrupt the current assistant response. */
510
+ interrupt(): void;
511
+
512
+ /** Return a previously-requested tool call result. */
513
+ sendToolResult(callId: string, output: string): void;
514
+
515
+ /** Subscribe to frames. Returns an unsubscribe callback. */
516
+ on(handler: RealtimeEventHandler): () => void;
517
+
518
+ /** Close the session. Safe to call multiple times. */
519
+ close(code?: number, reason?: string): void;
520
+ }
521
+
522
+ // ─── Voice (phone dial) ──────────────────────────────────────────────
523
+
524
+ export interface VoiceDialParams {
525
+ /** Destination number in E.164 format (e.g. "+12015551234"). */
526
+ to: string;
527
+ /** Caller ID. Falls back to the org default if omitted server-side. */
528
+ from?: string;
529
+ /** Persisted assistant to run for this call. When supplied, `intent` can be omitted. */
530
+ agentId?: string;
531
+ /** Routing intent — language is required, optimizeFor optional. */
532
+ intent?: RoutingIntent;
533
+ constraints?: PipelineConstraints;
534
+ /** TTS voice id passed through to the picked TTS provider. */
535
+ voice?: string;
536
+ /** Agent system prompt. */
537
+ systemPrompt?: string;
538
+ /** Optional first utterance. `null` is not accepted by phone dial; omit to use the agent default. */
539
+ firstMessage?: string;
540
+ /**
541
+ * Call-time values for template variables in `systemPrompt` / `firstMessage`.
542
+ * Sending this key (even `{}`) — or dialing with an agent that declares
543
+ * variables in its registry — compiles both strings as Liquid templates at
544
+ * call-create time: `{{name}}` interpolation, `{% if %}` / `{% elsif %}`
545
+ * branching, `| default:` filters, and platform-provided `system.*` values
546
+ * (`system.now`, `system.caller_number`, `system.call_id`, …).
547
+ *
548
+ * Resolution per variable: this map → the agent registry's default → inline
549
+ * `| default:` → the request fails with 400 `MISSING_TEMPLATE_VARIABLES`
550
+ * listing every unresolved name. Keys under `system.` are rejected. Omit
551
+ * this field entirely to send both strings verbatim (no compilation).
552
+ *
553
+ * @example
554
+ * ```ts
555
+ * await speko.voice.dial({
556
+ * to: '+12015551234',
557
+ * agentId: 'ag_123',
558
+ * systemPrompt:
559
+ * 'You are {{agent_name | default: "Ava"}} calling {{customer}}. ' +
560
+ * '{% if plan == "premium" %}Offer the priority upgrade.{% endif %} ' +
561
+ * 'The current time is {{system.now}}.',
562
+ * variables: { customer: 'Mr. Lee', plan: 'premium' },
563
+ * });
564
+ * ```
565
+ */
566
+ variables?: Record<string, string>;
567
+ /**
568
+ * Per-call values for TOOLS ONLY — e.g. a short-lived access token scoped to
569
+ * the person being called, or a per-tenant API base URL. Unlike `variables`
570
+ * these never enter the system prompt, the transcript, or the model's
571
+ * context; they are stored encrypted and released only to tool execution.
572
+ * Custom-code tools read `session.secrets.<name>`; webhook tools may
573
+ * reference `{{name}}` in their `url` and `headers`. Names must be
574
+ * identifiers (`[A-Za-z_][A-Za-z0-9_]*`); up to 32 entries, 6 chars–4 KB each.
575
+ *
576
+ * @example
577
+ * ```ts
578
+ * await speko.voice.dial({
579
+ * to: '+12015551234',
580
+ * agentId: 'ag_123',
581
+ * toolSecrets: { base_url: 'https://acme.example.com', access_token: token },
582
+ * });
583
+ * ```
584
+ */
585
+ toolSecrets?: Record<string, string>;
586
+ llm?: { temperature?: number; maxTokens?: number };
587
+ ttsOptions?: { sampleRate?: number; speed?: number };
588
+ sttOptions?: { keywords?: string[]; prompt?: string; language?: string };
589
+ /** Server-side wall-clock cap in seconds. Values are clamped server-side to 30s-4h. */
590
+ maxDurationSeconds?: number;
591
+ /**
592
+ * Optional per-call turn-taking overrides. `greetFirst` defaults ON for
593
+ * outbound (worker-side, 2026-07-03): the greeting plays immediately while
594
+ * AMD classifies in the background. Pass false to hold the greeting for the
595
+ * AMD verdict.
596
+ */
597
+ turnHandling?: {
598
+ /** Local VAD for cascaded calls. Omit to use Silero. */
599
+ vad?: { provider: 'silero' | 'ai-coustics' };
600
+ /**
601
+ * Caller-leg input enhancement (ai-coustics). Omit for the platform default;
602
+ * `enabled: false` turns it off; `model` is plain Quail (default) or Quail Voice
603
+ * Focus (primary-speaker isolation, explicit opt-in).
604
+ */
605
+ noiseCancellation?: { enabled: boolean; model?: 'quail' | 'quail-voice-focus' };
606
+ profile?: 'conversational' | 'ivr' | 'ivr_patient';
607
+ endpointing?: { minDelay?: number; maxDelay?: number };
608
+ interruption?: {
609
+ mode?: 'adaptive' | 'vad';
610
+ minDuration?: number;
611
+ minWords?: number;
612
+ };
613
+ turnDetection?: boolean | 'stt';
614
+ contextThreshold?: boolean;
615
+ greetFirst?: boolean;
616
+ /**
617
+ * Replaces the built-in prompt the answering-machine detector's classifier
618
+ * sees when deciding whether a human, an IVR menu or voicemail answered.
619
+ * Outbound only; max 2,000 characters.
620
+ */
621
+ amdPrompt?: string;
622
+ /**
623
+ * What happens when native detection identifies recordable voicemail. `hangup` ends the call at the verdict; `leave_message` waits
624
+ * for the greeting to finish, speaks `voicemailMessage` once, then hangs
625
+ * up; `agent_decides` (default) hands the verdict to the LLM and lets the
626
+ * prompt's own voicemail rules act. Unavailable mailboxes always end without
627
+ * a message. Does not apply to menus/screeners or enable disabled/carrier AMD.
628
+ */
629
+ onMachine?: 'hangup' | 'leave_message' | 'agent_decides';
630
+ /** Spoken once into the mailbox under `onMachine: 'leave_message'`. Renders the same `{{variables}}` as `firstMessage`. Max 2,000 characters. */
631
+ voicemailMessage?: string;
632
+ };
633
+ /** Optional per-call SIP routing hints. Carrier AMD requires trunk/provider support. */
634
+ telephony?: {
635
+ region?: string;
636
+ amd?: {
637
+ mode?: 'agent' | 'carrier' | 'disabled';
638
+ timeoutSeconds?: number;
639
+ };
640
+ };
641
+ /** Exact-match attributes used only for workspace webhook routing. Requires agentId. */
642
+ webhookTags?: Record<string, string>;
643
+ /** Free-form metadata round-tripped to your webhooks. */
644
+ metadata?: Record<string, unknown>;
645
+ /**
646
+ * @deprecated The agent-initiated end_call tool is now always on; the server
647
+ * accepts this field for compat but ignores it.
648
+ */
649
+ endCall?: { enabled: boolean };
650
+ }
651
+
652
+ export interface VoiceDialResult {
653
+ sessionId: string;
654
+ callControlId: string;
655
+ roomName: string;
656
+ /** 'dialing' on a real call, 'dialing-stub' if managed telephony isn't configured. */
657
+ status: 'dialing' | 'dialing-stub';
658
+ to: string;
659
+ from: string;
660
+ }
661
+
662
+ // ─── Sessions ────────────────────────────────────────────────────────
663
+
664
+ /**
665
+ * One turn from `GET /v1/sessions/:id/transcript` — the lightweight live
666
+ * transcript poll. Note the camelCase keys: this endpoint's serialization
667
+ * differs from the snake_case `CallTranscriptEntry` embedded in `CallDetail`.
668
+ */
669
+ export interface SessionTranscriptEntry {
670
+ id: string;
671
+ index: number;
672
+ source: 'user' | 'agent' | 'system';
673
+ text: string;
674
+ startedAt: string;
675
+ endedAt: string | null;
676
+ provider: string | null;
677
+ model: string | null;
678
+ /** Per-stage latency legs (ms) — null on user/system turns. */
679
+ eouMs: number | null;
680
+ llmTtftMs: number | null;
681
+ ttsTtfbMs: number | null;
682
+ latencyStatus: 'partial' | 'complete' | 'interrupted' | 'error' | null;
683
+ conversationalLatencyMs: number | null;
684
+ /** Tool calls the agent made on this turn (empty when none). */
685
+ toolCalls: { name: string; args: string }[];
686
+ }
687
+
688
+ export interface SessionTranscript {
689
+ entries: SessionTranscriptEntry[];
690
+ }
691
+
692
+ /**
693
+ * One push from `sessions.stream()` (SSE under the hood, auto-reconnecting).
694
+ * `end` is always the final event; transport-level reconnects and server
695
+ * stream rotations are handled inside the SDK and never surface here.
696
+ */
697
+ export type SessionStreamEvent =
698
+ | { type: 'status'; status: string; endedAt: string | null }
699
+ | { type: 'transcript'; turn: SessionTranscriptEntry }
700
+ | { type: 'event'; event: CallEvent }
701
+ | { type: 'end'; reason: 'session_ended' };
702
+
703
+ export interface SessionStreamOptions {
704
+ /**
705
+ * Resume position (`"<lastTurnIndex>:<lastEventCreatedAtMs>"`). Rarely
706
+ * needed — the iterator tracks it internally across reconnects; pass it
707
+ * only to resume a NEW iterator after your own process restarted.
708
+ */
709
+ cursor?: string;
710
+ /** Abort to stop streaming (the iterator returns). */
711
+ signal?: AbortSignal;
712
+ }
713
+
714
+ // ─── Phone numbers ───────────────────────────────────────────────────
715
+
716
+ export type PhoneNumberDirection = 'inbound' | 'outbound' | 'both';
717
+ export type PhoneNumberSource = 'managed' | 'sip_trunk';
718
+ export type PhoneNumberSmsAssignmentStatus =
719
+ | 'FAILED_ASSIGNMENT'
720
+ | 'PENDING_ASSIGNMENT'
721
+ | 'ASSIGNED'
722
+ | 'PENDING_UNASSIGNMENT'
723
+ | 'FAILED_UNASSIGNMENT';
724
+
725
+ export interface PhoneNumberSetupStatus {
726
+ status: 'ready' | 'action_required' | 'suspended';
727
+ inboundReady: boolean;
728
+ outboundReady: boolean;
729
+ agentReady: boolean;
730
+ forwardingRequired: boolean;
731
+ sipConnectionReady: boolean;
732
+ issues: string[];
733
+ }
734
+
735
+ export interface PhoneNumberRow {
736
+ id: string;
737
+ organizationId: string;
738
+ e164: string;
739
+ source: PhoneNumberSource;
740
+ /** Platform-neutral resource id for a platform-managed number. */
741
+ providerResourceId: string | null;
742
+ /** @deprecated Use `providerResourceId`. */
743
+ telnyxPhoneNumberId: string | null;
744
+ /** @deprecated LiveKit trunk IDs are internal and no longer exposed. */
745
+ sipTrunkId: string | null;
746
+ sipConnectionInstallationId: string | null;
747
+ sipProviderName: string | null;
748
+ direction: PhoneNumberDirection;
749
+ dispatchMetadataTemplate: Record<string, unknown> | null;
750
+ label: string | null;
751
+ sms10dlcProfileId: string | null;
752
+ smsCampaignId: string | null;
753
+ smsAssignmentStatus: PhoneNumberSmsAssignmentStatus | null;
754
+ smsAssignmentUpdatedAt: string | null;
755
+ telnyxMessagingProfileId: string | null;
756
+ smsMessagingProfileStatus: 'pending' | 'ready' | 'failed';
757
+ smsMessagingProfileUpdatedAt: string | null;
758
+ smsMessagingProfileError: string | null;
759
+ smsAutomationEnabled: boolean;
760
+ /**
761
+ * 1:1 link to a persisted agent. When set, inbound calls hydrate
762
+ * pipeline config from the agent row instead of (or alongside) the
763
+ * dispatch_metadata_template.
764
+ */
765
+ agentId: string | null;
766
+ /**
767
+ * Inbound destination when this number answers to a HUMAN rather than an
768
+ * agent — the org-defined broker whose softphone is rung. Mutually
769
+ * exclusive with `agentId`: assigning one clears the other, because a number
770
+ * routed to a broker is provisioned so that no agent joins ahead of them.
771
+ */
772
+ routeToBrokerId: string | null;
773
+ setupStatus: PhoneNumberSetupStatus;
774
+ nextChargeAt: string;
775
+ lastChargedAt: string | null;
776
+ /** Effective billing-or-compliance suspension timestamp. */
777
+ suspendedAt: string | null;
778
+ /** Billing-only suspension, retained independently from compliance review. */
779
+ billingSuspendedAt?: string | null;
780
+ /** Compliance-only suspension for Speko-managed numbers. */
781
+ complianceSuspendedAt?: string | null;
782
+ suspensionReason?: 'billing' | 'compliance' | null;
783
+ createdAt: string;
784
+ updatedAt: string;
785
+ }
786
+
787
+ export interface PhoneNumberCreateParams {
788
+ e164: string;
789
+ direction?: PhoneNumberDirection;
790
+ /** Dispatch metadata template (variables `{{var}}` resolved at dial). */
791
+ dispatchMetadataTemplate?: Record<string, unknown>;
792
+ label?: string;
793
+ /** 1:1 link to an agent in the same org. */
794
+ agentId?: string;
795
+ }
796
+
797
+ export type PhoneNumberImportSipTrunkParams = {
798
+ e164: string;
799
+ /** Optional provider/account label for display. */
800
+ sipProviderName?: string;
801
+ direction?: PhoneNumberDirection;
802
+ /** Dispatch metadata template (variables `{{var}}` resolved at dial). */
803
+ dispatchMetadataTemplate?: Record<string, unknown>;
804
+ label?: string;
805
+ /** 1:1 link to an agent in the same org. */
806
+ agentId?: string;
807
+ } & (
808
+ | {
809
+ /** Installed SIP connection integration id. Preferred for productized SIP connections. */
810
+ sipConnectionInstallationId: string;
811
+ /** Legacy LiveKit outbound trunk id. Ignored when `sipConnectionInstallationId` is present. */
812
+ sipTrunkId?: string;
813
+ }
814
+ | {
815
+ /** Legacy LiveKit outbound trunk id. Use `sipConnectionInstallationId` for new integrations. */
816
+ sipTrunkId: string;
817
+ sipConnectionInstallationId?: string;
818
+ }
819
+ );
820
+
821
+ export interface PhoneNumberUpdateParams {
822
+ direction?: PhoneNumberDirection;
823
+ dispatchMetadataTemplate?: Record<string, unknown> | null;
824
+ label?: string | null;
825
+ /** Pass `null` to unlink, a string to relink. */
826
+ agentId?: string | null;
827
+ /**
828
+ * Route inbound calls on this number to a human broker's softphone instead of
829
+ * an agent — pass your org-defined broker id, or `null` to stop. Setting
830
+ * it clears `agentId`, and setting `agentId` clears it; sending both in one
831
+ * request is a validation error. Requires the human-calling feature.
832
+ */
833
+ routeToBrokerId?: string | null;
834
+ /** Owner/admin-only opt-in for inbound SMS agent replies on this number. */
835
+ smsAutomationEnabled?: boolean;
836
+ }
837
+
838
+ export interface AvailablePhoneNumber {
839
+ e164: string;
840
+ friendlyName: string;
841
+ monthlyCostUsd: number;
842
+ upfrontCostUsd: number;
843
+ features: string[];
844
+ region: {
845
+ state: string | null;
846
+ locality: string | null;
847
+ rateCenter: string | null;
848
+ };
849
+ }
850
+
851
+ export interface PhoneNumberSearchParams {
852
+ /** 3-digit US area code, e.g. "415". */
853
+ areaCode?: string;
854
+ /** Optional locality filter, e.g. "San Francisco". */
855
+ locality?: string;
856
+ /** Max results. Default 10. */
857
+ limit?: number;
858
+ }
859
+
860
+ export type PhoneNumberKybStatus =
861
+ | 'missing'
862
+ | 'draft'
863
+ | 'submitted'
864
+ | 'approved'
865
+ | 'rejected'
866
+ | 'revoked';
867
+
868
+ export type PhoneNumberKybSubmissionStatus = Exclude<PhoneNumberKybStatus, 'missing'>;
869
+
870
+ export type PhoneNumberKybSlackNotificationStatus = 'not_queued' | 'queued' | 'enqueue_failed';
871
+
872
+ export interface PhoneNumberKybBusinessProfile {
873
+ legalName: string;
874
+ displayName: string;
875
+ entityType: string;
876
+ country: string;
877
+ registrationId?: string;
878
+ website: string;
879
+ address: {
880
+ street: string;
881
+ city: string;
882
+ state: string;
883
+ postalCode: string;
884
+ country: string;
885
+ };
886
+ useCase: string;
887
+ expectedUsage: string;
888
+ }
889
+
890
+ export interface PhoneNumberKybAuthorizedRepresentative {
891
+ name: string;
892
+ title: string;
893
+ email: string;
894
+ phone?: string;
895
+ }
896
+
897
+ export interface PhoneNumberKybDeclaration {
898
+ businessName: string;
899
+ useCase: string;
900
+ }
901
+
902
+ export type PhoneNumberKybAttestor =
903
+ | {
904
+ kind: 'user';
905
+ userId: string;
906
+ name: string;
907
+ email: string;
908
+ organizationRole: string | null;
909
+ }
910
+ | { kind: 'api_key'; apiKeyId: string };
911
+
912
+ export interface PhoneNumberKybAttestationContract {
913
+ version: string;
914
+ text: string;
915
+ termsVersion: string;
916
+ termsUrl: string;
917
+ }
918
+
919
+ export interface PhoneNumberKybDraftParams {
920
+ businessProfile: PhoneNumberKybBusinessProfile;
921
+ authorizedRepresentative: PhoneNumberKybAuthorizedRepresentative;
922
+ attestationAccepted?: boolean;
923
+ }
924
+
925
+ export type PhoneNumberKybSubmitParams =
926
+ | {
927
+ declaration: PhoneNumberKybDeclaration;
928
+ attestationAccepted: true;
929
+ attestationVersion: string;
930
+ }
931
+ | {
932
+ businessProfile: PhoneNumberKybBusinessProfile;
933
+ authorizedRepresentative: PhoneNumberKybAuthorizedRepresentative;
934
+ attestationAccepted: true;
935
+ attestationVersion?: string;
936
+ };
937
+
938
+ export interface PhoneNumberKybSubmission {
939
+ id: string;
940
+ organizationId: string;
941
+ status: PhoneNumberKybSubmissionStatus;
942
+ businessProfile: PhoneNumberKybBusinessProfile | null;
943
+ authorizedRepresentative: PhoneNumberKybAuthorizedRepresentative | null;
944
+ declaration?: PhoneNumberKybDeclaration | null;
945
+ attestor?: PhoneNumberKybAttestor | null;
946
+ attestationAccepted: boolean;
947
+ attestationVersion?: string | null;
948
+ attestationText?: string | null;
949
+ termsVersion?: string | null;
950
+ attestedAt: string | null;
951
+ accessHoldAt?: string | null;
952
+ accessHoldReason?: 'rejected' | 'revoked' | null;
953
+ submittedByUserId: string | null;
954
+ submittedByEmail: string | null;
955
+ submittedByApiKeyId: string | null;
956
+ submittedAt: string | null;
957
+ reviewerUserId: string | null;
958
+ reviewerEmail: string | null;
959
+ reviewedAt: string | null;
960
+ rejectionReason: string | null;
961
+ slackNotificationStatus: PhoneNumberKybSlackNotificationStatus;
962
+ slackNotificationJobId: string | null;
963
+ slackNotificationError: string | null;
964
+ createdAt: string;
965
+ updatedAt: string;
966
+ }
967
+
968
+ export interface PhoneNumberKybOverview {
969
+ status: PhoneNumberKybStatus;
970
+ submission: PhoneNumberKybSubmission | null;
971
+ declarationPrefill?: PhoneNumberKybDeclaration;
972
+ requiredAttestation?: PhoneNumberKybAttestationContract;
973
+ attestationRequired?: boolean;
974
+ complianceAccess?: 'enabled' | 'awaiting_attestation' | 'suspended';
975
+ prefill: {
976
+ businessProfile: PhoneNumberKybBusinessProfile;
977
+ authorizedRepresentative: PhoneNumberKybAuthorizedRepresentative;
978
+ } | null;
979
+ }
980
+
981
+ // ─── Agents ──────────────────────────────────────────────────────────
982
+
983
+ /**
984
+ * Routing intent for an agent's voice pipeline. Narrower than the
985
+ * top-level {@link RoutingIntent} — the agents API specifically
986
+ * accepts `latency`, `quality`, or `cost` (no `balanced` / `accuracy`).
987
+ */
988
+ export interface AgentIntent {
989
+ /** BCP-47 language tag, e.g. "en" or "es-MX". */
990
+ language: string;
991
+ optimizeFor?: 'latency' | 'quality' | 'cost';
992
+ }
993
+
994
+ export interface AgentLlmOptions {
995
+ temperature?: number;
996
+ maxTokens?: number;
997
+ model?: string;
998
+ }
999
+
1000
+ export interface AgentStackPreferences {
1001
+ allowedProviders?: {
1002
+ stt?: string[];
1003
+ llm?: string[];
1004
+ tts?: string[];
1005
+ s2s?: string[];
1006
+ };
1007
+ }
1008
+
1009
+ export interface AgentSttOptions {
1010
+ /** Vocabulary keywords forwarded to whichever STT provider the router picks. */
1011
+ keywords?: string[];
1012
+ /**
1013
+ * Free-text transcription context (domain, names, expected phrases), max
1014
+ * 2000 chars. Honored only by prompt-capable STT models (OpenAI
1015
+ * gpt-4o-transcribe family, AssemblyAI Universal-3 Pro tiers).
1016
+ */
1017
+ prompt?: string;
1018
+ /**
1019
+ * STT stream-language override. A BCP-47-ish tag ('en', 'es-MX'), a
1020
+ * provider keyword like Deepgram's 'multi', or 'auto' to let the provider
1021
+ * detect the spoken language itself (Soniox auto-detects when hints are
1022
+ * omitted). Never affects stack routing — that keeps the agent language.
1023
+ */
1024
+ language?: string;
1025
+ }
1026
+
1027
+ /**
1028
+ * Built-in ambience clip ids supported by the hosted worker. Custom clip
1029
+ * uploads are intentionally not yet supported — pinning to the built-ins
1030
+ * keeps the v1 API simple and lets the worker map straight to the
1031
+ * `BuiltinAudioClip` enum.
1032
+ */
1033
+ export type AgentAmbientClip =
1034
+ | 'office-ambience'
1035
+ | 'city-ambience'
1036
+ | 'forest-ambience'
1037
+ | 'crowded-room'
1038
+ | 'keyboard-typing'
1039
+ | 'keyboard-typing2';
1040
+
1041
+ /**
1042
+ * Per-agent background audio. Today only ambient (continuous loop) is
1043
+ * supported. The ambience plays on a separate media track mixed
1044
+ * server-side, so it reaches both browser (WebRTC) and phone (SIP) callers
1045
+ * without any client-side change.
1046
+ */
1047
+ export interface AgentBackgroundAudio {
1048
+ ambient?: {
1049
+ clip: AgentAmbientClip;
1050
+ /**
1051
+ * Linear gain in `[0, 16]`, defaulting to 1.0 — the clip's own recorded
1052
+ * level, which is not the same as "full volume". The built-in clips are
1053
+ * mastered roughly 30 dB apart, so the useful range differs per clip:
1054
+ * `office-ambience` is very quiet (about -52 LUFS) and needs ~5-10 to sit
1055
+ * audibly under speech, `city-ambience` is about right at 1, and
1056
+ * `crowded-room` is loud enough that it distorts past ~1.6.
1057
+ */
1058
+ volume?: number;
1059
+ };
1060
+ }
1061
+
1062
+ export interface AgentSpeechNormalization {
1063
+ pronunciationDictionary?: Record<string, string>;
1064
+ textReplacements?: Record<string, string>;
1065
+ }
1066
+
1067
+ /**
1068
+ * A caller-defined post-call extraction field. Only meaningful on the
1069
+ * `postCall` webhook: the call-analysis pass fills each from the transcript per
1070
+ * `description`, typed by `type`, and the values are delivered under the
1071
+ * webhook payload's top-level `custom_data` object keyed by `name`. `options`
1072
+ * is required for `enum` fields.
1073
+ */
1074
+ export interface AgentExtractionField {
1075
+ /**
1076
+ * Stable key the value lands under in `custom_data`. Must be a valid
1077
+ * identifier (`^[a-zA-Z_][a-zA-Z0-9_]*$`), up to 64 chars, unique per webhook.
1078
+ */
1079
+ name: string;
1080
+ type: 'string' | 'number' | 'boolean' | 'enum';
1081
+ /**
1082
+ * Instruction the LLM uses to extract this field. 1 to 10,000 characters,
1083
+ * and at most 40,000 characters combined across all fields on the webhook.
1084
+ */
1085
+ description: string;
1086
+ /**
1087
+ * Allowed values — required (and only valid) when `type` is `'enum'`.
1088
+ * 1–50 options, each up to 120 characters.
1089
+ */
1090
+ options?: string[];
1091
+ }
1092
+
1093
+ /**
1094
+ * Outbound auth header input — `value` is the plaintext credential Speko
1095
+ * encrypts at rest. Required on create; omit on update to keep the value
1096
+ * already stored under this header's ref.
1097
+ */
1098
+ export interface AgentWebhookAuthHeaderInput {
1099
+ name: string;
1100
+ value?: string;
1101
+ }
1102
+
1103
+ /** Outbound auth header as returned by the API — value stays server-side. */
1104
+ export interface AgentWebhookAuthHeader {
1105
+ name: string;
1106
+ secretRef: string;
1107
+ }
1108
+
1109
+ export interface AgentLifecycleWebhookCreate {
1110
+ url: string;
1111
+ /**
1112
+ * Optional per-webhook signing secret. When supplied, this endpoint signs
1113
+ * with its own secret instead of the shared org-level secret from API keys.
1114
+ */
1115
+ secret?: string;
1116
+ headers?: Record<string, string>;
1117
+ /** Secret-referenced outbound auth headers (e.g. a Bearer token your endpoint requires). */
1118
+ authHeaders?: AgentWebhookAuthHeaderInput[];
1119
+ timeoutMs?: number;
1120
+ responseMode?: 'sync' | 'async';
1121
+ asyncAck?: string;
1122
+ /** Post-call data-extraction fields. Applies to the `postCall` webhook only. */
1123
+ extractionFields?: AgentExtractionField[];
1124
+ }
1125
+
1126
+ export interface AgentLifecycleWebhookUpdate {
1127
+ url: string;
1128
+ /**
1129
+ * Optional per-webhook signing secret. Supply to set/rotate a per-webhook
1130
+ * secret; omit to keep the existing (or shared org-level) secret.
1131
+ */
1132
+ secret?: string;
1133
+ headers?: Record<string, string>;
1134
+ /** Secret-referenced outbound auth headers. Replaces the stored set; omit a `value` to keep it. */
1135
+ authHeaders?: AgentWebhookAuthHeaderInput[];
1136
+ timeoutMs?: number;
1137
+ responseMode?: 'sync' | 'async';
1138
+ asyncAck?: string;
1139
+ /** Post-call data-extraction fields. Applies to the `postCall` webhook only. */
1140
+ extractionFields?: AgentExtractionField[];
1141
+ }
1142
+
1143
+ export interface AgentLifecycleWebhookSerialized {
1144
+ url: string;
1145
+ secretRef: string;
1146
+ headers?: Record<string, string>;
1147
+ /** Outbound auth-header pointers; values stay encrypted server-side. */
1148
+ authHeaders?: AgentWebhookAuthHeader[];
1149
+ timeoutMs?: number;
1150
+ responseMode?: 'sync' | 'async';
1151
+ asyncAck?: string;
1152
+ /** Post-call data-extraction fields. Present on the `postCall` webhook only. */
1153
+ extractionFields?: AgentExtractionField[];
1154
+ }
1155
+
1156
+ export interface AgentWebhooksSerialized {
1157
+ preCall?: AgentLifecycleWebhookSerialized;
1158
+ postCall?: AgentLifecycleWebhookSerialized;
1159
+ status?: AgentLifecycleWebhookSerialized;
1160
+ /** Dedicated `call.analysis` webhook — LLM analysis results only. */
1161
+ analysis?: AgentLifecycleWebhookSerialized;
1162
+ /** Dedicated `call.recording` webhook — fires when the recording turns terminal. */
1163
+ recording?: AgentLifecycleWebhookSerialized;
1164
+ }
1165
+
1166
+ export interface AgentWebhooksCreate {
1167
+ preCall?: AgentLifecycleWebhookCreate;
1168
+ postCall?: AgentLifecycleWebhookCreate;
1169
+ status?: AgentLifecycleWebhookCreate;
1170
+ /**
1171
+ * Dedicated `call.analysis` webhook. Delivered once per call when the LLM
1172
+ * analysis completes: summary, outcome, structured_data, and custom_data —
1173
+ * without the transcript/cost/recording of the combined `call.report`.
1174
+ */
1175
+ analysis?: AgentLifecycleWebhookCreate;
1176
+ /**
1177
+ * Dedicated `call.recording` webhook. Delivered once per call when the
1178
+ * recording reaches a terminal state — `ready` carries the presigned
1179
+ * `recording_url` (7-day TTL), `failed` carries `recording_url: null`.
1180
+ */
1181
+ recording?: AgentLifecycleWebhookCreate;
1182
+ }
1183
+
1184
+ export interface AgentWebhooksUpdate {
1185
+ preCall?: AgentLifecycleWebhookUpdate | null;
1186
+ postCall?: AgentLifecycleWebhookUpdate | null;
1187
+ status?: AgentLifecycleWebhookUpdate | null;
1188
+ analysis?: AgentLifecycleWebhookUpdate | null;
1189
+ recording?: AgentLifecycleWebhookUpdate | null;
1190
+ }
1191
+
1192
+ // ─── Workspace webhooks ─────────────────────────────────────────────
1193
+
1194
+ /**
1195
+ * Everything a workspace webhook endpoint can subscribe to.
1196
+ *
1197
+ * Two families, and they behave differently on the wire:
1198
+ *
1199
+ * **AI voice-session events** (`call.pre_call` … `call.recording`) describe one
1200
+ * `voice_session` as it progresses, and carry a `session_id`.
1201
+ *
1202
+ * **Programmable-voice control events** (`call.initiated` … `call.hangup`) are
1203
+ * the webhook projection of the human-calling event stream — the same events
1204
+ * {@link CallControl.events} returns. A human call is not a `voice_session`, so
1205
+ * there is no session id to correlate on: the payload carries `call_id`,
1206
+ * `control_id` (null for call-scoped events that belong to no single leg),
1207
+ * `event_id` and `occurred_at`, merged with the event's own payload, and
1208
+ * `call_id` is the correlation key.
1209
+ *
1210
+ * Control events are delivered **once, without automatic retry**. Only
1211
+ * `call.report`, `call.analysis` and `call.recording` are durable — for those, a
1212
+ * failed delivery is re-attempted on a backoff. A control event that misses its
1213
+ * endpoint is gone from the webhook feed; the call's own event history
1214
+ * ({@link CallControl.events}) is the durable record, so reconcile from there
1215
+ * rather than treating the webhook as a queue.
1216
+ */
1217
+ export type WorkspaceWebhookEventType =
1218
+ | 'call.pre_call'
1219
+ | 'call.status'
1220
+ | 'call.report'
1221
+ | 'call.analysis'
1222
+ | 'call.recording'
1223
+ | 'call.initiated'
1224
+ | 'call.ringing'
1225
+ | 'call.answered'
1226
+ | 'call.bridged'
1227
+ | 'call.hold'
1228
+ | 'call.unhold'
1229
+ | 'call.mute'
1230
+ | 'call.unmute'
1231
+ // No `call.dtmf.received`: an inbound keypress reaches the platform as an
1232
+ // in-room data packet addressed to room participants, never to the webhook
1233
+ // receiver, so there is no server-side producer to subscribe to. Read it off
1234
+ // the room's data channel in the browser instead.
1235
+ | 'call.dtmf.sent'
1236
+ | 'call.transfer.initiated'
1237
+ | 'call.transfer.completed'
1238
+ | 'call.transfer.failed'
1239
+ | 'call.leg.hangup'
1240
+ | 'call.hangup'
1241
+ | 'sms.received'
1242
+ | 'sms.accepted'
1243
+ | 'sms.sent'
1244
+ | 'sms.delivered'
1245
+ | 'sms.delivery_failed'
1246
+ | 'sms.submission_unknown'
1247
+ | 'sms.opted_out'
1248
+ | 'sms.opted_in';
1249
+
1250
+ export type WebhookEventType =
1251
+ | WorkspaceWebhookEventType
1252
+ | 'imessage.received'
1253
+ | 'imessage.reaction_received'
1254
+ | 'imessage.sent'
1255
+ | 'imessage.delivered'
1256
+ | 'imessage.delivery_failed';
1257
+
1258
+ export type WebhookDeliveryStatus =
1259
+ | 'pending'
1260
+ | 'delivering'
1261
+ | 'succeeded'
1262
+ | 'failed'
1263
+ | 'cancelled'
1264
+ | 'expired';
1265
+
1266
+ export interface WebhookEndpointAuthHeaderInput {
1267
+ name: string;
1268
+ /** Write-only plaintext. The server encrypts it and never returns it. */
1269
+ value: string;
1270
+ }
1271
+
1272
+ export interface WebhookEndpointAuthHeaderUpdate {
1273
+ name: string;
1274
+ /** Supply to set or rotate; omit to retain the stored value for this header name. */
1275
+ value?: string;
1276
+ }
1277
+
1278
+ export interface WebhookEndpointInput {
1279
+ name: string;
1280
+ url: string;
1281
+ events: WorkspaceWebhookEventType[];
1282
+ /** Defaults to true. When false, agentIds must contain at least one agent. */
1283
+ allAgents?: boolean;
1284
+ agentIds?: string[];
1285
+ filterTags?: Record<string, string>;
1286
+ headers?: Record<string, string>;
1287
+ authHeaders?: WebhookEndpointAuthHeaderInput[];
1288
+ timeoutMs?: number;
1289
+ signingSecretSource?: 'workspace' | 'custom';
1290
+ /** Write-only. Required when signingSecretSource is custom. */
1291
+ signingSecret?: string;
1292
+ extractionFields?: AgentExtractionField[];
1293
+ /** Include message text, or emit metadata-only SMS payloads. Defaults to full. */
1294
+ contentMode?: 'full' | 'metadata_only';
1295
+ }
1296
+
1297
+ export type WebhookEndpointUpdate = Partial<Omit<WebhookEndpointInput, 'authHeaders'>> & {
1298
+ authHeaders?: WebhookEndpointAuthHeaderUpdate[];
1299
+ };
1300
+
1301
+ export interface WebhookEndpoint {
1302
+ id: string;
1303
+ name: string;
1304
+ url: string;
1305
+ events: WorkspaceWebhookEventType[];
1306
+ allAgents: boolean;
1307
+ agentIds: string[];
1308
+ filterTags: Record<string, string>;
1309
+ headers: Record<string, string>;
1310
+ authHeaders: Array<{ name: string; configured: true }>;
1311
+ timeoutMs: number;
1312
+ signingSecretSource: 'workspace' | 'custom';
1313
+ hasCustomSigningSecret: boolean;
1314
+ extractionFields: AgentExtractionField[];
1315
+ contentMode: 'full' | 'metadata_only';
1316
+ legacyManaged: boolean;
1317
+ createdAt: string;
1318
+ updatedAt: string;
1319
+ }
1320
+
1321
+ export interface WebhookDeliveryListParams {
1322
+ endpointId?: string;
1323
+ event?: WebhookEventType;
1324
+ agentId?: string;
1325
+ status?: WebhookDeliveryStatus;
1326
+ sessionId?: string;
1327
+ eventId?: string;
1328
+ from?: string;
1329
+ to?: string;
1330
+ cursor?: string;
1331
+ limit?: number;
1332
+ }
1333
+
1334
+ export interface WebhookDelivery {
1335
+ id: string;
1336
+ eventId: string;
1337
+ endpointId: string;
1338
+ endpointName: string;
1339
+ endpointKind: 'workspace' | 'imessage';
1340
+ endpointDeleted: boolean;
1341
+ event: WebhookEventType;
1342
+ sessionId: string | null;
1343
+ agentId: string | null;
1344
+ webhookTags: Record<string, string>;
1345
+ status: WebhookDeliveryStatus;
1346
+ attempts: number;
1347
+ httpStatus: number | null;
1348
+ error: string | null;
1349
+ occurredAt: string;
1350
+ expiresAt: string;
1351
+ deliveredAt: string | null;
1352
+ createdAt: string;
1353
+ /** False for provider-retried iMessage subscriber deliveries. */
1354
+ canRedeliver: boolean;
1355
+ }
1356
+
1357
+ export interface WebhookDeliveryEndpointOption {
1358
+ id: string;
1359
+ name: string;
1360
+ kind: 'workspace' | 'imessage';
1361
+ deleted: boolean;
1362
+ }
1363
+
1364
+ export interface WebhookDeliveryPage {
1365
+ data: WebhookDelivery[];
1366
+ nextCursor: string | null;
1367
+ endpointOptions: WebhookDeliveryEndpointOption[];
1368
+ }
1369
+
1370
+ export interface WebhookDeliveryAttempt {
1371
+ id: string;
1372
+ attemptNumber: number;
1373
+ trigger: 'automatic' | 'manual';
1374
+ requestUrl: string;
1375
+ requestHeaders: Record<string, string>;
1376
+ requestBody: Record<string, unknown>;
1377
+ responseStatus: number | null;
1378
+ responseBody: string | null;
1379
+ responseTruncated: boolean;
1380
+ durationMs: number;
1381
+ error: string | null;
1382
+ createdAt: string;
1383
+ }
1384
+
1385
+ export interface WebhookDeliveryDetail extends Omit<WebhookDelivery, 'attempts'> {
1386
+ attemptCount: number;
1387
+ requestPayload: Record<string, unknown>;
1388
+ attempts: WebhookDeliveryAttempt[];
1389
+ }
1390
+
1391
+ /**
1392
+ * One prompt-variable registry entry. `defaultValue` fills the variable when a
1393
+ * session/dial call omits it (empty string = declared optional: renders blank
1394
+ * and `{% if %}` branches false). Without a default the variable is required
1395
+ * per call — omitting it fails session create with 400
1396
+ * `MISSING_TEMPLATE_VARIABLES`. Names may not use the reserved `system.`
1397
+ * namespace.
1398
+ */
1399
+ export interface AgentPromptVariable {
1400
+ name: string;
1401
+ defaultValue?: string;
1402
+ description?: string;
1403
+ }
1404
+
1405
+ /** Turn-taking configuration for cascaded agents. Realtime agents ignore VAD. */
1406
+ export interface AgentTurnHandling {
1407
+ /** Local VAD provider. Omit to use Silero. */
1408
+ vad?: { provider: 'silero' | 'ai-coustics' };
1409
+ /**
1410
+ * Caller-leg input enhancement (ai-coustics). Omit for the platform default;
1411
+ * `enabled: false` turns it off; `model` is plain Quail (default) or Quail Voice
1412
+ * Focus (primary-speaker isolation, explicit opt-in).
1413
+ */
1414
+ noiseCancellation?: { enabled: boolean; model?: 'quail' | 'quail-voice-focus' };
1415
+ profile?: 'conversational' | 'ivr' | 'ivr_patient';
1416
+ endpointing?: { minDelay?: number; maxDelay?: number };
1417
+ interruption?: {
1418
+ mode?: 'adaptive' | 'vad';
1419
+ minDuration?: number;
1420
+ minWords?: number;
1421
+ };
1422
+ turnDetection?: boolean | 'stt';
1423
+ contextThreshold?: boolean;
1424
+ textGate?: boolean;
1425
+ turnDetector?: 'smart_turn' | 'speko_turn_v1';
1426
+ dtmfToolDescription?: string;
1427
+ amdPrompt?: string;
1428
+ waitForCallee?: boolean;
1429
+ onMachine?: 'hangup' | 'leave_message' | 'agent_decides';
1430
+ voicemailMessage?: string;
1431
+ }
1432
+
1433
+ export interface AgentRow {
1434
+ id: string;
1435
+ organizationId: string;
1436
+ name: string;
1437
+ systemPrompt: string;
1438
+ voice: string | null;
1439
+ intent: AgentIntent;
1440
+ llmOptions: AgentLlmOptions | null;
1441
+ stackPreferences: AgentStackPreferences | null;
1442
+ sttOptions: AgentSttOptions | null;
1443
+ backgroundAudio: AgentBackgroundAudio | null;
1444
+ speechNormalization: AgentSpeechNormalization | null;
1445
+ turnHandling: AgentTurnHandling | null;
1446
+ /** @deprecated Use organization-owned `speko.webhooks` endpoints. */
1447
+ webhooks: AgentWebhooksSerialized | null;
1448
+ /**
1449
+ * Post-call extraction schema on the agent itself — no webhook required.
1450
+ * Merged with `webhooks.postCall.extractionFields`; the agent-level
1451
+ * definition wins on a name collision.
1452
+ */
1453
+ extractionFields: AgentExtractionField[];
1454
+ /** Prompt-variable registry. Returned on single-agent reads; null = empty. */
1455
+ promptVariables?: AgentPromptVariable[] | null;
1456
+ createdAt: string;
1457
+ updatedAt: string;
1458
+ }
1459
+
1460
+ export interface AgentCreateParams {
1461
+ name: string;
1462
+ systemPrompt: string;
1463
+ voice?: string;
1464
+ intent: AgentIntent;
1465
+ llmOptions?: AgentLlmOptions;
1466
+ stackPreferences?: AgentStackPreferences;
1467
+ sttOptions?: AgentSttOptions;
1468
+ backgroundAudio?: AgentBackgroundAudio;
1469
+ speechNormalization?: AgentSpeechNormalization;
1470
+ turnHandling?: AgentTurnHandling;
1471
+ /** @deprecated Use `speko.webhooks.create()` after creating the agent. */
1472
+ webhooks?: AgentWebhooksCreate;
1473
+ /**
1474
+ * Post-call extraction schema on the agent itself — no webhook required.
1475
+ * Merged with `webhooks.postCall.extractionFields`; the agent-level
1476
+ * definition wins on a name collision.
1477
+ */
1478
+ extractionFields?: AgentExtractionField[];
1479
+ /** Declare the prompt's `{{variables}}` with per-agent defaults/descriptions. */
1480
+ promptVariables?: AgentPromptVariable[];
1481
+ }
1482
+
1483
+ export type AgentUpdateParams = Partial<Omit<AgentCreateParams, 'webhooks' | 'turnHandling'>> & {
1484
+ /** Set to null to clear all stored turn-taking overrides. */
1485
+ turnHandling?: AgentTurnHandling | null;
1486
+ /** @deprecated Use `speko.webhooks.update()` for organization-owned endpoints. */
1487
+ webhooks?: AgentWebhooksUpdate | null;
1488
+ /**
1489
+ * Post-call extraction schema on the agent itself — no webhook required.
1490
+ * Merged with `webhooks.postCall.extractionFields`; the agent-level
1491
+ * definition wins on a name collision.
1492
+ */
1493
+ /** `null` clears the schema. */
1494
+ extractionFields?: AgentExtractionField[] | null;
1495
+ };
1496
+
1497
+ // ─── Calls ───────────────────────────────────────────────────────────
1498
+
1499
+ export interface CallTranscriptEntry {
1500
+ id: string;
1501
+ index: number;
1502
+ source: 'user' | 'agent' | 'system';
1503
+ text: string;
1504
+ started_at: string;
1505
+ ended_at: string | null;
1506
+ provider: string | null;
1507
+ model: string | null;
1508
+ metadata: Record<string, unknown>;
1509
+ eou_ms?: number | null;
1510
+ llm_ttft_ms?: number | null;
1511
+ tts_ttfb_ms?: number | null;
1512
+ latency_status?: 'partial' | 'complete' | 'interrupted' | 'error' | null;
1513
+ conversational_latency_ms?: number | null;
1514
+ }
1515
+
1516
+ export interface CallCostLine {
1517
+ provider: string;
1518
+ metric: string;
1519
+ quantity: number;
1520
+ keySource: KeySource;
1521
+ costMicroUsd: string;
1522
+ }
1523
+
1524
+ export interface CallReportWebhookDelivery {
1525
+ endpointId: string;
1526
+ deliveryId: string;
1527
+ eventId: string;
1528
+ delivered: boolean;
1529
+ status: number | null;
1530
+ error: string | null;
1531
+ createdAt: string;
1532
+ }
1533
+
1534
+ export interface CallReport {
1535
+ session_id: string;
1536
+ organization_id: string;
1537
+ summary: string;
1538
+ outcome: string;
1539
+ structured_data: Record<string, unknown>;
1540
+ /**
1541
+ * Caller-defined extraction values, keyed by field name — the same object the
1542
+ * `call.report` webhook delivers. `{}` when the agent declares no fields.
1543
+ */
1544
+ custom_data: Record<string, unknown>;
1545
+ transcript: { entries: CallTranscriptEntry[] };
1546
+ cost_micro_usd: string;
1547
+ cost_breakdown: CallCostLine[];
1548
+ artifacts: Record<string, unknown>;
1549
+ metadata: Record<string, unknown>;
1550
+ scheduled_callback: ScheduledCallback | Record<string, unknown> | null;
1551
+ analysis_status: 'heuristic' | 'completed' | 'failed';
1552
+ analysis_provider: string | null;
1553
+ analysis_model: string | null;
1554
+ analysis_error: string | null;
1555
+ analysis_completed_at: string | null;
1556
+ /** @deprecated Aggregate retained through the current SDK major version. */
1557
+ post_call_webhook_status: 'not_configured' | 'pending' | 'delivered' | 'failed';
1558
+ /** @deprecated Aggregate retained through the current SDK major version. */
1559
+ post_call_webhook_attempts: number;
1560
+ /** @deprecated Aggregate retained through the current SDK major version. */
1561
+ post_call_webhook_next_retry_at: string | null;
1562
+ /** @deprecated Aggregate retained through the current SDK major version. */
1563
+ post_call_webhook_delivered_at: string | null;
1564
+ /** @deprecated Aggregate retained through the current SDK major version. */
1565
+ post_call_webhook_error: string | null;
1566
+ /** Canonical per-endpoint results; singular post-call fields are deprecated aggregates. */
1567
+ webhook_deliveries: CallReportWebhookDelivery[];
1568
+ created_at: string;
1569
+ updated_at: string;
1570
+ }
1571
+
1572
+ export type ScheduledCallbackStatus =
1573
+ | 'scheduled'
1574
+ | 'dispatching'
1575
+ | 'dispatched'
1576
+ | 'cancelled'
1577
+ | 'failed';
1578
+
1579
+ export interface ScheduledCallback {
1580
+ id: string;
1581
+ organization_id: string;
1582
+ source_session_id: string | null;
1583
+ created_session_id: string | null;
1584
+ agent_id: string | null;
1585
+ phone_number_id: string | null;
1586
+ to_number: string;
1587
+ from_number: string | null;
1588
+ scheduled_at: string;
1589
+ status: ScheduledCallbackStatus;
1590
+ reason: string | null;
1591
+ instructions: string | null;
1592
+ summary: string | null;
1593
+ pipeline_config: Record<string, unknown>;
1594
+ metadata: Record<string, unknown>;
1595
+ failure_cause: string | null;
1596
+ attempted_at: string | null;
1597
+ dispatched_at: string | null;
1598
+ cancelled_at: string | null;
1599
+ created_at: string;
1600
+ updated_at: string;
1601
+ }
1602
+
1603
+ export interface ScheduledCallbacksListParams {
1604
+ status?: ScheduledCallbackStatus;
1605
+ sourceSessionId?: string;
1606
+ limit?: number;
1607
+ }
1608
+
1609
+ export interface CancelScheduledCallbackParams {
1610
+ reason?: string;
1611
+ }
1612
+
1613
+ export interface FinalizeCallReportParams {
1614
+ forceAnalysis?: boolean;
1615
+ retryWebhook?: boolean;
1616
+ }
1617
+
1618
+ export interface FinalizeCallReportResult {
1619
+ session_id: string;
1620
+ summary: string;
1621
+ outcome: string;
1622
+ cost_micro_usd: string;
1623
+ /** @deprecated Aggregate retained through the current SDK major version. */
1624
+ webhook: unknown;
1625
+ webhook_deliveries: CallReportWebhookDelivery[];
1626
+ }
1627
+
1628
+ export interface CallRecording {
1629
+ url: string;
1630
+ }
1631
+
1632
+ export interface WebJoinParams {
1633
+ /** Display name other participants (and transcripts) see for the joiner. */
1634
+ displayName?: string;
1635
+ }
1636
+
1637
+ export interface WebJoinResult {
1638
+ /** LiveKit access token for the live call's room. Mint at click time — short TTL. */
1639
+ token: string;
1640
+ /** Public LiveKit URL the browser connects to (pass both to `@spekoai/client`). */
1641
+ url: string;
1642
+ /** Participant identity minted for this join (unique per join). */
1643
+ identity: string;
1644
+ roomName: string;
1645
+ /** ISO timestamp the token stops being accepted for NEW connections. */
1646
+ expiresAt: string;
1647
+ }
1648
+
1649
+ export interface EndCallResult {
1650
+ ok: true;
1651
+ /** `ending` when teardown was requested; `already_ended` when the call was over. */
1652
+ status: 'ending' | 'already_ended';
1653
+ /** ISO timestamp, present only with `already_ended`. */
1654
+ ended_at?: string;
1655
+ }
1656
+
1657
+ export interface CallEvent {
1658
+ id: string;
1659
+ session_id: string | null;
1660
+ organization_id: string;
1661
+ provider: 'livekit' | 'telnyx' | 'speko' | string;
1662
+ event_type: string;
1663
+ status: string | null;
1664
+ failure_cause: string | null;
1665
+ sip_status_code: number | null;
1666
+ sip_status: string | null;
1667
+ occurred_at: string;
1668
+ payload: Record<string, unknown>;
1669
+ created_at: string;
1670
+ }
1671
+
1672
+ export interface CallTransfer {
1673
+ id: string;
1674
+ session_id: string;
1675
+ organization_id: string;
1676
+ kind: 'blind' | 'warm';
1677
+ status: 'requested' | 'screening' | 'bridging' | 'completed' | 'failed' | 'cancelled';
1678
+ transfer_to: string;
1679
+ from_room_name: string | null;
1680
+ consultation_room_name: string | null;
1681
+ caller_participant_identity: string | null;
1682
+ recipient_participant_identity: string | null;
1683
+ outbound_trunk_id: string | null;
1684
+ screening_prompt: string | null;
1685
+ summary: string | null;
1686
+ failure_cause: string | null;
1687
+ metadata: Record<string, unknown>;
1688
+ created_at: string;
1689
+ updated_at: string;
1690
+ completed_at: string | null;
1691
+ }
1692
+
1693
+ export interface CallTransferResponse extends CallTransfer {
1694
+ routing_attempts?: (CallTransfer | null)[];
1695
+ next_transfer?: CallTransfer | null;
1696
+ fallback?: WarmTransferFallbackResult | null;
1697
+ }
1698
+
1699
+ export interface CallDetail {
1700
+ id: string;
1701
+ call_id: string;
1702
+ resource_uri: string;
1703
+ agent_id: string | null;
1704
+ status: string;
1705
+ kind: string;
1706
+ room_name: string | null;
1707
+ language: string;
1708
+ pipeline_config: Record<string, unknown>;
1709
+ metadata: Record<string, unknown>;
1710
+ created_at: string;
1711
+ updated_at: string;
1712
+ ended_at: string | null;
1713
+ duration_seconds: number | null;
1714
+ recording_status: string | null;
1715
+ recording_duration_ms: number | null;
1716
+ recording_resource_uri: string;
1717
+ report: CallReport | null;
1718
+ transfers: CallTransfer[];
1719
+ transcript: { entries: CallTranscriptEntry[] };
1720
+ span_tree: Record<string, unknown>;
1721
+ }
1722
+
1723
+ export interface BlindTransferParams {
1724
+ to: string;
1725
+ participantIdentity?: string;
1726
+ playDialtone?: boolean;
1727
+ ringingTimeout?: number;
1728
+ headers?: Record<string, string>;
1729
+ }
1730
+
1731
+ export interface WarmTransferDestination {
1732
+ to: string;
1733
+ label?: string;
1734
+ outboundTrunkId?: string;
1735
+ screeningPrompt?: string;
1736
+ summary?: string;
1737
+ metadata?: Record<string, unknown>;
1738
+ }
1739
+
1740
+ export interface WarmTransferFallback {
1741
+ strategy?: 'return_to_assistant' | 'take_message' | 'end_call';
1742
+ message?: string;
1743
+ takeMessagePrompt?: string;
1744
+ holdAudioUrl?: string;
1745
+ }
1746
+
1747
+ export interface WarmTransferVoicemailDetection {
1748
+ mode?: 'agent' | 'amd' | 'disabled';
1749
+ enabled?: boolean;
1750
+ timeoutSeconds?: number;
1751
+ }
1752
+
1753
+ export interface WarmTransferFallbackResult {
1754
+ action: 'return_to_assistant' | 'take_message' | 'end_call';
1755
+ message: string;
1756
+ take_message_prompt: string | null;
1757
+ hold_audio_url: string | null;
1758
+ voicemail_detected: boolean;
1759
+ }
1760
+
1761
+ export interface WarmTransferParams {
1762
+ to?: string;
1763
+ destinations?: WarmTransferDestination[];
1764
+ from?: string;
1765
+ participantIdentity?: string;
1766
+ outboundTrunkId?: string;
1767
+ screeningPrompt?: string;
1768
+ summary?: string;
1769
+ ringingTimeout?: number;
1770
+ waitUntilAnswered?: boolean;
1771
+ fallback?: WarmTransferFallback;
1772
+ voicemailDetection?: WarmTransferVoicemailDetection;
1773
+ metadata?: Record<string, unknown>;
1774
+ }
1775
+
1776
+ export interface CompleteWarmTransferParams {
1777
+ recipientParticipantIdentity?: string;
1778
+ summary?: string;
1779
+ }
1780
+
1781
+ export interface CancelWarmTransferParams {
1782
+ reason?: string;
1783
+ summary?: string;
1784
+ tryNext?: boolean;
1785
+ voicemailDetected?: boolean;
1786
+ }
1787
+
1788
+ export interface AgentCallListParams {
1789
+ /** Max rows. Default 50, server-capped at 100. */
1790
+ limit?: number;
1791
+ /** ISO timestamp returned as `next_cursor` from the previous page. */
1792
+ cursor?: string;
1793
+ /** ISO timestamp lower bound for calls to include. */
1794
+ since?: string;
1795
+ }
1796
+
1797
+ export interface AgentCallListEntry {
1798
+ id: string;
1799
+ call_id: string;
1800
+ resource_uri: string;
1801
+ agent_id: string;
1802
+ status: string;
1803
+ kind: string;
1804
+ room_name: string | null;
1805
+ language: string;
1806
+ created_at: string;
1807
+ ended_at: string | null;
1808
+ duration_seconds: number | null;
1809
+ recording_status: string | null;
1810
+ }
1811
+
1812
+ export interface AgentCallListPage {
1813
+ calls: AgentCallListEntry[];
1814
+ entries: AgentCallListEntry[];
1815
+ next_cursor: string | null;
1816
+ }
1817
+
1818
+ // ─── Agent tools ─────────────────────────────────────────────────────
1819
+
1820
+ export interface AgentToolSourceInline {
1821
+ kind: 'inline';
1822
+ }
1823
+
1824
+ /**
1825
+ * HTTP verb for a webhook tool. Omitting it means `POST`, so a tool
1826
+ * written before this field existed is unchanged.
1827
+ *
1828
+ * `GET` and `DELETE` send NO request body — not a `body` template and not
1829
+ * the default envelope either. Pairing one with `body` is rejected with
1830
+ * `422 WEBHOOK_TEMPLATE_INVALID` rather than silently dropping the body.
1831
+ */
1832
+ export type AgentToolWebhookMethod = 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'GET';
1833
+
1834
+ /**
1835
+ * JSON body template for a webhook tool.
1836
+ *
1837
+ * Omit it and Speko sends its fixed envelope —
1838
+ * `{tool, args, idempotency_key, session_id, tool_call_id}` — unchanged.
1839
+ * Supply one and it REPLACES that envelope, so a tool can post the shape
1840
+ * a third-party API actually wants.
1841
+ *
1842
+ * Every string leaf interpolates `{{name}}`: any session `variables` or
1843
+ * `toolSecrets` entry, plus these reserved names —
1844
+ *
1845
+ * | Name | Substitutes |
1846
+ * | --------------------- | ---------------------------------------------------- |
1847
+ * | `{{tool_name}}` | the tool's name |
1848
+ * | `{{session_id}}` | the session id |
1849
+ * | `{{tool_call_id}}` | the model's tool-call id |
1850
+ * | `{{idempotency_key}}` | `<session_id>:<tool_call_id>` |
1851
+ * | `{{args.<name>}}` | one argument, JSON type preserved |
1852
+ * | `{{args}}` | the whole arguments object — whole values only |
1853
+ *
1854
+ * Reserved names win over a session value of the same name. `{{args}}`
1855
+ * must be the ENTIRE value of a key; embedding it in a longer string is
1856
+ * rejected at write time instead of being JSON-stringified into it.
1857
+ *
1858
+ * Limits: 8 KB serialized, 8 levels of nesting, and no `__proto__`,
1859
+ * `constructor` or `prototype` key. A URL whose ORIGIN is templated may
1860
+ * not carry a body template at all — see the tool-calling guide.
1861
+ */
1862
+ export type AgentToolWebhookBody = Record<string, unknown> | unknown[];
1863
+
1864
+ /**
1865
+ * Webhook source as sent to {@link AgentTools.create}. The plaintext
1866
+ * `secret` is encrypted server-side; the returned row carries
1867
+ * `secretRef` instead.
1868
+ */
1869
+ export interface AgentToolSourceWebhookCreate {
1870
+ kind: 'webhook';
1871
+ url: string;
1872
+ /** Plaintext shared secret. Encrypted server-side at write time. */
1873
+ secret: string;
1874
+ headers?: Record<string, string>;
1875
+ /** Secret-referenced outbound auth headers (e.g. a Bearer token your endpoint requires). */
1876
+ authHeaders?: AgentWebhookAuthHeaderInput[];
1877
+ /** HTTP verb. Omit for `POST`. `GET`/`DELETE` send no body and reject `body`. */
1878
+ method?: AgentToolWebhookMethod;
1879
+ /** JSON body template replacing the default envelope. See {@link AgentToolWebhookBody}. */
1880
+ body?: AgentToolWebhookBody;
1881
+ timeoutMs?: number;
1882
+ }
1883
+
1884
+ /**
1885
+ * Webhook source as returned by the API. The plaintext secret never
1886
+ * leaves the server — only the {@link secretRef} pointer is exposed.
1887
+ */
1888
+ export interface AgentToolSourceWebhookSerialized {
1889
+ kind: 'webhook';
1890
+ url: string;
1891
+ /** Pointer into Speko's secrets store. */
1892
+ secretRef: string;
1893
+ headers?: Record<string, string>;
1894
+ /** Outbound auth-header pointers; values stay encrypted server-side. */
1895
+ authHeaders?: AgentWebhookAuthHeader[];
1896
+ /** Absent means `POST`. */
1897
+ method?: AgentToolWebhookMethod;
1898
+ /** The stored body template. Configuration, not a secret — returned as saved. */
1899
+ body?: AgentToolWebhookBody;
1900
+ timeoutMs?: number;
1901
+ }
1902
+
1903
+ export interface AgentToolSourceBuiltin {
1904
+ kind: 'builtin';
1905
+ name: string;
1906
+ config?: unknown;
1907
+ }
1908
+
1909
+ /**
1910
+ * Integration source — binds the tool to an org-installed Speko app action
1911
+ * (e.g. Google Calendar `create_event`). Speko resolves the installation and
1912
+ * runs the action server-side at completion time. The shape is identical on
1913
+ * create and in the serialized row (there is no secret to strip).
1914
+ */
1915
+ export interface AgentToolSourceIntegration {
1916
+ kind: 'integration';
1917
+ installationId: string;
1918
+ appKey: string;
1919
+ actionKey: string;
1920
+ config?: unknown;
1921
+ }
1922
+
1923
+ /**
1924
+ * Webhook source as sent to {@link AgentTools.update}. Unlike the create
1925
+ * shape, `secret` is optional: omit it to keep the existing encrypted secret
1926
+ * untouched, or supply a new one to rotate it.
1927
+ */
1928
+ export interface AgentToolSourceWebhookUpdate {
1929
+ kind: 'webhook';
1930
+ url: string;
1931
+ /** Plaintext shared secret. Omit to keep the existing stored secret; supply to rotate. */
1932
+ secret?: string;
1933
+ headers?: Record<string, string>;
1934
+ /** Secret-referenced outbound auth headers. Replaces the stored set; omit a `value` to keep it. */
1935
+ authHeaders?: AgentWebhookAuthHeaderInput[];
1936
+ /** HTTP verb. Omit for `POST`. `GET`/`DELETE` send no body and reject `body`. */
1937
+ method?: AgentToolWebhookMethod;
1938
+ /** JSON body template replacing the default envelope. See {@link AgentToolWebhookBody}. */
1939
+ body?: AgentToolWebhookBody;
1940
+ timeoutMs?: number;
1941
+ }
1942
+
1943
+ export type AgentToolSourceCreate =
1944
+ | AgentToolSourceInline
1945
+ | AgentToolSourceWebhookCreate
1946
+ | AgentToolSourceBuiltin
1947
+ | AgentToolSourceIntegration;
1948
+
1949
+ export type AgentToolSourceSerialized =
1950
+ | AgentToolSourceInline
1951
+ | AgentToolSourceWebhookSerialized
1952
+ | AgentToolSourceBuiltin
1953
+ | AgentToolSourceIntegration;
1954
+
1955
+ export type AgentToolSourceUpdate =
1956
+ | AgentToolSourceInline
1957
+ | AgentToolSourceWebhookUpdate
1958
+ | AgentToolSourceBuiltin
1959
+ | AgentToolSourceIntegration;
1960
+
1961
+ export interface AgentToolRow {
1962
+ id: string;
1963
+ agentId: string;
1964
+ name: string;
1965
+ description: string;
1966
+ parameters: Record<string, unknown>;
1967
+ source: AgentToolSourceSerialized;
1968
+ /** Spoken lead-in behavior before this tool executes. */
1969
+ preToolSpeech: ChatToolPreToolSpeech;
1970
+ createdAt: string;
1971
+ updatedAt: string;
1972
+ }
1973
+
1974
+ export interface AgentToolCreateParams {
1975
+ name: string;
1976
+ description: string;
1977
+ parameters: Record<string, unknown>;
1978
+ source: AgentToolSourceCreate;
1979
+ /** Spoken lead-in behavior before the tool executes. Defaults to `auto`. */
1980
+ preToolSpeech?: ChatToolPreToolSpeech;
1981
+ }
1982
+
1983
+ export interface AgentToolUpdateParams {
1984
+ description?: string;
1985
+ parameters?: Record<string, unknown>;
1986
+ source?: AgentToolSourceUpdate;
1987
+ preToolSpeech?: ChatToolPreToolSpeech;
1988
+ }
1989
+
1990
+ // ─── Knowledge bases ─────────────────────────────────────────────────
1991
+
1992
+ export interface KnowledgeBaseRow {
1993
+ id: string;
1994
+ organizationId: string;
1995
+ agentId: string;
1996
+ name: string;
1997
+ description: string | null;
1998
+ embeddingModel: string;
1999
+ documentCount: number;
2000
+ chunkCount: number;
2001
+ createdAt: string;
2002
+ updatedAt: string;
2003
+ }
2004
+
2005
+ export interface KnowledgeBaseCreateParams {
2006
+ agentId: string;
2007
+ name: string;
2008
+ description?: string;
2009
+ }
2010
+
2011
+ export interface KnowledgeBaseListParams {
2012
+ /** Filter to a single agent's KBs. */
2013
+ agentId?: string;
2014
+ }
2015
+
2016
+ export type KnowledgeBaseDocumentStatus = 'pending' | 'processing' | 'ready' | 'failed';
2017
+
2018
+ export interface KnowledgeBaseDocumentRow {
2019
+ id: string;
2020
+ knowledgeBaseId: string;
2021
+ filename: string;
2022
+ contentType: string;
2023
+ sizeBytes: number;
2024
+ status: KnowledgeBaseDocumentStatus;
2025
+ errorMessage: string | null;
2026
+ chunkCount: number;
2027
+ metadata: Record<string, unknown> | null;
2028
+ createdAt: string;
2029
+ updatedAt: string;
2030
+ ingestedAt: string | null;
2031
+ }
2032
+
2033
+ export interface KnowledgeBaseDocumentCreateParams {
2034
+ filename: string;
2035
+ /** MIME type. Currently the ingest pipeline accepts `text/plain` and `text/markdown` (plus `text/x-markdown`, `application/x-markdown`). */
2036
+ contentType: string;
2037
+ sizeBytes: number;
2038
+ metadata?: Record<string, unknown>;
2039
+ }
2040
+
2041
+ export interface KnowledgeBaseDocumentUploadSpec {
2042
+ /** Signed GCS URL valid for `expiresInSeconds` from issuance. */
2043
+ url: string;
2044
+ method: 'PUT';
2045
+ /** Headers that MUST be sent on the PUT (Content-Type, length-range, etc.). */
2046
+ headers: Record<string, string>;
2047
+ expiresInSeconds: number;
2048
+ }
2049
+
2050
+ export interface KnowledgeBaseDocumentCreateResult {
2051
+ document: KnowledgeBaseDocumentRow;
2052
+ upload: KnowledgeBaseDocumentUploadSpec;
2053
+ }
2054
+
2055
+ /**
2056
+ * Convenience parameter shape for {@link KnowledgeBases.uploadDocument}.
2057
+ * The wrapper computes `sizeBytes` from `data` automatically.
2058
+ */
2059
+ export interface KnowledgeBaseDocumentUploadParams {
2060
+ filename: string;
2061
+ contentType: string;
2062
+ data: ArrayBuffer | Uint8Array | Blob;
2063
+ metadata?: Record<string, unknown>;
2064
+ }
2065
+
2066
+ export interface KnowledgeBaseDocumentPollOptions {
2067
+ /** Polling interval in milliseconds. Default 2000. */
2068
+ intervalMs?: number;
2069
+ /** Total timeout in milliseconds. Default 120000 (2 min). */
2070
+ timeoutMs?: number;
2071
+ }
2072
+
2073
+ // --- Programmable voice -----------------------------------------------------
2074
+
2075
+ /**
2076
+ * Parameters for {@link CallControl.dial} — an outbound PSTN call placed by a
2077
+ * human broker, not by an AI agent. For an agent dial see
2078
+ * {@link VoiceDialParams}.
2079
+ */
2080
+ export interface CallControlDialParams {
2081
+ /** Destination in E.164 format (e.g. "+12015551234"). */
2082
+ to: string;
2083
+ /**
2084
+ * Caller ID to present, E.164. Must be a number your org owns; falls back to
2085
+ * the org's default outbound number when omitted.
2086
+ */
2087
+ from?: string;
2088
+ /** Opaque key/values stored on the call and echoed back on every read. */
2089
+ metadata?: Record<string, unknown>;
2090
+ }
2091
+
2092
+ /**
2093
+ * What {@link CallControl.dial} resolves to. The join credentials come back
2094
+ * *with* the call rather than from a second request, because the dialing
2095
+ * broker's softphone has to already be in the room when the far end answers —
2096
+ * fetch them afterwards and the first moments of the call are silence.
2097
+ *
2098
+ * Destructure both halves; `call` alone is not enough to be heard:
2099
+ *
2100
+ * ```ts
2101
+ * const { call, join } = await speko.callControl.dial({ to: '+12015551234' });
2102
+ * ```
2103
+ */
2104
+ export interface CallControlDialResult {
2105
+ /** The call and both of its legs — the `controlId`s every later command needs. */
2106
+ readonly call: CallResource;
2107
+ /** Room credentials for the dialing broker's own browser leg. */
2108
+ readonly join: CallJoinCredentials;
2109
+ }
2110
+
2111
+ /** Filters for {@link CallControl.list}. All optional; all AND-ed together. */
2112
+ export interface CallControlListParams {
2113
+ /**
2114
+ * Typed against the contract's `CallStatus` on purpose: the server validates
2115
+ * `?status=` against the same enum and rejects anything else, so a typo is a
2116
+ * compile error here instead of a `VALIDATION_ERROR` at runtime.
2117
+ */
2118
+ status?: CallStatus;
2119
+ direction?: CallDirection;
2120
+ /**
2121
+ * Only calls with a browser leg owned by this broker. Unlike dialing, reading
2122
+ * another broker's calls is allowed — a supervisor view is a legitimate use of
2123
+ * an org-scoped credential.
2124
+ */
2125
+ brokerId?: string;
2126
+ /** Newest first. Server-side default and cap apply. */
2127
+ limit?: number;
2128
+ }
2129
+
2130
+ // --- SMS messaging ---------------------------------------------------------
2131
+
2132
+ export type SmsMessageStatus =
2133
+ | 'queued'
2134
+ | 'scheduled'
2135
+ | 'submitting'
2136
+ | 'accepted'
2137
+ | 'sent'
2138
+ | 'delivered'
2139
+ | 'delivery_failed'
2140
+ | 'rejected'
2141
+ | 'submission_unknown'
2142
+ | 'canceled'
2143
+ | 'received';
2144
+ export type SmsMessageDirection = 'inbound' | 'outbound';
2145
+ export type SmsMessageOrigin = 'api' | 'dashboard' | 'agent_tool' | 'agent_auto_reply' | 'telnyx';
2146
+
2147
+ export interface SmsSegmentEstimate {
2148
+ readonly encoding: 'gsm7' | 'ucs2';
2149
+ readonly segments: number;
2150
+ readonly units: number;
2151
+ readonly per_segment: number;
2152
+ }
2153
+
2154
+ export interface SmsMessage {
2155
+ readonly id: string;
2156
+ readonly conversation_id: string;
2157
+ readonly batch_id: string | null;
2158
+ readonly from_phone_number_id: string;
2159
+ readonly direction: SmsMessageDirection;
2160
+ readonly origin: SmsMessageOrigin;
2161
+ readonly from: string;
2162
+ readonly to: string;
2163
+ readonly text: string | null;
2164
+ readonly campaign_id: string | null;
2165
+ readonly brand_id: string | null;
2166
+ readonly campaign_snapshot: Record<string, unknown> | null;
2167
+ readonly consent_id: string | null;
2168
+ readonly consent_basis: string | null;
2169
+ readonly recipient_timezone: string | null;
2170
+ readonly requested_send_at: string | null;
2171
+ readonly effective_send_at: string | null;
2172
+ readonly terminal_at: string | null;
2173
+ readonly status: SmsMessageStatus;
2174
+ readonly provider_status: string | null;
2175
+ readonly encoding: 'gsm7' | 'ucs2' | null;
2176
+ readonly estimated_segments: number;
2177
+ readonly segment_count: number;
2178
+ readonly estimated: SmsSegmentEstimate;
2179
+ readonly charged_micro_usd: string;
2180
+ readonly provider_cost_micro_usd: string | null;
2181
+ readonly metadata: Record<string, unknown>;
2182
+ readonly error: { readonly code: string; readonly detail: string | null } | null;
2183
+ readonly created_at: string;
2184
+ readonly updated_at: string;
2185
+ }
2186
+
2187
+ export interface SmsPage<T> {
2188
+ readonly data: T[];
2189
+ readonly next_cursor: string | null;
2190
+ }
2191
+
2192
+ export interface SmsSendParams {
2193
+ readonly from_phone_number_id: string;
2194
+ readonly to: string;
2195
+ readonly text: string;
2196
+ readonly idempotencyKey: string;
2197
+ readonly send_at?: string;
2198
+ readonly consent_id?: string | null;
2199
+ readonly recipient_timezone?: string | null;
2200
+ readonly metadata?: Record<string, unknown>;
2201
+ }
2202
+
2203
+ export interface SmsMessageListParams {
2204
+ readonly conversation_id?: string;
2205
+ readonly batch_id?: string;
2206
+ readonly from_phone_number_id?: string;
2207
+ readonly recipient?: string;
2208
+ readonly campaign_id?: string;
2209
+ readonly status?: SmsMessageStatus;
2210
+ readonly direction?: SmsMessageDirection;
2211
+ readonly origin?: SmsMessageOrigin;
2212
+ readonly created_after?: string;
2213
+ readonly created_before?: string;
2214
+ readonly cursor?: string;
2215
+ readonly limit?: number;
2216
+ }
2217
+
2218
+ export interface SmsBatchRecipient {
2219
+ readonly to: string;
2220
+ readonly text: string;
2221
+ readonly consent_id?: string | null;
2222
+ readonly recipient_timezone?: string | null;
2223
+ readonly metadata?: Record<string, unknown>;
2224
+ }
2225
+
2226
+ export interface SmsBatchCreateParams {
2227
+ readonly from_phone_number_id: string;
2228
+ readonly recipients: readonly SmsBatchRecipient[];
2229
+ readonly idempotencyKey: string;
2230
+ readonly send_at?: string;
2231
+ }
2232
+
2233
+ export interface SmsBatch {
2234
+ readonly id: string;
2235
+ readonly from_phone_number_id: string;
2236
+ readonly status:
2237
+ | 'queued'
2238
+ | 'scheduled'
2239
+ | 'processing'
2240
+ | 'completed'
2241
+ | 'partially_failed'
2242
+ | 'failed'
2243
+ | 'canceled';
2244
+ readonly requested_send_at: string | null;
2245
+ readonly total_count: number;
2246
+ readonly accepted_count: number;
2247
+ readonly rejected_count: number;
2248
+ readonly delivered_count: number;
2249
+ readonly failed_count: number;
2250
+ readonly canceled_at: string | null;
2251
+ readonly completed_at: string | null;
2252
+ readonly created_at: string;
2253
+ readonly updated_at: string;
2254
+ }
2255
+
2256
+ export interface SmsConversation {
2257
+ readonly id: string;
2258
+ readonly phone_number_id: string;
2259
+ readonly remote_phone_number: string;
2260
+ readonly campaign_id: string | null;
2261
+ readonly campaign_snapshot: Record<string, unknown> | null;
2262
+ readonly status: 'open' | 'closed' | 'spam';
2263
+ readonly automation_status: 'disabled' | 'enabled' | 'paused';
2264
+ readonly assigned_user_id: string | null;
2265
+ readonly assigned_agent_id: string | null;
2266
+ readonly unread_count: number;
2267
+ readonly recipient_timezone: string | null;
2268
+ readonly last_inbound_at: string | null;
2269
+ readonly last_outbound_at: string | null;
2270
+ readonly last_message_at: string;
2271
+ readonly content_redacted_at: string | null;
2272
+ readonly created_at: string;
2273
+ readonly updated_at: string;
2274
+ }
2275
+
2276
+ export interface SmsConversationListParams {
2277
+ readonly status?: SmsConversation['status'];
2278
+ readonly phone_number_id?: string;
2279
+ readonly assigned_user_id?: string;
2280
+ readonly assigned_agent_id?: string;
2281
+ readonly recipient?: string;
2282
+ readonly unread?: boolean;
2283
+ readonly cursor?: string;
2284
+ readonly limit?: number;
2285
+ }
2286
+
2287
+ export interface SmsConversationUpdate {
2288
+ readonly status?: SmsConversation['status'];
2289
+ readonly assigned_user_id?: string | null;
2290
+ readonly assigned_agent_id?: string | null;
2291
+ readonly automation_status?: SmsConversation['automation_status'];
2292
+ readonly recipient_timezone?: string | null;
2293
+ }
2294
+
2295
+ export interface SmsConversationSendParams {
2296
+ readonly text: string;
2297
+ readonly idempotencyKey: string;
2298
+ readonly send_at?: string;
2299
+ readonly consent_id?: string | null;
2300
+ readonly recipient_timezone?: string | null;
2301
+ readonly metadata?: Record<string, unknown>;
2302
+ }
2303
+
2304
+ export type SmsConsentSource =
2305
+ | 'inbound'
2306
+ | 'api'
2307
+ | 'keyword'
2308
+ | 'webform'
2309
+ | 'paper'
2310
+ | 'verbal'
2311
+ | 'import';
2312
+
2313
+ export interface SmsConsentInput {
2314
+ readonly recipient: string;
2315
+ readonly campaign_id: string;
2316
+ readonly source: SmsConsentSource;
2317
+ readonly proof_reference?: string | null;
2318
+ readonly proof?: string | null;
2319
+ readonly timezone?: string | null;
2320
+ readonly captured_at?: string;
2321
+ readonly expires_at?: string | null;
2322
+ readonly metadata?: Record<string, unknown>;
2323
+ }
2324
+
2325
+ export interface SmsConsent {
2326
+ readonly id: string;
2327
+ readonly recipient: string;
2328
+ readonly campaign_id: string;
2329
+ readonly status: 'active' | 'expired' | 'revoked';
2330
+ readonly source: SmsConsentSource;
2331
+ readonly proof_reference: string | null;
2332
+ readonly proof_hash: string | null;
2333
+ readonly timezone: string | null;
2334
+ readonly captured_at: string;
2335
+ readonly expires_at: string | null;
2336
+ readonly revoked_at: string | null;
2337
+ readonly revoked_reason: string | null;
2338
+ readonly metadata: Record<string, unknown>;
2339
+ readonly created_at: string;
2340
+ }
2341
+
2342
+ export interface SmsConsentListParams {
2343
+ readonly recipient?: string;
2344
+ readonly campaign_id?: string;
2345
+ readonly status?: SmsConsent['status'];
2346
+ readonly limit?: number;
2347
+ }
2348
+
2349
+ export interface SmsSuppression {
2350
+ readonly id: string;
2351
+ readonly recipient: string;
2352
+ readonly status: 'suppressed' | 'lifted';
2353
+ readonly keyword: string | null;
2354
+ readonly source_phone_number_id: string | null;
2355
+ readonly source_message_provider_id: string | null;
2356
+ readonly suppressed_at: string;
2357
+ readonly lifted_at: string | null;
2358
+ readonly updated_at: string;
2359
+ }
2360
+
2361
+ export interface SmsSettings {
2362
+ readonly messaging_profile_id: string | null;
2363
+ readonly messaging_profile_status: string;
2364
+ readonly webhook_config_version: number;
2365
+ readonly opt_out_config_version: number;
2366
+ readonly help_message: string;
2367
+ readonly opt_out_message: string;
2368
+ readonly opt_in_message: string;
2369
+ readonly retention_days: number;
2370
+ readonly quiet_hours_start: string;
2371
+ readonly quiet_hours_end: string;
2372
+ readonly default_timezone: string | null;
2373
+ readonly default_automation_enabled: boolean;
2374
+ readonly last_synced_at: string | null;
2375
+ readonly last_error: string | null;
2376
+ readonly updated_at: string;
2377
+ }
2378
+
2379
+ export type SmsSettingsUpdate = Partial<
2380
+ Pick<
2381
+ SmsSettings,
2382
+ | 'help_message'
2383
+ | 'opt_out_message'
2384
+ | 'opt_in_message'
2385
+ | 'retention_days'
2386
+ | 'quiet_hours_start'
2387
+ | 'quiet_hours_end'
2388
+ | 'default_timezone'
2389
+ | 'default_automation_enabled'
2390
+ >
2391
+ >;
2392
+
2393
+ export interface SmsConversationNote {
2394
+ readonly id: string;
2395
+ readonly conversation_id: string;
2396
+ readonly body: string | null;
2397
+ readonly created_by_user_id: string;
2398
+ readonly redacted_at: string | null;
2399
+ readonly created_at: string;
2400
+ }
2401
+
2402
+ export interface SmsStreamEvent {
2403
+ readonly event: string;
2404
+ readonly id?: string;
2405
+ readonly message_id: string;
2406
+ readonly conversation_id: string;
2407
+ readonly status: SmsMessageStatus;
2408
+ readonly occurred_at: string;
2409
+ }