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/dist/cjs/index.js
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
24
|
exports.Supafone = exports.SupafoneLabs = exports.SupafoneLabsError = void 0;
|
|
25
|
+
exports.generateCallStages = generateCallStages;
|
|
25
26
|
class SupafoneLabsError extends Error {
|
|
26
27
|
status;
|
|
27
28
|
body;
|
|
@@ -42,27 +43,40 @@ const COACH_SYSTEM = "You are the coaching core of a second mind for a live voic
|
|
|
42
43
|
class SupafoneLabs {
|
|
43
44
|
baseUrl;
|
|
44
45
|
supafoneApiBaseUrl;
|
|
46
|
+
appUrl;
|
|
45
47
|
apiKey;
|
|
46
48
|
supafoneApiKey;
|
|
47
49
|
timeoutMs;
|
|
48
50
|
sessionToken;
|
|
51
|
+
accountToken;
|
|
52
|
+
accountSessionToken;
|
|
53
|
+
accountEmail;
|
|
54
|
+
accountPassword;
|
|
49
55
|
labs;
|
|
50
56
|
builder;
|
|
51
57
|
qa;
|
|
52
58
|
optimizer;
|
|
59
|
+
campaigns;
|
|
53
60
|
constructor(opts) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
61
|
+
const hasAccountAuth = !!(opts?.accountToken || (opts?.accountEmail && opts?.accountPassword));
|
|
62
|
+
if (!opts?.apiKey && !hasAccountAuth) {
|
|
63
|
+
throw new SupafoneLabsError("apiKey is required — or, for campaigns/calls, pass accountToken or accountEmail + accountPassword");
|
|
64
|
+
}
|
|
65
|
+
this.apiKey = opts.apiKey ?? "";
|
|
57
66
|
this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE).replace(/\/$/, "");
|
|
58
|
-
this.supafoneApiKey = opts.supafoneApiKey ??
|
|
67
|
+
this.supafoneApiKey = opts.supafoneApiKey ?? this.apiKey;
|
|
59
68
|
this.supafoneApiBaseUrl = (opts.supafoneApiBaseUrl ?? DEFAULT_SUPAFONE_API_BASE).replace(/\/$/, "");
|
|
69
|
+
this.appUrl = (opts.appUrl ?? "https://app.supafone.ai").replace(/\/$/, "");
|
|
60
70
|
this.timeoutMs = opts.timeoutMs ?? 30_000;
|
|
61
71
|
this.sessionToken = opts.sessionToken;
|
|
72
|
+
this.accountToken = opts.accountToken;
|
|
73
|
+
this.accountEmail = opts.accountEmail;
|
|
74
|
+
this.accountPassword = opts.accountPassword;
|
|
62
75
|
this.labs = new LabsNamespace(this);
|
|
63
76
|
this.builder = new BuilderNamespace(this);
|
|
64
77
|
this.qa = new QANamespace(this);
|
|
65
78
|
this.optimizer = new OptimizerNamespace(this);
|
|
79
|
+
this.campaigns = new CampaignsNamespace(this);
|
|
66
80
|
}
|
|
67
81
|
/** True once login() (or a passed sessionToken) is in effect. */
|
|
68
82
|
get isLoggedIn() {
|
|
@@ -125,6 +139,87 @@ class SupafoneLabs {
|
|
|
125
139
|
clearTimeout(timer);
|
|
126
140
|
}
|
|
127
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Exchange the account email/password for a product-API JWT (the same login
|
|
144
|
+
* as app.supafone.ai). Called lazily by campaigns/calls — call directly only
|
|
145
|
+
* to fail fast.
|
|
146
|
+
*/
|
|
147
|
+
async accountLogin(email, password) {
|
|
148
|
+
const useEmail = email ?? this.accountEmail;
|
|
149
|
+
const usePassword = password ?? this.accountPassword;
|
|
150
|
+
if (!useEmail || !usePassword) {
|
|
151
|
+
throw new SupafoneLabsError("Not authenticated: pass accountToken, or accountEmail + accountPassword");
|
|
152
|
+
}
|
|
153
|
+
const body = await this.accountHttp("POST", "/api/v1/auth/login", { email: useEmail, password: usePassword }, "");
|
|
154
|
+
const token = body.access_token || body.token;
|
|
155
|
+
if (!token)
|
|
156
|
+
throw new SupafoneLabsError("Login succeeded but returned no token");
|
|
157
|
+
this.accountSessionToken = token;
|
|
158
|
+
return token;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* @internal JSON request to the Supafone product API with the ACCOUNT JWT
|
|
162
|
+
* (campaigns + real calls). A minted token that expires gets one transparent
|
|
163
|
+
* re-login; an explicit accountToken is the caller's to refresh.
|
|
164
|
+
*/
|
|
165
|
+
async requestAccountApi(method, path, body) {
|
|
166
|
+
const token = this.accountToken || this.accountSessionToken || (await this.accountLogin());
|
|
167
|
+
try {
|
|
168
|
+
return await this.accountHttp(method, path, body, token);
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
const expired = err instanceof SupafoneLabsError && err.status === 401;
|
|
172
|
+
if (expired && !this.accountToken && this.accountEmail && this.accountPassword) {
|
|
173
|
+
this.accountSessionToken = undefined;
|
|
174
|
+
return this.accountHttp(method, path, body, await this.accountLogin());
|
|
175
|
+
}
|
|
176
|
+
throw err;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/** @internal Raw product-API request with an explicit bearer ("" = none). */
|
|
180
|
+
async accountHttp(method, path, body, token) {
|
|
181
|
+
const ctrl = new AbortController();
|
|
182
|
+
const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
|
|
183
|
+
try {
|
|
184
|
+
const headers = { "Content-Type": "application/json" };
|
|
185
|
+
if (token)
|
|
186
|
+
headers.Authorization = `Bearer ${token}`;
|
|
187
|
+
const res = await fetch(this.supafoneApiBaseUrl + path, {
|
|
188
|
+
method,
|
|
189
|
+
signal: ctrl.signal,
|
|
190
|
+
headers,
|
|
191
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
192
|
+
});
|
|
193
|
+
const text = await res.text();
|
|
194
|
+
const parsed = text ? safeJson(text) : {};
|
|
195
|
+
if (!res.ok) {
|
|
196
|
+
const detail = parsed?.detail ?? text ?? `HTTP ${res.status}`;
|
|
197
|
+
throw new SupafoneLabsError(`${method} ${path}: ${detail}`, res.status, parsed);
|
|
198
|
+
}
|
|
199
|
+
return parsed;
|
|
200
|
+
}
|
|
201
|
+
finally {
|
|
202
|
+
clearTimeout(timer);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* PLACE A REAL OUTBOUND PHONE CALL: dials toNumber from the account's
|
|
207
|
+
* calling provider and bridges the voice agent onto the line.
|
|
208
|
+
*/
|
|
209
|
+
async placeCall(opts) {
|
|
210
|
+
if (!opts?.agentId)
|
|
211
|
+
throw new SupafoneLabsError("agentId is required (see listVoiceAgents())");
|
|
212
|
+
if (!opts?.toNumber)
|
|
213
|
+
throw new SupafoneLabsError("toNumber is required (E.164, e.g. +15551234567)");
|
|
214
|
+
return this.requestAccountApi("POST", "/api/v1/phone/test-call", {
|
|
215
|
+
agent_id: opts.agentId,
|
|
216
|
+
to_number: opts.toNumber,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
/** The account's voice agents — pick an agent id for campaigns/calls. */
|
|
220
|
+
async listVoiceAgents() {
|
|
221
|
+
return this.requestAccountApi("GET", "/api/v1/agents");
|
|
222
|
+
}
|
|
128
223
|
/** Raw oracle completion — full control over messages and model. */
|
|
129
224
|
async oracle(req) {
|
|
130
225
|
return this.request("POST", "/v1/oracle/complete", {
|
|
@@ -170,6 +265,10 @@ class SupafoneLabs {
|
|
|
170
265
|
clearTimeout(timer);
|
|
171
266
|
}
|
|
172
267
|
}
|
|
268
|
+
/** Convenience alias for voice preview UI/buttons. */
|
|
269
|
+
previewVoice(voice = "supafone-labs-calm-en", text = "Hi, this is your Supafone agent voice preview.") {
|
|
270
|
+
return this.tts(text, voice);
|
|
271
|
+
}
|
|
173
272
|
/** Hosted STT for a finished audio clip — returns transcript + language tags. */
|
|
174
273
|
async stt(audio, opts = {}) {
|
|
175
274
|
const bytes = audio instanceof ArrayBuffer ? new Uint8Array(audio) : audio;
|
|
@@ -216,6 +315,48 @@ class SupafoneLabs {
|
|
|
216
315
|
logs(limit = 100) {
|
|
217
316
|
return this.request("GET", `/v1/logs?limit=${limit}`);
|
|
218
317
|
}
|
|
318
|
+
/** Live audit-log stream. Works in Node 18+ and browsers via fetch streaming. */
|
|
319
|
+
async *streamLogs(opts = {}) {
|
|
320
|
+
const q = new URLSearchParams({
|
|
321
|
+
limit: String(opts.limit ?? 100),
|
|
322
|
+
poll_ms: String(opts.pollMs ?? 1000),
|
|
323
|
+
snapshot: String(opts.snapshot ?? true),
|
|
324
|
+
});
|
|
325
|
+
if (opts.afterId !== undefined)
|
|
326
|
+
q.set("after_id", String(opts.afterId));
|
|
327
|
+
const res = await fetch(`${this.baseUrl}/v1/logs/stream?${q}`, {
|
|
328
|
+
method: "GET",
|
|
329
|
+
signal: opts.signal,
|
|
330
|
+
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
331
|
+
});
|
|
332
|
+
if (!res.ok)
|
|
333
|
+
throw new SupafoneLabsError(`streamLogs: ${await res.text()}`, res.status);
|
|
334
|
+
if (!res.body)
|
|
335
|
+
throw new SupafoneLabsError("streamLogs: response body is not readable");
|
|
336
|
+
const reader = res.body.getReader();
|
|
337
|
+
const decoder = new TextDecoder();
|
|
338
|
+
let buffer = "";
|
|
339
|
+
try {
|
|
340
|
+
for (;;) {
|
|
341
|
+
const { done, value } = await reader.read();
|
|
342
|
+
if (done)
|
|
343
|
+
break;
|
|
344
|
+
buffer += decoder.decode(value, { stream: true });
|
|
345
|
+
let split = buffer.indexOf("\n\n");
|
|
346
|
+
while (split >= 0) {
|
|
347
|
+
const raw = buffer.slice(0, split);
|
|
348
|
+
buffer = buffer.slice(split + 2);
|
|
349
|
+
const parsed = parseSseLog(raw);
|
|
350
|
+
if (parsed)
|
|
351
|
+
yield parsed;
|
|
352
|
+
split = buffer.indexOf("\n\n");
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
finally {
|
|
357
|
+
reader.releaseLock();
|
|
358
|
+
}
|
|
359
|
+
}
|
|
219
360
|
/** The structured whisper feed (what the console shows). */
|
|
220
361
|
nudges(limit = 50) {
|
|
221
362
|
return this.request("GET", `/v1/nudges?limit=${limit}`);
|
|
@@ -242,6 +383,10 @@ class SupafoneLabs {
|
|
|
242
383
|
const d = await this.request("GET", "/v1/voices");
|
|
243
384
|
return d.voices.map((v) => (typeof v === "string" ? v : (v.voice ?? v.id ?? ""))).filter(Boolean);
|
|
244
385
|
}
|
|
386
|
+
/** Full hosted voice catalog with provider live/configured flags. */
|
|
387
|
+
voiceCatalog() {
|
|
388
|
+
return this.request("GET", "/v1/voices");
|
|
389
|
+
}
|
|
245
390
|
}
|
|
246
391
|
exports.SupafoneLabs = SupafoneLabs;
|
|
247
392
|
exports.Supafone = SupafoneLabs;
|
|
@@ -290,7 +435,131 @@ class LiveTranscription {
|
|
|
290
435
|
return this.ws;
|
|
291
436
|
}
|
|
292
437
|
}
|
|
293
|
-
/**
|
|
438
|
+
/**
|
|
439
|
+
* Typical flow:
|
|
440
|
+
* ```ts
|
|
441
|
+
* const sf = new Supafone({ accountEmail, accountPassword });
|
|
442
|
+
* const { agents } = await sf.listVoiceAgents();
|
|
443
|
+
* const { campaign } = await sf.campaigns.create({ name: "Q3 win-back", goal: "reengage", agentId: agents[0].id });
|
|
444
|
+
* await sf.campaigns.applyPreset(campaign.id, "win_back");
|
|
445
|
+
* await sf.campaigns.addRecipients(campaign.id, [{ name: "Jane", phone: "+15551234567", outreach_consent: "yes" }]);
|
|
446
|
+
* await sf.campaigns.launch(campaign.id);
|
|
447
|
+
* const live = await sf.campaigns.live(campaign.id); // in-flight calls + portal links
|
|
448
|
+
* ```
|
|
449
|
+
*/
|
|
450
|
+
class CampaignsNamespace {
|
|
451
|
+
sm;
|
|
452
|
+
constructor(sm) {
|
|
453
|
+
this.sm = sm;
|
|
454
|
+
}
|
|
455
|
+
list(opts = {}) {
|
|
456
|
+
const query = opts.accountId ? `?${new URLSearchParams({ account_id: opts.accountId })}` : "";
|
|
457
|
+
return this.sm.requestAccountApi("GET", `/api/v1/campaigns${query}`);
|
|
458
|
+
}
|
|
459
|
+
create(opts = {}) {
|
|
460
|
+
return this.sm.requestAccountApi("POST", "/api/v1/campaigns", compact({
|
|
461
|
+
name: opts.name ?? "New campaign",
|
|
462
|
+
goal: opts.goal ?? "book",
|
|
463
|
+
agent_id: opts.agentId,
|
|
464
|
+
account_id: opts.accountId,
|
|
465
|
+
}));
|
|
466
|
+
}
|
|
467
|
+
get(campaignId) {
|
|
468
|
+
return this.sm.requestAccountApi("GET", `/api/v1/campaigns/${encodeURIComponent(campaignId)}`);
|
|
469
|
+
}
|
|
470
|
+
update(campaignId, input) {
|
|
471
|
+
const payload = compact({
|
|
472
|
+
name: input.name,
|
|
473
|
+
goal: input.goal,
|
|
474
|
+
agent_id: input.agentId,
|
|
475
|
+
email_subject: input.emailSubject,
|
|
476
|
+
email_body: input.emailBody,
|
|
477
|
+
cadence: input.cadence,
|
|
478
|
+
settings: input.settings,
|
|
479
|
+
});
|
|
480
|
+
if (!Object.keys(payload).length) {
|
|
481
|
+
throw new SupafoneLabsError("Nothing to update — pass name, goal, agentId, emailSubject, emailBody, cadence, or settings");
|
|
482
|
+
}
|
|
483
|
+
return this.sm.requestAccountApi("PUT", `/api/v1/campaigns/${encodeURIComponent(campaignId)}`, payload);
|
|
484
|
+
}
|
|
485
|
+
/** Add consented leads: [{name, phone, email, outreach_consent: "yes"}]. */
|
|
486
|
+
addRecipients(campaignId, recipients) {
|
|
487
|
+
if (!Array.isArray(recipients) || !recipients.length) {
|
|
488
|
+
throw new SupafoneLabsError("recipients must be a non-empty array of lead rows");
|
|
489
|
+
}
|
|
490
|
+
return this.sm.requestAccountApi("POST", `/api/v1/campaigns/${encodeURIComponent(campaignId)}/recipients`, { recipients });
|
|
491
|
+
}
|
|
492
|
+
recipients(campaignId) {
|
|
493
|
+
return this.sm.requestAccountApi("GET", `/api/v1/campaigns/${encodeURIComponent(campaignId)}/recipients`);
|
|
494
|
+
}
|
|
495
|
+
/** Starts REAL calls/emails on the cadence immediately. */
|
|
496
|
+
launch(campaignId) {
|
|
497
|
+
return this.sm.requestAccountApi("POST", `/api/v1/campaigns/${encodeURIComponent(campaignId)}/launch`, {});
|
|
498
|
+
}
|
|
499
|
+
pause(campaignId) {
|
|
500
|
+
return this.sm.requestAccountApi("POST", `/api/v1/campaigns/${encodeURIComponent(campaignId)}/pause`, {});
|
|
501
|
+
}
|
|
502
|
+
/** Built-in playbooks + the account's saved custom presets. */
|
|
503
|
+
async presets() {
|
|
504
|
+
const builtIn = await this.sm.requestAccountApi("GET", "/api/v1/campaigns/outbound-presets");
|
|
505
|
+
let custom = [];
|
|
506
|
+
try {
|
|
507
|
+
const mine = await this.sm.requestAccountApi("GET", "/api/v1/campaigns/custom-presets");
|
|
508
|
+
custom = mine.presets ?? [];
|
|
509
|
+
}
|
|
510
|
+
catch {
|
|
511
|
+
/* custom presets need account scope — built-ins still return */
|
|
512
|
+
}
|
|
513
|
+
return { built_in: builtIn.presets ?? [], custom };
|
|
514
|
+
}
|
|
515
|
+
/** Materialize a preset (goal, questions, scripts, signing doc) in one write. */
|
|
516
|
+
applyPreset(campaignId, presetId) {
|
|
517
|
+
return this.sm.requestAccountApi("POST", `/api/v1/campaigns/${encodeURIComponent(campaignId)}/apply-preset`, {
|
|
518
|
+
preset_id: presetId,
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
stats(campaignId) {
|
|
522
|
+
return this.sm.requestAccountApi("GET", `/api/v1/campaigns/${encodeURIComponent(campaignId)}/stats`);
|
|
523
|
+
}
|
|
524
|
+
/** The live funnel + the campaign's most recent calls (newest first). */
|
|
525
|
+
activity(campaignId) {
|
|
526
|
+
return this.sm.requestAccountApi("GET", `/api/v1/campaigns/${encodeURIComponent(campaignId)}/activity`);
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* In-flight calls right now, each with a portal link to watch/listen. Poll
|
|
530
|
+
* getCall(callId) (or open the link) for the transcript as it grows.
|
|
531
|
+
*/
|
|
532
|
+
async live(campaignId) {
|
|
533
|
+
const activity = await this.activity(campaignId);
|
|
534
|
+
const inFlight = [];
|
|
535
|
+
for (const call of activity.calls ?? []) {
|
|
536
|
+
const status = String(call.status ?? "");
|
|
537
|
+
if (status === "initiated" || status === "dialing" || status === "in_progress") {
|
|
538
|
+
const id = String(call.id ?? "");
|
|
539
|
+
inFlight.push({
|
|
540
|
+
...call,
|
|
541
|
+
id,
|
|
542
|
+
status,
|
|
543
|
+
listen_url: `${this.sm.appUrl}/app/calls?call=${encodeURIComponent(id)}`,
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return {
|
|
548
|
+
campaign_id: campaignId,
|
|
549
|
+
in_flight: inFlight,
|
|
550
|
+
portal_url: `${this.sm.appUrl}/app/developer?campaign=${encodeURIComponent(campaignId)}`,
|
|
551
|
+
stats: activity.stats ?? null,
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
/** One call — while in_progress the transcript grows on each poll. */
|
|
555
|
+
getCall(callId) {
|
|
556
|
+
return this.sm.requestAccountApi("GET", `/api/v1/calls/${encodeURIComponent(callId)}`);
|
|
557
|
+
}
|
|
558
|
+
/** Mint a recipient's tracked tap-to-sign link (inherits the campaign's signing PDF). */
|
|
559
|
+
createSignLink(campaignId, recipientId, opts = {}) {
|
|
560
|
+
return this.sm.requestAccountApi("POST", `/api/v1/campaigns/${encodeURIComponent(campaignId)}/recipients/${encodeURIComponent(recipientId)}/sign-link`, compact({ title: opts.title, message: opts.message }));
|
|
561
|
+
}
|
|
562
|
+
}
|
|
294
563
|
class LabsNamespace {
|
|
295
564
|
sm;
|
|
296
565
|
agents;
|
|
@@ -299,6 +568,9 @@ class LabsNamespace {
|
|
|
299
568
|
voices;
|
|
300
569
|
phoneNumbers;
|
|
301
570
|
telephony;
|
|
571
|
+
calls;
|
|
572
|
+
recordings;
|
|
573
|
+
transcripts;
|
|
302
574
|
constructor(sm) {
|
|
303
575
|
this.sm = sm;
|
|
304
576
|
this.agents = new LabsAgentsNamespace(sm);
|
|
@@ -307,6 +579,9 @@ class LabsNamespace {
|
|
|
307
579
|
this.voices = new LabsVoicesNamespace(sm);
|
|
308
580
|
this.phoneNumbers = new LabsPhoneNumbersNamespace(sm);
|
|
309
581
|
this.telephony = new LabsTelephonyNamespace(sm);
|
|
582
|
+
this.calls = new LabsCallsNamespace(sm);
|
|
583
|
+
this.recordings = new LabsRecordingsNamespace(sm);
|
|
584
|
+
this.transcripts = new LabsTranscriptsNamespace(sm);
|
|
310
585
|
}
|
|
311
586
|
/** Discover the Supafone convenience layer over Ultravox. */
|
|
312
587
|
capabilities() {
|
|
@@ -402,6 +677,18 @@ class LabsAgentsNamespace {
|
|
|
402
677
|
const suffix = q.toString() ? `?${q}` : "";
|
|
403
678
|
return this.sm.requestSupafoneApi("GET", `/api/v1/labs/agents/${encodeURIComponent(agentKey)}${suffix}`);
|
|
404
679
|
}
|
|
680
|
+
/** Delete an agent. Optionally ask the backend to release assigned numbers. */
|
|
681
|
+
delete(agentKey, opts = {}) {
|
|
682
|
+
const q = new URLSearchParams();
|
|
683
|
+
const agencyId = opts.agency_id ?? opts.agencyId;
|
|
684
|
+
const releaseNumbers = opts.release_numbers ?? opts.releaseNumbers;
|
|
685
|
+
if (agencyId)
|
|
686
|
+
q.set("agency_id", agencyId);
|
|
687
|
+
if (releaseNumbers !== undefined)
|
|
688
|
+
q.set("release_numbers", String(releaseNumbers));
|
|
689
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
690
|
+
return this.sm.requestSupafoneApi("DELETE", `/api/v1/labs/agents/${encodeURIComponent(agentKey)}${suffix}`);
|
|
691
|
+
}
|
|
405
692
|
}
|
|
406
693
|
class LabsPresetsNamespace {
|
|
407
694
|
sm;
|
|
@@ -437,6 +724,75 @@ class LabsVoicesNamespace {
|
|
|
437
724
|
return this.sm.requestSupafoneApi("GET", `/api/v1/labs/voices${suffix}`);
|
|
438
725
|
}
|
|
439
726
|
}
|
|
727
|
+
class LabsCallsNamespace {
|
|
728
|
+
sm;
|
|
729
|
+
constructor(sm) {
|
|
730
|
+
this.sm = sm;
|
|
731
|
+
}
|
|
732
|
+
list(opts = {}) {
|
|
733
|
+
const q = hostedListQuery(opts);
|
|
734
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
735
|
+
return this.sm.requestSupafoneApi("GET", `/api/v1/labs/calls${suffix}`);
|
|
736
|
+
}
|
|
737
|
+
get(callId, opts = {}) {
|
|
738
|
+
const q = new URLSearchParams();
|
|
739
|
+
if (opts.agencyId)
|
|
740
|
+
q.set("agency_id", opts.agencyId);
|
|
741
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
742
|
+
return this.sm.requestSupafoneApi("GET", `/api/v1/labs/calls/${encodeURIComponent(callId)}${suffix}`);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
class LabsRecordingsNamespace {
|
|
746
|
+
sm;
|
|
747
|
+
constructor(sm) {
|
|
748
|
+
this.sm = sm;
|
|
749
|
+
}
|
|
750
|
+
list(opts = {}) {
|
|
751
|
+
const q = hostedListQuery(opts);
|
|
752
|
+
const callId = opts.call_id ?? opts.callId;
|
|
753
|
+
if (callId)
|
|
754
|
+
q.set("call_id", callId);
|
|
755
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
756
|
+
return this.sm.requestSupafoneApi("GET", `/api/v1/labs/recordings${suffix}`);
|
|
757
|
+
}
|
|
758
|
+
get(recordingId, opts = {}) {
|
|
759
|
+
const q = new URLSearchParams();
|
|
760
|
+
if (opts.agencyId)
|
|
761
|
+
q.set("agency_id", opts.agencyId);
|
|
762
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
763
|
+
return this.sm.requestSupafoneApi("GET", `/api/v1/labs/recordings/${encodeURIComponent(recordingId)}${suffix}`);
|
|
764
|
+
}
|
|
765
|
+
delete(recordingId, opts = {}) {
|
|
766
|
+
const q = new URLSearchParams();
|
|
767
|
+
if (opts.agencyId)
|
|
768
|
+
q.set("agency_id", opts.agencyId);
|
|
769
|
+
if (opts.reason)
|
|
770
|
+
q.set("reason", opts.reason);
|
|
771
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
772
|
+
return this.sm.requestSupafoneApi("DELETE", `/api/v1/labs/recordings/${encodeURIComponent(recordingId)}${suffix}`);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
class LabsTranscriptsNamespace {
|
|
776
|
+
sm;
|
|
777
|
+
constructor(sm) {
|
|
778
|
+
this.sm = sm;
|
|
779
|
+
}
|
|
780
|
+
list(opts = {}) {
|
|
781
|
+
const q = hostedListQuery(opts);
|
|
782
|
+
const callId = opts.call_id ?? opts.callId;
|
|
783
|
+
if (callId)
|
|
784
|
+
q.set("call_id", callId);
|
|
785
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
786
|
+
return this.sm.requestSupafoneApi("GET", `/api/v1/labs/transcripts${suffix}`);
|
|
787
|
+
}
|
|
788
|
+
get(transcriptId, opts = {}) {
|
|
789
|
+
const q = new URLSearchParams();
|
|
790
|
+
if (opts.agencyId)
|
|
791
|
+
q.set("agency_id", opts.agencyId);
|
|
792
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
793
|
+
return this.sm.requestSupafoneApi("GET", `/api/v1/labs/transcripts/${encodeURIComponent(transcriptId)}${suffix}`);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
440
796
|
class LabsPhoneNumbersNamespace {
|
|
441
797
|
sm;
|
|
442
798
|
constructor(sm) {
|
|
@@ -467,6 +823,27 @@ class LabsPhoneNumbersNamespace {
|
|
|
467
823
|
assign(numberId, input = {}) {
|
|
468
824
|
return this.sm.requestSupafoneApi("POST", `/api/v1/labs/phone-numbers/${encodeURIComponent(numberId)}/assign`, phoneNumberAssignPayload(input));
|
|
469
825
|
}
|
|
826
|
+
/** Unassign a number from an agent but keep it reserved on the account. */
|
|
827
|
+
unassign(numberId, input = {}) {
|
|
828
|
+
return this.sm.requestSupafoneApi("POST", `/api/v1/labs/phone-numbers/${encodeURIComponent(numberId)}/unassign`, phoneNumberReleasePayload(input));
|
|
829
|
+
}
|
|
830
|
+
/** Give a number back to the shared pool or release the reservation. */
|
|
831
|
+
release(numberId, input = {}) {
|
|
832
|
+
return this.sm.requestSupafoneApi("POST", `/api/v1/labs/phone-numbers/${encodeURIComponent(numberId)}/release`, phoneNumberReleasePayload({ ...input, returnToPool: input.returnToPool ?? input.return_to_pool ?? true }));
|
|
833
|
+
}
|
|
834
|
+
/** Alias for release(numberId, { returnToPool: true }). */
|
|
835
|
+
returnToPool(numberId, input = {}) {
|
|
836
|
+
return this.release(numberId, { ...input, returnToPool: true });
|
|
837
|
+
}
|
|
838
|
+
/** Delete/release a number reservation. Backend policy decides whether this is allowed. */
|
|
839
|
+
delete(numberId, input = {}) {
|
|
840
|
+
const q = new URLSearchParams();
|
|
841
|
+
const agencyId = input.agency_id ?? input.agencyId;
|
|
842
|
+
if (agencyId)
|
|
843
|
+
q.set("agency_id", agencyId);
|
|
844
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
845
|
+
return this.sm.requestSupafoneApi("DELETE", `/api/v1/labs/phone-numbers/${encodeURIComponent(numberId)}${suffix}`, phoneNumberReleasePayload(input));
|
|
846
|
+
}
|
|
470
847
|
/**
|
|
471
848
|
* Search if needed, buy the first matching Supafone-managed number, and assign
|
|
472
849
|
* it to the supplied agent. This is the zero-Twilio-account happy path.
|
|
@@ -584,17 +961,26 @@ function labsAgentPayload(input) {
|
|
|
584
961
|
industry: input.industry,
|
|
585
962
|
website_url: input.website_url ?? input.websiteUrl,
|
|
586
963
|
phone_number: input.phone_number ?? input.phoneNumber,
|
|
964
|
+
number_strategy: input.number_strategy ?? input.numberStrategy,
|
|
965
|
+
number_pool: input.number_pool ?? input.numberPool,
|
|
966
|
+
premium: input.premium,
|
|
587
967
|
direction: input.direction,
|
|
588
968
|
preset_key: input.preset_key ?? input.presetKey,
|
|
589
969
|
runtime_mode: input.runtime_mode ?? input.runtimeMode,
|
|
970
|
+
call_stages: callStagesPayload(input),
|
|
590
971
|
goal: input.goal,
|
|
591
972
|
greeting: input.greeting,
|
|
592
973
|
system_prompt: input.system_prompt ?? input.systemPrompt,
|
|
593
974
|
language: input.language,
|
|
594
975
|
voice: input.voice ? voicePayload(input.voice) : undefined,
|
|
595
976
|
provider_keys: input.provider_keys ?? (input.providerKeys ? providerKeysPayload(input.providerKeys) : undefined),
|
|
596
|
-
byok: input.byok ?
|
|
977
|
+
byok: input.byok ? byokPayload(input.byok) : undefined,
|
|
597
978
|
telephony: input.telephony ? telephonyPayload(input.telephony) : undefined,
|
|
979
|
+
custom_sip: customSipPayload(input.custom_sip ?? input.customSip ?? input.sip),
|
|
980
|
+
recording: input.recording ? recordingPayload(input.recording) : undefined,
|
|
981
|
+
transcription: input.transcription ? transcriptionPayload(input.transcription) : undefined,
|
|
982
|
+
artifacts: input.artifacts ? artifactsPayload(input.artifacts) : undefined,
|
|
983
|
+
compliance: input.compliance,
|
|
598
984
|
tools: input.tools ? toolsPayload(input.tools) : undefined,
|
|
599
985
|
labs: input.labs ? labsPayload(input.labs) : undefined,
|
|
600
986
|
ultravox: input.ultravox ? ultravoxPayload(input.ultravox) : undefined,
|
|
@@ -603,13 +989,30 @@ function labsAgentPayload(input) {
|
|
|
603
989
|
metadata: input.metadata,
|
|
604
990
|
});
|
|
605
991
|
}
|
|
992
|
+
function hostedListQuery(opts) {
|
|
993
|
+
const q = new URLSearchParams();
|
|
994
|
+
if (opts.agencyId)
|
|
995
|
+
q.set("agency_id", opts.agencyId);
|
|
996
|
+
const agentKey = opts.agent_key ?? opts.agentKey;
|
|
997
|
+
if (agentKey)
|
|
998
|
+
q.set("agent_key", agentKey);
|
|
999
|
+
if (opts.limit !== undefined)
|
|
1000
|
+
q.set("limit", String(opts.limit));
|
|
1001
|
+
return q;
|
|
1002
|
+
}
|
|
606
1003
|
function telephonyPayload(input) {
|
|
607
1004
|
return compact({
|
|
608
1005
|
agency_id: input.agency_id ?? input.agencyId,
|
|
609
1006
|
mode: input.mode,
|
|
610
1007
|
provider: input.provider,
|
|
1008
|
+
number_strategy: input.number_strategy ?? input.numberStrategy,
|
|
1009
|
+
number_pool: input.number_pool ?? input.numberPool,
|
|
1010
|
+
number_id: input.number_id ?? input.numberId,
|
|
1011
|
+
premium: input.premium,
|
|
611
1012
|
label: input.label,
|
|
612
1013
|
credentials: input.credentials ? telephonyCredentialsPayload(input.credentials) : undefined,
|
|
1014
|
+
provider_settings: input.provider_settings ?? input.providerSettings,
|
|
1015
|
+
custom_sip: customSipPayload(input.custom_sip ?? input.customSip),
|
|
613
1016
|
metadata: input.metadata,
|
|
614
1017
|
});
|
|
615
1018
|
}
|
|
@@ -627,11 +1030,22 @@ function telephonyCredentialsPayload(input) {
|
|
|
627
1030
|
username: input.username,
|
|
628
1031
|
password: input.password,
|
|
629
1032
|
webhook_secret: input.webhook_secret ?? input.webhookSecret,
|
|
1033
|
+
telnyx_connection_id: input.telnyx_connection_id ?? input.telnyxConnectionId,
|
|
1034
|
+
signalwire_space_url: input.signalwire_space_url ?? input.signalwireSpaceUrl,
|
|
1035
|
+
project_id: input.project_id ?? input.projectId,
|
|
1036
|
+
application_id: input.application_id ?? input.applicationId,
|
|
1037
|
+
trunk_id: input.trunk_id ?? input.trunkId,
|
|
1038
|
+
endpoint_id: input.endpoint_id ?? input.endpointId,
|
|
1039
|
+
token: input.token,
|
|
1040
|
+
secret: input.secret,
|
|
1041
|
+
custom_sip: customSipPayload(input.custom_sip ?? input.customSip),
|
|
630
1042
|
});
|
|
631
1043
|
}
|
|
632
1044
|
function phoneNumberSearchPayload(input) {
|
|
633
1045
|
return compact({
|
|
634
1046
|
agency_id: input.agency_id ?? input.agencyId,
|
|
1047
|
+
number_pool: input.number_pool ?? input.numberPool,
|
|
1048
|
+
number_strategy: input.number_strategy ?? input.numberStrategy,
|
|
635
1049
|
country_code: input.country_code ?? input.countryCode,
|
|
636
1050
|
area_code: input.area_code ?? input.areaCode,
|
|
637
1051
|
postal_code: input.postal_code ?? input.postalCode,
|
|
@@ -652,6 +1066,9 @@ function phoneNumberProvisionPayload(input) {
|
|
|
652
1066
|
agent_id: input.agent_id ?? input.agentId,
|
|
653
1067
|
agent_name: input.agent_name ?? input.agentName,
|
|
654
1068
|
preset_key: input.preset_key ?? input.presetKey,
|
|
1069
|
+
number_strategy: input.number_strategy ?? input.numberStrategy,
|
|
1070
|
+
number_pool: input.number_pool ?? input.numberPool,
|
|
1071
|
+
premium: input.premium,
|
|
655
1072
|
style: input.agent_style ?? input.agentStyle ?? input.style,
|
|
656
1073
|
direction: input.direction,
|
|
657
1074
|
telephony: input.telephony ? telephonyPayload(input.telephony) : undefined,
|
|
@@ -666,12 +1083,99 @@ function phoneNumberAssignPayload(input) {
|
|
|
666
1083
|
agent_name: input.agent_name ?? input.agentName,
|
|
667
1084
|
friendly_name: input.friendly_name ?? input.friendlyName,
|
|
668
1085
|
preset_key: input.preset_key ?? input.presetKey,
|
|
1086
|
+
number_strategy: input.number_strategy ?? input.numberStrategy,
|
|
1087
|
+
number_pool: input.number_pool ?? input.numberPool,
|
|
1088
|
+
premium: input.premium,
|
|
669
1089
|
style: input.agent_style ?? input.agentStyle ?? input.style,
|
|
670
1090
|
direction: input.direction,
|
|
671
1091
|
telephony: input.telephony ? telephonyPayload(input.telephony) : undefined,
|
|
672
1092
|
metadata: input.metadata,
|
|
673
1093
|
});
|
|
674
1094
|
}
|
|
1095
|
+
function phoneNumberReleasePayload(input) {
|
|
1096
|
+
return compact({
|
|
1097
|
+
agency_id: input.agency_id ?? input.agencyId,
|
|
1098
|
+
reason: input.reason,
|
|
1099
|
+
return_to_pool: input.return_to_pool ?? input.returnToPool,
|
|
1100
|
+
metadata: input.metadata,
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
function callStagesPayload(input) {
|
|
1104
|
+
const explicit = input.call_stages ?? input.callStages ?? input.stages;
|
|
1105
|
+
const auto = input.auto_call_stages ?? input.autoCallStages;
|
|
1106
|
+
if (Array.isArray(explicit))
|
|
1107
|
+
return explicit.map(callStagePayload);
|
|
1108
|
+
if (explicit === false || auto === false)
|
|
1109
|
+
return undefined;
|
|
1110
|
+
return generateCallStages(input).map(callStagePayload);
|
|
1111
|
+
}
|
|
1112
|
+
function callStagePayload(input) {
|
|
1113
|
+
return compact({
|
|
1114
|
+
key: input.key ?? input.id,
|
|
1115
|
+
name: input.name,
|
|
1116
|
+
goal: input.goal,
|
|
1117
|
+
instructions: input.instructions,
|
|
1118
|
+
exit_criteria: input.exit_criteria ?? input.exitCriteria,
|
|
1119
|
+
tools: input.tools,
|
|
1120
|
+
metadata: input.metadata,
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
function generateCallStages(input) {
|
|
1124
|
+
const direction = String(input.direction ?? input.agent_style ?? input.agentStyle ?? input.style ?? "inbound").toLowerCase();
|
|
1125
|
+
const haystack = [
|
|
1126
|
+
input.name,
|
|
1127
|
+
input.assistant_name ?? input.assistantName,
|
|
1128
|
+
input.business_name ?? input.businessName,
|
|
1129
|
+
input.industry,
|
|
1130
|
+
input.goal,
|
|
1131
|
+
input.system_prompt ?? input.systemPrompt,
|
|
1132
|
+
input.preset_key ?? input.presetKey,
|
|
1133
|
+
].filter(Boolean).join(" ").toLowerCase();
|
|
1134
|
+
const meta = { auto_generated: true, source: "supafone-labs-sdk" };
|
|
1135
|
+
if (direction === "outbound" || haystack.includes("sales") || haystack.includes("lead")) {
|
|
1136
|
+
return [
|
|
1137
|
+
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),
|
|
1138
|
+
stage("qualification", "Qualification", "Confirm fit, urgency, decision process, and the best next step.", ["Need and timeline are clear"], meta),
|
|
1139
|
+
stage("offer", "Offer", "Explain the next step in plain language without unsupported claims.", ["Caller understands the next step"], meta),
|
|
1140
|
+
stage("booking", "Booking", "Book or route the caller only after confirming required details.", ["Next step is confirmed by a tool or human handoff"], meta),
|
|
1141
|
+
stage("close", "Close", "Summarize what will happen next and end politely.", ["Caller knows the follow-up path"], meta),
|
|
1142
|
+
];
|
|
1143
|
+
}
|
|
1144
|
+
if (haystack.includes("legal") || haystack.includes("law") || haystack.includes("injury") || haystack.includes("intake")) {
|
|
1145
|
+
return [
|
|
1146
|
+
stage("greeting", "Greeting", "Open warmly and acknowledge the caller before logistics.", ["Caller need is understood"], meta),
|
|
1147
|
+
stage("incident", "Incident details", "Collect what happened, when it happened, injuries, insurance, and contact details.", ["Core facts are collected"], meta),
|
|
1148
|
+
stage("screening", "Screening", "Identify urgency, jurisdiction, conflicts, and whether human escalation is required.", ["Escalation decision is clear"], meta),
|
|
1149
|
+
stage("booking", "Consult booking", "Book the right next step without quoting fees or inventing availability.", ["Booking or handoff is tool-confirmed"], meta),
|
|
1150
|
+
stage("close", "Close", "Summarize the next step and set expectations accurately.", ["Caller knows exactly what happens next"], meta),
|
|
1151
|
+
];
|
|
1152
|
+
}
|
|
1153
|
+
if (haystack.includes("medical") || haystack.includes("clinic") || haystack.includes("patient") || haystack.includes("health")) {
|
|
1154
|
+
return [
|
|
1155
|
+
stage("greeting", "Greeting", "Identify the caller need and keep the tone calm and concise.", ["Caller need is understood"], meta),
|
|
1156
|
+
stage("patient_context", "Patient context", "Collect non-sensitive scheduling context and avoid medical advice.", ["Required scheduling context is collected"], meta),
|
|
1157
|
+
stage("routing", "Routing", "Route urgent, billing, clinical, and scheduling requests correctly.", ["Correct route is selected"], meta),
|
|
1158
|
+
stage("appointment", "Appointment", "Book or request the appointment only after confirming details.", ["Appointment path is confirmed"], meta),
|
|
1159
|
+
stage("close", "Close", "Recap next steps and any confirmed timing.", ["Caller knows the next step"], meta),
|
|
1160
|
+
];
|
|
1161
|
+
}
|
|
1162
|
+
return [
|
|
1163
|
+
stage("greeting", "Greeting", "Open naturally, identify the caller need, and set a helpful tone.", ["Caller need is understood"], meta),
|
|
1164
|
+
stage("discovery", "Discovery", "Ask one question at a time until the key details are clear.", ["Required details are collected"], meta),
|
|
1165
|
+
stage("resolution", "Resolution", "Answer approved questions or route to the right workflow.", ["Resolution path is selected"], meta),
|
|
1166
|
+
stage("action", "Action", "Use tools for booking, routing, messaging, or handoff before claiming success.", ["Action is confirmed by a tool or handoff"], meta),
|
|
1167
|
+
stage("close", "Close", "Summarize the outcome and next step accurately.", ["Caller knows what happens next"], meta),
|
|
1168
|
+
];
|
|
1169
|
+
}
|
|
1170
|
+
function stage(key, name, instructions, exitCriteria, metadata) {
|
|
1171
|
+
return {
|
|
1172
|
+
key,
|
|
1173
|
+
name,
|
|
1174
|
+
instructions,
|
|
1175
|
+
exitCriteria,
|
|
1176
|
+
metadata,
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
675
1179
|
function voicePayload(input) {
|
|
676
1180
|
return compact({
|
|
677
1181
|
provider: input.provider,
|
|
@@ -683,6 +1187,30 @@ function providerKeysPayload(input) {
|
|
|
683
1187
|
return compact({
|
|
684
1188
|
ultravox: input.ultravox,
|
|
685
1189
|
ultravox_api_key: input.ultravox_api_key ?? input.ultravoxApiKey,
|
|
1190
|
+
retell: input.retell,
|
|
1191
|
+
retell_api_key: input.retell_api_key ?? input.retellApiKey,
|
|
1192
|
+
vapi: input.vapi,
|
|
1193
|
+
vapi_api_key: input.vapi_api_key ?? input.vapiApiKey,
|
|
1194
|
+
bland: input.bland,
|
|
1195
|
+
bland_api_key: input.bland_api_key ?? input.blandApiKey,
|
|
1196
|
+
livekit: input.livekit,
|
|
1197
|
+
livekit_api_key: input.livekit_api_key ?? input.livekitApiKey,
|
|
1198
|
+
livekit_api_secret: input.livekit_api_secret ?? input.livekitApiSecret,
|
|
1199
|
+
pipecat: input.pipecat,
|
|
1200
|
+
pipecat_api_key: input.pipecat_api_key ?? input.pipecatApiKey,
|
|
1201
|
+
twilio: input.twilio,
|
|
1202
|
+
twilio_account_sid: input.twilio_account_sid ?? input.twilioAccountSid,
|
|
1203
|
+
twilio_auth_token: input.twilio_auth_token ?? input.twilioAuthToken,
|
|
1204
|
+
twilio_api_key_sid: input.twilio_api_key_sid ?? input.twilioApiKeySid,
|
|
1205
|
+
twilio_api_key_secret: input.twilio_api_key_secret ?? input.twilioApiKeySecret,
|
|
1206
|
+
telnyx: input.telnyx,
|
|
1207
|
+
telnyx_api_key: input.telnyx_api_key ?? input.telnyxApiKey,
|
|
1208
|
+
plivo: input.plivo,
|
|
1209
|
+
plivo_auth_id: input.plivo_auth_id ?? input.plivoAuthId,
|
|
1210
|
+
plivo_auth_token: input.plivo_auth_token ?? input.plivoAuthToken,
|
|
1211
|
+
signalwire: input.signalwire,
|
|
1212
|
+
signalwire_api_token: input.signalwire_api_token ?? input.signalwireApiToken,
|
|
1213
|
+
signalwire_project_id: input.signalwire_project_id ?? input.signalwireProjectId,
|
|
686
1214
|
elevenlabs: input.elevenlabs,
|
|
687
1215
|
elevenlabs_api_key: input.elevenlabs_api_key ?? input.elevenlabsApiKey,
|
|
688
1216
|
cartesia: input.cartesia,
|
|
@@ -691,6 +1219,56 @@ function providerKeysPayload(input) {
|
|
|
691
1219
|
inworld_api_key: input.inworld_api_key ?? input.inworldApiKey,
|
|
692
1220
|
deepgram: input.deepgram,
|
|
693
1221
|
deepgram_api_key: input.deepgram_api_key ?? input.deepgramApiKey,
|
|
1222
|
+
anthropic: input.anthropic,
|
|
1223
|
+
anthropic_api_key: input.anthropic_api_key ?? input.anthropicApiKey,
|
|
1224
|
+
openai: input.openai,
|
|
1225
|
+
openai_api_key: input.openai_api_key ?? input.openaiApiKey,
|
|
1226
|
+
xai: input.xai,
|
|
1227
|
+
xai_api_key: input.xai_api_key ?? input.xaiApiKey,
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
function byokPayload(input) {
|
|
1231
|
+
const structured = input;
|
|
1232
|
+
if (structured.agentProvider ||
|
|
1233
|
+
structured.agent_provider ||
|
|
1234
|
+
structured.runtime ||
|
|
1235
|
+
structured.telephony ||
|
|
1236
|
+
structured.tts ||
|
|
1237
|
+
structured.stt ||
|
|
1238
|
+
structured.llm ||
|
|
1239
|
+
structured.providerKeys ||
|
|
1240
|
+
structured.provider_keys ||
|
|
1241
|
+
structured.customSip ||
|
|
1242
|
+
structured.custom_sip ||
|
|
1243
|
+
structured.sip) {
|
|
1244
|
+
return compact({
|
|
1245
|
+
provider_keys: providerKeysPayload(structured.provider_keys ?? structured.providerKeys ?? {}),
|
|
1246
|
+
agent_provider: providerConfigPayload(structured.agent_provider ?? structured.agentProvider ?? structured.runtime),
|
|
1247
|
+
telephony: structured.telephony ? telephonyPayload(structured.telephony) : undefined,
|
|
1248
|
+
tts: providerConfigPayload(structured.tts),
|
|
1249
|
+
stt: providerConfigPayload(structured.stt),
|
|
1250
|
+
llm: providerConfigPayload(structured.llm),
|
|
1251
|
+
custom_sip: customSipPayload(structured.custom_sip ?? structured.customSip ?? structured.sip),
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
return providerKeysPayload(input);
|
|
1255
|
+
}
|
|
1256
|
+
function providerConfigPayload(input) {
|
|
1257
|
+
if (!input)
|
|
1258
|
+
return undefined;
|
|
1259
|
+
const out = { ...input };
|
|
1260
|
+
delete out.apiKey;
|
|
1261
|
+
delete out.api_key;
|
|
1262
|
+
delete out.voiceId;
|
|
1263
|
+
delete out.voice_id;
|
|
1264
|
+
return compact({
|
|
1265
|
+
...out,
|
|
1266
|
+
provider: input.provider,
|
|
1267
|
+
api_key: input.api_key ?? input.apiKey,
|
|
1268
|
+
credentials: input.credentials,
|
|
1269
|
+
settings: input.settings,
|
|
1270
|
+
model: input.model,
|
|
1271
|
+
voice_id: input.voice_id ?? input.voiceId,
|
|
694
1272
|
});
|
|
695
1273
|
}
|
|
696
1274
|
function toolsPayload(input) {
|
|
@@ -707,11 +1285,54 @@ function toolsPayload(input) {
|
|
|
707
1285
|
custom_tools: input.custom_tools ?? input.customTools,
|
|
708
1286
|
});
|
|
709
1287
|
}
|
|
1288
|
+
function recordingPayload(input) {
|
|
1289
|
+
return compact({
|
|
1290
|
+
enabled: input.enabled,
|
|
1291
|
+
record_audio: input.record_audio ?? input.recordAudio,
|
|
1292
|
+
consent_required: input.consent_required ?? input.consentRequired,
|
|
1293
|
+
announcement: input.announcement,
|
|
1294
|
+
retention_days: input.retention_days ?? input.retentionDays,
|
|
1295
|
+
storage: input.storage,
|
|
1296
|
+
redact_pii: input.redact_pii ?? input.redactPii,
|
|
1297
|
+
metadata: input.metadata,
|
|
1298
|
+
});
|
|
1299
|
+
}
|
|
1300
|
+
function transcriptionPayload(input) {
|
|
1301
|
+
return compact({
|
|
1302
|
+
enabled: input.enabled,
|
|
1303
|
+
provider: input.provider,
|
|
1304
|
+
model: input.model,
|
|
1305
|
+
language: input.language,
|
|
1306
|
+
redact_pii: input.redact_pii ?? input.redactPii,
|
|
1307
|
+
diarization: input.diarization,
|
|
1308
|
+
timestamps: input.timestamps,
|
|
1309
|
+
metadata: input.metadata,
|
|
1310
|
+
});
|
|
1311
|
+
}
|
|
1312
|
+
function artifactsPayload(input) {
|
|
1313
|
+
return compact({
|
|
1314
|
+
recordings: input.recordings,
|
|
1315
|
+
transcripts: input.transcripts,
|
|
1316
|
+
summaries: input.summaries,
|
|
1317
|
+
qa_reports: input.qa_reports ?? input.qaReports,
|
|
1318
|
+
logs: input.logs,
|
|
1319
|
+
webhooks: input.webhooks,
|
|
1320
|
+
retention_days: input.retention_days ?? input.retentionDays,
|
|
1321
|
+
metadata: input.metadata,
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
710
1324
|
function labsPayload(input) {
|
|
711
1325
|
return compact({
|
|
712
1326
|
enabled: input.enabled,
|
|
713
1327
|
voice_watcher: input.voice_watcher ?? input.voiceWatcher,
|
|
1328
|
+
api_key: input.api_key ?? input.apiKey,
|
|
714
1329
|
model: input.model,
|
|
1330
|
+
mode: input.mode,
|
|
1331
|
+
managed_infrastructure: input.managed_infrastructure ?? input.managedInfrastructure,
|
|
1332
|
+
stt: input.stt,
|
|
1333
|
+
llm: input.llm,
|
|
1334
|
+
tts: input.tts,
|
|
1335
|
+
provider_keys: input.provider_keys ?? input.providerKeys,
|
|
715
1336
|
label: input.label,
|
|
716
1337
|
});
|
|
717
1338
|
}
|
|
@@ -740,6 +1361,24 @@ function ultravoxPayload(input) {
|
|
|
740
1361
|
voiceOverrides: input.voiceOverrides ?? input.voice_overrides,
|
|
741
1362
|
retentionPolicy: input.retentionPolicy ?? input.retention_policy,
|
|
742
1363
|
callTemplate: input.callTemplate ?? input.call_template,
|
|
1364
|
+
custom_sip: customSipPayload(input.custom_sip ?? input.customSip ?? input.sip),
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1367
|
+
function customSipPayload(input) {
|
|
1368
|
+
if (!input)
|
|
1369
|
+
return undefined;
|
|
1370
|
+
return compact({
|
|
1371
|
+
sip_trunk_uri: input.sip_trunk_uri ?? input.sipTrunkUri,
|
|
1372
|
+
trunk_uri: input.trunk_uri ?? input.trunkUri,
|
|
1373
|
+
sip_host: input.sip_host ?? input.sipHost,
|
|
1374
|
+
from_number: input.from_number ?? input.fromNumber,
|
|
1375
|
+
username: input.username,
|
|
1376
|
+
password: input.password,
|
|
1377
|
+
transport: input.transport,
|
|
1378
|
+
headers: input.headers,
|
|
1379
|
+
codecs: input.codecs,
|
|
1380
|
+
dtmf_mode: input.dtmf_mode ?? input.dtmfMode,
|
|
1381
|
+
metadata: input.metadata,
|
|
743
1382
|
});
|
|
744
1383
|
}
|
|
745
1384
|
function compact(input) {
|
|
@@ -750,6 +1389,21 @@ function compact(input) {
|
|
|
750
1389
|
}
|
|
751
1390
|
return out;
|
|
752
1391
|
}
|
|
1392
|
+
function parseSseLog(raw) {
|
|
1393
|
+
const data = [];
|
|
1394
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
1395
|
+
if (!line || line.startsWith(":"))
|
|
1396
|
+
continue;
|
|
1397
|
+
if (line.startsWith("data:"))
|
|
1398
|
+
data.push(line.slice(5).trimStart());
|
|
1399
|
+
}
|
|
1400
|
+
if (!data.length)
|
|
1401
|
+
return null;
|
|
1402
|
+
const parsed = safeJson(data.join("\n"));
|
|
1403
|
+
if (!parsed || typeof parsed !== "object")
|
|
1404
|
+
return null;
|
|
1405
|
+
return parsed;
|
|
1406
|
+
}
|
|
753
1407
|
function safeJson(text) {
|
|
754
1408
|
try {
|
|
755
1409
|
return JSON.parse(text);
|