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