supafone-labs 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -1
- package/dist/cjs/index.d.ts +529 -4
- package/dist/cjs/index.js +660 -6
- package/dist/index.d.ts +529 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +659 -6
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/index.ts +1120 -8
package/src/index.ts
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
export interface SupafoneLabsOptions {
|
|
24
24
|
/** Your key from https://labs.supafone.ai/get-key.html */
|
|
25
|
-
apiKey
|
|
25
|
+
apiKey?: string;
|
|
26
26
|
/** Override the gateway (default: the hosted cloud). */
|
|
27
27
|
baseUrl?: string;
|
|
28
28
|
/** Supafone app/API key for hosted agent provisioning. Defaults to apiKey. */
|
|
@@ -33,6 +33,17 @@ export interface SupafoneLabsOptions {
|
|
|
33
33
|
timeoutMs?: number;
|
|
34
34
|
/** Optional pre-obtained session token (else use login()). */
|
|
35
35
|
sessionToken?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Account (app.supafone.ai) auth — powers campaigns + real calls. Pass a
|
|
38
|
+
* JWT directly, or accountEmail + accountPassword and the client logs in
|
|
39
|
+
* lazily (and re-logs-in once when the token expires). With account auth
|
|
40
|
+
* present, apiKey becomes optional.
|
|
41
|
+
*/
|
|
42
|
+
accountToken?: string;
|
|
43
|
+
accountEmail?: string;
|
|
44
|
+
accountPassword?: string;
|
|
45
|
+
/** Portal base for listen/monitor links (default https://app.supafone.ai). */
|
|
46
|
+
appUrl?: string;
|
|
36
47
|
}
|
|
37
48
|
|
|
38
49
|
export interface ChatMessage {
|
|
@@ -75,11 +86,51 @@ export interface UsageToday {
|
|
|
75
86
|
usage: Record<string, { used: number; cap: number }>;
|
|
76
87
|
}
|
|
77
88
|
|
|
89
|
+
export interface LabsLogEntry {
|
|
90
|
+
id: number;
|
|
91
|
+
at: string;
|
|
92
|
+
endpoint: string;
|
|
93
|
+
seconds_billed: number;
|
|
94
|
+
duration_ms: number;
|
|
95
|
+
detail: string;
|
|
96
|
+
meta: Record<string, unknown>;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface StreamLogsOptions {
|
|
100
|
+
limit?: number;
|
|
101
|
+
afterId?: number;
|
|
102
|
+
pollMs?: number;
|
|
103
|
+
snapshot?: boolean;
|
|
104
|
+
signal?: AbortSignal;
|
|
105
|
+
}
|
|
106
|
+
|
|
78
107
|
export type LabsAgentType = "phone" | "web" | "campaign";
|
|
79
108
|
export type LabsAgentStyle = "inbound" | "outbound";
|
|
80
109
|
export type LabsRuntimeMode = "multi_stage" | "single_stage";
|
|
81
110
|
export type LabsTelephonyMode = "supafone_managed" | "byok";
|
|
82
|
-
export type LabsTelephonyProvider =
|
|
111
|
+
export type LabsTelephonyProvider =
|
|
112
|
+
| "supafone"
|
|
113
|
+
| "twilio"
|
|
114
|
+
| "telnyx"
|
|
115
|
+
| "plivo"
|
|
116
|
+
| "signalwire"
|
|
117
|
+
| "sip"
|
|
118
|
+
| "custom_sip"
|
|
119
|
+
| "custom"
|
|
120
|
+
| string;
|
|
121
|
+
export type LabsNumberStrategy = "default_pool" | "dedicated" | "premium" | "byok" | string;
|
|
122
|
+
|
|
123
|
+
export interface LabsCallStage {
|
|
124
|
+
key?: string;
|
|
125
|
+
id?: string;
|
|
126
|
+
name: string;
|
|
127
|
+
goal?: string;
|
|
128
|
+
instructions?: string;
|
|
129
|
+
exitCriteria?: string[];
|
|
130
|
+
exit_criteria?: string[];
|
|
131
|
+
tools?: string[];
|
|
132
|
+
metadata?: Record<string, unknown>;
|
|
133
|
+
}
|
|
83
134
|
|
|
84
135
|
export interface LabsVoiceSelection {
|
|
85
136
|
provider?: string;
|
|
@@ -89,9 +140,51 @@ export interface LabsVoiceSelection {
|
|
|
89
140
|
}
|
|
90
141
|
|
|
91
142
|
export interface LabsProviderKeys {
|
|
143
|
+
/** Agent runtime/platform providers. */
|
|
92
144
|
ultravox?: string;
|
|
93
145
|
ultravoxApiKey?: string;
|
|
94
146
|
ultravox_api_key?: string;
|
|
147
|
+
retell?: string;
|
|
148
|
+
retellApiKey?: string;
|
|
149
|
+
retell_api_key?: string;
|
|
150
|
+
vapi?: string;
|
|
151
|
+
vapiApiKey?: string;
|
|
152
|
+
vapi_api_key?: string;
|
|
153
|
+
bland?: string;
|
|
154
|
+
blandApiKey?: string;
|
|
155
|
+
bland_api_key?: string;
|
|
156
|
+
livekit?: string;
|
|
157
|
+
livekitApiKey?: string;
|
|
158
|
+
livekit_api_key?: string;
|
|
159
|
+
livekitApiSecret?: string;
|
|
160
|
+
livekit_api_secret?: string;
|
|
161
|
+
pipecat?: string;
|
|
162
|
+
pipecatApiKey?: string;
|
|
163
|
+
pipecat_api_key?: string;
|
|
164
|
+
/** Telephony/carrier providers. */
|
|
165
|
+
twilio?: string;
|
|
166
|
+
twilioAccountSid?: string;
|
|
167
|
+
twilio_account_sid?: string;
|
|
168
|
+
twilioAuthToken?: string;
|
|
169
|
+
twilio_auth_token?: string;
|
|
170
|
+
twilioApiKeySid?: string;
|
|
171
|
+
twilio_api_key_sid?: string;
|
|
172
|
+
twilioApiKeySecret?: string;
|
|
173
|
+
twilio_api_key_secret?: string;
|
|
174
|
+
telnyx?: string;
|
|
175
|
+
telnyxApiKey?: string;
|
|
176
|
+
telnyx_api_key?: string;
|
|
177
|
+
plivo?: string;
|
|
178
|
+
plivoAuthId?: string;
|
|
179
|
+
plivo_auth_id?: string;
|
|
180
|
+
plivoAuthToken?: string;
|
|
181
|
+
plivo_auth_token?: string;
|
|
182
|
+
signalwire?: string;
|
|
183
|
+
signalwireApiToken?: string;
|
|
184
|
+
signalwire_api_token?: string;
|
|
185
|
+
signalwireProjectId?: string;
|
|
186
|
+
signalwire_project_id?: string;
|
|
187
|
+
/** TTS providers. */
|
|
95
188
|
elevenlabs?: string;
|
|
96
189
|
elevenlabsApiKey?: string;
|
|
97
190
|
elevenlabs_api_key?: string;
|
|
@@ -104,6 +197,65 @@ export interface LabsProviderKeys {
|
|
|
104
197
|
deepgram?: string;
|
|
105
198
|
deepgramApiKey?: string;
|
|
106
199
|
deepgram_api_key?: string;
|
|
200
|
+
/** Brain/STT providers used by Labs watcher or customer BYOK stacks. */
|
|
201
|
+
anthropic?: string;
|
|
202
|
+
anthropicApiKey?: string;
|
|
203
|
+
anthropic_api_key?: string;
|
|
204
|
+
openai?: string;
|
|
205
|
+
openaiApiKey?: string;
|
|
206
|
+
openai_api_key?: string;
|
|
207
|
+
xai?: string;
|
|
208
|
+
xaiApiKey?: string;
|
|
209
|
+
xai_api_key?: string;
|
|
210
|
+
[extra: string]: unknown;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export interface LabsCustomSipConfig {
|
|
214
|
+
sipTrunkUri?: string;
|
|
215
|
+
sip_trunk_uri?: string;
|
|
216
|
+
trunkUri?: string;
|
|
217
|
+
trunk_uri?: string;
|
|
218
|
+
sipHost?: string;
|
|
219
|
+
sip_host?: string;
|
|
220
|
+
fromNumber?: string;
|
|
221
|
+
from_number?: string;
|
|
222
|
+
username?: string;
|
|
223
|
+
password?: string;
|
|
224
|
+
transport?: string;
|
|
225
|
+
headers?: Record<string, string>;
|
|
226
|
+
codecs?: string[];
|
|
227
|
+
dtmfMode?: string;
|
|
228
|
+
dtmf_mode?: string;
|
|
229
|
+
metadata?: Record<string, unknown>;
|
|
230
|
+
[extra: string]: unknown;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export interface LabsProviderByokConfig {
|
|
234
|
+
provider?: string;
|
|
235
|
+
apiKey?: string;
|
|
236
|
+
api_key?: string;
|
|
237
|
+
credentials?: Record<string, unknown>;
|
|
238
|
+
settings?: Record<string, unknown>;
|
|
239
|
+
model?: string;
|
|
240
|
+
voiceId?: string;
|
|
241
|
+
voice_id?: string;
|
|
242
|
+
[extra: string]: unknown;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export interface LabsByokConfig {
|
|
246
|
+
providerKeys?: LabsProviderKeys;
|
|
247
|
+
provider_keys?: LabsProviderKeys;
|
|
248
|
+
agentProvider?: LabsProviderByokConfig;
|
|
249
|
+
agent_provider?: LabsProviderByokConfig;
|
|
250
|
+
runtime?: LabsProviderByokConfig;
|
|
251
|
+
telephony?: LabsTelephonyConfig;
|
|
252
|
+
tts?: LabsProviderByokConfig;
|
|
253
|
+
stt?: LabsProviderByokConfig;
|
|
254
|
+
llm?: LabsProviderByokConfig;
|
|
255
|
+
customSip?: LabsCustomSipConfig;
|
|
256
|
+
custom_sip?: LabsCustomSipConfig;
|
|
257
|
+
sip?: LabsCustomSipConfig;
|
|
258
|
+
[extra: string]: unknown;
|
|
107
259
|
}
|
|
108
260
|
|
|
109
261
|
export interface LabsTelephonyCredentials {
|
|
@@ -119,6 +271,20 @@ export interface LabsTelephonyCredentials {
|
|
|
119
271
|
auth_id?: string;
|
|
120
272
|
connectionId?: string;
|
|
121
273
|
connection_id?: string;
|
|
274
|
+
telnyxConnectionId?: string;
|
|
275
|
+
telnyx_connection_id?: string;
|
|
276
|
+
signalwireSpaceUrl?: string;
|
|
277
|
+
signalwire_space_url?: string;
|
|
278
|
+
projectId?: string;
|
|
279
|
+
project_id?: string;
|
|
280
|
+
applicationId?: string;
|
|
281
|
+
application_id?: string;
|
|
282
|
+
trunkId?: string;
|
|
283
|
+
trunk_id?: string;
|
|
284
|
+
endpointId?: string;
|
|
285
|
+
endpoint_id?: string;
|
|
286
|
+
token?: string;
|
|
287
|
+
secret?: string;
|
|
122
288
|
fromNumber?: string;
|
|
123
289
|
from_number?: string;
|
|
124
290
|
sipTrunkUri?: string;
|
|
@@ -129,6 +295,9 @@ export interface LabsTelephonyCredentials {
|
|
|
129
295
|
password?: string;
|
|
130
296
|
webhookSecret?: string;
|
|
131
297
|
webhook_secret?: string;
|
|
298
|
+
customSip?: LabsCustomSipConfig;
|
|
299
|
+
custom_sip?: LabsCustomSipConfig;
|
|
300
|
+
[extra: string]: unknown;
|
|
132
301
|
}
|
|
133
302
|
|
|
134
303
|
export interface LabsTelephonyConfig {
|
|
@@ -137,9 +306,22 @@ export interface LabsTelephonyConfig {
|
|
|
137
306
|
/** Default is Supafone-managed; developers do not need Twilio for this path. */
|
|
138
307
|
mode?: LabsTelephonyMode;
|
|
139
308
|
provider?: LabsTelephonyProvider;
|
|
309
|
+
/** default_pool reuses idle Supafone numbers; dedicated/premium reserve a number-month. */
|
|
310
|
+
numberStrategy?: LabsNumberStrategy;
|
|
311
|
+
number_strategy?: LabsNumberStrategy;
|
|
312
|
+
numberPool?: string;
|
|
313
|
+
number_pool?: string;
|
|
314
|
+
numberId?: string;
|
|
315
|
+
number_id?: string;
|
|
316
|
+
premium?: boolean;
|
|
140
317
|
label?: string;
|
|
141
318
|
credentials?: LabsTelephonyCredentials;
|
|
319
|
+
providerSettings?: Record<string, unknown>;
|
|
320
|
+
provider_settings?: Record<string, unknown>;
|
|
321
|
+
customSip?: LabsCustomSipConfig;
|
|
322
|
+
custom_sip?: LabsCustomSipConfig;
|
|
142
323
|
metadata?: Record<string, unknown>;
|
|
324
|
+
[extra: string]: unknown;
|
|
143
325
|
}
|
|
144
326
|
|
|
145
327
|
export interface LabsToolsConfig {
|
|
@@ -161,11 +343,61 @@ export interface LabsToolsConfig {
|
|
|
161
343
|
custom_tools?: Array<Record<string, unknown>>;
|
|
162
344
|
}
|
|
163
345
|
|
|
346
|
+
export interface LabsRecordingConfig {
|
|
347
|
+
enabled?: boolean;
|
|
348
|
+
recordAudio?: boolean;
|
|
349
|
+
record_audio?: boolean;
|
|
350
|
+
consentRequired?: boolean;
|
|
351
|
+
consent_required?: boolean;
|
|
352
|
+
announcement?: string;
|
|
353
|
+
retentionDays?: number;
|
|
354
|
+
retention_days?: number;
|
|
355
|
+
storage?: "supafone_managed" | "byok" | string;
|
|
356
|
+
redactPii?: boolean;
|
|
357
|
+
redact_pii?: boolean;
|
|
358
|
+
metadata?: Record<string, unknown>;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export interface LabsTranscriptionConfig {
|
|
362
|
+
enabled?: boolean;
|
|
363
|
+
provider?: string;
|
|
364
|
+
model?: string;
|
|
365
|
+
language?: string;
|
|
366
|
+
redactPii?: boolean;
|
|
367
|
+
redact_pii?: boolean;
|
|
368
|
+
diarization?: boolean;
|
|
369
|
+
timestamps?: boolean;
|
|
370
|
+
metadata?: Record<string, unknown>;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export interface LabsArtifactsConfig {
|
|
374
|
+
recordings?: boolean;
|
|
375
|
+
transcripts?: boolean;
|
|
376
|
+
summaries?: boolean;
|
|
377
|
+
qaReports?: boolean;
|
|
378
|
+
qa_reports?: boolean;
|
|
379
|
+
logs?: boolean;
|
|
380
|
+
webhooks?: boolean;
|
|
381
|
+
retentionDays?: number;
|
|
382
|
+
retention_days?: number;
|
|
383
|
+
metadata?: Record<string, unknown>;
|
|
384
|
+
}
|
|
385
|
+
|
|
164
386
|
export interface LabsWatcherConfig {
|
|
165
387
|
enabled?: boolean;
|
|
166
388
|
voiceWatcher?: boolean;
|
|
167
389
|
voice_watcher?: boolean;
|
|
390
|
+
apiKey?: string;
|
|
391
|
+
api_key?: string;
|
|
168
392
|
model?: string;
|
|
393
|
+
mode?: "supafone_managed" | "byok" | string;
|
|
394
|
+
managedInfrastructure?: boolean;
|
|
395
|
+
managed_infrastructure?: boolean;
|
|
396
|
+
stt?: Record<string, unknown>;
|
|
397
|
+
llm?: Record<string, unknown>;
|
|
398
|
+
tts?: Record<string, unknown>;
|
|
399
|
+
providerKeys?: Record<string, unknown>;
|
|
400
|
+
provider_keys?: Record<string, unknown>;
|
|
169
401
|
label?: string;
|
|
170
402
|
}
|
|
171
403
|
|
|
@@ -211,6 +443,10 @@ export interface LabsUltravoxRuntime {
|
|
|
211
443
|
retention_policy?: string;
|
|
212
444
|
callTemplate?: Record<string, unknown>;
|
|
213
445
|
call_template?: Record<string, unknown>;
|
|
446
|
+
customSip?: LabsCustomSipConfig;
|
|
447
|
+
custom_sip?: LabsCustomSipConfig;
|
|
448
|
+
sip?: LabsCustomSipConfig;
|
|
449
|
+
[extra: string]: unknown;
|
|
214
450
|
}
|
|
215
451
|
|
|
216
452
|
export interface CreateLabsAgentRequest {
|
|
@@ -234,11 +470,22 @@ export interface CreateLabsAgentRequest {
|
|
|
234
470
|
website_url?: string;
|
|
235
471
|
phoneNumber?: string;
|
|
236
472
|
phone_number?: string;
|
|
473
|
+
numberStrategy?: LabsNumberStrategy;
|
|
474
|
+
number_strategy?: LabsNumberStrategy;
|
|
475
|
+
numberPool?: string;
|
|
476
|
+
number_pool?: string;
|
|
477
|
+
premium?: boolean;
|
|
237
478
|
direction?: string;
|
|
238
479
|
presetKey?: string;
|
|
239
480
|
preset_key?: string;
|
|
240
481
|
runtimeMode?: LabsRuntimeMode;
|
|
241
482
|
runtime_mode?: LabsRuntimeMode;
|
|
483
|
+
/** Default true. When true, the SDK creates sensible call stages from prompt metadata. */
|
|
484
|
+
callStages?: boolean | LabsCallStage[];
|
|
485
|
+
call_stages?: boolean | LabsCallStage[];
|
|
486
|
+
stages?: LabsCallStage[];
|
|
487
|
+
autoCallStages?: boolean;
|
|
488
|
+
auto_call_stages?: boolean;
|
|
242
489
|
goal?: string;
|
|
243
490
|
greeting?: string;
|
|
244
491
|
systemPrompt?: string;
|
|
@@ -247,8 +494,15 @@ export interface CreateLabsAgentRequest {
|
|
|
247
494
|
voice?: LabsVoiceSelection;
|
|
248
495
|
providerKeys?: LabsProviderKeys;
|
|
249
496
|
provider_keys?: LabsProviderKeys;
|
|
250
|
-
byok?: LabsProviderKeys;
|
|
497
|
+
byok?: LabsProviderKeys | LabsByokConfig;
|
|
251
498
|
telephony?: LabsTelephonyConfig;
|
|
499
|
+
customSip?: LabsCustomSipConfig;
|
|
500
|
+
custom_sip?: LabsCustomSipConfig;
|
|
501
|
+
sip?: LabsCustomSipConfig;
|
|
502
|
+
recording?: LabsRecordingConfig;
|
|
503
|
+
transcription?: LabsTranscriptionConfig;
|
|
504
|
+
artifacts?: LabsArtifactsConfig;
|
|
505
|
+
compliance?: Record<string, unknown>;
|
|
252
506
|
tools?: LabsToolsConfig;
|
|
253
507
|
labs?: LabsWatcherConfig;
|
|
254
508
|
ultravox?: LabsUltravoxRuntime;
|
|
@@ -267,6 +521,21 @@ export interface ListLabsAgentsOptions {
|
|
|
267
521
|
|
|
268
522
|
export interface GetLabsAgentOptions extends ListLabsAgentsOptions {}
|
|
269
523
|
|
|
524
|
+
export interface DeleteLabsAgentOptions {
|
|
525
|
+
agencyId?: string;
|
|
526
|
+
agency_id?: string;
|
|
527
|
+
releaseNumbers?: boolean;
|
|
528
|
+
release_numbers?: boolean;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
export interface DeleteLabsAgentResponse {
|
|
532
|
+
success?: boolean;
|
|
533
|
+
deleted?: boolean;
|
|
534
|
+
agent_key?: string;
|
|
535
|
+
released_numbers?: unknown[];
|
|
536
|
+
[extra: string]: unknown;
|
|
537
|
+
}
|
|
538
|
+
|
|
270
539
|
export interface LabsCapabilitiesResponse {
|
|
271
540
|
product: string;
|
|
272
541
|
api_namespace: string;
|
|
@@ -299,9 +568,57 @@ export interface LabsVoiceListResponse {
|
|
|
299
568
|
errors?: Record<string, string>;
|
|
300
569
|
}
|
|
301
570
|
|
|
571
|
+
export interface LabsCallListOptions {
|
|
572
|
+
agencyId?: string;
|
|
573
|
+
agentKey?: string;
|
|
574
|
+
agent_key?: string;
|
|
575
|
+
limit?: number;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
export interface LabsCallArtifact {
|
|
579
|
+
id?: string;
|
|
580
|
+
call_id?: string;
|
|
581
|
+
agent_key?: string;
|
|
582
|
+
status?: string;
|
|
583
|
+
started_at?: string;
|
|
584
|
+
duration_seconds?: number;
|
|
585
|
+
recording_url?: string;
|
|
586
|
+
transcript_url?: string;
|
|
587
|
+
[extra: string]: unknown;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
export interface LabsCallListResponse {
|
|
591
|
+
calls: LabsCallArtifact[];
|
|
592
|
+
[extra: string]: unknown;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
export interface LabsRecordingListOptions extends LabsCallListOptions {
|
|
596
|
+
callId?: string;
|
|
597
|
+
call_id?: string;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
export interface LabsRecordingListResponse {
|
|
601
|
+
recordings: Array<Record<string, unknown>>;
|
|
602
|
+
[extra: string]: unknown;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
export interface LabsTranscriptListOptions extends LabsCallListOptions {
|
|
606
|
+
callId?: string;
|
|
607
|
+
call_id?: string;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
export interface LabsTranscriptListResponse {
|
|
611
|
+
transcripts: Array<Record<string, unknown>>;
|
|
612
|
+
[extra: string]: unknown;
|
|
613
|
+
}
|
|
614
|
+
|
|
302
615
|
export interface LabsPhoneNumberSearchOptions {
|
|
303
616
|
agencyId?: string;
|
|
304
617
|
agency_id?: string;
|
|
618
|
+
numberPool?: string;
|
|
619
|
+
number_pool?: string;
|
|
620
|
+
numberStrategy?: LabsNumberStrategy;
|
|
621
|
+
number_strategy?: LabsNumberStrategy;
|
|
305
622
|
countryCode?: string;
|
|
306
623
|
country_code?: string;
|
|
307
624
|
areaCode?: string;
|
|
@@ -376,6 +693,11 @@ export interface LabsPhoneNumberProvisionRequest {
|
|
|
376
693
|
agent_name?: string;
|
|
377
694
|
presetKey?: string;
|
|
378
695
|
preset_key?: string;
|
|
696
|
+
numberStrategy?: LabsNumberStrategy;
|
|
697
|
+
number_strategy?: LabsNumberStrategy;
|
|
698
|
+
numberPool?: string;
|
|
699
|
+
number_pool?: string;
|
|
700
|
+
premium?: boolean;
|
|
379
701
|
style?: LabsAgentStyle;
|
|
380
702
|
agentStyle?: LabsAgentStyle;
|
|
381
703
|
agent_style?: LabsAgentStyle;
|
|
@@ -386,6 +708,15 @@ export interface LabsPhoneNumberProvisionRequest {
|
|
|
386
708
|
|
|
387
709
|
export interface LabsPhoneNumberAssignRequest extends Omit<LabsPhoneNumberProvisionRequest, "phoneNumber" | "phone_number" | "departmentId" | "department_id"> {}
|
|
388
710
|
|
|
711
|
+
export interface LabsPhoneNumberReleaseRequest {
|
|
712
|
+
agencyId?: string;
|
|
713
|
+
agency_id?: string;
|
|
714
|
+
reason?: string;
|
|
715
|
+
returnToPool?: boolean;
|
|
716
|
+
return_to_pool?: boolean;
|
|
717
|
+
metadata?: Record<string, unknown>;
|
|
718
|
+
}
|
|
719
|
+
|
|
389
720
|
export interface LabsPhoneNumberProvisionResponse {
|
|
390
721
|
success: boolean;
|
|
391
722
|
number: LabsPhoneNumberRecord;
|
|
@@ -400,6 +731,14 @@ export interface LabsPhoneNumberAssignResponse {
|
|
|
400
731
|
assignment?: Record<string, unknown>;
|
|
401
732
|
}
|
|
402
733
|
|
|
734
|
+
export interface LabsPhoneNumberReleaseResponse {
|
|
735
|
+
success: boolean;
|
|
736
|
+
number?: LabsPhoneNumberRecord;
|
|
737
|
+
released?: boolean;
|
|
738
|
+
returned_to_pool?: boolean;
|
|
739
|
+
[extra: string]: unknown;
|
|
740
|
+
}
|
|
741
|
+
|
|
403
742
|
export interface LabsPhoneNumberBuyAndAssignRequest extends LabsPhoneNumberProvisionRequest {
|
|
404
743
|
search?: LabsPhoneNumberSearchOptions;
|
|
405
744
|
}
|
|
@@ -553,28 +892,44 @@ const COACH_SYSTEM =
|
|
|
553
892
|
export class SupafoneLabs {
|
|
554
893
|
readonly baseUrl: string;
|
|
555
894
|
readonly supafoneApiBaseUrl: string;
|
|
895
|
+
readonly appUrl: string;
|
|
556
896
|
private readonly apiKey: string;
|
|
557
897
|
private readonly supafoneApiKey: string;
|
|
558
898
|
private readonly timeoutMs: number;
|
|
559
899
|
private sessionToken?: string;
|
|
900
|
+
private accountToken?: string;
|
|
901
|
+
private accountSessionToken?: string;
|
|
902
|
+
private readonly accountEmail?: string;
|
|
903
|
+
private readonly accountPassword?: string;
|
|
560
904
|
|
|
561
905
|
readonly labs: LabsNamespace;
|
|
562
906
|
readonly builder: BuilderNamespace;
|
|
563
907
|
readonly qa: QANamespace;
|
|
564
908
|
readonly optimizer: OptimizerNamespace;
|
|
909
|
+
readonly campaigns: CampaignsNamespace;
|
|
565
910
|
|
|
566
911
|
constructor(opts: SupafoneLabsOptions) {
|
|
567
|
-
|
|
568
|
-
|
|
912
|
+
const hasAccountAuth = !!(opts?.accountToken || (opts?.accountEmail && opts?.accountPassword));
|
|
913
|
+
if (!opts?.apiKey && !hasAccountAuth) {
|
|
914
|
+
throw new SupafoneLabsError(
|
|
915
|
+
"apiKey is required — or, for campaigns/calls, pass accountToken or accountEmail + accountPassword",
|
|
916
|
+
);
|
|
917
|
+
}
|
|
918
|
+
this.apiKey = opts.apiKey ?? "";
|
|
569
919
|
this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE).replace(/\/$/, "");
|
|
570
|
-
this.supafoneApiKey = opts.supafoneApiKey ??
|
|
920
|
+
this.supafoneApiKey = opts.supafoneApiKey ?? this.apiKey;
|
|
571
921
|
this.supafoneApiBaseUrl = (opts.supafoneApiBaseUrl ?? DEFAULT_SUPAFONE_API_BASE).replace(/\/$/, "");
|
|
922
|
+
this.appUrl = (opts.appUrl ?? "https://app.supafone.ai").replace(/\/$/, "");
|
|
572
923
|
this.timeoutMs = opts.timeoutMs ?? 30_000;
|
|
573
924
|
this.sessionToken = opts.sessionToken;
|
|
925
|
+
this.accountToken = opts.accountToken;
|
|
926
|
+
this.accountEmail = opts.accountEmail;
|
|
927
|
+
this.accountPassword = opts.accountPassword;
|
|
574
928
|
this.labs = new LabsNamespace(this);
|
|
575
929
|
this.builder = new BuilderNamespace(this);
|
|
576
930
|
this.qa = new QANamespace(this);
|
|
577
931
|
this.optimizer = new OptimizerNamespace(this);
|
|
932
|
+
this.campaigns = new CampaignsNamespace(this);
|
|
578
933
|
}
|
|
579
934
|
|
|
580
935
|
/** True once login() (or a passed sessionToken) is in effect. */
|
|
@@ -639,6 +994,93 @@ export class SupafoneLabs {
|
|
|
639
994
|
}
|
|
640
995
|
}
|
|
641
996
|
|
|
997
|
+
/**
|
|
998
|
+
* Exchange the account email/password for a product-API JWT (the same login
|
|
999
|
+
* as app.supafone.ai). Called lazily by campaigns/calls — call directly only
|
|
1000
|
+
* to fail fast.
|
|
1001
|
+
*/
|
|
1002
|
+
async accountLogin(email?: string, password?: string): Promise<string> {
|
|
1003
|
+
const useEmail = email ?? this.accountEmail;
|
|
1004
|
+
const usePassword = password ?? this.accountPassword;
|
|
1005
|
+
if (!useEmail || !usePassword) {
|
|
1006
|
+
throw new SupafoneLabsError(
|
|
1007
|
+
"Not authenticated: pass accountToken, or accountEmail + accountPassword",
|
|
1008
|
+
);
|
|
1009
|
+
}
|
|
1010
|
+
const body = await this.accountHttp<{ token?: string; access_token?: string }>(
|
|
1011
|
+
"POST",
|
|
1012
|
+
"/api/v1/auth/login",
|
|
1013
|
+
{ email: useEmail, password: usePassword },
|
|
1014
|
+
"",
|
|
1015
|
+
);
|
|
1016
|
+
const token = body.access_token || body.token;
|
|
1017
|
+
if (!token) throw new SupafoneLabsError("Login succeeded but returned no token");
|
|
1018
|
+
this.accountSessionToken = token;
|
|
1019
|
+
return token;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
/**
|
|
1023
|
+
* @internal JSON request to the Supafone product API with the ACCOUNT JWT
|
|
1024
|
+
* (campaigns + real calls). A minted token that expires gets one transparent
|
|
1025
|
+
* re-login; an explicit accountToken is the caller's to refresh.
|
|
1026
|
+
*/
|
|
1027
|
+
async requestAccountApi<T>(method: string, path: string, body?: unknown): Promise<T> {
|
|
1028
|
+
const token = this.accountToken || this.accountSessionToken || (await this.accountLogin());
|
|
1029
|
+
try {
|
|
1030
|
+
return await this.accountHttp<T>(method, path, body, token);
|
|
1031
|
+
} catch (err) {
|
|
1032
|
+
const expired = err instanceof SupafoneLabsError && err.status === 401;
|
|
1033
|
+
if (expired && !this.accountToken && this.accountEmail && this.accountPassword) {
|
|
1034
|
+
this.accountSessionToken = undefined;
|
|
1035
|
+
return this.accountHttp<T>(method, path, body, await this.accountLogin());
|
|
1036
|
+
}
|
|
1037
|
+
throw err;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
/** @internal Raw product-API request with an explicit bearer ("" = none). */
|
|
1042
|
+
private async accountHttp<T>(method: string, path: string, body: unknown, token: string): Promise<T> {
|
|
1043
|
+
const ctrl = new AbortController();
|
|
1044
|
+
const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
|
|
1045
|
+
try {
|
|
1046
|
+
const headers: Record<string, string> = { "Content-Type": "application/json" };
|
|
1047
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
1048
|
+
const res = await fetch(this.supafoneApiBaseUrl + path, {
|
|
1049
|
+
method,
|
|
1050
|
+
signal: ctrl.signal,
|
|
1051
|
+
headers,
|
|
1052
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
1053
|
+
});
|
|
1054
|
+
const text = await res.text();
|
|
1055
|
+
const parsed = text ? safeJson(text) : {};
|
|
1056
|
+
if (!res.ok) {
|
|
1057
|
+
const detail = (parsed as { detail?: string })?.detail ?? text ?? `HTTP ${res.status}`;
|
|
1058
|
+
throw new SupafoneLabsError(`${method} ${path}: ${detail}`, res.status, parsed);
|
|
1059
|
+
}
|
|
1060
|
+
return parsed as T;
|
|
1061
|
+
} finally {
|
|
1062
|
+
clearTimeout(timer);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
/**
|
|
1067
|
+
* PLACE A REAL OUTBOUND PHONE CALL: dials toNumber from the account's
|
|
1068
|
+
* calling provider and bridges the voice agent onto the line.
|
|
1069
|
+
*/
|
|
1070
|
+
async placeCall(opts: { agentId: string; toNumber: string }): Promise<PlaceCallResult> {
|
|
1071
|
+
if (!opts?.agentId) throw new SupafoneLabsError("agentId is required (see listVoiceAgents())");
|
|
1072
|
+
if (!opts?.toNumber) throw new SupafoneLabsError("toNumber is required (E.164, e.g. +15551234567)");
|
|
1073
|
+
return this.requestAccountApi<PlaceCallResult>("POST", "/api/v1/phone/test-call", {
|
|
1074
|
+
agent_id: opts.agentId,
|
|
1075
|
+
to_number: opts.toNumber,
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
/** The account's voice agents — pick an agent id for campaigns/calls. */
|
|
1080
|
+
async listVoiceAgents(): Promise<{ agents: Record<string, unknown>[] }> {
|
|
1081
|
+
return this.requestAccountApi("GET", "/api/v1/agents");
|
|
1082
|
+
}
|
|
1083
|
+
|
|
642
1084
|
/** Raw oracle completion — full control over messages and model. */
|
|
643
1085
|
async oracle(req: OracleRequest): Promise<OracleResult> {
|
|
644
1086
|
return this.request<OracleResult>("POST", "/v1/oracle/complete", {
|
|
@@ -685,6 +1127,14 @@ export class SupafoneLabs {
|
|
|
685
1127
|
}
|
|
686
1128
|
}
|
|
687
1129
|
|
|
1130
|
+
/** Convenience alias for voice preview UI/buttons. */
|
|
1131
|
+
previewVoice(
|
|
1132
|
+
voice = "supafone-labs-calm-en",
|
|
1133
|
+
text = "Hi, this is your Supafone agent voice preview.",
|
|
1134
|
+
): Promise<Uint8Array> {
|
|
1135
|
+
return this.tts(text, voice);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
688
1138
|
/** Hosted STT for a finished audio clip — returns transcript + language tags. */
|
|
689
1139
|
async stt(
|
|
690
1140
|
audio: Uint8Array | ArrayBuffer,
|
|
@@ -742,10 +1192,47 @@ export class SupafoneLabs {
|
|
|
742
1192
|
}
|
|
743
1193
|
|
|
744
1194
|
/** The auditable whisper/billing log. */
|
|
745
|
-
logs(limit = 100): Promise<{ logs:
|
|
1195
|
+
logs(limit = 100): Promise<{ logs: LabsLogEntry[] }> {
|
|
746
1196
|
return this.request("GET", `/v1/logs?limit=${limit}`);
|
|
747
1197
|
}
|
|
748
1198
|
|
|
1199
|
+
/** Live audit-log stream. Works in Node 18+ and browsers via fetch streaming. */
|
|
1200
|
+
async *streamLogs(opts: StreamLogsOptions = {}): AsyncGenerator<LabsLogEntry> {
|
|
1201
|
+
const q = new URLSearchParams({
|
|
1202
|
+
limit: String(opts.limit ?? 100),
|
|
1203
|
+
poll_ms: String(opts.pollMs ?? 1000),
|
|
1204
|
+
snapshot: String(opts.snapshot ?? true),
|
|
1205
|
+
});
|
|
1206
|
+
if (opts.afterId !== undefined) q.set("after_id", String(opts.afterId));
|
|
1207
|
+
const res = await fetch(`${this.baseUrl}/v1/logs/stream?${q}`, {
|
|
1208
|
+
method: "GET",
|
|
1209
|
+
signal: opts.signal,
|
|
1210
|
+
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
1211
|
+
});
|
|
1212
|
+
if (!res.ok) throw new SupafoneLabsError(`streamLogs: ${await res.text()}`, res.status);
|
|
1213
|
+
if (!res.body) throw new SupafoneLabsError("streamLogs: response body is not readable");
|
|
1214
|
+
const reader = res.body.getReader();
|
|
1215
|
+
const decoder = new TextDecoder();
|
|
1216
|
+
let buffer = "";
|
|
1217
|
+
try {
|
|
1218
|
+
for (;;) {
|
|
1219
|
+
const { done, value } = await reader.read();
|
|
1220
|
+
if (done) break;
|
|
1221
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1222
|
+
let split = buffer.indexOf("\n\n");
|
|
1223
|
+
while (split >= 0) {
|
|
1224
|
+
const raw = buffer.slice(0, split);
|
|
1225
|
+
buffer = buffer.slice(split + 2);
|
|
1226
|
+
const parsed = parseSseLog(raw);
|
|
1227
|
+
if (parsed) yield parsed;
|
|
1228
|
+
split = buffer.indexOf("\n\n");
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
} finally {
|
|
1232
|
+
reader.releaseLock();
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
|
|
749
1236
|
/** The structured whisper feed (what the console shows). */
|
|
750
1237
|
nudges(limit = 50): Promise<{ nudges: unknown[] }> {
|
|
751
1238
|
return this.request("GET", `/v1/nudges?limit=${limit}`);
|
|
@@ -780,6 +1267,11 @@ export class SupafoneLabs {
|
|
|
780
1267
|
);
|
|
781
1268
|
return d.voices.map((v) => (typeof v === "string" ? v : (v.voice ?? v.id ?? ""))).filter(Boolean);
|
|
782
1269
|
}
|
|
1270
|
+
|
|
1271
|
+
/** Full hosted voice catalog with provider live/configured flags. */
|
|
1272
|
+
voiceCatalog(): Promise<{ voices: Array<Record<string, unknown>> }> {
|
|
1273
|
+
return this.request("GET", "/v1/voices");
|
|
1274
|
+
}
|
|
783
1275
|
}
|
|
784
1276
|
|
|
785
1277
|
export interface LiveTranscribeOptions {
|
|
@@ -849,6 +1341,216 @@ class LiveTranscription {
|
|
|
849
1341
|
}
|
|
850
1342
|
|
|
851
1343
|
/** Programmatic hosted Supafone agents, inside the Supafone API. */
|
|
1344
|
+
// ---------------------------------------------------------------------------
|
|
1345
|
+
// Campaigns — the outbound AI campaign engine the app.supafone.ai builder
|
|
1346
|
+
// drives, packaged. Account-JWT authenticated (accountToken / accountEmail +
|
|
1347
|
+
// accountPassword on the client options).
|
|
1348
|
+
// ---------------------------------------------------------------------------
|
|
1349
|
+
|
|
1350
|
+
export interface PlaceCallResult {
|
|
1351
|
+
success: boolean;
|
|
1352
|
+
simulated?: boolean;
|
|
1353
|
+
call_sid?: string | null;
|
|
1354
|
+
provider?: string;
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
export interface CampaignRecipientInput {
|
|
1358
|
+
name?: string;
|
|
1359
|
+
phone?: string;
|
|
1360
|
+
email?: string;
|
|
1361
|
+
/** Warm-outreach consent — required before any voice/email touch. */
|
|
1362
|
+
outreach_consent?: string;
|
|
1363
|
+
[field: string]: unknown;
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
export interface CampaignSummary {
|
|
1367
|
+
id: string;
|
|
1368
|
+
name: string;
|
|
1369
|
+
goal: string;
|
|
1370
|
+
status: string;
|
|
1371
|
+
agent_id?: string | null;
|
|
1372
|
+
stats?: Record<string, unknown>;
|
|
1373
|
+
settings?: Record<string, unknown>;
|
|
1374
|
+
[field: string]: unknown;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
export interface CampaignLiveCall {
|
|
1378
|
+
id: string;
|
|
1379
|
+
status: string;
|
|
1380
|
+
/** Portal deep link to watch this call (live transcript while in flight). */
|
|
1381
|
+
listen_url: string;
|
|
1382
|
+
[field: string]: unknown;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
export interface CampaignLiveView {
|
|
1386
|
+
campaign_id: string;
|
|
1387
|
+
in_flight: CampaignLiveCall[];
|
|
1388
|
+
/** Developer-portal link showing this campaign's agents/calls live. */
|
|
1389
|
+
portal_url: string;
|
|
1390
|
+
stats?: Record<string, unknown> | null;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
export interface CampaignUpdateInput {
|
|
1394
|
+
name?: string;
|
|
1395
|
+
goal?: string;
|
|
1396
|
+
agentId?: string;
|
|
1397
|
+
emailSubject?: string;
|
|
1398
|
+
emailBody?: string;
|
|
1399
|
+
cadence?: { channel: "voice" | "email"; delay_hours: number }[];
|
|
1400
|
+
settings?: Record<string, unknown>;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
/**
|
|
1404
|
+
* Typical flow:
|
|
1405
|
+
* ```ts
|
|
1406
|
+
* const sf = new Supafone({ accountEmail, accountPassword });
|
|
1407
|
+
* const { agents } = await sf.listVoiceAgents();
|
|
1408
|
+
* const { campaign } = await sf.campaigns.create({ name: "Q3 win-back", goal: "reengage", agentId: agents[0].id });
|
|
1409
|
+
* await sf.campaigns.applyPreset(campaign.id, "win_back");
|
|
1410
|
+
* await sf.campaigns.addRecipients(campaign.id, [{ name: "Jane", phone: "+15551234567", outreach_consent: "yes" }]);
|
|
1411
|
+
* await sf.campaigns.launch(campaign.id);
|
|
1412
|
+
* const live = await sf.campaigns.live(campaign.id); // in-flight calls + portal links
|
|
1413
|
+
* ```
|
|
1414
|
+
*/
|
|
1415
|
+
class CampaignsNamespace {
|
|
1416
|
+
constructor(private sm: SupafoneLabs) {}
|
|
1417
|
+
|
|
1418
|
+
list(opts: { accountId?: string } = {}): Promise<{ campaigns: CampaignSummary[] }> {
|
|
1419
|
+
const query = opts.accountId ? `?${new URLSearchParams({ account_id: opts.accountId })}` : "";
|
|
1420
|
+
return this.sm.requestAccountApi("GET", `/api/v1/campaigns${query}`);
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
create(opts: { name?: string; goal?: string; agentId?: string; accountId?: string } = {}): Promise<{ campaign: CampaignSummary }> {
|
|
1424
|
+
return this.sm.requestAccountApi("POST", "/api/v1/campaigns", compact({
|
|
1425
|
+
name: opts.name ?? "New campaign",
|
|
1426
|
+
goal: opts.goal ?? "book",
|
|
1427
|
+
agent_id: opts.agentId,
|
|
1428
|
+
account_id: opts.accountId,
|
|
1429
|
+
}));
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
get(campaignId: string): Promise<{ campaign: CampaignSummary }> {
|
|
1433
|
+
return this.sm.requestAccountApi("GET", `/api/v1/campaigns/${encodeURIComponent(campaignId)}`);
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
update(campaignId: string, input: CampaignUpdateInput): Promise<{ campaign: CampaignSummary }> {
|
|
1437
|
+
const payload = compact({
|
|
1438
|
+
name: input.name,
|
|
1439
|
+
goal: input.goal,
|
|
1440
|
+
agent_id: input.agentId,
|
|
1441
|
+
email_subject: input.emailSubject,
|
|
1442
|
+
email_body: input.emailBody,
|
|
1443
|
+
cadence: input.cadence,
|
|
1444
|
+
settings: input.settings,
|
|
1445
|
+
});
|
|
1446
|
+
if (!Object.keys(payload as Record<string, unknown>).length) {
|
|
1447
|
+
throw new SupafoneLabsError("Nothing to update — pass name, goal, agentId, emailSubject, emailBody, cadence, or settings");
|
|
1448
|
+
}
|
|
1449
|
+
return this.sm.requestAccountApi("PUT", `/api/v1/campaigns/${encodeURIComponent(campaignId)}`, payload);
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
/** Add consented leads: [{name, phone, email, outreach_consent: "yes"}]. */
|
|
1453
|
+
addRecipients(campaignId: string, recipients: CampaignRecipientInput[]): Promise<{ added: number; stats: Record<string, unknown> }> {
|
|
1454
|
+
if (!Array.isArray(recipients) || !recipients.length) {
|
|
1455
|
+
throw new SupafoneLabsError("recipients must be a non-empty array of lead rows");
|
|
1456
|
+
}
|
|
1457
|
+
return this.sm.requestAccountApi("POST", `/api/v1/campaigns/${encodeURIComponent(campaignId)}/recipients`, { recipients });
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
recipients(campaignId: string): Promise<{ recipients: Record<string, unknown>[] }> {
|
|
1461
|
+
return this.sm.requestAccountApi("GET", `/api/v1/campaigns/${encodeURIComponent(campaignId)}/recipients`);
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
/** Starts REAL calls/emails on the cadence immediately. */
|
|
1465
|
+
launch(campaignId: string): Promise<{ campaign: CampaignSummary }> {
|
|
1466
|
+
return this.sm.requestAccountApi("POST", `/api/v1/campaigns/${encodeURIComponent(campaignId)}/launch`, {});
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
pause(campaignId: string): Promise<{ campaign: CampaignSummary }> {
|
|
1470
|
+
return this.sm.requestAccountApi("POST", `/api/v1/campaigns/${encodeURIComponent(campaignId)}/pause`, {});
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
/** Built-in playbooks + the account's saved custom presets. */
|
|
1474
|
+
async presets(): Promise<{ built_in: Record<string, unknown>[]; custom: Record<string, unknown>[] }> {
|
|
1475
|
+
const builtIn = await this.sm.requestAccountApi<{ presets: Record<string, unknown>[] }>(
|
|
1476
|
+
"GET",
|
|
1477
|
+
"/api/v1/campaigns/outbound-presets",
|
|
1478
|
+
);
|
|
1479
|
+
let custom: Record<string, unknown>[] = [];
|
|
1480
|
+
try {
|
|
1481
|
+
const mine = await this.sm.requestAccountApi<{ presets: Record<string, unknown>[] }>(
|
|
1482
|
+
"GET",
|
|
1483
|
+
"/api/v1/campaigns/custom-presets",
|
|
1484
|
+
);
|
|
1485
|
+
custom = mine.presets ?? [];
|
|
1486
|
+
} catch {
|
|
1487
|
+
/* custom presets need account scope — built-ins still return */
|
|
1488
|
+
}
|
|
1489
|
+
return { built_in: builtIn.presets ?? [], custom };
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
/** Materialize a preset (goal, questions, scripts, signing doc) in one write. */
|
|
1493
|
+
applyPreset(campaignId: string, presetId: string): Promise<{ campaign: CampaignSummary }> {
|
|
1494
|
+
return this.sm.requestAccountApi("POST", `/api/v1/campaigns/${encodeURIComponent(campaignId)}/apply-preset`, {
|
|
1495
|
+
preset_id: presetId,
|
|
1496
|
+
});
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
stats(campaignId: string): Promise<{ stats: Record<string, unknown> }> {
|
|
1500
|
+
return this.sm.requestAccountApi("GET", `/api/v1/campaigns/${encodeURIComponent(campaignId)}/stats`);
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
/** The live funnel + the campaign's most recent calls (newest first). */
|
|
1504
|
+
activity(campaignId: string): Promise<{ stats?: Record<string, unknown>; calls?: Record<string, unknown>[] }> {
|
|
1505
|
+
return this.sm.requestAccountApi("GET", `/api/v1/campaigns/${encodeURIComponent(campaignId)}/activity`);
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
/**
|
|
1509
|
+
* In-flight calls right now, each with a portal link to watch/listen. Poll
|
|
1510
|
+
* getCall(callId) (or open the link) for the transcript as it grows.
|
|
1511
|
+
*/
|
|
1512
|
+
async live(campaignId: string): Promise<CampaignLiveView> {
|
|
1513
|
+
const activity = await this.activity(campaignId);
|
|
1514
|
+
const inFlight: CampaignLiveCall[] = [];
|
|
1515
|
+
for (const call of activity.calls ?? []) {
|
|
1516
|
+
const status = String((call as { status?: unknown }).status ?? "");
|
|
1517
|
+
if (status === "initiated" || status === "dialing" || status === "in_progress") {
|
|
1518
|
+
const id = String((call as { id?: unknown }).id ?? "");
|
|
1519
|
+
inFlight.push({
|
|
1520
|
+
...(call as Record<string, unknown>),
|
|
1521
|
+
id,
|
|
1522
|
+
status,
|
|
1523
|
+
listen_url: `${this.sm.appUrl}/app/calls?call=${encodeURIComponent(id)}`,
|
|
1524
|
+
});
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
return {
|
|
1528
|
+
campaign_id: campaignId,
|
|
1529
|
+
in_flight: inFlight,
|
|
1530
|
+
portal_url: `${this.sm.appUrl}/app/developer?campaign=${encodeURIComponent(campaignId)}`,
|
|
1531
|
+
stats: activity.stats ?? null,
|
|
1532
|
+
};
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
/** One call — while in_progress the transcript grows on each poll. */
|
|
1536
|
+
getCall(callId: string): Promise<{ call: Record<string, unknown> }> {
|
|
1537
|
+
return this.sm.requestAccountApi("GET", `/api/v1/calls/${encodeURIComponent(callId)}`);
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
/** Mint a recipient's tracked tap-to-sign link (inherits the campaign's signing PDF). */
|
|
1541
|
+
createSignLink(
|
|
1542
|
+
campaignId: string,
|
|
1543
|
+
recipientId: string,
|
|
1544
|
+
opts: { title?: string; message?: string } = {},
|
|
1545
|
+
): Promise<{ link: Record<string, unknown> }> {
|
|
1546
|
+
return this.sm.requestAccountApi(
|
|
1547
|
+
"POST",
|
|
1548
|
+
`/api/v1/campaigns/${encodeURIComponent(campaignId)}/recipients/${encodeURIComponent(recipientId)}/sign-link`,
|
|
1549
|
+
compact({ title: opts.title, message: opts.message }),
|
|
1550
|
+
);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
|
|
852
1554
|
class LabsNamespace {
|
|
853
1555
|
readonly agents: LabsAgentsNamespace;
|
|
854
1556
|
readonly presets: LabsPresetsNamespace;
|
|
@@ -856,6 +1558,9 @@ class LabsNamespace {
|
|
|
856
1558
|
readonly voices: LabsVoicesNamespace;
|
|
857
1559
|
readonly phoneNumbers: LabsPhoneNumbersNamespace;
|
|
858
1560
|
readonly telephony: LabsTelephonyNamespace;
|
|
1561
|
+
readonly calls: LabsCallsNamespace;
|
|
1562
|
+
readonly recordings: LabsRecordingsNamespace;
|
|
1563
|
+
readonly transcripts: LabsTranscriptsNamespace;
|
|
859
1564
|
|
|
860
1565
|
constructor(private sm: SupafoneLabs) {
|
|
861
1566
|
this.agents = new LabsAgentsNamespace(sm);
|
|
@@ -864,6 +1569,9 @@ class LabsNamespace {
|
|
|
864
1569
|
this.voices = new LabsVoicesNamespace(sm);
|
|
865
1570
|
this.phoneNumbers = new LabsPhoneNumbersNamespace(sm);
|
|
866
1571
|
this.telephony = new LabsTelephonyNamespace(sm);
|
|
1572
|
+
this.calls = new LabsCallsNamespace(sm);
|
|
1573
|
+
this.recordings = new LabsRecordingsNamespace(sm);
|
|
1574
|
+
this.transcripts = new LabsTranscriptsNamespace(sm);
|
|
867
1575
|
}
|
|
868
1576
|
|
|
869
1577
|
/** Discover the Supafone convenience layer over Ultravox. */
|
|
@@ -971,6 +1679,20 @@ class LabsAgentsNamespace {
|
|
|
971
1679
|
`/api/v1/labs/agents/${encodeURIComponent(agentKey)}${suffix}`,
|
|
972
1680
|
);
|
|
973
1681
|
}
|
|
1682
|
+
|
|
1683
|
+
/** Delete an agent. Optionally ask the backend to release assigned numbers. */
|
|
1684
|
+
delete(agentKey: string, opts: DeleteLabsAgentOptions = {}): Promise<DeleteLabsAgentResponse> {
|
|
1685
|
+
const q = new URLSearchParams();
|
|
1686
|
+
const agencyId = opts.agency_id ?? opts.agencyId;
|
|
1687
|
+
const releaseNumbers = opts.release_numbers ?? opts.releaseNumbers;
|
|
1688
|
+
if (agencyId) q.set("agency_id", agencyId);
|
|
1689
|
+
if (releaseNumbers !== undefined) q.set("release_numbers", String(releaseNumbers));
|
|
1690
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
1691
|
+
return this.sm.requestSupafoneApi<DeleteLabsAgentResponse>(
|
|
1692
|
+
"DELETE",
|
|
1693
|
+
`/api/v1/labs/agents/${encodeURIComponent(agentKey)}${suffix}`,
|
|
1694
|
+
);
|
|
1695
|
+
}
|
|
974
1696
|
}
|
|
975
1697
|
|
|
976
1698
|
class LabsPresetsNamespace {
|
|
@@ -1003,6 +1725,69 @@ class LabsVoicesNamespace {
|
|
|
1003
1725
|
}
|
|
1004
1726
|
}
|
|
1005
1727
|
|
|
1728
|
+
class LabsCallsNamespace {
|
|
1729
|
+
constructor(private sm: SupafoneLabs) {}
|
|
1730
|
+
|
|
1731
|
+
list(opts: LabsCallListOptions = {}): Promise<LabsCallListResponse> {
|
|
1732
|
+
const q = hostedListQuery(opts);
|
|
1733
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
1734
|
+
return this.sm.requestSupafoneApi<LabsCallListResponse>("GET", `/api/v1/labs/calls${suffix}`);
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
get(callId: string, opts: { agencyId?: string } = {}): Promise<Record<string, unknown>> {
|
|
1738
|
+
const q = new URLSearchParams();
|
|
1739
|
+
if (opts.agencyId) q.set("agency_id", opts.agencyId);
|
|
1740
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
1741
|
+
return this.sm.requestSupafoneApi("GET", `/api/v1/labs/calls/${encodeURIComponent(callId)}${suffix}`);
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
class LabsRecordingsNamespace {
|
|
1746
|
+
constructor(private sm: SupafoneLabs) {}
|
|
1747
|
+
|
|
1748
|
+
list(opts: LabsRecordingListOptions = {}): Promise<LabsRecordingListResponse> {
|
|
1749
|
+
const q = hostedListQuery(opts);
|
|
1750
|
+
const callId = opts.call_id ?? opts.callId;
|
|
1751
|
+
if (callId) q.set("call_id", callId);
|
|
1752
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
1753
|
+
return this.sm.requestSupafoneApi<LabsRecordingListResponse>("GET", `/api/v1/labs/recordings${suffix}`);
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
get(recordingId: string, opts: { agencyId?: string } = {}): Promise<Record<string, unknown>> {
|
|
1757
|
+
const q = new URLSearchParams();
|
|
1758
|
+
if (opts.agencyId) q.set("agency_id", opts.agencyId);
|
|
1759
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
1760
|
+
return this.sm.requestSupafoneApi("GET", `/api/v1/labs/recordings/${encodeURIComponent(recordingId)}${suffix}`);
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
delete(recordingId: string, opts: { agencyId?: string; reason?: string } = {}): Promise<Record<string, unknown>> {
|
|
1764
|
+
const q = new URLSearchParams();
|
|
1765
|
+
if (opts.agencyId) q.set("agency_id", opts.agencyId);
|
|
1766
|
+
if (opts.reason) q.set("reason", opts.reason);
|
|
1767
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
1768
|
+
return this.sm.requestSupafoneApi("DELETE", `/api/v1/labs/recordings/${encodeURIComponent(recordingId)}${suffix}`);
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
class LabsTranscriptsNamespace {
|
|
1773
|
+
constructor(private sm: SupafoneLabs) {}
|
|
1774
|
+
|
|
1775
|
+
list(opts: LabsTranscriptListOptions = {}): Promise<LabsTranscriptListResponse> {
|
|
1776
|
+
const q = hostedListQuery(opts);
|
|
1777
|
+
const callId = opts.call_id ?? opts.callId;
|
|
1778
|
+
if (callId) q.set("call_id", callId);
|
|
1779
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
1780
|
+
return this.sm.requestSupafoneApi<LabsTranscriptListResponse>("GET", `/api/v1/labs/transcripts${suffix}`);
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
get(transcriptId: string, opts: { agencyId?: string } = {}): Promise<Record<string, unknown>> {
|
|
1784
|
+
const q = new URLSearchParams();
|
|
1785
|
+
if (opts.agencyId) q.set("agency_id", opts.agencyId);
|
|
1786
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
1787
|
+
return this.sm.requestSupafoneApi("GET", `/api/v1/labs/transcripts/${encodeURIComponent(transcriptId)}${suffix}`);
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1006
1791
|
class LabsPhoneNumbersNamespace {
|
|
1007
1792
|
constructor(private sm: SupafoneLabs) {}
|
|
1008
1793
|
|
|
@@ -1048,6 +1833,42 @@ class LabsPhoneNumbersNamespace {
|
|
|
1048
1833
|
);
|
|
1049
1834
|
}
|
|
1050
1835
|
|
|
1836
|
+
/** Unassign a number from an agent but keep it reserved on the account. */
|
|
1837
|
+
unassign(numberId: string, input: LabsPhoneNumberReleaseRequest = {}): Promise<LabsPhoneNumberReleaseResponse> {
|
|
1838
|
+
return this.sm.requestSupafoneApi<LabsPhoneNumberReleaseResponse>(
|
|
1839
|
+
"POST",
|
|
1840
|
+
`/api/v1/labs/phone-numbers/${encodeURIComponent(numberId)}/unassign`,
|
|
1841
|
+
phoneNumberReleasePayload(input),
|
|
1842
|
+
);
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
/** Give a number back to the shared pool or release the reservation. */
|
|
1846
|
+
release(numberId: string, input: LabsPhoneNumberReleaseRequest = {}): Promise<LabsPhoneNumberReleaseResponse> {
|
|
1847
|
+
return this.sm.requestSupafoneApi<LabsPhoneNumberReleaseResponse>(
|
|
1848
|
+
"POST",
|
|
1849
|
+
`/api/v1/labs/phone-numbers/${encodeURIComponent(numberId)}/release`,
|
|
1850
|
+
phoneNumberReleasePayload({ ...input, returnToPool: input.returnToPool ?? input.return_to_pool ?? true }),
|
|
1851
|
+
);
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
/** Alias for release(numberId, { returnToPool: true }). */
|
|
1855
|
+
returnToPool(numberId: string, input: LabsPhoneNumberReleaseRequest = {}): Promise<LabsPhoneNumberReleaseResponse> {
|
|
1856
|
+
return this.release(numberId, { ...input, returnToPool: true });
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
/** Delete/release a number reservation. Backend policy decides whether this is allowed. */
|
|
1860
|
+
delete(numberId: string, input: LabsPhoneNumberReleaseRequest = {}): Promise<LabsPhoneNumberReleaseResponse> {
|
|
1861
|
+
const q = new URLSearchParams();
|
|
1862
|
+
const agencyId = input.agency_id ?? input.agencyId;
|
|
1863
|
+
if (agencyId) q.set("agency_id", agencyId);
|
|
1864
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
1865
|
+
return this.sm.requestSupafoneApi<LabsPhoneNumberReleaseResponse>(
|
|
1866
|
+
"DELETE",
|
|
1867
|
+
`/api/v1/labs/phone-numbers/${encodeURIComponent(numberId)}${suffix}`,
|
|
1868
|
+
phoneNumberReleasePayload(input),
|
|
1869
|
+
);
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1051
1872
|
/**
|
|
1052
1873
|
* Search if needed, buy the first matching Supafone-managed number, and assign
|
|
1053
1874
|
* it to the supplied agent. This is the zero-Twilio-account happy path.
|
|
@@ -1180,17 +2001,26 @@ function labsAgentPayload(input: CreateLabsAgentRequest): Record<string, unknown
|
|
|
1180
2001
|
industry: input.industry,
|
|
1181
2002
|
website_url: input.website_url ?? input.websiteUrl,
|
|
1182
2003
|
phone_number: input.phone_number ?? input.phoneNumber,
|
|
2004
|
+
number_strategy: input.number_strategy ?? input.numberStrategy,
|
|
2005
|
+
number_pool: input.number_pool ?? input.numberPool,
|
|
2006
|
+
premium: input.premium,
|
|
1183
2007
|
direction: input.direction,
|
|
1184
2008
|
preset_key: input.preset_key ?? input.presetKey,
|
|
1185
2009
|
runtime_mode: input.runtime_mode ?? input.runtimeMode,
|
|
2010
|
+
call_stages: callStagesPayload(input),
|
|
1186
2011
|
goal: input.goal,
|
|
1187
2012
|
greeting: input.greeting,
|
|
1188
2013
|
system_prompt: input.system_prompt ?? input.systemPrompt,
|
|
1189
2014
|
language: input.language,
|
|
1190
2015
|
voice: input.voice ? voicePayload(input.voice) : undefined,
|
|
1191
2016
|
provider_keys: input.provider_keys ?? (input.providerKeys ? providerKeysPayload(input.providerKeys) : undefined),
|
|
1192
|
-
byok: input.byok ?
|
|
2017
|
+
byok: input.byok ? byokPayload(input.byok) : undefined,
|
|
1193
2018
|
telephony: input.telephony ? telephonyPayload(input.telephony) : undefined,
|
|
2019
|
+
custom_sip: customSipPayload(input.custom_sip ?? input.customSip ?? input.sip),
|
|
2020
|
+
recording: input.recording ? recordingPayload(input.recording) : undefined,
|
|
2021
|
+
transcription: input.transcription ? transcriptionPayload(input.transcription) : undefined,
|
|
2022
|
+
artifacts: input.artifacts ? artifactsPayload(input.artifacts) : undefined,
|
|
2023
|
+
compliance: input.compliance,
|
|
1194
2024
|
tools: input.tools ? toolsPayload(input.tools) : undefined,
|
|
1195
2025
|
labs: input.labs ? labsPayload(input.labs) : undefined,
|
|
1196
2026
|
ultravox: input.ultravox ? ultravoxPayload(input.ultravox) : undefined,
|
|
@@ -1200,13 +2030,28 @@ function labsAgentPayload(input: CreateLabsAgentRequest): Record<string, unknown
|
|
|
1200
2030
|
});
|
|
1201
2031
|
}
|
|
1202
2032
|
|
|
2033
|
+
function hostedListQuery(opts: LabsCallListOptions): URLSearchParams {
|
|
2034
|
+
const q = new URLSearchParams();
|
|
2035
|
+
if (opts.agencyId) q.set("agency_id", opts.agencyId);
|
|
2036
|
+
const agentKey = opts.agent_key ?? opts.agentKey;
|
|
2037
|
+
if (agentKey) q.set("agent_key", agentKey);
|
|
2038
|
+
if (opts.limit !== undefined) q.set("limit", String(opts.limit));
|
|
2039
|
+
return q;
|
|
2040
|
+
}
|
|
2041
|
+
|
|
1203
2042
|
function telephonyPayload(input: LabsTelephonyConfig): Record<string, unknown> {
|
|
1204
2043
|
return compact({
|
|
1205
2044
|
agency_id: input.agency_id ?? input.agencyId,
|
|
1206
2045
|
mode: input.mode,
|
|
1207
2046
|
provider: input.provider,
|
|
2047
|
+
number_strategy: input.number_strategy ?? input.numberStrategy,
|
|
2048
|
+
number_pool: input.number_pool ?? input.numberPool,
|
|
2049
|
+
number_id: input.number_id ?? input.numberId,
|
|
2050
|
+
premium: input.premium,
|
|
1208
2051
|
label: input.label,
|
|
1209
2052
|
credentials: input.credentials ? telephonyCredentialsPayload(input.credentials) : undefined,
|
|
2053
|
+
provider_settings: input.provider_settings ?? input.providerSettings,
|
|
2054
|
+
custom_sip: customSipPayload(input.custom_sip ?? input.customSip),
|
|
1210
2055
|
metadata: input.metadata,
|
|
1211
2056
|
});
|
|
1212
2057
|
}
|
|
@@ -1225,12 +2070,23 @@ function telephonyCredentialsPayload(input: LabsTelephonyCredentials): Record<st
|
|
|
1225
2070
|
username: input.username,
|
|
1226
2071
|
password: input.password,
|
|
1227
2072
|
webhook_secret: input.webhook_secret ?? input.webhookSecret,
|
|
2073
|
+
telnyx_connection_id: input.telnyx_connection_id ?? input.telnyxConnectionId,
|
|
2074
|
+
signalwire_space_url: input.signalwire_space_url ?? input.signalwireSpaceUrl,
|
|
2075
|
+
project_id: input.project_id ?? input.projectId,
|
|
2076
|
+
application_id: input.application_id ?? input.applicationId,
|
|
2077
|
+
trunk_id: input.trunk_id ?? input.trunkId,
|
|
2078
|
+
endpoint_id: input.endpoint_id ?? input.endpointId,
|
|
2079
|
+
token: input.token,
|
|
2080
|
+
secret: input.secret,
|
|
2081
|
+
custom_sip: customSipPayload(input.custom_sip ?? input.customSip),
|
|
1228
2082
|
});
|
|
1229
2083
|
}
|
|
1230
2084
|
|
|
1231
2085
|
function phoneNumberSearchPayload(input: LabsPhoneNumberSearchOptions): Record<string, unknown> {
|
|
1232
2086
|
return compact({
|
|
1233
2087
|
agency_id: input.agency_id ?? input.agencyId,
|
|
2088
|
+
number_pool: input.number_pool ?? input.numberPool,
|
|
2089
|
+
number_strategy: input.number_strategy ?? input.numberStrategy,
|
|
1234
2090
|
country_code: input.country_code ?? input.countryCode,
|
|
1235
2091
|
area_code: input.area_code ?? input.areaCode,
|
|
1236
2092
|
postal_code: input.postal_code ?? input.postalCode,
|
|
@@ -1252,6 +2108,9 @@ function phoneNumberProvisionPayload(input: LabsPhoneNumberProvisionRequest): Re
|
|
|
1252
2108
|
agent_id: input.agent_id ?? input.agentId,
|
|
1253
2109
|
agent_name: input.agent_name ?? input.agentName,
|
|
1254
2110
|
preset_key: input.preset_key ?? input.presetKey,
|
|
2111
|
+
number_strategy: input.number_strategy ?? input.numberStrategy,
|
|
2112
|
+
number_pool: input.number_pool ?? input.numberPool,
|
|
2113
|
+
premium: input.premium,
|
|
1255
2114
|
style: input.agent_style ?? input.agentStyle ?? input.style,
|
|
1256
2115
|
direction: input.direction,
|
|
1257
2116
|
telephony: input.telephony ? telephonyPayload(input.telephony) : undefined,
|
|
@@ -1267,6 +2126,9 @@ function phoneNumberAssignPayload(input: LabsPhoneNumberAssignRequest): Record<s
|
|
|
1267
2126
|
agent_name: input.agent_name ?? input.agentName,
|
|
1268
2127
|
friendly_name: input.friendly_name ?? input.friendlyName,
|
|
1269
2128
|
preset_key: input.preset_key ?? input.presetKey,
|
|
2129
|
+
number_strategy: input.number_strategy ?? input.numberStrategy,
|
|
2130
|
+
number_pool: input.number_pool ?? input.numberPool,
|
|
2131
|
+
premium: input.premium,
|
|
1270
2132
|
style: input.agent_style ?? input.agentStyle ?? input.style,
|
|
1271
2133
|
direction: input.direction,
|
|
1272
2134
|
telephony: input.telephony ? telephonyPayload(input.telephony) : undefined,
|
|
@@ -1274,6 +2136,103 @@ function phoneNumberAssignPayload(input: LabsPhoneNumberAssignRequest): Record<s
|
|
|
1274
2136
|
});
|
|
1275
2137
|
}
|
|
1276
2138
|
|
|
2139
|
+
function phoneNumberReleasePayload(input: LabsPhoneNumberReleaseRequest): Record<string, unknown> {
|
|
2140
|
+
return compact({
|
|
2141
|
+
agency_id: input.agency_id ?? input.agencyId,
|
|
2142
|
+
reason: input.reason,
|
|
2143
|
+
return_to_pool: input.return_to_pool ?? input.returnToPool,
|
|
2144
|
+
metadata: input.metadata,
|
|
2145
|
+
});
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
function callStagesPayload(input: CreateLabsAgentRequest): Record<string, unknown>[] | undefined {
|
|
2149
|
+
const explicit = input.call_stages ?? input.callStages ?? input.stages;
|
|
2150
|
+
const auto = input.auto_call_stages ?? input.autoCallStages;
|
|
2151
|
+
if (Array.isArray(explicit)) return explicit.map(callStagePayload);
|
|
2152
|
+
if (explicit === false || auto === false) return undefined;
|
|
2153
|
+
return generateCallStages(input).map(callStagePayload);
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
function callStagePayload(input: LabsCallStage): Record<string, unknown> {
|
|
2157
|
+
return compact({
|
|
2158
|
+
key: input.key ?? input.id,
|
|
2159
|
+
name: input.name,
|
|
2160
|
+
goal: input.goal,
|
|
2161
|
+
instructions: input.instructions,
|
|
2162
|
+
exit_criteria: input.exit_criteria ?? input.exitCriteria,
|
|
2163
|
+
tools: input.tools,
|
|
2164
|
+
metadata: input.metadata,
|
|
2165
|
+
});
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
export function generateCallStages(input: CreateLabsAgentRequest): LabsCallStage[] {
|
|
2169
|
+
const direction = String(input.direction ?? input.agent_style ?? input.agentStyle ?? input.style ?? "inbound").toLowerCase();
|
|
2170
|
+
const haystack = [
|
|
2171
|
+
input.name,
|
|
2172
|
+
input.assistant_name ?? input.assistantName,
|
|
2173
|
+
input.business_name ?? input.businessName,
|
|
2174
|
+
input.industry,
|
|
2175
|
+
input.goal,
|
|
2176
|
+
input.system_prompt ?? input.systemPrompt,
|
|
2177
|
+
input.preset_key ?? input.presetKey,
|
|
2178
|
+
].filter(Boolean).join(" ").toLowerCase();
|
|
2179
|
+
const meta = { auto_generated: true, source: "supafone-labs-sdk" };
|
|
2180
|
+
|
|
2181
|
+
if (direction === "outbound" || haystack.includes("sales") || haystack.includes("lead")) {
|
|
2182
|
+
return [
|
|
2183
|
+
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),
|
|
2184
|
+
stage("qualification", "Qualification", "Confirm fit, urgency, decision process, and the best next step.", ["Need and timeline are clear"], meta),
|
|
2185
|
+
stage("offer", "Offer", "Explain the next step in plain language without unsupported claims.", ["Caller understands the next step"], meta),
|
|
2186
|
+
stage("booking", "Booking", "Book or route the caller only after confirming required details.", ["Next step is confirmed by a tool or human handoff"], meta),
|
|
2187
|
+
stage("close", "Close", "Summarize what will happen next and end politely.", ["Caller knows the follow-up path"], meta),
|
|
2188
|
+
];
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
if (haystack.includes("legal") || haystack.includes("law") || haystack.includes("injury") || haystack.includes("intake")) {
|
|
2192
|
+
return [
|
|
2193
|
+
stage("greeting", "Greeting", "Open warmly and acknowledge the caller before logistics.", ["Caller need is understood"], meta),
|
|
2194
|
+
stage("incident", "Incident details", "Collect what happened, when it happened, injuries, insurance, and contact details.", ["Core facts are collected"], meta),
|
|
2195
|
+
stage("screening", "Screening", "Identify urgency, jurisdiction, conflicts, and whether human escalation is required.", ["Escalation decision is clear"], meta),
|
|
2196
|
+
stage("booking", "Consult booking", "Book the right next step without quoting fees or inventing availability.", ["Booking or handoff is tool-confirmed"], meta),
|
|
2197
|
+
stage("close", "Close", "Summarize the next step and set expectations accurately.", ["Caller knows exactly what happens next"], meta),
|
|
2198
|
+
];
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
if (haystack.includes("medical") || haystack.includes("clinic") || haystack.includes("patient") || haystack.includes("health")) {
|
|
2202
|
+
return [
|
|
2203
|
+
stage("greeting", "Greeting", "Identify the caller need and keep the tone calm and concise.", ["Caller need is understood"], meta),
|
|
2204
|
+
stage("patient_context", "Patient context", "Collect non-sensitive scheduling context and avoid medical advice.", ["Required scheduling context is collected"], meta),
|
|
2205
|
+
stage("routing", "Routing", "Route urgent, billing, clinical, and scheduling requests correctly.", ["Correct route is selected"], meta),
|
|
2206
|
+
stage("appointment", "Appointment", "Book or request the appointment only after confirming details.", ["Appointment path is confirmed"], meta),
|
|
2207
|
+
stage("close", "Close", "Recap next steps and any confirmed timing.", ["Caller knows the next step"], meta),
|
|
2208
|
+
];
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
return [
|
|
2212
|
+
stage("greeting", "Greeting", "Open naturally, identify the caller need, and set a helpful tone.", ["Caller need is understood"], meta),
|
|
2213
|
+
stage("discovery", "Discovery", "Ask one question at a time until the key details are clear.", ["Required details are collected"], meta),
|
|
2214
|
+
stage("resolution", "Resolution", "Answer approved questions or route to the right workflow.", ["Resolution path is selected"], meta),
|
|
2215
|
+
stage("action", "Action", "Use tools for booking, routing, messaging, or handoff before claiming success.", ["Action is confirmed by a tool or handoff"], meta),
|
|
2216
|
+
stage("close", "Close", "Summarize the outcome and next step accurately.", ["Caller knows what happens next"], meta),
|
|
2217
|
+
];
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
function stage(
|
|
2221
|
+
key: string,
|
|
2222
|
+
name: string,
|
|
2223
|
+
instructions: string,
|
|
2224
|
+
exitCriteria: string[],
|
|
2225
|
+
metadata: Record<string, unknown>,
|
|
2226
|
+
): LabsCallStage {
|
|
2227
|
+
return {
|
|
2228
|
+
key,
|
|
2229
|
+
name,
|
|
2230
|
+
instructions,
|
|
2231
|
+
exitCriteria,
|
|
2232
|
+
metadata,
|
|
2233
|
+
};
|
|
2234
|
+
}
|
|
2235
|
+
|
|
1277
2236
|
function voicePayload(input: LabsVoiceSelection): Record<string, unknown> {
|
|
1278
2237
|
return compact({
|
|
1279
2238
|
provider: input.provider,
|
|
@@ -1286,6 +2245,30 @@ function providerKeysPayload(input: LabsProviderKeys): Record<string, unknown> {
|
|
|
1286
2245
|
return compact({
|
|
1287
2246
|
ultravox: input.ultravox,
|
|
1288
2247
|
ultravox_api_key: input.ultravox_api_key ?? input.ultravoxApiKey,
|
|
2248
|
+
retell: input.retell,
|
|
2249
|
+
retell_api_key: input.retell_api_key ?? input.retellApiKey,
|
|
2250
|
+
vapi: input.vapi,
|
|
2251
|
+
vapi_api_key: input.vapi_api_key ?? input.vapiApiKey,
|
|
2252
|
+
bland: input.bland,
|
|
2253
|
+
bland_api_key: input.bland_api_key ?? input.blandApiKey,
|
|
2254
|
+
livekit: input.livekit,
|
|
2255
|
+
livekit_api_key: input.livekit_api_key ?? input.livekitApiKey,
|
|
2256
|
+
livekit_api_secret: input.livekit_api_secret ?? input.livekitApiSecret,
|
|
2257
|
+
pipecat: input.pipecat,
|
|
2258
|
+
pipecat_api_key: input.pipecat_api_key ?? input.pipecatApiKey,
|
|
2259
|
+
twilio: input.twilio,
|
|
2260
|
+
twilio_account_sid: input.twilio_account_sid ?? input.twilioAccountSid,
|
|
2261
|
+
twilio_auth_token: input.twilio_auth_token ?? input.twilioAuthToken,
|
|
2262
|
+
twilio_api_key_sid: input.twilio_api_key_sid ?? input.twilioApiKeySid,
|
|
2263
|
+
twilio_api_key_secret: input.twilio_api_key_secret ?? input.twilioApiKeySecret,
|
|
2264
|
+
telnyx: input.telnyx,
|
|
2265
|
+
telnyx_api_key: input.telnyx_api_key ?? input.telnyxApiKey,
|
|
2266
|
+
plivo: input.plivo,
|
|
2267
|
+
plivo_auth_id: input.plivo_auth_id ?? input.plivoAuthId,
|
|
2268
|
+
plivo_auth_token: input.plivo_auth_token ?? input.plivoAuthToken,
|
|
2269
|
+
signalwire: input.signalwire,
|
|
2270
|
+
signalwire_api_token: input.signalwire_api_token ?? input.signalwireApiToken,
|
|
2271
|
+
signalwire_project_id: input.signalwire_project_id ?? input.signalwireProjectId,
|
|
1289
2272
|
elevenlabs: input.elevenlabs,
|
|
1290
2273
|
elevenlabs_api_key: input.elevenlabs_api_key ?? input.elevenlabsApiKey,
|
|
1291
2274
|
cartesia: input.cartesia,
|
|
@@ -1294,6 +2277,59 @@ function providerKeysPayload(input: LabsProviderKeys): Record<string, unknown> {
|
|
|
1294
2277
|
inworld_api_key: input.inworld_api_key ?? input.inworldApiKey,
|
|
1295
2278
|
deepgram: input.deepgram,
|
|
1296
2279
|
deepgram_api_key: input.deepgram_api_key ?? input.deepgramApiKey,
|
|
2280
|
+
anthropic: input.anthropic,
|
|
2281
|
+
anthropic_api_key: input.anthropic_api_key ?? input.anthropicApiKey,
|
|
2282
|
+
openai: input.openai,
|
|
2283
|
+
openai_api_key: input.openai_api_key ?? input.openaiApiKey,
|
|
2284
|
+
xai: input.xai,
|
|
2285
|
+
xai_api_key: input.xai_api_key ?? input.xaiApiKey,
|
|
2286
|
+
});
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
function byokPayload(input: LabsProviderKeys | LabsByokConfig): Record<string, unknown> {
|
|
2290
|
+
const structured = input as LabsByokConfig;
|
|
2291
|
+
if (
|
|
2292
|
+
structured.agentProvider ||
|
|
2293
|
+
structured.agent_provider ||
|
|
2294
|
+
structured.runtime ||
|
|
2295
|
+
structured.telephony ||
|
|
2296
|
+
structured.tts ||
|
|
2297
|
+
structured.stt ||
|
|
2298
|
+
structured.llm ||
|
|
2299
|
+
structured.providerKeys ||
|
|
2300
|
+
structured.provider_keys ||
|
|
2301
|
+
structured.customSip ||
|
|
2302
|
+
structured.custom_sip ||
|
|
2303
|
+
structured.sip
|
|
2304
|
+
) {
|
|
2305
|
+
return compact({
|
|
2306
|
+
provider_keys: providerKeysPayload(structured.provider_keys ?? structured.providerKeys ?? {}),
|
|
2307
|
+
agent_provider: providerConfigPayload(structured.agent_provider ?? structured.agentProvider ?? structured.runtime),
|
|
2308
|
+
telephony: structured.telephony ? telephonyPayload(structured.telephony) : undefined,
|
|
2309
|
+
tts: providerConfigPayload(structured.tts),
|
|
2310
|
+
stt: providerConfigPayload(structured.stt),
|
|
2311
|
+
llm: providerConfigPayload(structured.llm),
|
|
2312
|
+
custom_sip: customSipPayload(structured.custom_sip ?? structured.customSip ?? structured.sip),
|
|
2313
|
+
});
|
|
2314
|
+
}
|
|
2315
|
+
return providerKeysPayload(input as LabsProviderKeys);
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
function providerConfigPayload(input?: LabsProviderByokConfig): Record<string, unknown> | undefined {
|
|
2319
|
+
if (!input) return undefined;
|
|
2320
|
+
const out: Record<string, unknown> = { ...input };
|
|
2321
|
+
delete out.apiKey;
|
|
2322
|
+
delete out.api_key;
|
|
2323
|
+
delete out.voiceId;
|
|
2324
|
+
delete out.voice_id;
|
|
2325
|
+
return compact({
|
|
2326
|
+
...out,
|
|
2327
|
+
provider: input.provider,
|
|
2328
|
+
api_key: input.api_key ?? input.apiKey,
|
|
2329
|
+
credentials: input.credentials,
|
|
2330
|
+
settings: input.settings,
|
|
2331
|
+
model: input.model,
|
|
2332
|
+
voice_id: input.voice_id ?? input.voiceId,
|
|
1297
2333
|
});
|
|
1298
2334
|
}
|
|
1299
2335
|
|
|
@@ -1312,11 +2348,57 @@ function toolsPayload(input: LabsToolsConfig): Record<string, unknown> {
|
|
|
1312
2348
|
});
|
|
1313
2349
|
}
|
|
1314
2350
|
|
|
2351
|
+
function recordingPayload(input: LabsRecordingConfig): Record<string, unknown> {
|
|
2352
|
+
return compact({
|
|
2353
|
+
enabled: input.enabled,
|
|
2354
|
+
record_audio: input.record_audio ?? input.recordAudio,
|
|
2355
|
+
consent_required: input.consent_required ?? input.consentRequired,
|
|
2356
|
+
announcement: input.announcement,
|
|
2357
|
+
retention_days: input.retention_days ?? input.retentionDays,
|
|
2358
|
+
storage: input.storage,
|
|
2359
|
+
redact_pii: input.redact_pii ?? input.redactPii,
|
|
2360
|
+
metadata: input.metadata,
|
|
2361
|
+
});
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
function transcriptionPayload(input: LabsTranscriptionConfig): Record<string, unknown> {
|
|
2365
|
+
return compact({
|
|
2366
|
+
enabled: input.enabled,
|
|
2367
|
+
provider: input.provider,
|
|
2368
|
+
model: input.model,
|
|
2369
|
+
language: input.language,
|
|
2370
|
+
redact_pii: input.redact_pii ?? input.redactPii,
|
|
2371
|
+
diarization: input.diarization,
|
|
2372
|
+
timestamps: input.timestamps,
|
|
2373
|
+
metadata: input.metadata,
|
|
2374
|
+
});
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2377
|
+
function artifactsPayload(input: LabsArtifactsConfig): Record<string, unknown> {
|
|
2378
|
+
return compact({
|
|
2379
|
+
recordings: input.recordings,
|
|
2380
|
+
transcripts: input.transcripts,
|
|
2381
|
+
summaries: input.summaries,
|
|
2382
|
+
qa_reports: input.qa_reports ?? input.qaReports,
|
|
2383
|
+
logs: input.logs,
|
|
2384
|
+
webhooks: input.webhooks,
|
|
2385
|
+
retention_days: input.retention_days ?? input.retentionDays,
|
|
2386
|
+
metadata: input.metadata,
|
|
2387
|
+
});
|
|
2388
|
+
}
|
|
2389
|
+
|
|
1315
2390
|
function labsPayload(input: LabsWatcherConfig): Record<string, unknown> {
|
|
1316
2391
|
return compact({
|
|
1317
2392
|
enabled: input.enabled,
|
|
1318
2393
|
voice_watcher: input.voice_watcher ?? input.voiceWatcher,
|
|
2394
|
+
api_key: input.api_key ?? input.apiKey,
|
|
1319
2395
|
model: input.model,
|
|
2396
|
+
mode: input.mode,
|
|
2397
|
+
managed_infrastructure: input.managed_infrastructure ?? input.managedInfrastructure,
|
|
2398
|
+
stt: input.stt,
|
|
2399
|
+
llm: input.llm,
|
|
2400
|
+
tts: input.tts,
|
|
2401
|
+
provider_keys: input.provider_keys ?? input.providerKeys,
|
|
1320
2402
|
label: input.label,
|
|
1321
2403
|
});
|
|
1322
2404
|
}
|
|
@@ -1346,6 +2428,24 @@ function ultravoxPayload(input: LabsUltravoxRuntime): Record<string, unknown> {
|
|
|
1346
2428
|
voiceOverrides: input.voiceOverrides ?? input.voice_overrides,
|
|
1347
2429
|
retentionPolicy: input.retentionPolicy ?? input.retention_policy,
|
|
1348
2430
|
callTemplate: input.callTemplate ?? input.call_template,
|
|
2431
|
+
custom_sip: customSipPayload(input.custom_sip ?? input.customSip ?? input.sip),
|
|
2432
|
+
});
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
function customSipPayload(input?: LabsCustomSipConfig): Record<string, unknown> | undefined {
|
|
2436
|
+
if (!input) return undefined;
|
|
2437
|
+
return compact({
|
|
2438
|
+
sip_trunk_uri: input.sip_trunk_uri ?? input.sipTrunkUri,
|
|
2439
|
+
trunk_uri: input.trunk_uri ?? input.trunkUri,
|
|
2440
|
+
sip_host: input.sip_host ?? input.sipHost,
|
|
2441
|
+
from_number: input.from_number ?? input.fromNumber,
|
|
2442
|
+
username: input.username,
|
|
2443
|
+
password: input.password,
|
|
2444
|
+
transport: input.transport,
|
|
2445
|
+
headers: input.headers,
|
|
2446
|
+
codecs: input.codecs,
|
|
2447
|
+
dtmf_mode: input.dtmf_mode ?? input.dtmfMode,
|
|
2448
|
+
metadata: input.metadata,
|
|
1349
2449
|
});
|
|
1350
2450
|
}
|
|
1351
2451
|
|
|
@@ -1357,6 +2457,18 @@ function compact(input: Record<string, unknown>): Record<string, unknown> {
|
|
|
1357
2457
|
return out;
|
|
1358
2458
|
}
|
|
1359
2459
|
|
|
2460
|
+
function parseSseLog(raw: string): LabsLogEntry | null {
|
|
2461
|
+
const data: string[] = [];
|
|
2462
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
2463
|
+
if (!line || line.startsWith(":")) continue;
|
|
2464
|
+
if (line.startsWith("data:")) data.push(line.slice(5).trimStart());
|
|
2465
|
+
}
|
|
2466
|
+
if (!data.length) return null;
|
|
2467
|
+
const parsed = safeJson(data.join("\n"));
|
|
2468
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
2469
|
+
return parsed as LabsLogEntry;
|
|
2470
|
+
}
|
|
2471
|
+
|
|
1360
2472
|
function safeJson(text: string): unknown {
|
|
1361
2473
|
try {
|
|
1362
2474
|
return JSON.parse(text);
|