supafone-labs 0.3.0 → 0.3.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.
package/src/index.ts CHANGED
@@ -9,9 +9,9 @@
9
9
  *
10
10
  * Works in Node 18+ (native fetch/WebSocket) and the browser.
11
11
  *
12
- * npm i @supafonesupafone-labs
12
+ * npm i supafone-labs
13
13
  *
14
- * import { Supafone } from "@supafonesupafone-labs";
14
+ * import { Supafone } from "supafone-labs";
15
15
  * const supafone = new Supafone({ apiKey: process.env.SUPAFONE_API_KEY! });
16
16
  * const agent = await supafone.labs.agents.createInboundWithNumber({
17
17
  * agentKey: "northline-intake",
@@ -21,7 +21,7 @@
21
21
  */
22
22
 
23
23
  export interface SupafoneLabsOptions {
24
- /** Your key from https:/supafone-labs.supafone.ai/get-key.html */
24
+ /** Your key from https://labs.supafone.ai/get-key.html */
25
25
  apiKey: string;
26
26
  /** Override the gateway (default: the hosted cloud). */
27
27
  baseUrl?: string;
@@ -75,11 +75,51 @@ export interface UsageToday {
75
75
  usage: Record<string, { used: number; cap: number }>;
76
76
  }
77
77
 
78
+ export interface LabsLogEntry {
79
+ id: number;
80
+ at: string;
81
+ endpoint: string;
82
+ seconds_billed: number;
83
+ duration_ms: number;
84
+ detail: string;
85
+ meta: Record<string, unknown>;
86
+ }
87
+
88
+ export interface StreamLogsOptions {
89
+ limit?: number;
90
+ afterId?: number;
91
+ pollMs?: number;
92
+ snapshot?: boolean;
93
+ signal?: AbortSignal;
94
+ }
95
+
78
96
  export type LabsAgentType = "phone" | "web" | "campaign";
79
97
  export type LabsAgentStyle = "inbound" | "outbound";
80
98
  export type LabsRuntimeMode = "multi_stage" | "single_stage";
81
99
  export type LabsTelephonyMode = "supafone_managed" | "byok";
82
- export type LabsTelephonyProvider = "supafone" | "twilio" | "telnyx" | "plivo" | "sip" | string;
100
+ export type LabsTelephonyProvider =
101
+ | "supafone"
102
+ | "twilio"
103
+ | "telnyx"
104
+ | "plivo"
105
+ | "signalwire"
106
+ | "sip"
107
+ | "custom_sip"
108
+ | "custom"
109
+ | string;
110
+ export type LabsNumberStrategy = "default_pool" | "dedicated" | "premium" | "byok" | string;
111
+
112
+ export interface LabsCallStage {
113
+ key?: string;
114
+ id?: string;
115
+ name: string;
116
+ goal?: string;
117
+ instructions?: string;
118
+ exitCriteria?: string[];
119
+ exit_criteria?: string[];
120
+ tools?: string[];
121
+ metadata?: Record<string, unknown>;
122
+ }
83
123
 
84
124
  export interface LabsVoiceSelection {
85
125
  provider?: string;
@@ -89,9 +129,51 @@ export interface LabsVoiceSelection {
89
129
  }
90
130
 
91
131
  export interface LabsProviderKeys {
132
+ /** Agent runtime/platform providers. */
92
133
  ultravox?: string;
93
134
  ultravoxApiKey?: string;
94
135
  ultravox_api_key?: string;
136
+ retell?: string;
137
+ retellApiKey?: string;
138
+ retell_api_key?: string;
139
+ vapi?: string;
140
+ vapiApiKey?: string;
141
+ vapi_api_key?: string;
142
+ bland?: string;
143
+ blandApiKey?: string;
144
+ bland_api_key?: string;
145
+ livekit?: string;
146
+ livekitApiKey?: string;
147
+ livekit_api_key?: string;
148
+ livekitApiSecret?: string;
149
+ livekit_api_secret?: string;
150
+ pipecat?: string;
151
+ pipecatApiKey?: string;
152
+ pipecat_api_key?: string;
153
+ /** Telephony/carrier providers. */
154
+ twilio?: string;
155
+ twilioAccountSid?: string;
156
+ twilio_account_sid?: string;
157
+ twilioAuthToken?: string;
158
+ twilio_auth_token?: string;
159
+ twilioApiKeySid?: string;
160
+ twilio_api_key_sid?: string;
161
+ twilioApiKeySecret?: string;
162
+ twilio_api_key_secret?: string;
163
+ telnyx?: string;
164
+ telnyxApiKey?: string;
165
+ telnyx_api_key?: string;
166
+ plivo?: string;
167
+ plivoAuthId?: string;
168
+ plivo_auth_id?: string;
169
+ plivoAuthToken?: string;
170
+ plivo_auth_token?: string;
171
+ signalwire?: string;
172
+ signalwireApiToken?: string;
173
+ signalwire_api_token?: string;
174
+ signalwireProjectId?: string;
175
+ signalwire_project_id?: string;
176
+ /** TTS providers. */
95
177
  elevenlabs?: string;
96
178
  elevenlabsApiKey?: string;
97
179
  elevenlabs_api_key?: string;
@@ -104,6 +186,65 @@ export interface LabsProviderKeys {
104
186
  deepgram?: string;
105
187
  deepgramApiKey?: string;
106
188
  deepgram_api_key?: string;
189
+ /** Brain/STT providers used by Labs watcher or customer BYOK stacks. */
190
+ anthropic?: string;
191
+ anthropicApiKey?: string;
192
+ anthropic_api_key?: string;
193
+ openai?: string;
194
+ openaiApiKey?: string;
195
+ openai_api_key?: string;
196
+ xai?: string;
197
+ xaiApiKey?: string;
198
+ xai_api_key?: string;
199
+ [extra: string]: unknown;
200
+ }
201
+
202
+ export interface LabsCustomSipConfig {
203
+ sipTrunkUri?: string;
204
+ sip_trunk_uri?: string;
205
+ trunkUri?: string;
206
+ trunk_uri?: string;
207
+ sipHost?: string;
208
+ sip_host?: string;
209
+ fromNumber?: string;
210
+ from_number?: string;
211
+ username?: string;
212
+ password?: string;
213
+ transport?: string;
214
+ headers?: Record<string, string>;
215
+ codecs?: string[];
216
+ dtmfMode?: string;
217
+ dtmf_mode?: string;
218
+ metadata?: Record<string, unknown>;
219
+ [extra: string]: unknown;
220
+ }
221
+
222
+ export interface LabsProviderByokConfig {
223
+ provider?: string;
224
+ apiKey?: string;
225
+ api_key?: string;
226
+ credentials?: Record<string, unknown>;
227
+ settings?: Record<string, unknown>;
228
+ model?: string;
229
+ voiceId?: string;
230
+ voice_id?: string;
231
+ [extra: string]: unknown;
232
+ }
233
+
234
+ export interface LabsByokConfig {
235
+ providerKeys?: LabsProviderKeys;
236
+ provider_keys?: LabsProviderKeys;
237
+ agentProvider?: LabsProviderByokConfig;
238
+ agent_provider?: LabsProviderByokConfig;
239
+ runtime?: LabsProviderByokConfig;
240
+ telephony?: LabsTelephonyConfig;
241
+ tts?: LabsProviderByokConfig;
242
+ stt?: LabsProviderByokConfig;
243
+ llm?: LabsProviderByokConfig;
244
+ customSip?: LabsCustomSipConfig;
245
+ custom_sip?: LabsCustomSipConfig;
246
+ sip?: LabsCustomSipConfig;
247
+ [extra: string]: unknown;
107
248
  }
108
249
 
109
250
  export interface LabsTelephonyCredentials {
@@ -119,6 +260,20 @@ export interface LabsTelephonyCredentials {
119
260
  auth_id?: string;
120
261
  connectionId?: string;
121
262
  connection_id?: string;
263
+ telnyxConnectionId?: string;
264
+ telnyx_connection_id?: string;
265
+ signalwireSpaceUrl?: string;
266
+ signalwire_space_url?: string;
267
+ projectId?: string;
268
+ project_id?: string;
269
+ applicationId?: string;
270
+ application_id?: string;
271
+ trunkId?: string;
272
+ trunk_id?: string;
273
+ endpointId?: string;
274
+ endpoint_id?: string;
275
+ token?: string;
276
+ secret?: string;
122
277
  fromNumber?: string;
123
278
  from_number?: string;
124
279
  sipTrunkUri?: string;
@@ -129,6 +284,9 @@ export interface LabsTelephonyCredentials {
129
284
  password?: string;
130
285
  webhookSecret?: string;
131
286
  webhook_secret?: string;
287
+ customSip?: LabsCustomSipConfig;
288
+ custom_sip?: LabsCustomSipConfig;
289
+ [extra: string]: unknown;
132
290
  }
133
291
 
134
292
  export interface LabsTelephonyConfig {
@@ -137,9 +295,22 @@ export interface LabsTelephonyConfig {
137
295
  /** Default is Supafone-managed; developers do not need Twilio for this path. */
138
296
  mode?: LabsTelephonyMode;
139
297
  provider?: LabsTelephonyProvider;
298
+ /** default_pool reuses idle Supafone numbers; dedicated/premium reserve a number-month. */
299
+ numberStrategy?: LabsNumberStrategy;
300
+ number_strategy?: LabsNumberStrategy;
301
+ numberPool?: string;
302
+ number_pool?: string;
303
+ numberId?: string;
304
+ number_id?: string;
305
+ premium?: boolean;
140
306
  label?: string;
141
307
  credentials?: LabsTelephonyCredentials;
308
+ providerSettings?: Record<string, unknown>;
309
+ provider_settings?: Record<string, unknown>;
310
+ customSip?: LabsCustomSipConfig;
311
+ custom_sip?: LabsCustomSipConfig;
142
312
  metadata?: Record<string, unknown>;
313
+ [extra: string]: unknown;
143
314
  }
144
315
 
145
316
  export interface LabsToolsConfig {
@@ -161,11 +332,61 @@ export interface LabsToolsConfig {
161
332
  custom_tools?: Array<Record<string, unknown>>;
162
333
  }
163
334
 
335
+ export interface LabsRecordingConfig {
336
+ enabled?: boolean;
337
+ recordAudio?: boolean;
338
+ record_audio?: boolean;
339
+ consentRequired?: boolean;
340
+ consent_required?: boolean;
341
+ announcement?: string;
342
+ retentionDays?: number;
343
+ retention_days?: number;
344
+ storage?: "supafone_managed" | "byok" | string;
345
+ redactPii?: boolean;
346
+ redact_pii?: boolean;
347
+ metadata?: Record<string, unknown>;
348
+ }
349
+
350
+ export interface LabsTranscriptionConfig {
351
+ enabled?: boolean;
352
+ provider?: string;
353
+ model?: string;
354
+ language?: string;
355
+ redactPii?: boolean;
356
+ redact_pii?: boolean;
357
+ diarization?: boolean;
358
+ timestamps?: boolean;
359
+ metadata?: Record<string, unknown>;
360
+ }
361
+
362
+ export interface LabsArtifactsConfig {
363
+ recordings?: boolean;
364
+ transcripts?: boolean;
365
+ summaries?: boolean;
366
+ qaReports?: boolean;
367
+ qa_reports?: boolean;
368
+ logs?: boolean;
369
+ webhooks?: boolean;
370
+ retentionDays?: number;
371
+ retention_days?: number;
372
+ metadata?: Record<string, unknown>;
373
+ }
374
+
164
375
  export interface LabsWatcherConfig {
165
376
  enabled?: boolean;
166
377
  voiceWatcher?: boolean;
167
378
  voice_watcher?: boolean;
379
+ apiKey?: string;
380
+ api_key?: string;
168
381
  model?: string;
382
+ mode?: "supafone_managed" | "byok" | string;
383
+ managedInfrastructure?: boolean;
384
+ managed_infrastructure?: boolean;
385
+ stt?: Record<string, unknown>;
386
+ llm?: Record<string, unknown>;
387
+ tts?: Record<string, unknown>;
388
+ providerKeys?: Record<string, unknown>;
389
+ provider_keys?: Record<string, unknown>;
169
390
  label?: string;
170
391
  }
171
392
 
@@ -211,6 +432,10 @@ export interface LabsUltravoxRuntime {
211
432
  retention_policy?: string;
212
433
  callTemplate?: Record<string, unknown>;
213
434
  call_template?: Record<string, unknown>;
435
+ customSip?: LabsCustomSipConfig;
436
+ custom_sip?: LabsCustomSipConfig;
437
+ sip?: LabsCustomSipConfig;
438
+ [extra: string]: unknown;
214
439
  }
215
440
 
216
441
  export interface CreateLabsAgentRequest {
@@ -234,11 +459,22 @@ export interface CreateLabsAgentRequest {
234
459
  website_url?: string;
235
460
  phoneNumber?: string;
236
461
  phone_number?: string;
462
+ numberStrategy?: LabsNumberStrategy;
463
+ number_strategy?: LabsNumberStrategy;
464
+ numberPool?: string;
465
+ number_pool?: string;
466
+ premium?: boolean;
237
467
  direction?: string;
238
468
  presetKey?: string;
239
469
  preset_key?: string;
240
470
  runtimeMode?: LabsRuntimeMode;
241
471
  runtime_mode?: LabsRuntimeMode;
472
+ /** Default true. When true, the SDK creates sensible call stages from prompt metadata. */
473
+ callStages?: boolean | LabsCallStage[];
474
+ call_stages?: boolean | LabsCallStage[];
475
+ stages?: LabsCallStage[];
476
+ autoCallStages?: boolean;
477
+ auto_call_stages?: boolean;
242
478
  goal?: string;
243
479
  greeting?: string;
244
480
  systemPrompt?: string;
@@ -247,8 +483,15 @@ export interface CreateLabsAgentRequest {
247
483
  voice?: LabsVoiceSelection;
248
484
  providerKeys?: LabsProviderKeys;
249
485
  provider_keys?: LabsProviderKeys;
250
- byok?: LabsProviderKeys;
486
+ byok?: LabsProviderKeys | LabsByokConfig;
251
487
  telephony?: LabsTelephonyConfig;
488
+ customSip?: LabsCustomSipConfig;
489
+ custom_sip?: LabsCustomSipConfig;
490
+ sip?: LabsCustomSipConfig;
491
+ recording?: LabsRecordingConfig;
492
+ transcription?: LabsTranscriptionConfig;
493
+ artifacts?: LabsArtifactsConfig;
494
+ compliance?: Record<string, unknown>;
252
495
  tools?: LabsToolsConfig;
253
496
  labs?: LabsWatcherConfig;
254
497
  ultravox?: LabsUltravoxRuntime;
@@ -267,6 +510,21 @@ export interface ListLabsAgentsOptions {
267
510
 
268
511
  export interface GetLabsAgentOptions extends ListLabsAgentsOptions {}
269
512
 
513
+ export interface DeleteLabsAgentOptions {
514
+ agencyId?: string;
515
+ agency_id?: string;
516
+ releaseNumbers?: boolean;
517
+ release_numbers?: boolean;
518
+ }
519
+
520
+ export interface DeleteLabsAgentResponse {
521
+ success?: boolean;
522
+ deleted?: boolean;
523
+ agent_key?: string;
524
+ released_numbers?: unknown[];
525
+ [extra: string]: unknown;
526
+ }
527
+
270
528
  export interface LabsCapabilitiesResponse {
271
529
  product: string;
272
530
  api_namespace: string;
@@ -299,9 +557,57 @@ export interface LabsVoiceListResponse {
299
557
  errors?: Record<string, string>;
300
558
  }
301
559
 
560
+ export interface LabsCallListOptions {
561
+ agencyId?: string;
562
+ agentKey?: string;
563
+ agent_key?: string;
564
+ limit?: number;
565
+ }
566
+
567
+ export interface LabsCallArtifact {
568
+ id?: string;
569
+ call_id?: string;
570
+ agent_key?: string;
571
+ status?: string;
572
+ started_at?: string;
573
+ duration_seconds?: number;
574
+ recording_url?: string;
575
+ transcript_url?: string;
576
+ [extra: string]: unknown;
577
+ }
578
+
579
+ export interface LabsCallListResponse {
580
+ calls: LabsCallArtifact[];
581
+ [extra: string]: unknown;
582
+ }
583
+
584
+ export interface LabsRecordingListOptions extends LabsCallListOptions {
585
+ callId?: string;
586
+ call_id?: string;
587
+ }
588
+
589
+ export interface LabsRecordingListResponse {
590
+ recordings: Array<Record<string, unknown>>;
591
+ [extra: string]: unknown;
592
+ }
593
+
594
+ export interface LabsTranscriptListOptions extends LabsCallListOptions {
595
+ callId?: string;
596
+ call_id?: string;
597
+ }
598
+
599
+ export interface LabsTranscriptListResponse {
600
+ transcripts: Array<Record<string, unknown>>;
601
+ [extra: string]: unknown;
602
+ }
603
+
302
604
  export interface LabsPhoneNumberSearchOptions {
303
605
  agencyId?: string;
304
606
  agency_id?: string;
607
+ numberPool?: string;
608
+ number_pool?: string;
609
+ numberStrategy?: LabsNumberStrategy;
610
+ number_strategy?: LabsNumberStrategy;
305
611
  countryCode?: string;
306
612
  country_code?: string;
307
613
  areaCode?: string;
@@ -376,6 +682,11 @@ export interface LabsPhoneNumberProvisionRequest {
376
682
  agent_name?: string;
377
683
  presetKey?: string;
378
684
  preset_key?: string;
685
+ numberStrategy?: LabsNumberStrategy;
686
+ number_strategy?: LabsNumberStrategy;
687
+ numberPool?: string;
688
+ number_pool?: string;
689
+ premium?: boolean;
379
690
  style?: LabsAgentStyle;
380
691
  agentStyle?: LabsAgentStyle;
381
692
  agent_style?: LabsAgentStyle;
@@ -386,6 +697,15 @@ export interface LabsPhoneNumberProvisionRequest {
386
697
 
387
698
  export interface LabsPhoneNumberAssignRequest extends Omit<LabsPhoneNumberProvisionRequest, "phoneNumber" | "phone_number" | "departmentId" | "department_id"> {}
388
699
 
700
+ export interface LabsPhoneNumberReleaseRequest {
701
+ agencyId?: string;
702
+ agency_id?: string;
703
+ reason?: string;
704
+ returnToPool?: boolean;
705
+ return_to_pool?: boolean;
706
+ metadata?: Record<string, unknown>;
707
+ }
708
+
389
709
  export interface LabsPhoneNumberProvisionResponse {
390
710
  success: boolean;
391
711
  number: LabsPhoneNumberRecord;
@@ -400,6 +720,14 @@ export interface LabsPhoneNumberAssignResponse {
400
720
  assignment?: Record<string, unknown>;
401
721
  }
402
722
 
723
+ export interface LabsPhoneNumberReleaseResponse {
724
+ success: boolean;
725
+ number?: LabsPhoneNumberRecord;
726
+ released?: boolean;
727
+ returned_to_pool?: boolean;
728
+ [extra: string]: unknown;
729
+ }
730
+
403
731
  export interface LabsPhoneNumberBuyAndAssignRequest extends LabsPhoneNumberProvisionRequest {
404
732
  search?: LabsPhoneNumberSearchOptions;
405
733
  }
@@ -616,7 +944,7 @@ export class SupafoneLabs {
616
944
  }
617
945
  }
618
946
 
619
- /** @internal Authenticated JSON request to the Supafone app API (`/api/v1supafone-labs/*`). */
947
+ /** @internal Authenticated JSON request to the Supafone app API (`/api/v1/labs/*`). */
620
948
  async requestSupafoneApi<T>(method: string, path: string, body?: unknown): Promise<T> {
621
949
  const ctrl = new AbortController();
622
950
  const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
@@ -685,6 +1013,14 @@ export class SupafoneLabs {
685
1013
  }
686
1014
  }
687
1015
 
1016
+ /** Convenience alias for voice preview UI/buttons. */
1017
+ previewVoice(
1018
+ voice = "supafone-labs-calm-en",
1019
+ text = "Hi, this is your Supafone agent voice preview.",
1020
+ ): Promise<Uint8Array> {
1021
+ return this.tts(text, voice);
1022
+ }
1023
+
688
1024
  /** Hosted STT for a finished audio clip — returns transcript + language tags. */
689
1025
  async stt(
690
1026
  audio: Uint8Array | ArrayBuffer,
@@ -742,10 +1078,47 @@ export class SupafoneLabs {
742
1078
  }
743
1079
 
744
1080
  /** The auditable whisper/billing log. */
745
- logs(limit = 100): Promise<{ logs: unknown[] }> {
1081
+ logs(limit = 100): Promise<{ logs: LabsLogEntry[] }> {
746
1082
  return this.request("GET", `/v1/logs?limit=${limit}`);
747
1083
  }
748
1084
 
1085
+ /** Live audit-log stream. Works in Node 18+ and browsers via fetch streaming. */
1086
+ async *streamLogs(opts: StreamLogsOptions = {}): AsyncGenerator<LabsLogEntry> {
1087
+ const q = new URLSearchParams({
1088
+ limit: String(opts.limit ?? 100),
1089
+ poll_ms: String(opts.pollMs ?? 1000),
1090
+ snapshot: String(opts.snapshot ?? true),
1091
+ });
1092
+ if (opts.afterId !== undefined) q.set("after_id", String(opts.afterId));
1093
+ const res = await fetch(`${this.baseUrl}/v1/logs/stream?${q}`, {
1094
+ method: "GET",
1095
+ signal: opts.signal,
1096
+ headers: { Authorization: `Bearer ${this.apiKey}` },
1097
+ });
1098
+ if (!res.ok) throw new SupafoneLabsError(`streamLogs: ${await res.text()}`, res.status);
1099
+ if (!res.body) throw new SupafoneLabsError("streamLogs: response body is not readable");
1100
+ const reader = res.body.getReader();
1101
+ const decoder = new TextDecoder();
1102
+ let buffer = "";
1103
+ try {
1104
+ for (;;) {
1105
+ const { done, value } = await reader.read();
1106
+ if (done) break;
1107
+ buffer += decoder.decode(value, { stream: true });
1108
+ let split = buffer.indexOf("\n\n");
1109
+ while (split >= 0) {
1110
+ const raw = buffer.slice(0, split);
1111
+ buffer = buffer.slice(split + 2);
1112
+ const parsed = parseSseLog(raw);
1113
+ if (parsed) yield parsed;
1114
+ split = buffer.indexOf("\n\n");
1115
+ }
1116
+ }
1117
+ } finally {
1118
+ reader.releaseLock();
1119
+ }
1120
+ }
1121
+
749
1122
  /** The structured whisper feed (what the console shows). */
750
1123
  nudges(limit = 50): Promise<{ nudges: unknown[] }> {
751
1124
  return this.request("GET", `/v1/nudges?limit=${limit}`);
@@ -780,6 +1153,11 @@ export class SupafoneLabs {
780
1153
  );
781
1154
  return d.voices.map((v) => (typeof v === "string" ? v : (v.voice ?? v.id ?? ""))).filter(Boolean);
782
1155
  }
1156
+
1157
+ /** Full hosted voice catalog with provider live/configured flags. */
1158
+ voiceCatalog(): Promise<{ voices: Array<Record<string, unknown>> }> {
1159
+ return this.request("GET", "/v1/voices");
1160
+ }
783
1161
  }
784
1162
 
785
1163
  export interface LiveTranscribeOptions {
@@ -856,6 +1234,9 @@ class LabsNamespace {
856
1234
  readonly voices: LabsVoicesNamespace;
857
1235
  readonly phoneNumbers: LabsPhoneNumbersNamespace;
858
1236
  readonly telephony: LabsTelephonyNamespace;
1237
+ readonly calls: LabsCallsNamespace;
1238
+ readonly recordings: LabsRecordingsNamespace;
1239
+ readonly transcripts: LabsTranscriptsNamespace;
859
1240
 
860
1241
  constructor(private sm: SupafoneLabs) {
861
1242
  this.agents = new LabsAgentsNamespace(sm);
@@ -864,11 +1245,14 @@ class LabsNamespace {
864
1245
  this.voices = new LabsVoicesNamespace(sm);
865
1246
  this.phoneNumbers = new LabsPhoneNumbersNamespace(sm);
866
1247
  this.telephony = new LabsTelephonyNamespace(sm);
1248
+ this.calls = new LabsCallsNamespace(sm);
1249
+ this.recordings = new LabsRecordingsNamespace(sm);
1250
+ this.transcripts = new LabsTranscriptsNamespace(sm);
867
1251
  }
868
1252
 
869
1253
  /** Discover the Supafone convenience layer over Ultravox. */
870
1254
  capabilities(): Promise<LabsCapabilitiesResponse> {
871
- return this.sm.requestSupafoneApi<LabsCapabilitiesResponse>("GET", "/api/v1supafone-labs/capabilities");
1255
+ return this.sm.requestSupafoneApi<LabsCapabilitiesResponse>("GET", "/api/v1/labs/capabilities");
872
1256
  }
873
1257
  }
874
1258
 
@@ -879,7 +1263,7 @@ class LabsAgentsNamespace {
879
1263
  create(input: CreateLabsAgentRequest): Promise<CreateLabsAgentResponse> {
880
1264
  return this.sm.requestSupafoneApi<CreateLabsAgentResponse>(
881
1265
  "POST",
882
- "/api/v1supafone-labs/agents",
1266
+ "/api/v1/labs/agents",
883
1267
  labsAgentPayload(input),
884
1268
  );
885
1269
  }
@@ -957,7 +1341,7 @@ class LabsAgentsNamespace {
957
1341
  if (opts.agentType) q.set("agent_type", opts.agentType);
958
1342
  if (opts.style) q.set("style", opts.style);
959
1343
  const suffix = q.toString() ? `?${q}` : "";
960
- return this.sm.requestSupafoneApi<ListLabsAgentsResponse>("GET", `/api/v1supafone-labs/agents${suffix}`);
1344
+ return this.sm.requestSupafoneApi<ListLabsAgentsResponse>("GET", `/api/v1/labs/agents${suffix}`);
961
1345
  }
962
1346
 
963
1347
  /** Fetch one durable agent by key. */
@@ -968,7 +1352,21 @@ class LabsAgentsNamespace {
968
1352
  const suffix = q.toString() ? `?${q}` : "";
969
1353
  return this.sm.requestSupafoneApi<GetLabsAgentResponse>(
970
1354
  "GET",
971
- `/api/v1supafone-labs/agents/${encodeURIComponent(agentKey)}${suffix}`,
1355
+ `/api/v1/labs/agents/${encodeURIComponent(agentKey)}${suffix}`,
1356
+ );
1357
+ }
1358
+
1359
+ /** Delete an agent. Optionally ask the backend to release assigned numbers. */
1360
+ delete(agentKey: string, opts: DeleteLabsAgentOptions = {}): Promise<DeleteLabsAgentResponse> {
1361
+ const q = new URLSearchParams();
1362
+ const agencyId = opts.agency_id ?? opts.agencyId;
1363
+ const releaseNumbers = opts.release_numbers ?? opts.releaseNumbers;
1364
+ if (agencyId) q.set("agency_id", agencyId);
1365
+ if (releaseNumbers !== undefined) q.set("release_numbers", String(releaseNumbers));
1366
+ const suffix = q.toString() ? `?${q}` : "";
1367
+ return this.sm.requestSupafoneApi<DeleteLabsAgentResponse>(
1368
+ "DELETE",
1369
+ `/api/v1/labs/agents/${encodeURIComponent(agentKey)}${suffix}`,
972
1370
  );
973
1371
  }
974
1372
  }
@@ -978,7 +1376,7 @@ class LabsPresetsNamespace {
978
1376
 
979
1377
  /** Out-of-the-box multistage agent presets. */
980
1378
  list(): Promise<LabsPresetListResponse> {
981
- return this.sm.requestSupafoneApi<LabsPresetListResponse>("GET", "/api/v1supafone-labs/presets");
1379
+ return this.sm.requestSupafoneApi<LabsPresetListResponse>("GET", "/api/v1/labs/presets");
982
1380
  }
983
1381
  }
984
1382
 
@@ -987,7 +1385,7 @@ class LabsToolsNamespace {
987
1385
 
988
1386
  /** Built-in tools Supafone agents can use. */
989
1387
  list(): Promise<LabsToolListResponse> {
990
- return this.sm.requestSupafoneApi<LabsToolListResponse>("GET", "/api/v1supafone-labs/tools");
1388
+ return this.sm.requestSupafoneApi<LabsToolListResponse>("GET", "/api/v1/labs/tools");
991
1389
  }
992
1390
  }
993
1391
 
@@ -999,7 +1397,70 @@ class LabsVoicesNamespace {
999
1397
  const q = new URLSearchParams();
1000
1398
  if (opts.provider) q.set("provider", opts.provider);
1001
1399
  const suffix = q.toString() ? `?${q}` : "";
1002
- return this.sm.requestSupafoneApi<LabsVoiceListResponse>("GET", `/api/v1supafone-labs/voices${suffix}`);
1400
+ return this.sm.requestSupafoneApi<LabsVoiceListResponse>("GET", `/api/v1/labs/voices${suffix}`);
1401
+ }
1402
+ }
1403
+
1404
+ class LabsCallsNamespace {
1405
+ constructor(private sm: SupafoneLabs) {}
1406
+
1407
+ list(opts: LabsCallListOptions = {}): Promise<LabsCallListResponse> {
1408
+ const q = hostedListQuery(opts);
1409
+ const suffix = q.toString() ? `?${q}` : "";
1410
+ return this.sm.requestSupafoneApi<LabsCallListResponse>("GET", `/api/v1/labs/calls${suffix}`);
1411
+ }
1412
+
1413
+ get(callId: string, opts: { agencyId?: string } = {}): Promise<Record<string, unknown>> {
1414
+ const q = new URLSearchParams();
1415
+ if (opts.agencyId) q.set("agency_id", opts.agencyId);
1416
+ const suffix = q.toString() ? `?${q}` : "";
1417
+ return this.sm.requestSupafoneApi("GET", `/api/v1/labs/calls/${encodeURIComponent(callId)}${suffix}`);
1418
+ }
1419
+ }
1420
+
1421
+ class LabsRecordingsNamespace {
1422
+ constructor(private sm: SupafoneLabs) {}
1423
+
1424
+ list(opts: LabsRecordingListOptions = {}): Promise<LabsRecordingListResponse> {
1425
+ const q = hostedListQuery(opts);
1426
+ const callId = opts.call_id ?? opts.callId;
1427
+ if (callId) q.set("call_id", callId);
1428
+ const suffix = q.toString() ? `?${q}` : "";
1429
+ return this.sm.requestSupafoneApi<LabsRecordingListResponse>("GET", `/api/v1/labs/recordings${suffix}`);
1430
+ }
1431
+
1432
+ get(recordingId: string, opts: { agencyId?: string } = {}): Promise<Record<string, unknown>> {
1433
+ const q = new URLSearchParams();
1434
+ if (opts.agencyId) q.set("agency_id", opts.agencyId);
1435
+ const suffix = q.toString() ? `?${q}` : "";
1436
+ return this.sm.requestSupafoneApi("GET", `/api/v1/labs/recordings/${encodeURIComponent(recordingId)}${suffix}`);
1437
+ }
1438
+
1439
+ delete(recordingId: string, opts: { agencyId?: string; reason?: string } = {}): Promise<Record<string, unknown>> {
1440
+ const q = new URLSearchParams();
1441
+ if (opts.agencyId) q.set("agency_id", opts.agencyId);
1442
+ if (opts.reason) q.set("reason", opts.reason);
1443
+ const suffix = q.toString() ? `?${q}` : "";
1444
+ return this.sm.requestSupafoneApi("DELETE", `/api/v1/labs/recordings/${encodeURIComponent(recordingId)}${suffix}`);
1445
+ }
1446
+ }
1447
+
1448
+ class LabsTranscriptsNamespace {
1449
+ constructor(private sm: SupafoneLabs) {}
1450
+
1451
+ list(opts: LabsTranscriptListOptions = {}): Promise<LabsTranscriptListResponse> {
1452
+ const q = hostedListQuery(opts);
1453
+ const callId = opts.call_id ?? opts.callId;
1454
+ if (callId) q.set("call_id", callId);
1455
+ const suffix = q.toString() ? `?${q}` : "";
1456
+ return this.sm.requestSupafoneApi<LabsTranscriptListResponse>("GET", `/api/v1/labs/transcripts${suffix}`);
1457
+ }
1458
+
1459
+ get(transcriptId: string, opts: { agencyId?: string } = {}): Promise<Record<string, unknown>> {
1460
+ const q = new URLSearchParams();
1461
+ if (opts.agencyId) q.set("agency_id", opts.agencyId);
1462
+ const suffix = q.toString() ? `?${q}` : "";
1463
+ return this.sm.requestSupafoneApi("GET", `/api/v1/labs/transcripts/${encodeURIComponent(transcriptId)}${suffix}`);
1003
1464
  }
1004
1465
  }
1005
1466
 
@@ -1014,7 +1475,7 @@ class LabsPhoneNumbersNamespace {
1014
1475
  const suffix = q.toString() ? `?${q}` : "";
1015
1476
  return this.sm.requestSupafoneApi<LabsPhoneNumberListResponse>(
1016
1477
  "GET",
1017
- `/api/v1supafone-labs/phone-numbers${suffix}`,
1478
+ `/api/v1/labs/phone-numbers${suffix}`,
1018
1479
  );
1019
1480
  }
1020
1481
 
@@ -1022,7 +1483,7 @@ class LabsPhoneNumbersNamespace {
1022
1483
  search(opts: LabsPhoneNumberSearchOptions = {}): Promise<LabsPhoneNumberSearchResponse> {
1023
1484
  return this.sm.requestSupafoneApi<LabsPhoneNumberSearchResponse>(
1024
1485
  "POST",
1025
- "/api/v1supafone-labs/phone-numbers/search",
1486
+ "/api/v1/labs/phone-numbers/search",
1026
1487
  phoneNumberSearchPayload(opts),
1027
1488
  );
1028
1489
  }
@@ -1031,7 +1492,7 @@ class LabsPhoneNumbersNamespace {
1031
1492
  buy(input: LabsPhoneNumberProvisionRequest): Promise<LabsPhoneNumberProvisionResponse> {
1032
1493
  return this.sm.requestSupafoneApi<LabsPhoneNumberProvisionResponse>(
1033
1494
  "POST",
1034
- "/api/v1supafone-labs/phone-numbers",
1495
+ "/api/v1/labs/phone-numbers",
1035
1496
  phoneNumberProvisionPayload({
1036
1497
  ...input,
1037
1498
  telephony: input.telephony ?? { mode: "supafone_managed", provider: "supafone" },
@@ -1043,11 +1504,47 @@ class LabsPhoneNumbersNamespace {
1043
1504
  assign(numberId: string, input: LabsPhoneNumberAssignRequest = {}): Promise<LabsPhoneNumberAssignResponse> {
1044
1505
  return this.sm.requestSupafoneApi<LabsPhoneNumberAssignResponse>(
1045
1506
  "POST",
1046
- `/api/v1supafone-labs/phone-numbers/${encodeURIComponent(numberId)}/assign`,
1507
+ `/api/v1/labs/phone-numbers/${encodeURIComponent(numberId)}/assign`,
1047
1508
  phoneNumberAssignPayload(input),
1048
1509
  );
1049
1510
  }
1050
1511
 
1512
+ /** Unassign a number from an agent but keep it reserved on the account. */
1513
+ unassign(numberId: string, input: LabsPhoneNumberReleaseRequest = {}): Promise<LabsPhoneNumberReleaseResponse> {
1514
+ return this.sm.requestSupafoneApi<LabsPhoneNumberReleaseResponse>(
1515
+ "POST",
1516
+ `/api/v1/labs/phone-numbers/${encodeURIComponent(numberId)}/unassign`,
1517
+ phoneNumberReleasePayload(input),
1518
+ );
1519
+ }
1520
+
1521
+ /** Give a number back to the shared pool or release the reservation. */
1522
+ release(numberId: string, input: LabsPhoneNumberReleaseRequest = {}): Promise<LabsPhoneNumberReleaseResponse> {
1523
+ return this.sm.requestSupafoneApi<LabsPhoneNumberReleaseResponse>(
1524
+ "POST",
1525
+ `/api/v1/labs/phone-numbers/${encodeURIComponent(numberId)}/release`,
1526
+ phoneNumberReleasePayload({ ...input, returnToPool: input.returnToPool ?? input.return_to_pool ?? true }),
1527
+ );
1528
+ }
1529
+
1530
+ /** Alias for release(numberId, { returnToPool: true }). */
1531
+ returnToPool(numberId: string, input: LabsPhoneNumberReleaseRequest = {}): Promise<LabsPhoneNumberReleaseResponse> {
1532
+ return this.release(numberId, { ...input, returnToPool: true });
1533
+ }
1534
+
1535
+ /** Delete/release a number reservation. Backend policy decides whether this is allowed. */
1536
+ delete(numberId: string, input: LabsPhoneNumberReleaseRequest = {}): Promise<LabsPhoneNumberReleaseResponse> {
1537
+ const q = new URLSearchParams();
1538
+ const agencyId = input.agency_id ?? input.agencyId;
1539
+ if (agencyId) q.set("agency_id", agencyId);
1540
+ const suffix = q.toString() ? `?${q}` : "";
1541
+ return this.sm.requestSupafoneApi<LabsPhoneNumberReleaseResponse>(
1542
+ "DELETE",
1543
+ `/api/v1/labs/phone-numbers/${encodeURIComponent(numberId)}${suffix}`,
1544
+ phoneNumberReleasePayload(input),
1545
+ );
1546
+ }
1547
+
1051
1548
  /**
1052
1549
  * Search if needed, buy the first matching Supafone-managed number, and assign
1053
1550
  * it to the supplied agent. This is the zero-Twilio-account happy path.
@@ -1081,14 +1578,14 @@ class LabsTelephonyNamespace {
1081
1578
  const q = new URLSearchParams();
1082
1579
  if (opts.agencyId) q.set("agency_id", opts.agencyId);
1083
1580
  const suffix = q.toString() ? `?${q}` : "";
1084
- return this.sm.requestSupafoneApi<LabsTelephonyResponse>("GET", `/api/v1supafone-labs/telephony${suffix}`);
1581
+ return this.sm.requestSupafoneApi<LabsTelephonyResponse>("GET", `/api/v1/labs/telephony${suffix}`);
1085
1582
  }
1086
1583
 
1087
1584
  /** Configure advanced BYOK telephony, or reset back to Supafone-managed. */
1088
1585
  configure(input: LabsTelephonyConfig): Promise<LabsTelephonyConfigureResponse> {
1089
1586
  return this.sm.requestSupafoneApi<LabsTelephonyConfigureResponse>(
1090
1587
  "PUT",
1091
- "/api/v1supafone-labs/telephony",
1588
+ "/api/v1/labs/telephony",
1092
1589
  telephonyPayload(input),
1093
1590
  );
1094
1591
  }
@@ -1159,6 +1656,16 @@ class OptimizerNamespace {
1159
1656
  standing(agent = "builder"): Promise<{ version: number; text: string }> {
1160
1657
  return this.sm.request("GET", `/v1/optimizer/standing?agent=${encodeURIComponent(agent)}`);
1161
1658
  }
1659
+ /** SSR grade distribution: five nominal levels folded into a real score distribution. */
1660
+ distribution(agent = "builder", limit = 500): Promise<{
1661
+ agent: string; calls: number; counts: Record<string, number>;
1662
+ mean_score: number; buckets: number[]; bucket_edges: number[];
1663
+ }> {
1664
+ return this.sm.request(
1665
+ "GET",
1666
+ `/v1/objective/distribution?agent=${encodeURIComponent(agent)}&limit=${limit}`,
1667
+ );
1668
+ }
1162
1669
  /** List the post-call reports behind the optimizer. */
1163
1670
  reports(agent = "builder", limit = 40): Promise<{ reports: unknown[] }> {
1164
1671
  return this.sm.request(
@@ -1180,17 +1687,26 @@ function labsAgentPayload(input: CreateLabsAgentRequest): Record<string, unknown
1180
1687
  industry: input.industry,
1181
1688
  website_url: input.website_url ?? input.websiteUrl,
1182
1689
  phone_number: input.phone_number ?? input.phoneNumber,
1690
+ number_strategy: input.number_strategy ?? input.numberStrategy,
1691
+ number_pool: input.number_pool ?? input.numberPool,
1692
+ premium: input.premium,
1183
1693
  direction: input.direction,
1184
1694
  preset_key: input.preset_key ?? input.presetKey,
1185
1695
  runtime_mode: input.runtime_mode ?? input.runtimeMode,
1696
+ call_stages: callStagesPayload(input),
1186
1697
  goal: input.goal,
1187
1698
  greeting: input.greeting,
1188
1699
  system_prompt: input.system_prompt ?? input.systemPrompt,
1189
1700
  language: input.language,
1190
1701
  voice: input.voice ? voicePayload(input.voice) : undefined,
1191
1702
  provider_keys: input.provider_keys ?? (input.providerKeys ? providerKeysPayload(input.providerKeys) : undefined),
1192
- byok: input.byok ? providerKeysPayload(input.byok) : undefined,
1703
+ byok: input.byok ? byokPayload(input.byok) : undefined,
1193
1704
  telephony: input.telephony ? telephonyPayload(input.telephony) : undefined,
1705
+ custom_sip: customSipPayload(input.custom_sip ?? input.customSip ?? input.sip),
1706
+ recording: input.recording ? recordingPayload(input.recording) : undefined,
1707
+ transcription: input.transcription ? transcriptionPayload(input.transcription) : undefined,
1708
+ artifacts: input.artifacts ? artifactsPayload(input.artifacts) : undefined,
1709
+ compliance: input.compliance,
1194
1710
  tools: input.tools ? toolsPayload(input.tools) : undefined,
1195
1711
  labs: input.labs ? labsPayload(input.labs) : undefined,
1196
1712
  ultravox: input.ultravox ? ultravoxPayload(input.ultravox) : undefined,
@@ -1200,13 +1716,28 @@ function labsAgentPayload(input: CreateLabsAgentRequest): Record<string, unknown
1200
1716
  });
1201
1717
  }
1202
1718
 
1719
+ function hostedListQuery(opts: LabsCallListOptions): URLSearchParams {
1720
+ const q = new URLSearchParams();
1721
+ if (opts.agencyId) q.set("agency_id", opts.agencyId);
1722
+ const agentKey = opts.agent_key ?? opts.agentKey;
1723
+ if (agentKey) q.set("agent_key", agentKey);
1724
+ if (opts.limit !== undefined) q.set("limit", String(opts.limit));
1725
+ return q;
1726
+ }
1727
+
1203
1728
  function telephonyPayload(input: LabsTelephonyConfig): Record<string, unknown> {
1204
1729
  return compact({
1205
1730
  agency_id: input.agency_id ?? input.agencyId,
1206
1731
  mode: input.mode,
1207
1732
  provider: input.provider,
1733
+ number_strategy: input.number_strategy ?? input.numberStrategy,
1734
+ number_pool: input.number_pool ?? input.numberPool,
1735
+ number_id: input.number_id ?? input.numberId,
1736
+ premium: input.premium,
1208
1737
  label: input.label,
1209
1738
  credentials: input.credentials ? telephonyCredentialsPayload(input.credentials) : undefined,
1739
+ provider_settings: input.provider_settings ?? input.providerSettings,
1740
+ custom_sip: customSipPayload(input.custom_sip ?? input.customSip),
1210
1741
  metadata: input.metadata,
1211
1742
  });
1212
1743
  }
@@ -1225,12 +1756,23 @@ function telephonyCredentialsPayload(input: LabsTelephonyCredentials): Record<st
1225
1756
  username: input.username,
1226
1757
  password: input.password,
1227
1758
  webhook_secret: input.webhook_secret ?? input.webhookSecret,
1759
+ telnyx_connection_id: input.telnyx_connection_id ?? input.telnyxConnectionId,
1760
+ signalwire_space_url: input.signalwire_space_url ?? input.signalwireSpaceUrl,
1761
+ project_id: input.project_id ?? input.projectId,
1762
+ application_id: input.application_id ?? input.applicationId,
1763
+ trunk_id: input.trunk_id ?? input.trunkId,
1764
+ endpoint_id: input.endpoint_id ?? input.endpointId,
1765
+ token: input.token,
1766
+ secret: input.secret,
1767
+ custom_sip: customSipPayload(input.custom_sip ?? input.customSip),
1228
1768
  });
1229
1769
  }
1230
1770
 
1231
1771
  function phoneNumberSearchPayload(input: LabsPhoneNumberSearchOptions): Record<string, unknown> {
1232
1772
  return compact({
1233
1773
  agency_id: input.agency_id ?? input.agencyId,
1774
+ number_pool: input.number_pool ?? input.numberPool,
1775
+ number_strategy: input.number_strategy ?? input.numberStrategy,
1234
1776
  country_code: input.country_code ?? input.countryCode,
1235
1777
  area_code: input.area_code ?? input.areaCode,
1236
1778
  postal_code: input.postal_code ?? input.postalCode,
@@ -1252,6 +1794,9 @@ function phoneNumberProvisionPayload(input: LabsPhoneNumberProvisionRequest): Re
1252
1794
  agent_id: input.agent_id ?? input.agentId,
1253
1795
  agent_name: input.agent_name ?? input.agentName,
1254
1796
  preset_key: input.preset_key ?? input.presetKey,
1797
+ number_strategy: input.number_strategy ?? input.numberStrategy,
1798
+ number_pool: input.number_pool ?? input.numberPool,
1799
+ premium: input.premium,
1255
1800
  style: input.agent_style ?? input.agentStyle ?? input.style,
1256
1801
  direction: input.direction,
1257
1802
  telephony: input.telephony ? telephonyPayload(input.telephony) : undefined,
@@ -1267,6 +1812,9 @@ function phoneNumberAssignPayload(input: LabsPhoneNumberAssignRequest): Record<s
1267
1812
  agent_name: input.agent_name ?? input.agentName,
1268
1813
  friendly_name: input.friendly_name ?? input.friendlyName,
1269
1814
  preset_key: input.preset_key ?? input.presetKey,
1815
+ number_strategy: input.number_strategy ?? input.numberStrategy,
1816
+ number_pool: input.number_pool ?? input.numberPool,
1817
+ premium: input.premium,
1270
1818
  style: input.agent_style ?? input.agentStyle ?? input.style,
1271
1819
  direction: input.direction,
1272
1820
  telephony: input.telephony ? telephonyPayload(input.telephony) : undefined,
@@ -1274,6 +1822,103 @@ function phoneNumberAssignPayload(input: LabsPhoneNumberAssignRequest): Record<s
1274
1822
  });
1275
1823
  }
1276
1824
 
1825
+ function phoneNumberReleasePayload(input: LabsPhoneNumberReleaseRequest): Record<string, unknown> {
1826
+ return compact({
1827
+ agency_id: input.agency_id ?? input.agencyId,
1828
+ reason: input.reason,
1829
+ return_to_pool: input.return_to_pool ?? input.returnToPool,
1830
+ metadata: input.metadata,
1831
+ });
1832
+ }
1833
+
1834
+ function callStagesPayload(input: CreateLabsAgentRequest): Record<string, unknown>[] | undefined {
1835
+ const explicit = input.call_stages ?? input.callStages ?? input.stages;
1836
+ const auto = input.auto_call_stages ?? input.autoCallStages;
1837
+ if (Array.isArray(explicit)) return explicit.map(callStagePayload);
1838
+ if (explicit === false || auto === false) return undefined;
1839
+ return generateCallStages(input).map(callStagePayload);
1840
+ }
1841
+
1842
+ function callStagePayload(input: LabsCallStage): Record<string, unknown> {
1843
+ return compact({
1844
+ key: input.key ?? input.id,
1845
+ name: input.name,
1846
+ goal: input.goal,
1847
+ instructions: input.instructions,
1848
+ exit_criteria: input.exit_criteria ?? input.exitCriteria,
1849
+ tools: input.tools,
1850
+ metadata: input.metadata,
1851
+ });
1852
+ }
1853
+
1854
+ export function generateCallStages(input: CreateLabsAgentRequest): LabsCallStage[] {
1855
+ const direction = String(input.direction ?? input.agent_style ?? input.agentStyle ?? input.style ?? "inbound").toLowerCase();
1856
+ const haystack = [
1857
+ input.name,
1858
+ input.assistant_name ?? input.assistantName,
1859
+ input.business_name ?? input.businessName,
1860
+ input.industry,
1861
+ input.goal,
1862
+ input.system_prompt ?? input.systemPrompt,
1863
+ input.preset_key ?? input.presetKey,
1864
+ ].filter(Boolean).join(" ").toLowerCase();
1865
+ const meta = { auto_generated: true, source: "supafone-labs-sdk" };
1866
+
1867
+ if (direction === "outbound" || haystack.includes("sales") || haystack.includes("lead")) {
1868
+ return [
1869
+ stage("intro_consent", "Intro and consent", "State who you are, why you are calling, and offer an immediate opt-out.", ["Caller understands purpose", "Opt-out is honored"], meta),
1870
+ stage("qualification", "Qualification", "Confirm fit, urgency, decision process, and the best next step.", ["Need and timeline are clear"], meta),
1871
+ stage("offer", "Offer", "Explain the next step in plain language without unsupported claims.", ["Caller understands the next step"], meta),
1872
+ stage("booking", "Booking", "Book or route the caller only after confirming required details.", ["Next step is confirmed by a tool or human handoff"], meta),
1873
+ stage("close", "Close", "Summarize what will happen next and end politely.", ["Caller knows the follow-up path"], meta),
1874
+ ];
1875
+ }
1876
+
1877
+ if (haystack.includes("legal") || haystack.includes("law") || haystack.includes("injury") || haystack.includes("intake")) {
1878
+ return [
1879
+ stage("greeting", "Greeting", "Open warmly and acknowledge the caller before logistics.", ["Caller need is understood"], meta),
1880
+ stage("incident", "Incident details", "Collect what happened, when it happened, injuries, insurance, and contact details.", ["Core facts are collected"], meta),
1881
+ stage("screening", "Screening", "Identify urgency, jurisdiction, conflicts, and whether human escalation is required.", ["Escalation decision is clear"], meta),
1882
+ stage("booking", "Consult booking", "Book the right next step without quoting fees or inventing availability.", ["Booking or handoff is tool-confirmed"], meta),
1883
+ stage("close", "Close", "Summarize the next step and set expectations accurately.", ["Caller knows exactly what happens next"], meta),
1884
+ ];
1885
+ }
1886
+
1887
+ if (haystack.includes("medical") || haystack.includes("clinic") || haystack.includes("patient") || haystack.includes("health")) {
1888
+ return [
1889
+ stage("greeting", "Greeting", "Identify the caller need and keep the tone calm and concise.", ["Caller need is understood"], meta),
1890
+ stage("patient_context", "Patient context", "Collect non-sensitive scheduling context and avoid medical advice.", ["Required scheduling context is collected"], meta),
1891
+ stage("routing", "Routing", "Route urgent, billing, clinical, and scheduling requests correctly.", ["Correct route is selected"], meta),
1892
+ stage("appointment", "Appointment", "Book or request the appointment only after confirming details.", ["Appointment path is confirmed"], meta),
1893
+ stage("close", "Close", "Recap next steps and any confirmed timing.", ["Caller knows the next step"], meta),
1894
+ ];
1895
+ }
1896
+
1897
+ return [
1898
+ stage("greeting", "Greeting", "Open naturally, identify the caller need, and set a helpful tone.", ["Caller need is understood"], meta),
1899
+ stage("discovery", "Discovery", "Ask one question at a time until the key details are clear.", ["Required details are collected"], meta),
1900
+ stage("resolution", "Resolution", "Answer approved questions or route to the right workflow.", ["Resolution path is selected"], meta),
1901
+ stage("action", "Action", "Use tools for booking, routing, messaging, or handoff before claiming success.", ["Action is confirmed by a tool or handoff"], meta),
1902
+ stage("close", "Close", "Summarize the outcome and next step accurately.", ["Caller knows what happens next"], meta),
1903
+ ];
1904
+ }
1905
+
1906
+ function stage(
1907
+ key: string,
1908
+ name: string,
1909
+ instructions: string,
1910
+ exitCriteria: string[],
1911
+ metadata: Record<string, unknown>,
1912
+ ): LabsCallStage {
1913
+ return {
1914
+ key,
1915
+ name,
1916
+ instructions,
1917
+ exitCriteria,
1918
+ metadata,
1919
+ };
1920
+ }
1921
+
1277
1922
  function voicePayload(input: LabsVoiceSelection): Record<string, unknown> {
1278
1923
  return compact({
1279
1924
  provider: input.provider,
@@ -1286,6 +1931,30 @@ function providerKeysPayload(input: LabsProviderKeys): Record<string, unknown> {
1286
1931
  return compact({
1287
1932
  ultravox: input.ultravox,
1288
1933
  ultravox_api_key: input.ultravox_api_key ?? input.ultravoxApiKey,
1934
+ retell: input.retell,
1935
+ retell_api_key: input.retell_api_key ?? input.retellApiKey,
1936
+ vapi: input.vapi,
1937
+ vapi_api_key: input.vapi_api_key ?? input.vapiApiKey,
1938
+ bland: input.bland,
1939
+ bland_api_key: input.bland_api_key ?? input.blandApiKey,
1940
+ livekit: input.livekit,
1941
+ livekit_api_key: input.livekit_api_key ?? input.livekitApiKey,
1942
+ livekit_api_secret: input.livekit_api_secret ?? input.livekitApiSecret,
1943
+ pipecat: input.pipecat,
1944
+ pipecat_api_key: input.pipecat_api_key ?? input.pipecatApiKey,
1945
+ twilio: input.twilio,
1946
+ twilio_account_sid: input.twilio_account_sid ?? input.twilioAccountSid,
1947
+ twilio_auth_token: input.twilio_auth_token ?? input.twilioAuthToken,
1948
+ twilio_api_key_sid: input.twilio_api_key_sid ?? input.twilioApiKeySid,
1949
+ twilio_api_key_secret: input.twilio_api_key_secret ?? input.twilioApiKeySecret,
1950
+ telnyx: input.telnyx,
1951
+ telnyx_api_key: input.telnyx_api_key ?? input.telnyxApiKey,
1952
+ plivo: input.plivo,
1953
+ plivo_auth_id: input.plivo_auth_id ?? input.plivoAuthId,
1954
+ plivo_auth_token: input.plivo_auth_token ?? input.plivoAuthToken,
1955
+ signalwire: input.signalwire,
1956
+ signalwire_api_token: input.signalwire_api_token ?? input.signalwireApiToken,
1957
+ signalwire_project_id: input.signalwire_project_id ?? input.signalwireProjectId,
1289
1958
  elevenlabs: input.elevenlabs,
1290
1959
  elevenlabs_api_key: input.elevenlabs_api_key ?? input.elevenlabsApiKey,
1291
1960
  cartesia: input.cartesia,
@@ -1294,6 +1963,59 @@ function providerKeysPayload(input: LabsProviderKeys): Record<string, unknown> {
1294
1963
  inworld_api_key: input.inworld_api_key ?? input.inworldApiKey,
1295
1964
  deepgram: input.deepgram,
1296
1965
  deepgram_api_key: input.deepgram_api_key ?? input.deepgramApiKey,
1966
+ anthropic: input.anthropic,
1967
+ anthropic_api_key: input.anthropic_api_key ?? input.anthropicApiKey,
1968
+ openai: input.openai,
1969
+ openai_api_key: input.openai_api_key ?? input.openaiApiKey,
1970
+ xai: input.xai,
1971
+ xai_api_key: input.xai_api_key ?? input.xaiApiKey,
1972
+ });
1973
+ }
1974
+
1975
+ function byokPayload(input: LabsProviderKeys | LabsByokConfig): Record<string, unknown> {
1976
+ const structured = input as LabsByokConfig;
1977
+ if (
1978
+ structured.agentProvider ||
1979
+ structured.agent_provider ||
1980
+ structured.runtime ||
1981
+ structured.telephony ||
1982
+ structured.tts ||
1983
+ structured.stt ||
1984
+ structured.llm ||
1985
+ structured.providerKeys ||
1986
+ structured.provider_keys ||
1987
+ structured.customSip ||
1988
+ structured.custom_sip ||
1989
+ structured.sip
1990
+ ) {
1991
+ return compact({
1992
+ provider_keys: providerKeysPayload(structured.provider_keys ?? structured.providerKeys ?? {}),
1993
+ agent_provider: providerConfigPayload(structured.agent_provider ?? structured.agentProvider ?? structured.runtime),
1994
+ telephony: structured.telephony ? telephonyPayload(structured.telephony) : undefined,
1995
+ tts: providerConfigPayload(structured.tts),
1996
+ stt: providerConfigPayload(structured.stt),
1997
+ llm: providerConfigPayload(structured.llm),
1998
+ custom_sip: customSipPayload(structured.custom_sip ?? structured.customSip ?? structured.sip),
1999
+ });
2000
+ }
2001
+ return providerKeysPayload(input as LabsProviderKeys);
2002
+ }
2003
+
2004
+ function providerConfigPayload(input?: LabsProviderByokConfig): Record<string, unknown> | undefined {
2005
+ if (!input) return undefined;
2006
+ const out: Record<string, unknown> = { ...input };
2007
+ delete out.apiKey;
2008
+ delete out.api_key;
2009
+ delete out.voiceId;
2010
+ delete out.voice_id;
2011
+ return compact({
2012
+ ...out,
2013
+ provider: input.provider,
2014
+ api_key: input.api_key ?? input.apiKey,
2015
+ credentials: input.credentials,
2016
+ settings: input.settings,
2017
+ model: input.model,
2018
+ voice_id: input.voice_id ?? input.voiceId,
1297
2019
  });
1298
2020
  }
1299
2021
 
@@ -1312,11 +2034,57 @@ function toolsPayload(input: LabsToolsConfig): Record<string, unknown> {
1312
2034
  });
1313
2035
  }
1314
2036
 
2037
+ function recordingPayload(input: LabsRecordingConfig): Record<string, unknown> {
2038
+ return compact({
2039
+ enabled: input.enabled,
2040
+ record_audio: input.record_audio ?? input.recordAudio,
2041
+ consent_required: input.consent_required ?? input.consentRequired,
2042
+ announcement: input.announcement,
2043
+ retention_days: input.retention_days ?? input.retentionDays,
2044
+ storage: input.storage,
2045
+ redact_pii: input.redact_pii ?? input.redactPii,
2046
+ metadata: input.metadata,
2047
+ });
2048
+ }
2049
+
2050
+ function transcriptionPayload(input: LabsTranscriptionConfig): Record<string, unknown> {
2051
+ return compact({
2052
+ enabled: input.enabled,
2053
+ provider: input.provider,
2054
+ model: input.model,
2055
+ language: input.language,
2056
+ redact_pii: input.redact_pii ?? input.redactPii,
2057
+ diarization: input.diarization,
2058
+ timestamps: input.timestamps,
2059
+ metadata: input.metadata,
2060
+ });
2061
+ }
2062
+
2063
+ function artifactsPayload(input: LabsArtifactsConfig): Record<string, unknown> {
2064
+ return compact({
2065
+ recordings: input.recordings,
2066
+ transcripts: input.transcripts,
2067
+ summaries: input.summaries,
2068
+ qa_reports: input.qa_reports ?? input.qaReports,
2069
+ logs: input.logs,
2070
+ webhooks: input.webhooks,
2071
+ retention_days: input.retention_days ?? input.retentionDays,
2072
+ metadata: input.metadata,
2073
+ });
2074
+ }
2075
+
1315
2076
  function labsPayload(input: LabsWatcherConfig): Record<string, unknown> {
1316
2077
  return compact({
1317
2078
  enabled: input.enabled,
1318
2079
  voice_watcher: input.voice_watcher ?? input.voiceWatcher,
2080
+ api_key: input.api_key ?? input.apiKey,
1319
2081
  model: input.model,
2082
+ mode: input.mode,
2083
+ managed_infrastructure: input.managed_infrastructure ?? input.managedInfrastructure,
2084
+ stt: input.stt,
2085
+ llm: input.llm,
2086
+ tts: input.tts,
2087
+ provider_keys: input.provider_keys ?? input.providerKeys,
1320
2088
  label: input.label,
1321
2089
  });
1322
2090
  }
@@ -1346,6 +2114,24 @@ function ultravoxPayload(input: LabsUltravoxRuntime): Record<string, unknown> {
1346
2114
  voiceOverrides: input.voiceOverrides ?? input.voice_overrides,
1347
2115
  retentionPolicy: input.retentionPolicy ?? input.retention_policy,
1348
2116
  callTemplate: input.callTemplate ?? input.call_template,
2117
+ custom_sip: customSipPayload(input.custom_sip ?? input.customSip ?? input.sip),
2118
+ });
2119
+ }
2120
+
2121
+ function customSipPayload(input?: LabsCustomSipConfig): Record<string, unknown> | undefined {
2122
+ if (!input) return undefined;
2123
+ return compact({
2124
+ sip_trunk_uri: input.sip_trunk_uri ?? input.sipTrunkUri,
2125
+ trunk_uri: input.trunk_uri ?? input.trunkUri,
2126
+ sip_host: input.sip_host ?? input.sipHost,
2127
+ from_number: input.from_number ?? input.fromNumber,
2128
+ username: input.username,
2129
+ password: input.password,
2130
+ transport: input.transport,
2131
+ headers: input.headers,
2132
+ codecs: input.codecs,
2133
+ dtmf_mode: input.dtmf_mode ?? input.dtmfMode,
2134
+ metadata: input.metadata,
1349
2135
  });
1350
2136
  }
1351
2137
 
@@ -1357,6 +2143,18 @@ function compact(input: Record<string, unknown>): Record<string, unknown> {
1357
2143
  return out;
1358
2144
  }
1359
2145
 
2146
+ function parseSseLog(raw: string): LabsLogEntry | null {
2147
+ const data: string[] = [];
2148
+ for (const line of raw.split(/\r?\n/)) {
2149
+ if (!line || line.startsWith(":")) continue;
2150
+ if (line.startsWith("data:")) data.push(line.slice(5).trimStart());
2151
+ }
2152
+ if (!data.length) return null;
2153
+ const parsed = safeJson(data.join("\n"));
2154
+ if (!parsed || typeof parsed !== "object") return null;
2155
+ return parsed as LabsLogEntry;
2156
+ }
2157
+
1360
2158
  function safeJson(text: string): unknown {
1361
2159
  try {
1362
2160
  return JSON.parse(text);