supafone-labs 0.3.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/LICENSE +21 -0
- package/README.md +259 -0
- package/dist/cjs/index.d.ts +736 -0
- package/dist/cjs/index.js +761 -0
- package/dist/cjs/package.json +1 -0
- package/dist/index.d.ts +737 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +757 -0
- package/dist/index.js.map +1 -0
- package/package.json +67 -0
- package/src/index.ts +1370 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,1370 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supafone Labs — the agent framework behind Supafone.
|
|
3
|
+
*
|
|
4
|
+
* A dependency-free TypeScript client for creating hosted Supafone agents
|
|
5
|
+
* through the Supafone API, including managed phone numbers, voices, stages,
|
|
6
|
+
* tools, recordings, transcripts, widgets, and Supafone Pro watcher. It also
|
|
7
|
+
* includes the Labs cloud sidecar oracle, hosted TTS/STT, live multilingual
|
|
8
|
+
* transcription, telemetry, agent builder, and objective-driven optimizer.
|
|
9
|
+
*
|
|
10
|
+
* Works in Node 18+ (native fetch/WebSocket) and the browser.
|
|
11
|
+
*
|
|
12
|
+
* npm i @supafonesupafone-labs
|
|
13
|
+
*
|
|
14
|
+
* import { Supafone } from "@supafonesupafone-labs";
|
|
15
|
+
* const supafone = new Supafone({ apiKey: process.env.SUPAFONE_API_KEY! });
|
|
16
|
+
* const agent = await supafone.labs.agents.createInboundWithNumber({
|
|
17
|
+
* agentKey: "northline-intake",
|
|
18
|
+
* name: "Northline intake",
|
|
19
|
+
* number: { search: { areaCode: "415" } },
|
|
20
|
+
* });
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export interface SupafoneLabsOptions {
|
|
24
|
+
/** Your key from https:/supafone-labs.supafone.ai/get-key.html */
|
|
25
|
+
apiKey: string;
|
|
26
|
+
/** Override the gateway (default: the hosted cloud). */
|
|
27
|
+
baseUrl?: string;
|
|
28
|
+
/** Supafone app/API key for hosted agent provisioning. Defaults to apiKey. */
|
|
29
|
+
supafoneApiKey?: string;
|
|
30
|
+
/** Override the Supafone API used by supafone.labs.* (default: https://api.supafone.ai). */
|
|
31
|
+
supafoneApiBaseUrl?: string;
|
|
32
|
+
/** Per-request timeout in ms (default 30_000). */
|
|
33
|
+
timeoutMs?: number;
|
|
34
|
+
/** Optional pre-obtained session token (else use login()). */
|
|
35
|
+
sessionToken?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ChatMessage {
|
|
39
|
+
role: "system" | "user" | "assistant";
|
|
40
|
+
content: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface OracleRequest {
|
|
44
|
+
messages: ChatMessage[];
|
|
45
|
+
/** Any claude-* / gpt-* / grok-* id, or the alias "supafone-labs-oracle". */
|
|
46
|
+
model?: string;
|
|
47
|
+
maxTokens?: number;
|
|
48
|
+
temperature?: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface OracleResult {
|
|
52
|
+
text: string;
|
|
53
|
+
model: string;
|
|
54
|
+
usage?: Record<string, number>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface WhisperOptions {
|
|
58
|
+
model?: string;
|
|
59
|
+
/** Extra operator rules folded into the coaching system prompt. */
|
|
60
|
+
guardrails?: string;
|
|
61
|
+
maxTokens?: number;
|
|
62
|
+
temperature?: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface Balance {
|
|
66
|
+
plan: string;
|
|
67
|
+
seconds_remaining: number;
|
|
68
|
+
minutes_remaining: number;
|
|
69
|
+
top_up?: { subscribe_monthly?: string; buy_credit_pack?: string; note?: string };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface UsageToday {
|
|
73
|
+
plan: string;
|
|
74
|
+
day: string;
|
|
75
|
+
usage: Record<string, { used: number; cap: number }>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type LabsAgentType = "phone" | "web" | "campaign";
|
|
79
|
+
export type LabsAgentStyle = "inbound" | "outbound";
|
|
80
|
+
export type LabsRuntimeMode = "multi_stage" | "single_stage";
|
|
81
|
+
export type LabsTelephonyMode = "supafone_managed" | "byok";
|
|
82
|
+
export type LabsTelephonyProvider = "supafone" | "twilio" | "telnyx" | "plivo" | "sip" | string;
|
|
83
|
+
|
|
84
|
+
export interface LabsVoiceSelection {
|
|
85
|
+
provider?: string;
|
|
86
|
+
voiceId?: string;
|
|
87
|
+
voice_id?: string;
|
|
88
|
+
model?: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface LabsProviderKeys {
|
|
92
|
+
ultravox?: string;
|
|
93
|
+
ultravoxApiKey?: string;
|
|
94
|
+
ultravox_api_key?: string;
|
|
95
|
+
elevenlabs?: string;
|
|
96
|
+
elevenlabsApiKey?: string;
|
|
97
|
+
elevenlabs_api_key?: string;
|
|
98
|
+
cartesia?: string;
|
|
99
|
+
cartesiaApiKey?: string;
|
|
100
|
+
cartesia_api_key?: string;
|
|
101
|
+
inworld?: string;
|
|
102
|
+
inworldApiKey?: string;
|
|
103
|
+
inworld_api_key?: string;
|
|
104
|
+
deepgram?: string;
|
|
105
|
+
deepgramApiKey?: string;
|
|
106
|
+
deepgram_api_key?: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface LabsTelephonyCredentials {
|
|
110
|
+
accountSid?: string;
|
|
111
|
+
account_sid?: string;
|
|
112
|
+
authToken?: string;
|
|
113
|
+
auth_token?: string;
|
|
114
|
+
apiKey?: string;
|
|
115
|
+
api_key?: string;
|
|
116
|
+
apiSecret?: string;
|
|
117
|
+
api_secret?: string;
|
|
118
|
+
authId?: string;
|
|
119
|
+
auth_id?: string;
|
|
120
|
+
connectionId?: string;
|
|
121
|
+
connection_id?: string;
|
|
122
|
+
fromNumber?: string;
|
|
123
|
+
from_number?: string;
|
|
124
|
+
sipTrunkUri?: string;
|
|
125
|
+
sip_trunk_uri?: string;
|
|
126
|
+
sipHost?: string;
|
|
127
|
+
sip_host?: string;
|
|
128
|
+
username?: string;
|
|
129
|
+
password?: string;
|
|
130
|
+
webhookSecret?: string;
|
|
131
|
+
webhook_secret?: string;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface LabsTelephonyConfig {
|
|
135
|
+
agencyId?: string;
|
|
136
|
+
agency_id?: string;
|
|
137
|
+
/** Default is Supafone-managed; developers do not need Twilio for this path. */
|
|
138
|
+
mode?: LabsTelephonyMode;
|
|
139
|
+
provider?: LabsTelephonyProvider;
|
|
140
|
+
label?: string;
|
|
141
|
+
credentials?: LabsTelephonyCredentials;
|
|
142
|
+
metadata?: Record<string, unknown>;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface LabsToolsConfig {
|
|
146
|
+
callRouting?: boolean;
|
|
147
|
+
call_routing?: boolean;
|
|
148
|
+
scheduling?: boolean;
|
|
149
|
+
sms?: boolean;
|
|
150
|
+
email?: boolean;
|
|
151
|
+
intakeForms?: boolean;
|
|
152
|
+
intake_forms?: boolean;
|
|
153
|
+
firmKnowledge?: boolean;
|
|
154
|
+
firm_knowledge?: boolean;
|
|
155
|
+
existingClientLookup?: boolean;
|
|
156
|
+
existing_client_lookup?: boolean;
|
|
157
|
+
voicemail?: boolean;
|
|
158
|
+
emergencyEscalation?: boolean;
|
|
159
|
+
emergency_escalation?: boolean;
|
|
160
|
+
customTools?: Array<Record<string, unknown>>;
|
|
161
|
+
custom_tools?: Array<Record<string, unknown>>;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export interface LabsWatcherConfig {
|
|
165
|
+
enabled?: boolean;
|
|
166
|
+
voiceWatcher?: boolean;
|
|
167
|
+
voice_watcher?: boolean;
|
|
168
|
+
model?: string;
|
|
169
|
+
label?: string;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface LabsUltravoxRuntime {
|
|
173
|
+
model?: string;
|
|
174
|
+
temperature?: number;
|
|
175
|
+
medium?: Record<string, unknown>;
|
|
176
|
+
vadSettings?: Record<string, unknown>;
|
|
177
|
+
vad_settings?: Record<string, unknown>;
|
|
178
|
+
speakerFirst?: boolean;
|
|
179
|
+
speaker_first?: boolean;
|
|
180
|
+
firstSpeaker?: string;
|
|
181
|
+
first_speaker?: string;
|
|
182
|
+
firstSpeakerSettings?: Record<string, unknown>;
|
|
183
|
+
first_speaker_settings?: Record<string, unknown>;
|
|
184
|
+
selectedTools?: Array<Record<string, unknown>>;
|
|
185
|
+
selected_tools?: Array<Record<string, unknown>>;
|
|
186
|
+
initialMessages?: Array<Record<string, unknown>>;
|
|
187
|
+
initial_messages?: Array<Record<string, unknown>>;
|
|
188
|
+
initialState?: Record<string, unknown>;
|
|
189
|
+
initial_state?: Record<string, unknown>;
|
|
190
|
+
initialOutputMedium?: string;
|
|
191
|
+
initial_output_medium?: string;
|
|
192
|
+
joinTimeout?: string;
|
|
193
|
+
join_timeout?: string;
|
|
194
|
+
maxDuration?: string;
|
|
195
|
+
max_duration?: string;
|
|
196
|
+
maxDurationSeconds?: number;
|
|
197
|
+
max_duration_seconds?: number;
|
|
198
|
+
timeExceededMessage?: string;
|
|
199
|
+
time_exceeded_message?: string;
|
|
200
|
+
inactivityMessages?: Array<Record<string, unknown>>;
|
|
201
|
+
inactivity_messages?: Array<Record<string, unknown>>;
|
|
202
|
+
dataConnection?: Record<string, unknown>;
|
|
203
|
+
data_connection?: Record<string, unknown>;
|
|
204
|
+
callbacks?: Record<string, unknown>;
|
|
205
|
+
metadata?: Record<string, unknown>;
|
|
206
|
+
experimentalSettings?: Record<string, unknown>;
|
|
207
|
+
experimental_settings?: Record<string, unknown>;
|
|
208
|
+
voiceOverrides?: Record<string, unknown>;
|
|
209
|
+
voice_overrides?: Record<string, unknown>;
|
|
210
|
+
retentionPolicy?: string;
|
|
211
|
+
retention_policy?: string;
|
|
212
|
+
callTemplate?: Record<string, unknown>;
|
|
213
|
+
call_template?: Record<string, unknown>;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export interface CreateLabsAgentRequest {
|
|
217
|
+
agencyId?: string;
|
|
218
|
+
agency_id?: string;
|
|
219
|
+
agentKey?: string;
|
|
220
|
+
agent_key?: string;
|
|
221
|
+
agentType?: LabsAgentType;
|
|
222
|
+
agent_type?: LabsAgentType;
|
|
223
|
+
/** inbound = receptionist/intake, outbound = sales/speed-to-lead/campaign. */
|
|
224
|
+
style?: LabsAgentStyle;
|
|
225
|
+
agentStyle?: LabsAgentStyle;
|
|
226
|
+
agent_style?: LabsAgentStyle;
|
|
227
|
+
name: string;
|
|
228
|
+
assistantName?: string;
|
|
229
|
+
assistant_name?: string;
|
|
230
|
+
businessName?: string;
|
|
231
|
+
business_name?: string;
|
|
232
|
+
industry?: string;
|
|
233
|
+
websiteUrl?: string;
|
|
234
|
+
website_url?: string;
|
|
235
|
+
phoneNumber?: string;
|
|
236
|
+
phone_number?: string;
|
|
237
|
+
direction?: string;
|
|
238
|
+
presetKey?: string;
|
|
239
|
+
preset_key?: string;
|
|
240
|
+
runtimeMode?: LabsRuntimeMode;
|
|
241
|
+
runtime_mode?: LabsRuntimeMode;
|
|
242
|
+
goal?: string;
|
|
243
|
+
greeting?: string;
|
|
244
|
+
systemPrompt?: string;
|
|
245
|
+
system_prompt?: string;
|
|
246
|
+
language?: string;
|
|
247
|
+
voice?: LabsVoiceSelection;
|
|
248
|
+
providerKeys?: LabsProviderKeys;
|
|
249
|
+
provider_keys?: LabsProviderKeys;
|
|
250
|
+
byok?: LabsProviderKeys;
|
|
251
|
+
telephony?: LabsTelephonyConfig;
|
|
252
|
+
tools?: LabsToolsConfig;
|
|
253
|
+
labs?: LabsWatcherConfig;
|
|
254
|
+
ultravox?: LabsUltravoxRuntime;
|
|
255
|
+
voiceWatcher?: boolean;
|
|
256
|
+
voice_watcher?: boolean;
|
|
257
|
+
voiceWatcherModel?: string;
|
|
258
|
+
voice_watcher_model?: string;
|
|
259
|
+
metadata?: Record<string, unknown>;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export interface ListLabsAgentsOptions {
|
|
263
|
+
agencyId?: string;
|
|
264
|
+
agentType?: LabsAgentType;
|
|
265
|
+
style?: LabsAgentStyle;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export interface GetLabsAgentOptions extends ListLabsAgentsOptions {}
|
|
269
|
+
|
|
270
|
+
export interface LabsCapabilitiesResponse {
|
|
271
|
+
product: string;
|
|
272
|
+
api_namespace: string;
|
|
273
|
+
compatibility_namespace?: string;
|
|
274
|
+
default_agent_contract: Record<string, unknown>;
|
|
275
|
+
capabilities: Array<Record<string, unknown>>;
|
|
276
|
+
recommended_next_api_additions?: Array<Record<string, unknown>>;
|
|
277
|
+
[extra: string]: unknown;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export interface LabsPresetListResponse {
|
|
281
|
+
default_preset_key: string;
|
|
282
|
+
router_policies?: Record<string, string>;
|
|
283
|
+
presets: Array<Record<string, unknown>>;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export interface LabsToolListResponse {
|
|
287
|
+
tools: Array<Record<string, unknown>>;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export interface LabsVoiceListOptions {
|
|
291
|
+
provider?: string;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export interface LabsVoiceListResponse {
|
|
295
|
+
voices: Array<Record<string, unknown>>;
|
|
296
|
+
total: number;
|
|
297
|
+
providers: Array<Record<string, unknown>>;
|
|
298
|
+
provider_accounts?: Record<string, unknown>;
|
|
299
|
+
errors?: Record<string, string>;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export interface LabsPhoneNumberSearchOptions {
|
|
303
|
+
agencyId?: string;
|
|
304
|
+
agency_id?: string;
|
|
305
|
+
countryCode?: string;
|
|
306
|
+
country_code?: string;
|
|
307
|
+
areaCode?: string;
|
|
308
|
+
area_code?: string;
|
|
309
|
+
postalCode?: string;
|
|
310
|
+
postal_code?: string;
|
|
311
|
+
zipCode?: string;
|
|
312
|
+
zip_code?: string;
|
|
313
|
+
contains?: string;
|
|
314
|
+
numberType?: "local" | "toll_free" | "mobile" | string;
|
|
315
|
+
number_type?: string;
|
|
316
|
+
limit?: number;
|
|
317
|
+
capabilities?: string[];
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export interface LabsPhoneNumberOption {
|
|
321
|
+
phone_number: string;
|
|
322
|
+
number_type?: string;
|
|
323
|
+
region?: string;
|
|
324
|
+
locality?: string;
|
|
325
|
+
monthly_cost?: number;
|
|
326
|
+
setup_cost?: number;
|
|
327
|
+
capabilities?: string[];
|
|
328
|
+
managed_by?: string;
|
|
329
|
+
telephony_mode?: LabsTelephonyMode | string;
|
|
330
|
+
[extra: string]: unknown;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export interface LabsPhoneNumberSearchResponse {
|
|
334
|
+
numbers: LabsPhoneNumberOption[];
|
|
335
|
+
search_context?: Record<string, unknown>;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export interface LabsPhoneNumberRecord {
|
|
339
|
+
number_id?: string;
|
|
340
|
+
phone_number?: string;
|
|
341
|
+
status?: string;
|
|
342
|
+
friendly_name?: string;
|
|
343
|
+
number_type?: string;
|
|
344
|
+
monthly_cost?: number;
|
|
345
|
+
provisioned_at?: string;
|
|
346
|
+
capabilities?: string[];
|
|
347
|
+
managed_by?: string;
|
|
348
|
+
telephony_mode?: LabsTelephonyMode | string;
|
|
349
|
+
[extra: string]: unknown;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export interface LabsPhoneNumberListOptions {
|
|
353
|
+
agencyId?: string;
|
|
354
|
+
activeOnly?: boolean;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export interface LabsPhoneNumberListResponse {
|
|
358
|
+
numbers: LabsPhoneNumberRecord[];
|
|
359
|
+
telephony?: Record<string, unknown>;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export interface LabsPhoneNumberProvisionRequest {
|
|
363
|
+
agencyId?: string;
|
|
364
|
+
agency_id?: string;
|
|
365
|
+
phoneNumber?: string;
|
|
366
|
+
phone_number?: string;
|
|
367
|
+
friendlyName?: string;
|
|
368
|
+
friendly_name?: string;
|
|
369
|
+
departmentId?: string;
|
|
370
|
+
department_id?: string;
|
|
371
|
+
agentKey?: string;
|
|
372
|
+
agent_key?: string;
|
|
373
|
+
agentId?: string;
|
|
374
|
+
agent_id?: string;
|
|
375
|
+
agentName?: string;
|
|
376
|
+
agent_name?: string;
|
|
377
|
+
presetKey?: string;
|
|
378
|
+
preset_key?: string;
|
|
379
|
+
style?: LabsAgentStyle;
|
|
380
|
+
agentStyle?: LabsAgentStyle;
|
|
381
|
+
agent_style?: LabsAgentStyle;
|
|
382
|
+
direction?: LabsAgentStyle;
|
|
383
|
+
telephony?: LabsTelephonyConfig;
|
|
384
|
+
metadata?: Record<string, unknown>;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export interface LabsPhoneNumberAssignRequest extends Omit<LabsPhoneNumberProvisionRequest, "phoneNumber" | "phone_number" | "departmentId" | "department_id"> {}
|
|
388
|
+
|
|
389
|
+
export interface LabsPhoneNumberProvisionResponse {
|
|
390
|
+
success: boolean;
|
|
391
|
+
number: LabsPhoneNumberRecord;
|
|
392
|
+
assignment?: Record<string, unknown>;
|
|
393
|
+
telephony?: Record<string, unknown>;
|
|
394
|
+
[extra: string]: unknown;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export interface LabsPhoneNumberAssignResponse {
|
|
398
|
+
success: boolean;
|
|
399
|
+
number: LabsPhoneNumberRecord;
|
|
400
|
+
assignment?: Record<string, unknown>;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export interface LabsPhoneNumberBuyAndAssignRequest extends LabsPhoneNumberProvisionRequest {
|
|
404
|
+
search?: LabsPhoneNumberSearchOptions;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
export interface LabsTelephonyResponse {
|
|
408
|
+
telephony: Record<string, unknown>;
|
|
409
|
+
default?: Record<string, unknown>;
|
|
410
|
+
byok?: Record<string, unknown>;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export interface LabsTelephonyConfigureResponse {
|
|
414
|
+
success: boolean;
|
|
415
|
+
telephony: Record<string, unknown>;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export interface CreateLabsAgentWithNumberRequest extends CreateLabsAgentRequest {
|
|
419
|
+
number?: LabsPhoneNumberBuyAndAssignRequest;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export interface CreateLabsAgentWithNumberResponse extends CreateLabsAgentResponse {
|
|
423
|
+
number?: LabsPhoneNumberProvisionResponse;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
export interface LabsAgentResponse {
|
|
427
|
+
id?: string;
|
|
428
|
+
agency_id?: string;
|
|
429
|
+
agent_type?: LabsAgentType | string;
|
|
430
|
+
agent_key?: string;
|
|
431
|
+
source_id?: string;
|
|
432
|
+
display_name?: string;
|
|
433
|
+
runtime_mode?: string;
|
|
434
|
+
preset_key?: string;
|
|
435
|
+
profile?: Record<string, unknown>;
|
|
436
|
+
runtime?: Record<string, unknown>;
|
|
437
|
+
[extra: string]: unknown;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export interface CreateLabsAgentResponse {
|
|
441
|
+
success: boolean;
|
|
442
|
+
agent: LabsAgentResponse;
|
|
443
|
+
runtime: Record<string, unknown>;
|
|
444
|
+
widget?: {
|
|
445
|
+
widget_key?: string;
|
|
446
|
+
snippet?: string;
|
|
447
|
+
[extra: string]: unknown;
|
|
448
|
+
};
|
|
449
|
+
[extra: string]: unknown;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
export interface ListLabsAgentsResponse {
|
|
453
|
+
agents: LabsAgentResponse[];
|
|
454
|
+
total: number;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export interface GetLabsAgentResponse {
|
|
458
|
+
agent: LabsAgentResponse;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export interface STTResult {
|
|
462
|
+
transcript: string;
|
|
463
|
+
languages: string[];
|
|
464
|
+
duration: number;
|
|
465
|
+
raw?: unknown;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/** A whisper/nudge event you log for the console feed + metrics (zero-billed). */
|
|
469
|
+
export interface NudgeEvent {
|
|
470
|
+
text: string;
|
|
471
|
+
session_id?: string;
|
|
472
|
+
provider?: string;
|
|
473
|
+
confidence?: number;
|
|
474
|
+
injected?: boolean;
|
|
475
|
+
kind?: string;
|
|
476
|
+
language?: string;
|
|
477
|
+
emotion?: string;
|
|
478
|
+
intent?: string;
|
|
479
|
+
urgency?: number;
|
|
480
|
+
latency_ms?: number;
|
|
481
|
+
model?: string;
|
|
482
|
+
turns?: number;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/** A post-call report — the fuel the optimizer improves against. */
|
|
486
|
+
export interface CallReportInput {
|
|
487
|
+
session_id?: string;
|
|
488
|
+
agent?: string;
|
|
489
|
+
score?: number;
|
|
490
|
+
outcome?: string;
|
|
491
|
+
summary?: string;
|
|
492
|
+
nudges?: number;
|
|
493
|
+
turns?: number;
|
|
494
|
+
language?: string;
|
|
495
|
+
[extra: string]: unknown;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
export interface BuilderTurn {
|
|
499
|
+
role: "caller" | "agent" | "whisper";
|
|
500
|
+
text: string;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
export interface BuilderChatResult {
|
|
504
|
+
whisper: string;
|
|
505
|
+
agent_reply: string;
|
|
506
|
+
emotion?: string;
|
|
507
|
+
language?: string;
|
|
508
|
+
intent?: string;
|
|
509
|
+
oracle_ms?: number;
|
|
510
|
+
standing_version?: number;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
export interface QAResult {
|
|
514
|
+
agent: string;
|
|
515
|
+
turns: number;
|
|
516
|
+
results: Array<{
|
|
517
|
+
scenario: string;
|
|
518
|
+
title: string;
|
|
519
|
+
assertion: string;
|
|
520
|
+
unsupervised: { passed: boolean; score: number; evidence: string; whispers: number };
|
|
521
|
+
supervised: { passed: boolean; score: number; evidence: string; whispers: number };
|
|
522
|
+
lift: number;
|
|
523
|
+
}>;
|
|
524
|
+
summary: {
|
|
525
|
+
scenarios: number;
|
|
526
|
+
passed_supervised: number;
|
|
527
|
+
passed_unsupervised: number;
|
|
528
|
+
avg_lift: number;
|
|
529
|
+
oracle_calls_billed: number;
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
export class SupafoneLabsError extends Error {
|
|
534
|
+
constructor(
|
|
535
|
+
message: string,
|
|
536
|
+
readonly status?: number,
|
|
537
|
+
readonly body?: unknown,
|
|
538
|
+
) {
|
|
539
|
+
super(message);
|
|
540
|
+
this.name = "SupafoneLabsError";
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const DEFAULT_BASE = "https://api.labs.supafone.ai";
|
|
545
|
+
const DEFAULT_SUPAFONE_API_BASE = "https://api.supafone.ai";
|
|
546
|
+
|
|
547
|
+
const COACH_SYSTEM =
|
|
548
|
+
"You are the coaching core of a second mind for a live voice agent. Read the " +
|
|
549
|
+
"conversation and return ONE short, silent directive the agent reads but never " +
|
|
550
|
+
"speaks aloud — a correction or nudge, phrased imperatively. If nothing needs " +
|
|
551
|
+
"correcting, return an empty string.";
|
|
552
|
+
|
|
553
|
+
export class SupafoneLabs {
|
|
554
|
+
readonly baseUrl: string;
|
|
555
|
+
readonly supafoneApiBaseUrl: string;
|
|
556
|
+
private readonly apiKey: string;
|
|
557
|
+
private readonly supafoneApiKey: string;
|
|
558
|
+
private readonly timeoutMs: number;
|
|
559
|
+
private sessionToken?: string;
|
|
560
|
+
|
|
561
|
+
readonly labs: LabsNamespace;
|
|
562
|
+
readonly builder: BuilderNamespace;
|
|
563
|
+
readonly qa: QANamespace;
|
|
564
|
+
readonly optimizer: OptimizerNamespace;
|
|
565
|
+
|
|
566
|
+
constructor(opts: SupafoneLabsOptions) {
|
|
567
|
+
if (!opts?.apiKey) throw new SupafoneLabsError("apiKey is required");
|
|
568
|
+
this.apiKey = opts.apiKey;
|
|
569
|
+
this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE).replace(/\/$/, "");
|
|
570
|
+
this.supafoneApiKey = opts.supafoneApiKey ?? opts.apiKey;
|
|
571
|
+
this.supafoneApiBaseUrl = (opts.supafoneApiBaseUrl ?? DEFAULT_SUPAFONE_API_BASE).replace(/\/$/, "");
|
|
572
|
+
this.timeoutMs = opts.timeoutMs ?? 30_000;
|
|
573
|
+
this.sessionToken = opts.sessionToken;
|
|
574
|
+
this.labs = new LabsNamespace(this);
|
|
575
|
+
this.builder = new BuilderNamespace(this);
|
|
576
|
+
this.qa = new QANamespace(this);
|
|
577
|
+
this.optimizer = new OptimizerNamespace(this);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/** True once login() (or a passed sessionToken) is in effect. */
|
|
581
|
+
get isLoggedIn(): boolean {
|
|
582
|
+
return !!this.sessionToken;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Exchange email/password for a console session. Required by the builder.*
|
|
587
|
+
* methods and qa.run (which are account/session-scoped, not key-scoped).
|
|
588
|
+
*/
|
|
589
|
+
async login(email: string, password: string): Promise<void> {
|
|
590
|
+
const d = await this.request<{ token?: string }>("POST", "/v1/auth/login", { email, password });
|
|
591
|
+
if (!d.token) throw new SupafoneLabsError("Login succeeded but returned no token");
|
|
592
|
+
this.sessionToken = d.token;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/** @internal Authenticated JSON request. `useSession` prefers the login token. */
|
|
596
|
+
async request<T>(method: string, path: string, body?: unknown, useSession = false): Promise<T> {
|
|
597
|
+
const token = useSession && this.sessionToken ? this.sessionToken : this.apiKey;
|
|
598
|
+
const ctrl = new AbortController();
|
|
599
|
+
const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
|
|
600
|
+
try {
|
|
601
|
+
const res = await fetch(this.baseUrl + path, {
|
|
602
|
+
method,
|
|
603
|
+
signal: ctrl.signal,
|
|
604
|
+
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
|
|
605
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
606
|
+
});
|
|
607
|
+
const text = await res.text();
|
|
608
|
+
const parsed = text ? safeJson(text) : {};
|
|
609
|
+
if (!res.ok) {
|
|
610
|
+
const detail = (parsed as { detail?: string })?.detail ?? text ?? `HTTP ${res.status}`;
|
|
611
|
+
throw new SupafoneLabsError(`${method} ${path}: ${detail}`, res.status, parsed);
|
|
612
|
+
}
|
|
613
|
+
return parsed as T;
|
|
614
|
+
} finally {
|
|
615
|
+
clearTimeout(timer);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/** @internal Authenticated JSON request to the Supafone app API (`/api/v1supafone-labs/*`). */
|
|
620
|
+
async requestSupafoneApi<T>(method: string, path: string, body?: unknown): Promise<T> {
|
|
621
|
+
const ctrl = new AbortController();
|
|
622
|
+
const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
|
|
623
|
+
try {
|
|
624
|
+
const res = await fetch(this.supafoneApiBaseUrl + path, {
|
|
625
|
+
method,
|
|
626
|
+
signal: ctrl.signal,
|
|
627
|
+
headers: { Authorization: `Bearer ${this.supafoneApiKey}`, "Content-Type": "application/json" },
|
|
628
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
629
|
+
});
|
|
630
|
+
const text = await res.text();
|
|
631
|
+
const parsed = text ? safeJson(text) : {};
|
|
632
|
+
if (!res.ok) {
|
|
633
|
+
const detail = (parsed as { detail?: string })?.detail ?? text ?? `HTTP ${res.status}`;
|
|
634
|
+
throw new SupafoneLabsError(`${method} ${path}: ${detail}`, res.status, parsed);
|
|
635
|
+
}
|
|
636
|
+
return parsed as T;
|
|
637
|
+
} finally {
|
|
638
|
+
clearTimeout(timer);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/** Raw oracle completion — full control over messages and model. */
|
|
643
|
+
async oracle(req: OracleRequest): Promise<OracleResult> {
|
|
644
|
+
return this.request<OracleResult>("POST", "/v1/oracle/complete", {
|
|
645
|
+
messages: req.messages,
|
|
646
|
+
model: req.model ?? "supafone-labs-oracle",
|
|
647
|
+
max_tokens: req.maxTokens ?? 256,
|
|
648
|
+
...(req.temperature !== undefined ? { temperature: req.temperature } : {}),
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* The one-liner: hand it the running transcript, get back a silent directive
|
|
654
|
+
* (empty string when the agent is doing fine).
|
|
655
|
+
*/
|
|
656
|
+
async whisper(transcript: string, opts: WhisperOptions = {}): Promise<string> {
|
|
657
|
+
const system = opts.guardrails ? `${COACH_SYSTEM}\n\nOperator rules:\n${opts.guardrails}` : COACH_SYSTEM;
|
|
658
|
+
const out = await this.oracle({
|
|
659
|
+
model: opts.model,
|
|
660
|
+
maxTokens: opts.maxTokens ?? 120,
|
|
661
|
+
...(opts.temperature !== undefined ? { temperature: opts.temperature } : {}),
|
|
662
|
+
messages: [
|
|
663
|
+
{ role: "system", content: system },
|
|
664
|
+
{ role: "user", content: transcript },
|
|
665
|
+
],
|
|
666
|
+
});
|
|
667
|
+
return out.text.trim();
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/** Hosted TTS — returns raw audio bytes (WAV/PCM per voice). */
|
|
671
|
+
async tts(text: string, voice = "supafone-labs-calm-en"): Promise<Uint8Array> {
|
|
672
|
+
const ctrl = new AbortController();
|
|
673
|
+
const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
|
|
674
|
+
try {
|
|
675
|
+
const res = await fetch(this.baseUrl + "/v1/tts", {
|
|
676
|
+
method: "POST",
|
|
677
|
+
signal: ctrl.signal,
|
|
678
|
+
headers: { Authorization: `Bearer ${this.apiKey}`, "Content-Type": "application/json" },
|
|
679
|
+
body: JSON.stringify({ voice, text }),
|
|
680
|
+
});
|
|
681
|
+
if (!res.ok) throw new SupafoneLabsError(`tts: ${await res.text()}`, res.status);
|
|
682
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
683
|
+
} finally {
|
|
684
|
+
clearTimeout(timer);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/** Hosted STT for a finished audio clip — returns transcript + language tags. */
|
|
689
|
+
async stt(
|
|
690
|
+
audio: Uint8Array | ArrayBuffer,
|
|
691
|
+
opts: { language?: string; mimetype?: string } = {},
|
|
692
|
+
): Promise<STTResult> {
|
|
693
|
+
const bytes = audio instanceof ArrayBuffer ? new Uint8Array(audio) : audio;
|
|
694
|
+
const ctrl = new AbortController();
|
|
695
|
+
const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
|
|
696
|
+
try {
|
|
697
|
+
const res = await fetch(
|
|
698
|
+
this.baseUrl + `/v1/stt?language=${encodeURIComponent(opts.language ?? "multi")}`,
|
|
699
|
+
{
|
|
700
|
+
method: "POST",
|
|
701
|
+
signal: ctrl.signal,
|
|
702
|
+
headers: {
|
|
703
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
704
|
+
"Content-Type": opts.mimetype ?? "application/octet-stream",
|
|
705
|
+
},
|
|
706
|
+
body: bytes as unknown as BodyInit,
|
|
707
|
+
},
|
|
708
|
+
);
|
|
709
|
+
if (!res.ok) throw new SupafoneLabsError(`stt: ${await res.text()}`, res.status);
|
|
710
|
+
const d = (await res.json()) as {
|
|
711
|
+
transcript?: string;
|
|
712
|
+
languages?: string[];
|
|
713
|
+
duration?: number;
|
|
714
|
+
results?: { channels?: Array<{ alternatives?: Array<{ transcript?: string }> }> };
|
|
715
|
+
};
|
|
716
|
+
// Flattened shape first; fall back to raw Deepgram nesting for older gateways.
|
|
717
|
+
const transcript =
|
|
718
|
+
d.transcript ?? d.results?.channels?.[0]?.alternatives?.[0]?.transcript ?? "";
|
|
719
|
+
return { transcript, languages: d.languages ?? [], duration: d.duration ?? 0, raw: d };
|
|
720
|
+
} finally {
|
|
721
|
+
clearTimeout(timer);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* Open a live multilingual transcription socket. Feed PCM frames with
|
|
727
|
+
* `feed()`; language-tagged results arrive via `onResult`. Uses the global
|
|
728
|
+
* WebSocket (browser, Node 22+); pass one in `opts.WebSocketImpl` on older Node.
|
|
729
|
+
*/
|
|
730
|
+
liveTranscribe(opts: LiveTranscribeOptions = {}): LiveTranscription {
|
|
731
|
+
return new LiveTranscription(this, opts);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/** Remaining prepaid balance. */
|
|
735
|
+
balance(): Promise<Balance> {
|
|
736
|
+
return this.request<Balance>("GET", "/v1/billing/balance");
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/** Today's usage against your plan caps (oracle/tts/stt/…). */
|
|
740
|
+
usage(): Promise<UsageToday> {
|
|
741
|
+
return this.request<UsageToday>("GET", "/v1/usage");
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/** The auditable whisper/billing log. */
|
|
745
|
+
logs(limit = 100): Promise<{ logs: unknown[] }> {
|
|
746
|
+
return this.request("GET", `/v1/logs?limit=${limit}`);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/** The structured whisper feed (what the console shows). */
|
|
750
|
+
nudges(limit = 50): Promise<{ nudges: unknown[] }> {
|
|
751
|
+
return this.request("GET", `/v1/nudges?limit=${limit}`);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/** Aggregated metrics — injection rate, latency, by-dimension breakdowns. */
|
|
755
|
+
metrics(days = 7): Promise<Record<string, unknown>> {
|
|
756
|
+
return this.request("GET", `/v1/metrics?days=${days}`);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/** Log one whisper event (zero-billed) for the console feed + metrics. */
|
|
760
|
+
reportNudge(event: NudgeEvent): Promise<{ ok?: boolean }> {
|
|
761
|
+
return this.request("POST", "/v1/events/nudge", event);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/** File a post-call report — the fuel optimizer.improve() learns from. */
|
|
765
|
+
reportCall(report: CallReportInput): Promise<Record<string, unknown>> {
|
|
766
|
+
return this.request("POST", "/v1/events/call_report", report);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/** Available oracle model ids (live vendor catalog). */
|
|
770
|
+
async models(): Promise<string[]> {
|
|
771
|
+
const d = await this.request<{ models: Array<string | { id: string }> }>("GET", "/v1/models");
|
|
772
|
+
return d.models.map((m) => (typeof m === "string" ? m : m.id));
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/** Available TTS voice ids. */
|
|
776
|
+
async voices(): Promise<string[]> {
|
|
777
|
+
const d = await this.request<{ voices: Array<string | { voice?: string; id?: string }> }>(
|
|
778
|
+
"GET",
|
|
779
|
+
"/v1/voices",
|
|
780
|
+
);
|
|
781
|
+
return d.voices.map((v) => (typeof v === "string" ? v : (v.voice ?? v.id ?? ""))).filter(Boolean);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
export interface LiveTranscribeOptions {
|
|
786
|
+
language?: string; // "multi" for code-switching
|
|
787
|
+
encoding?: string; // "linear16"
|
|
788
|
+
sampleRate?: number; // 16000
|
|
789
|
+
onResult?: (r: LiveResult) => void;
|
|
790
|
+
onError?: (e: unknown) => void;
|
|
791
|
+
onClose?: () => void;
|
|
792
|
+
/** Inject a WebSocket implementation for Node < 22 (e.g. `ws`). */
|
|
793
|
+
WebSocketImpl?: typeof WebSocket;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
export interface LiveResult {
|
|
797
|
+
transcript: string;
|
|
798
|
+
languages: string[];
|
|
799
|
+
isFinal: boolean;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
class LiveTranscription {
|
|
803
|
+
private ws: WebSocket;
|
|
804
|
+
|
|
805
|
+
constructor(sm: SupafoneLabs, opts: LiveTranscribeOptions) {
|
|
806
|
+
const WS = opts.WebSocketImpl ?? (globalThis as { WebSocket?: typeof WebSocket }).WebSocket;
|
|
807
|
+
if (!WS) throw new SupafoneLabsError("No WebSocket available — pass opts.WebSocketImpl (e.g. the `ws` package)");
|
|
808
|
+
const base = sm.baseUrl.replace(/^http/, "ws");
|
|
809
|
+
const q = new URLSearchParams({
|
|
810
|
+
// The key rides in the query string because browsers can't set WS headers.
|
|
811
|
+
api_key: (sm as unknown as { apiKey: string }).apiKey,
|
|
812
|
+
language: opts.language ?? "multi",
|
|
813
|
+
encoding: opts.encoding ?? "linear16",
|
|
814
|
+
sample_rate: String(opts.sampleRate ?? 16000),
|
|
815
|
+
});
|
|
816
|
+
this.ws = new WS(`${base}/v1/stt/live?${q}`);
|
|
817
|
+
this.ws.addEventListener?.("message", (ev: MessageEvent) => {
|
|
818
|
+
const d = safeJson(typeof ev.data === "string" ? ev.data : String(ev.data)) as {
|
|
819
|
+
channel?: { alternatives?: Array<{ transcript?: string; languages?: string[] }> };
|
|
820
|
+
is_final?: boolean;
|
|
821
|
+
};
|
|
822
|
+
const alt = d.channel?.alternatives?.[0];
|
|
823
|
+
if (alt?.transcript && opts.onResult) {
|
|
824
|
+
opts.onResult({ transcript: alt.transcript, languages: alt.languages ?? [], isFinal: !!d.is_final });
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
if (opts.onError) this.ws.addEventListener?.("error", opts.onError as EventListener);
|
|
828
|
+
if (opts.onClose) this.ws.addEventListener?.("close", opts.onClose);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
/** Send one PCM audio frame. */
|
|
832
|
+
feed(frame: Uint8Array | ArrayBuffer): void {
|
|
833
|
+
this.ws.send(frame as ArrayBuffer);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
/** Signal end-of-stream and close. */
|
|
837
|
+
close(): void {
|
|
838
|
+
try {
|
|
839
|
+
this.ws.send(JSON.stringify({ type: "CloseStream" }));
|
|
840
|
+
} catch {
|
|
841
|
+
/* already closing */
|
|
842
|
+
}
|
|
843
|
+
this.ws.close();
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
get socket(): WebSocket {
|
|
847
|
+
return this.ws;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/** Programmatic hosted Supafone agents, inside the Supafone API. */
|
|
852
|
+
class LabsNamespace {
|
|
853
|
+
readonly agents: LabsAgentsNamespace;
|
|
854
|
+
readonly presets: LabsPresetsNamespace;
|
|
855
|
+
readonly tools: LabsToolsNamespace;
|
|
856
|
+
readonly voices: LabsVoicesNamespace;
|
|
857
|
+
readonly phoneNumbers: LabsPhoneNumbersNamespace;
|
|
858
|
+
readonly telephony: LabsTelephonyNamespace;
|
|
859
|
+
|
|
860
|
+
constructor(private sm: SupafoneLabs) {
|
|
861
|
+
this.agents = new LabsAgentsNamespace(sm);
|
|
862
|
+
this.presets = new LabsPresetsNamespace(sm);
|
|
863
|
+
this.tools = new LabsToolsNamespace(sm);
|
|
864
|
+
this.voices = new LabsVoicesNamespace(sm);
|
|
865
|
+
this.phoneNumbers = new LabsPhoneNumbersNamespace(sm);
|
|
866
|
+
this.telephony = new LabsTelephonyNamespace(sm);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/** Discover the Supafone convenience layer over Ultravox. */
|
|
870
|
+
capabilities(): Promise<LabsCapabilitiesResponse> {
|
|
871
|
+
return this.sm.requestSupafoneApi<LabsCapabilitiesResponse>("GET", "/api/v1supafone-labs/capabilities");
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
class LabsAgentsNamespace {
|
|
876
|
+
constructor(private sm: SupafoneLabs) {}
|
|
877
|
+
|
|
878
|
+
/** Spawn a durable hosted Supafone agent backed by Ultravox and Supafone-managed providers. */
|
|
879
|
+
create(input: CreateLabsAgentRequest): Promise<CreateLabsAgentResponse> {
|
|
880
|
+
return this.sm.requestSupafoneApi<CreateLabsAgentResponse>(
|
|
881
|
+
"POST",
|
|
882
|
+
"/api/v1supafone-labs/agents",
|
|
883
|
+
labsAgentPayload(input),
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
/** Create an inbound receptionist/intake agent. No Twilio account is required. */
|
|
888
|
+
createInbound(input: CreateLabsAgentRequest): Promise<CreateLabsAgentResponse> {
|
|
889
|
+
return this.create({
|
|
890
|
+
...input,
|
|
891
|
+
style: "inbound",
|
|
892
|
+
direction: "inbound",
|
|
893
|
+
agentType: input.agentType ?? input.agent_type ?? "phone",
|
|
894
|
+
presetKey: input.presetKey ?? input.preset_key ?? "general_intake_receptionist",
|
|
895
|
+
telephony: input.telephony ?? { mode: "supafone_managed", provider: "supafone" },
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
/** Create an outbound sales/speed-to-lead/campaign agent. No Twilio account is required. */
|
|
900
|
+
createOutbound(input: CreateLabsAgentRequest): Promise<CreateLabsAgentResponse> {
|
|
901
|
+
return this.create({
|
|
902
|
+
...input,
|
|
903
|
+
style: "outbound",
|
|
904
|
+
direction: "outbound",
|
|
905
|
+
agentType: input.agentType ?? input.agent_type ?? "campaign",
|
|
906
|
+
presetKey: input.presetKey ?? input.preset_key ?? "speed_to_lead_caller",
|
|
907
|
+
telephony: input.telephony ?? { mode: "supafone_managed", provider: "supafone" },
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/**
|
|
912
|
+
* Create an inbound agent, buy a Supafone-managed phone number, and assign it.
|
|
913
|
+
* This is the zero-Twilio-account happy path.
|
|
914
|
+
*/
|
|
915
|
+
async createInboundWithNumber(
|
|
916
|
+
input: CreateLabsAgentWithNumberRequest,
|
|
917
|
+
): Promise<CreateLabsAgentWithNumberResponse> {
|
|
918
|
+
const agent = await this.createInbound(input);
|
|
919
|
+
const agentKey = String(agent.agent?.agent_key ?? input.agentKey ?? input.agent_key ?? "");
|
|
920
|
+
const number = await new LabsPhoneNumbersNamespace(this.sm).buyAndAssign({
|
|
921
|
+
...(input.number ?? {}),
|
|
922
|
+
agentKey,
|
|
923
|
+
agentName: input.assistantName ?? input.assistant_name ?? input.name,
|
|
924
|
+
friendlyName: input.number?.friendlyName ?? input.number?.friendly_name ?? input.name,
|
|
925
|
+
style: "inbound",
|
|
926
|
+
presetKey: input.presetKey ?? input.preset_key ?? "general_intake_receptionist",
|
|
927
|
+
telephony: input.number?.telephony ?? { mode: "supafone_managed", provider: "supafone" },
|
|
928
|
+
});
|
|
929
|
+
return { ...agent, number };
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* Create an outbound agent, buy a Supafone-managed caller ID, and assign it.
|
|
934
|
+
* For sales teams this is the easiest path: Supafone owns telephony setup.
|
|
935
|
+
*/
|
|
936
|
+
async createOutboundWithNumber(
|
|
937
|
+
input: CreateLabsAgentWithNumberRequest,
|
|
938
|
+
): Promise<CreateLabsAgentWithNumberResponse> {
|
|
939
|
+
const agent = await this.createOutbound(input);
|
|
940
|
+
const agentKey = String(agent.agent?.agent_key ?? input.agentKey ?? input.agent_key ?? "");
|
|
941
|
+
const number = await new LabsPhoneNumbersNamespace(this.sm).buyAndAssign({
|
|
942
|
+
...(input.number ?? {}),
|
|
943
|
+
agentKey,
|
|
944
|
+
agentName: input.assistantName ?? input.assistant_name ?? input.name,
|
|
945
|
+
friendlyName: input.number?.friendlyName ?? input.number?.friendly_name ?? input.name,
|
|
946
|
+
style: "outbound",
|
|
947
|
+
presetKey: input.presetKey ?? input.preset_key ?? "speed_to_lead_caller",
|
|
948
|
+
telephony: input.number?.telephony ?? { mode: "supafone_managed", provider: "supafone" },
|
|
949
|
+
});
|
|
950
|
+
return { ...agent, number };
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/** List durable agents created in the Supafone account tied to this API key. */
|
|
954
|
+
list(opts: ListLabsAgentsOptions = {}): Promise<ListLabsAgentsResponse> {
|
|
955
|
+
const q = new URLSearchParams();
|
|
956
|
+
if (opts.agencyId) q.set("agency_id", opts.agencyId);
|
|
957
|
+
if (opts.agentType) q.set("agent_type", opts.agentType);
|
|
958
|
+
if (opts.style) q.set("style", opts.style);
|
|
959
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
960
|
+
return this.sm.requestSupafoneApi<ListLabsAgentsResponse>("GET", `/api/v1supafone-labs/agents${suffix}`);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
/** Fetch one durable agent by key. */
|
|
964
|
+
get(agentKey: string, opts: GetLabsAgentOptions = {}): Promise<GetLabsAgentResponse> {
|
|
965
|
+
const q = new URLSearchParams();
|
|
966
|
+
if (opts.agencyId) q.set("agency_id", opts.agencyId);
|
|
967
|
+
if (opts.agentType) q.set("agent_type", opts.agentType);
|
|
968
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
969
|
+
return this.sm.requestSupafoneApi<GetLabsAgentResponse>(
|
|
970
|
+
"GET",
|
|
971
|
+
`/api/v1supafone-labs/agents/${encodeURIComponent(agentKey)}${suffix}`,
|
|
972
|
+
);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
class LabsPresetsNamespace {
|
|
977
|
+
constructor(private sm: SupafoneLabs) {}
|
|
978
|
+
|
|
979
|
+
/** Out-of-the-box multistage agent presets. */
|
|
980
|
+
list(): Promise<LabsPresetListResponse> {
|
|
981
|
+
return this.sm.requestSupafoneApi<LabsPresetListResponse>("GET", "/api/v1supafone-labs/presets");
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
class LabsToolsNamespace {
|
|
986
|
+
constructor(private sm: SupafoneLabs) {}
|
|
987
|
+
|
|
988
|
+
/** Built-in tools Supafone agents can use. */
|
|
989
|
+
list(): Promise<LabsToolListResponse> {
|
|
990
|
+
return this.sm.requestSupafoneApi<LabsToolListResponse>("GET", "/api/v1supafone-labs/tools");
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
class LabsVoicesNamespace {
|
|
995
|
+
constructor(private sm: SupafoneLabs) {}
|
|
996
|
+
|
|
997
|
+
/** Supafone-managed Ultravox, Cartesia, Inworld, and ElevenLabs-compatible voices. */
|
|
998
|
+
list(opts: LabsVoiceListOptions = {}): Promise<LabsVoiceListResponse> {
|
|
999
|
+
const q = new URLSearchParams();
|
|
1000
|
+
if (opts.provider) q.set("provider", opts.provider);
|
|
1001
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
1002
|
+
return this.sm.requestSupafoneApi<LabsVoiceListResponse>("GET", `/api/v1supafone-labs/voices${suffix}`);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
class LabsPhoneNumbersNamespace {
|
|
1007
|
+
constructor(private sm: SupafoneLabs) {}
|
|
1008
|
+
|
|
1009
|
+
/** List numbers already owned by this Supafone account. */
|
|
1010
|
+
list(opts: LabsPhoneNumberListOptions = {}): Promise<LabsPhoneNumberListResponse> {
|
|
1011
|
+
const q = new URLSearchParams();
|
|
1012
|
+
if (opts.agencyId) q.set("agency_id", opts.agencyId);
|
|
1013
|
+
if (opts.activeOnly !== undefined) q.set("active_only", String(opts.activeOnly));
|
|
1014
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
1015
|
+
return this.sm.requestSupafoneApi<LabsPhoneNumberListResponse>(
|
|
1016
|
+
"GET",
|
|
1017
|
+
`/api/v1supafone-labs/phone-numbers${suffix}`,
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
/** Search Supafone-managed inventory. This uses Supafone's master telephony account. */
|
|
1022
|
+
search(opts: LabsPhoneNumberSearchOptions = {}): Promise<LabsPhoneNumberSearchResponse> {
|
|
1023
|
+
return this.sm.requestSupafoneApi<LabsPhoneNumberSearchResponse>(
|
|
1024
|
+
"POST",
|
|
1025
|
+
"/api/v1supafone-labs/phone-numbers/search",
|
|
1026
|
+
phoneNumberSearchPayload(opts),
|
|
1027
|
+
);
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
/** Buy a Supafone-managed number. Developers do not need a Twilio account. */
|
|
1031
|
+
buy(input: LabsPhoneNumberProvisionRequest): Promise<LabsPhoneNumberProvisionResponse> {
|
|
1032
|
+
return this.sm.requestSupafoneApi<LabsPhoneNumberProvisionResponse>(
|
|
1033
|
+
"POST",
|
|
1034
|
+
"/api/v1supafone-labs/phone-numbers",
|
|
1035
|
+
phoneNumberProvisionPayload({
|
|
1036
|
+
...input,
|
|
1037
|
+
telephony: input.telephony ?? { mode: "supafone_managed", provider: "supafone" },
|
|
1038
|
+
}),
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
/** Attach an existing Supafone number to an inbound or outbound agent. */
|
|
1043
|
+
assign(numberId: string, input: LabsPhoneNumberAssignRequest = {}): Promise<LabsPhoneNumberAssignResponse> {
|
|
1044
|
+
return this.sm.requestSupafoneApi<LabsPhoneNumberAssignResponse>(
|
|
1045
|
+
"POST",
|
|
1046
|
+
`/api/v1supafone-labs/phone-numbers/${encodeURIComponent(numberId)}/assign`,
|
|
1047
|
+
phoneNumberAssignPayload(input),
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
/**
|
|
1052
|
+
* Search if needed, buy the first matching Supafone-managed number, and assign
|
|
1053
|
+
* it to the supplied agent. This is the zero-Twilio-account happy path.
|
|
1054
|
+
*/
|
|
1055
|
+
async buyAndAssign(input: LabsPhoneNumberBuyAndAssignRequest): Promise<LabsPhoneNumberProvisionResponse> {
|
|
1056
|
+
let phoneNumber = input.phoneNumber ?? input.phone_number ?? "";
|
|
1057
|
+
if (!phoneNumber) {
|
|
1058
|
+
const found = await this.search({
|
|
1059
|
+
...(input.search ?? {}),
|
|
1060
|
+
agencyId: input.agencyId ?? input.agency_id ?? input.search?.agencyId,
|
|
1061
|
+
limit: input.search?.limit ?? 1,
|
|
1062
|
+
});
|
|
1063
|
+
phoneNumber = found.numbers[0]?.phone_number ?? "";
|
|
1064
|
+
if (!phoneNumber) {
|
|
1065
|
+
throw new SupafoneLabsError("No Supafone-managed phone numbers matched the search");
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
return this.buy({
|
|
1069
|
+
...input,
|
|
1070
|
+
phoneNumber,
|
|
1071
|
+
telephony: input.telephony ?? { mode: "supafone_managed", provider: "supafone" },
|
|
1072
|
+
});
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
class LabsTelephonyNamespace {
|
|
1077
|
+
constructor(private sm: SupafoneLabs) {}
|
|
1078
|
+
|
|
1079
|
+
/** Read the account telephony contract. Defaults to Supafone-managed. */
|
|
1080
|
+
get(opts: { agencyId?: string } = {}): Promise<LabsTelephonyResponse> {
|
|
1081
|
+
const q = new URLSearchParams();
|
|
1082
|
+
if (opts.agencyId) q.set("agency_id", opts.agencyId);
|
|
1083
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
1084
|
+
return this.sm.requestSupafoneApi<LabsTelephonyResponse>("GET", `/api/v1supafone-labs/telephony${suffix}`);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
/** Configure advanced BYOK telephony, or reset back to Supafone-managed. */
|
|
1088
|
+
configure(input: LabsTelephonyConfig): Promise<LabsTelephonyConfigureResponse> {
|
|
1089
|
+
return this.sm.requestSupafoneApi<LabsTelephonyConfigureResponse>(
|
|
1090
|
+
"PUT",
|
|
1091
|
+
"/api/v1supafone-labs/telephony",
|
|
1092
|
+
telephonyPayload(input),
|
|
1093
|
+
);
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
/** Reset to the seamless default where Supafone buys and routes numbers. */
|
|
1097
|
+
useSupafoneManaged(agencyId?: string): Promise<LabsTelephonyConfigureResponse> {
|
|
1098
|
+
return this.configure({ agencyId, mode: "supafone_managed", provider: "supafone" });
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
/**
|
|
1103
|
+
* The agent builder. These methods are account/session-scoped — call
|
|
1104
|
+
* `login(email, password)` first, or the gateway returns 401 "Log in first".
|
|
1105
|
+
*/
|
|
1106
|
+
class BuilderNamespace {
|
|
1107
|
+
constructor(private sm: SupafoneLabs) {}
|
|
1108
|
+
/** One supervised builder turn: whisper + guided agent reply. */
|
|
1109
|
+
chat(sessionId: string, messages: BuilderTurn[]): Promise<BuilderChatResult> {
|
|
1110
|
+
return this.sm.request<BuilderChatResult>(
|
|
1111
|
+
"POST",
|
|
1112
|
+
"/v1/builder/chat",
|
|
1113
|
+
{ session_id: sessionId, messages },
|
|
1114
|
+
true,
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
/** End a test call: grades it and files a report for the optimizer. */
|
|
1118
|
+
finish(
|
|
1119
|
+
sessionId: string,
|
|
1120
|
+
messages: BuilderTurn[],
|
|
1121
|
+
): Promise<{ score: number; outcome: string; summary: string }> {
|
|
1122
|
+
return this.sm.request("POST", "/v1/builder/finish", { session_id: sessionId, messages }, true);
|
|
1123
|
+
}
|
|
1124
|
+
config(): Promise<unknown> {
|
|
1125
|
+
return this.sm.request("GET", "/v1/builder/config", undefined, true);
|
|
1126
|
+
}
|
|
1127
|
+
saveConfig(config: unknown): Promise<unknown> {
|
|
1128
|
+
return this.sm.request("POST", "/v1/builder/config", config, true);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
class QANamespace {
|
|
1133
|
+
constructor(private sm: SupafoneLabs) {}
|
|
1134
|
+
/**
|
|
1135
|
+
* Run the adversarial QA suite, A/B (supervised vs unsupervised).
|
|
1136
|
+
* Session-scoped — call login() first.
|
|
1137
|
+
*/
|
|
1138
|
+
run(opts: { scenarios?: string[]; turns?: number } = {}): Promise<QAResult> {
|
|
1139
|
+
return this.sm.request<QAResult>(
|
|
1140
|
+
"POST",
|
|
1141
|
+
"/v1/qa/run",
|
|
1142
|
+
{ scenarios: opts.scenarios ?? [], turns: opts.turns ?? 2 },
|
|
1143
|
+
true,
|
|
1144
|
+
);
|
|
1145
|
+
}
|
|
1146
|
+
/** Past QA runs (works with the API key). */
|
|
1147
|
+
history(agent = "builder", limit = 40): Promise<unknown> {
|
|
1148
|
+
return this.sm.request("GET", `/v1/qa/runs?agent=${encodeURIComponent(agent)}&limit=${limit}`);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
class OptimizerNamespace {
|
|
1153
|
+
constructor(private sm: SupafoneLabs) {}
|
|
1154
|
+
/** Improve the standing directive from accumulated call reports (OPRO-style). */
|
|
1155
|
+
improve(agent = "builder"): Promise<{ version: number; text: string; rationale: string }> {
|
|
1156
|
+
return this.sm.request("POST", "/v1/optimizer/improve", { agent });
|
|
1157
|
+
}
|
|
1158
|
+
/** Fetch the current standing directive. */
|
|
1159
|
+
standing(agent = "builder"): Promise<{ version: number; text: string }> {
|
|
1160
|
+
return this.sm.request("GET", `/v1/optimizer/standing?agent=${encodeURIComponent(agent)}`);
|
|
1161
|
+
}
|
|
1162
|
+
/** List the post-call reports behind the optimizer. */
|
|
1163
|
+
reports(agent = "builder", limit = 40): Promise<{ reports: unknown[] }> {
|
|
1164
|
+
return this.sm.request(
|
|
1165
|
+
"GET",
|
|
1166
|
+
`/v1/optimizer/reports?agent=${encodeURIComponent(agent)}&limit=${limit}`,
|
|
1167
|
+
);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
function labsAgentPayload(input: CreateLabsAgentRequest): Record<string, unknown> {
|
|
1172
|
+
return compact({
|
|
1173
|
+
agency_id: input.agency_id ?? input.agencyId,
|
|
1174
|
+
agent_key: input.agent_key ?? input.agentKey,
|
|
1175
|
+
agent_type: input.agent_type ?? input.agentType,
|
|
1176
|
+
style: input.agent_style ?? input.agentStyle ?? input.style,
|
|
1177
|
+
name: input.name,
|
|
1178
|
+
assistant_name: input.assistant_name ?? input.assistantName,
|
|
1179
|
+
business_name: input.business_name ?? input.businessName,
|
|
1180
|
+
industry: input.industry,
|
|
1181
|
+
website_url: input.website_url ?? input.websiteUrl,
|
|
1182
|
+
phone_number: input.phone_number ?? input.phoneNumber,
|
|
1183
|
+
direction: input.direction,
|
|
1184
|
+
preset_key: input.preset_key ?? input.presetKey,
|
|
1185
|
+
runtime_mode: input.runtime_mode ?? input.runtimeMode,
|
|
1186
|
+
goal: input.goal,
|
|
1187
|
+
greeting: input.greeting,
|
|
1188
|
+
system_prompt: input.system_prompt ?? input.systemPrompt,
|
|
1189
|
+
language: input.language,
|
|
1190
|
+
voice: input.voice ? voicePayload(input.voice) : undefined,
|
|
1191
|
+
provider_keys: input.provider_keys ?? (input.providerKeys ? providerKeysPayload(input.providerKeys) : undefined),
|
|
1192
|
+
byok: input.byok ? providerKeysPayload(input.byok) : undefined,
|
|
1193
|
+
telephony: input.telephony ? telephonyPayload(input.telephony) : undefined,
|
|
1194
|
+
tools: input.tools ? toolsPayload(input.tools) : undefined,
|
|
1195
|
+
labs: input.labs ? labsPayload(input.labs) : undefined,
|
|
1196
|
+
ultravox: input.ultravox ? ultravoxPayload(input.ultravox) : undefined,
|
|
1197
|
+
voice_watcher: input.voice_watcher ?? input.voiceWatcher,
|
|
1198
|
+
voice_watcher_model: input.voice_watcher_model ?? input.voiceWatcherModel,
|
|
1199
|
+
metadata: input.metadata,
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
function telephonyPayload(input: LabsTelephonyConfig): Record<string, unknown> {
|
|
1204
|
+
return compact({
|
|
1205
|
+
agency_id: input.agency_id ?? input.agencyId,
|
|
1206
|
+
mode: input.mode,
|
|
1207
|
+
provider: input.provider,
|
|
1208
|
+
label: input.label,
|
|
1209
|
+
credentials: input.credentials ? telephonyCredentialsPayload(input.credentials) : undefined,
|
|
1210
|
+
metadata: input.metadata,
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
function telephonyCredentialsPayload(input: LabsTelephonyCredentials): Record<string, unknown> {
|
|
1215
|
+
return compact({
|
|
1216
|
+
account_sid: input.account_sid ?? input.accountSid,
|
|
1217
|
+
auth_token: input.auth_token ?? input.authToken,
|
|
1218
|
+
api_key: input.api_key ?? input.apiKey,
|
|
1219
|
+
api_secret: input.api_secret ?? input.apiSecret,
|
|
1220
|
+
auth_id: input.auth_id ?? input.authId,
|
|
1221
|
+
connection_id: input.connection_id ?? input.connectionId,
|
|
1222
|
+
from_number: input.from_number ?? input.fromNumber,
|
|
1223
|
+
sip_trunk_uri: input.sip_trunk_uri ?? input.sipTrunkUri,
|
|
1224
|
+
sip_host: input.sip_host ?? input.sipHost,
|
|
1225
|
+
username: input.username,
|
|
1226
|
+
password: input.password,
|
|
1227
|
+
webhook_secret: input.webhook_secret ?? input.webhookSecret,
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
function phoneNumberSearchPayload(input: LabsPhoneNumberSearchOptions): Record<string, unknown> {
|
|
1232
|
+
return compact({
|
|
1233
|
+
agency_id: input.agency_id ?? input.agencyId,
|
|
1234
|
+
country_code: input.country_code ?? input.countryCode,
|
|
1235
|
+
area_code: input.area_code ?? input.areaCode,
|
|
1236
|
+
postal_code: input.postal_code ?? input.postalCode,
|
|
1237
|
+
zip_code: input.zip_code ?? input.zipCode,
|
|
1238
|
+
contains: input.contains,
|
|
1239
|
+
number_type: input.number_type ?? input.numberType,
|
|
1240
|
+
limit: input.limit,
|
|
1241
|
+
capabilities: input.capabilities,
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
function phoneNumberProvisionPayload(input: LabsPhoneNumberProvisionRequest): Record<string, unknown> {
|
|
1246
|
+
return compact({
|
|
1247
|
+
agency_id: input.agency_id ?? input.agencyId,
|
|
1248
|
+
phone_number: input.phone_number ?? input.phoneNumber,
|
|
1249
|
+
friendly_name: input.friendly_name ?? input.friendlyName,
|
|
1250
|
+
department_id: input.department_id ?? input.departmentId,
|
|
1251
|
+
agent_key: input.agent_key ?? input.agentKey,
|
|
1252
|
+
agent_id: input.agent_id ?? input.agentId,
|
|
1253
|
+
agent_name: input.agent_name ?? input.agentName,
|
|
1254
|
+
preset_key: input.preset_key ?? input.presetKey,
|
|
1255
|
+
style: input.agent_style ?? input.agentStyle ?? input.style,
|
|
1256
|
+
direction: input.direction,
|
|
1257
|
+
telephony: input.telephony ? telephonyPayload(input.telephony) : undefined,
|
|
1258
|
+
metadata: input.metadata,
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
function phoneNumberAssignPayload(input: LabsPhoneNumberAssignRequest): Record<string, unknown> {
|
|
1263
|
+
return compact({
|
|
1264
|
+
agency_id: input.agency_id ?? input.agencyId,
|
|
1265
|
+
agent_key: input.agent_key ?? input.agentKey,
|
|
1266
|
+
agent_id: input.agent_id ?? input.agentId,
|
|
1267
|
+
agent_name: input.agent_name ?? input.agentName,
|
|
1268
|
+
friendly_name: input.friendly_name ?? input.friendlyName,
|
|
1269
|
+
preset_key: input.preset_key ?? input.presetKey,
|
|
1270
|
+
style: input.agent_style ?? input.agentStyle ?? input.style,
|
|
1271
|
+
direction: input.direction,
|
|
1272
|
+
telephony: input.telephony ? telephonyPayload(input.telephony) : undefined,
|
|
1273
|
+
metadata: input.metadata,
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
function voicePayload(input: LabsVoiceSelection): Record<string, unknown> {
|
|
1278
|
+
return compact({
|
|
1279
|
+
provider: input.provider,
|
|
1280
|
+
voice_id: input.voice_id ?? input.voiceId,
|
|
1281
|
+
model: input.model,
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
function providerKeysPayload(input: LabsProviderKeys): Record<string, unknown> {
|
|
1286
|
+
return compact({
|
|
1287
|
+
ultravox: input.ultravox,
|
|
1288
|
+
ultravox_api_key: input.ultravox_api_key ?? input.ultravoxApiKey,
|
|
1289
|
+
elevenlabs: input.elevenlabs,
|
|
1290
|
+
elevenlabs_api_key: input.elevenlabs_api_key ?? input.elevenlabsApiKey,
|
|
1291
|
+
cartesia: input.cartesia,
|
|
1292
|
+
cartesia_api_key: input.cartesia_api_key ?? input.cartesiaApiKey,
|
|
1293
|
+
inworld: input.inworld,
|
|
1294
|
+
inworld_api_key: input.inworld_api_key ?? input.inworldApiKey,
|
|
1295
|
+
deepgram: input.deepgram,
|
|
1296
|
+
deepgram_api_key: input.deepgram_api_key ?? input.deepgramApiKey,
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
function toolsPayload(input: LabsToolsConfig): Record<string, unknown> {
|
|
1301
|
+
return compact({
|
|
1302
|
+
call_routing: input.call_routing ?? input.callRouting,
|
|
1303
|
+
scheduling: input.scheduling,
|
|
1304
|
+
sms: input.sms,
|
|
1305
|
+
email: input.email,
|
|
1306
|
+
intake_forms: input.intake_forms ?? input.intakeForms,
|
|
1307
|
+
firm_knowledge: input.firm_knowledge ?? input.firmKnowledge,
|
|
1308
|
+
existing_client_lookup: input.existing_client_lookup ?? input.existingClientLookup,
|
|
1309
|
+
voicemail: input.voicemail,
|
|
1310
|
+
emergency_escalation: input.emergency_escalation ?? input.emergencyEscalation,
|
|
1311
|
+
custom_tools: input.custom_tools ?? input.customTools,
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
function labsPayload(input: LabsWatcherConfig): Record<string, unknown> {
|
|
1316
|
+
return compact({
|
|
1317
|
+
enabled: input.enabled,
|
|
1318
|
+
voice_watcher: input.voice_watcher ?? input.voiceWatcher,
|
|
1319
|
+
model: input.model,
|
|
1320
|
+
label: input.label,
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
function ultravoxPayload(input: LabsUltravoxRuntime): Record<string, unknown> {
|
|
1325
|
+
return compact({
|
|
1326
|
+
model: input.model,
|
|
1327
|
+
temperature: input.temperature,
|
|
1328
|
+
medium: input.medium,
|
|
1329
|
+
vadSettings: input.vadSettings ?? input.vad_settings,
|
|
1330
|
+
speaker_first: input.speaker_first ?? input.speakerFirst,
|
|
1331
|
+
firstSpeaker: input.firstSpeaker ?? input.first_speaker,
|
|
1332
|
+
firstSpeakerSettings: input.firstSpeakerSettings ?? input.first_speaker_settings,
|
|
1333
|
+
selectedTools: input.selectedTools ?? input.selected_tools,
|
|
1334
|
+
initialMessages: input.initialMessages ?? input.initial_messages,
|
|
1335
|
+
initialState: input.initialState ?? input.initial_state,
|
|
1336
|
+
initialOutputMedium: input.initialOutputMedium ?? input.initial_output_medium,
|
|
1337
|
+
joinTimeout: input.joinTimeout ?? input.join_timeout,
|
|
1338
|
+
maxDuration: input.maxDuration ?? input.max_duration,
|
|
1339
|
+
max_duration_seconds: input.max_duration_seconds ?? input.maxDurationSeconds,
|
|
1340
|
+
timeExceededMessage: input.timeExceededMessage ?? input.time_exceeded_message,
|
|
1341
|
+
inactivityMessages: input.inactivityMessages ?? input.inactivity_messages,
|
|
1342
|
+
dataConnection: input.dataConnection ?? input.data_connection,
|
|
1343
|
+
callbacks: input.callbacks,
|
|
1344
|
+
metadata: input.metadata,
|
|
1345
|
+
experimentalSettings: input.experimentalSettings ?? input.experimental_settings,
|
|
1346
|
+
voiceOverrides: input.voiceOverrides ?? input.voice_overrides,
|
|
1347
|
+
retentionPolicy: input.retentionPolicy ?? input.retention_policy,
|
|
1348
|
+
callTemplate: input.callTemplate ?? input.call_template,
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
function compact(input: Record<string, unknown>): Record<string, unknown> {
|
|
1353
|
+
const out: Record<string, unknown> = {};
|
|
1354
|
+
for (const [key, value] of Object.entries(input)) {
|
|
1355
|
+
if (value !== undefined) out[key] = value;
|
|
1356
|
+
}
|
|
1357
|
+
return out;
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
function safeJson(text: string): unknown {
|
|
1361
|
+
try {
|
|
1362
|
+
return JSON.parse(text);
|
|
1363
|
+
} catch {
|
|
1364
|
+
return { detail: text };
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
export { SupafoneLabs as Supafone };
|
|
1369
|
+
|
|
1370
|
+
export default SupafoneLabs;
|