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.
@@ -0,0 +1,761 @@
1
+ "use strict";
2
+ /**
3
+ * Supafone Labs — the agent framework behind Supafone.
4
+ *
5
+ * A dependency-free TypeScript client for creating hosted Supafone agents
6
+ * through the Supafone API, including managed phone numbers, voices, stages,
7
+ * tools, recordings, transcripts, widgets, and Supafone Pro watcher. It also
8
+ * includes the Labs cloud sidecar oracle, hosted TTS/STT, live multilingual
9
+ * transcription, telemetry, agent builder, and objective-driven optimizer.
10
+ *
11
+ * Works in Node 18+ (native fetch/WebSocket) and the browser.
12
+ *
13
+ * npm i @supafonesupafone-labs
14
+ *
15
+ * import { Supafone } from "@supafonesupafone-labs";
16
+ * const supafone = new Supafone({ apiKey: process.env.SUPAFONE_API_KEY! });
17
+ * const agent = await supafone.labs.agents.createInboundWithNumber({
18
+ * agentKey: "northline-intake",
19
+ * name: "Northline intake",
20
+ * number: { search: { areaCode: "415" } },
21
+ * });
22
+ */
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.Supafone = exports.SupafoneLabs = exports.SupafoneLabsError = void 0;
25
+ class SupafoneLabsError extends Error {
26
+ status;
27
+ body;
28
+ constructor(message, status, body) {
29
+ super(message);
30
+ this.status = status;
31
+ this.body = body;
32
+ this.name = "SupafoneLabsError";
33
+ }
34
+ }
35
+ exports.SupafoneLabsError = SupafoneLabsError;
36
+ const DEFAULT_BASE = "https://api.labs.supafone.ai";
37
+ const DEFAULT_SUPAFONE_API_BASE = "https://api.supafone.ai";
38
+ const COACH_SYSTEM = "You are the coaching core of a second mind for a live voice agent. Read the " +
39
+ "conversation and return ONE short, silent directive the agent reads but never " +
40
+ "speaks aloud — a correction or nudge, phrased imperatively. If nothing needs " +
41
+ "correcting, return an empty string.";
42
+ class SupafoneLabs {
43
+ baseUrl;
44
+ supafoneApiBaseUrl;
45
+ apiKey;
46
+ supafoneApiKey;
47
+ timeoutMs;
48
+ sessionToken;
49
+ labs;
50
+ builder;
51
+ qa;
52
+ optimizer;
53
+ constructor(opts) {
54
+ if (!opts?.apiKey)
55
+ throw new SupafoneLabsError("apiKey is required");
56
+ this.apiKey = opts.apiKey;
57
+ this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE).replace(/\/$/, "");
58
+ this.supafoneApiKey = opts.supafoneApiKey ?? opts.apiKey;
59
+ this.supafoneApiBaseUrl = (opts.supafoneApiBaseUrl ?? DEFAULT_SUPAFONE_API_BASE).replace(/\/$/, "");
60
+ this.timeoutMs = opts.timeoutMs ?? 30_000;
61
+ this.sessionToken = opts.sessionToken;
62
+ this.labs = new LabsNamespace(this);
63
+ this.builder = new BuilderNamespace(this);
64
+ this.qa = new QANamespace(this);
65
+ this.optimizer = new OptimizerNamespace(this);
66
+ }
67
+ /** True once login() (or a passed sessionToken) is in effect. */
68
+ get isLoggedIn() {
69
+ return !!this.sessionToken;
70
+ }
71
+ /**
72
+ * Exchange email/password for a console session. Required by the builder.*
73
+ * methods and qa.run (which are account/session-scoped, not key-scoped).
74
+ */
75
+ async login(email, password) {
76
+ const d = await this.request("POST", "/v1/auth/login", { email, password });
77
+ if (!d.token)
78
+ throw new SupafoneLabsError("Login succeeded but returned no token");
79
+ this.sessionToken = d.token;
80
+ }
81
+ /** @internal Authenticated JSON request. `useSession` prefers the login token. */
82
+ async request(method, path, body, useSession = false) {
83
+ const token = useSession && this.sessionToken ? this.sessionToken : this.apiKey;
84
+ const ctrl = new AbortController();
85
+ const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
86
+ try {
87
+ const res = await fetch(this.baseUrl + path, {
88
+ method,
89
+ signal: ctrl.signal,
90
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
91
+ body: body === undefined ? undefined : JSON.stringify(body),
92
+ });
93
+ const text = await res.text();
94
+ const parsed = text ? safeJson(text) : {};
95
+ if (!res.ok) {
96
+ const detail = parsed?.detail ?? text ?? `HTTP ${res.status}`;
97
+ throw new SupafoneLabsError(`${method} ${path}: ${detail}`, res.status, parsed);
98
+ }
99
+ return parsed;
100
+ }
101
+ finally {
102
+ clearTimeout(timer);
103
+ }
104
+ }
105
+ /** @internal Authenticated JSON request to the Supafone app API (`/api/v1supafone-labs/*`). */
106
+ async requestSupafoneApi(method, path, body) {
107
+ const ctrl = new AbortController();
108
+ const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
109
+ try {
110
+ const res = await fetch(this.supafoneApiBaseUrl + path, {
111
+ method,
112
+ signal: ctrl.signal,
113
+ headers: { Authorization: `Bearer ${this.supafoneApiKey}`, "Content-Type": "application/json" },
114
+ body: body === undefined ? undefined : JSON.stringify(body),
115
+ });
116
+ const text = await res.text();
117
+ const parsed = text ? safeJson(text) : {};
118
+ if (!res.ok) {
119
+ const detail = parsed?.detail ?? text ?? `HTTP ${res.status}`;
120
+ throw new SupafoneLabsError(`${method} ${path}: ${detail}`, res.status, parsed);
121
+ }
122
+ return parsed;
123
+ }
124
+ finally {
125
+ clearTimeout(timer);
126
+ }
127
+ }
128
+ /** Raw oracle completion — full control over messages and model. */
129
+ async oracle(req) {
130
+ return this.request("POST", "/v1/oracle/complete", {
131
+ messages: req.messages,
132
+ model: req.model ?? "supafone-labs-oracle",
133
+ max_tokens: req.maxTokens ?? 256,
134
+ ...(req.temperature !== undefined ? { temperature: req.temperature } : {}),
135
+ });
136
+ }
137
+ /**
138
+ * The one-liner: hand it the running transcript, get back a silent directive
139
+ * (empty string when the agent is doing fine).
140
+ */
141
+ async whisper(transcript, opts = {}) {
142
+ const system = opts.guardrails ? `${COACH_SYSTEM}\n\nOperator rules:\n${opts.guardrails}` : COACH_SYSTEM;
143
+ const out = await this.oracle({
144
+ model: opts.model,
145
+ maxTokens: opts.maxTokens ?? 120,
146
+ ...(opts.temperature !== undefined ? { temperature: opts.temperature } : {}),
147
+ messages: [
148
+ { role: "system", content: system },
149
+ { role: "user", content: transcript },
150
+ ],
151
+ });
152
+ return out.text.trim();
153
+ }
154
+ /** Hosted TTS — returns raw audio bytes (WAV/PCM per voice). */
155
+ async tts(text, voice = "supafone-labs-calm-en") {
156
+ const ctrl = new AbortController();
157
+ const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
158
+ try {
159
+ const res = await fetch(this.baseUrl + "/v1/tts", {
160
+ method: "POST",
161
+ signal: ctrl.signal,
162
+ headers: { Authorization: `Bearer ${this.apiKey}`, "Content-Type": "application/json" },
163
+ body: JSON.stringify({ voice, text }),
164
+ });
165
+ if (!res.ok)
166
+ throw new SupafoneLabsError(`tts: ${await res.text()}`, res.status);
167
+ return new Uint8Array(await res.arrayBuffer());
168
+ }
169
+ finally {
170
+ clearTimeout(timer);
171
+ }
172
+ }
173
+ /** Hosted STT for a finished audio clip — returns transcript + language tags. */
174
+ async stt(audio, opts = {}) {
175
+ const bytes = audio instanceof ArrayBuffer ? new Uint8Array(audio) : audio;
176
+ const ctrl = new AbortController();
177
+ const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
178
+ try {
179
+ const res = await fetch(this.baseUrl + `/v1/stt?language=${encodeURIComponent(opts.language ?? "multi")}`, {
180
+ method: "POST",
181
+ signal: ctrl.signal,
182
+ headers: {
183
+ Authorization: `Bearer ${this.apiKey}`,
184
+ "Content-Type": opts.mimetype ?? "application/octet-stream",
185
+ },
186
+ body: bytes,
187
+ });
188
+ if (!res.ok)
189
+ throw new SupafoneLabsError(`stt: ${await res.text()}`, res.status);
190
+ const d = (await res.json());
191
+ // Flattened shape first; fall back to raw Deepgram nesting for older gateways.
192
+ const transcript = d.transcript ?? d.results?.channels?.[0]?.alternatives?.[0]?.transcript ?? "";
193
+ return { transcript, languages: d.languages ?? [], duration: d.duration ?? 0, raw: d };
194
+ }
195
+ finally {
196
+ clearTimeout(timer);
197
+ }
198
+ }
199
+ /**
200
+ * Open a live multilingual transcription socket. Feed PCM frames with
201
+ * `feed()`; language-tagged results arrive via `onResult`. Uses the global
202
+ * WebSocket (browser, Node 22+); pass one in `opts.WebSocketImpl` on older Node.
203
+ */
204
+ liveTranscribe(opts = {}) {
205
+ return new LiveTranscription(this, opts);
206
+ }
207
+ /** Remaining prepaid balance. */
208
+ balance() {
209
+ return this.request("GET", "/v1/billing/balance");
210
+ }
211
+ /** Today's usage against your plan caps (oracle/tts/stt/…). */
212
+ usage() {
213
+ return this.request("GET", "/v1/usage");
214
+ }
215
+ /** The auditable whisper/billing log. */
216
+ logs(limit = 100) {
217
+ return this.request("GET", `/v1/logs?limit=${limit}`);
218
+ }
219
+ /** The structured whisper feed (what the console shows). */
220
+ nudges(limit = 50) {
221
+ return this.request("GET", `/v1/nudges?limit=${limit}`);
222
+ }
223
+ /** Aggregated metrics — injection rate, latency, by-dimension breakdowns. */
224
+ metrics(days = 7) {
225
+ return this.request("GET", `/v1/metrics?days=${days}`);
226
+ }
227
+ /** Log one whisper event (zero-billed) for the console feed + metrics. */
228
+ reportNudge(event) {
229
+ return this.request("POST", "/v1/events/nudge", event);
230
+ }
231
+ /** File a post-call report — the fuel optimizer.improve() learns from. */
232
+ reportCall(report) {
233
+ return this.request("POST", "/v1/events/call_report", report);
234
+ }
235
+ /** Available oracle model ids (live vendor catalog). */
236
+ async models() {
237
+ const d = await this.request("GET", "/v1/models");
238
+ return d.models.map((m) => (typeof m === "string" ? m : m.id));
239
+ }
240
+ /** Available TTS voice ids. */
241
+ async voices() {
242
+ const d = await this.request("GET", "/v1/voices");
243
+ return d.voices.map((v) => (typeof v === "string" ? v : (v.voice ?? v.id ?? ""))).filter(Boolean);
244
+ }
245
+ }
246
+ exports.SupafoneLabs = SupafoneLabs;
247
+ exports.Supafone = SupafoneLabs;
248
+ class LiveTranscription {
249
+ ws;
250
+ constructor(sm, opts) {
251
+ const WS = opts.WebSocketImpl ?? globalThis.WebSocket;
252
+ if (!WS)
253
+ throw new SupafoneLabsError("No WebSocket available — pass opts.WebSocketImpl (e.g. the `ws` package)");
254
+ const base = sm.baseUrl.replace(/^http/, "ws");
255
+ const q = new URLSearchParams({
256
+ // The key rides in the query string because browsers can't set WS headers.
257
+ api_key: sm.apiKey,
258
+ language: opts.language ?? "multi",
259
+ encoding: opts.encoding ?? "linear16",
260
+ sample_rate: String(opts.sampleRate ?? 16000),
261
+ });
262
+ this.ws = new WS(`${base}/v1/stt/live?${q}`);
263
+ this.ws.addEventListener?.("message", (ev) => {
264
+ const d = safeJson(typeof ev.data === "string" ? ev.data : String(ev.data));
265
+ const alt = d.channel?.alternatives?.[0];
266
+ if (alt?.transcript && opts.onResult) {
267
+ opts.onResult({ transcript: alt.transcript, languages: alt.languages ?? [], isFinal: !!d.is_final });
268
+ }
269
+ });
270
+ if (opts.onError)
271
+ this.ws.addEventListener?.("error", opts.onError);
272
+ if (opts.onClose)
273
+ this.ws.addEventListener?.("close", opts.onClose);
274
+ }
275
+ /** Send one PCM audio frame. */
276
+ feed(frame) {
277
+ this.ws.send(frame);
278
+ }
279
+ /** Signal end-of-stream and close. */
280
+ close() {
281
+ try {
282
+ this.ws.send(JSON.stringify({ type: "CloseStream" }));
283
+ }
284
+ catch {
285
+ /* already closing */
286
+ }
287
+ this.ws.close();
288
+ }
289
+ get socket() {
290
+ return this.ws;
291
+ }
292
+ }
293
+ /** Programmatic hosted Supafone agents, inside the Supafone API. */
294
+ class LabsNamespace {
295
+ sm;
296
+ agents;
297
+ presets;
298
+ tools;
299
+ voices;
300
+ phoneNumbers;
301
+ telephony;
302
+ constructor(sm) {
303
+ this.sm = sm;
304
+ this.agents = new LabsAgentsNamespace(sm);
305
+ this.presets = new LabsPresetsNamespace(sm);
306
+ this.tools = new LabsToolsNamespace(sm);
307
+ this.voices = new LabsVoicesNamespace(sm);
308
+ this.phoneNumbers = new LabsPhoneNumbersNamespace(sm);
309
+ this.telephony = new LabsTelephonyNamespace(sm);
310
+ }
311
+ /** Discover the Supafone convenience layer over Ultravox. */
312
+ capabilities() {
313
+ return this.sm.requestSupafoneApi("GET", "/api/v1supafone-labs/capabilities");
314
+ }
315
+ }
316
+ class LabsAgentsNamespace {
317
+ sm;
318
+ constructor(sm) {
319
+ this.sm = sm;
320
+ }
321
+ /** Spawn a durable hosted Supafone agent backed by Ultravox and Supafone-managed providers. */
322
+ create(input) {
323
+ return this.sm.requestSupafoneApi("POST", "/api/v1supafone-labs/agents", labsAgentPayload(input));
324
+ }
325
+ /** Create an inbound receptionist/intake agent. No Twilio account is required. */
326
+ createInbound(input) {
327
+ return this.create({
328
+ ...input,
329
+ style: "inbound",
330
+ direction: "inbound",
331
+ agentType: input.agentType ?? input.agent_type ?? "phone",
332
+ presetKey: input.presetKey ?? input.preset_key ?? "general_intake_receptionist",
333
+ telephony: input.telephony ?? { mode: "supafone_managed", provider: "supafone" },
334
+ });
335
+ }
336
+ /** Create an outbound sales/speed-to-lead/campaign agent. No Twilio account is required. */
337
+ createOutbound(input) {
338
+ return this.create({
339
+ ...input,
340
+ style: "outbound",
341
+ direction: "outbound",
342
+ agentType: input.agentType ?? input.agent_type ?? "campaign",
343
+ presetKey: input.presetKey ?? input.preset_key ?? "speed_to_lead_caller",
344
+ telephony: input.telephony ?? { mode: "supafone_managed", provider: "supafone" },
345
+ });
346
+ }
347
+ /**
348
+ * Create an inbound agent, buy a Supafone-managed phone number, and assign it.
349
+ * This is the zero-Twilio-account happy path.
350
+ */
351
+ async createInboundWithNumber(input) {
352
+ const agent = await this.createInbound(input);
353
+ const agentKey = String(agent.agent?.agent_key ?? input.agentKey ?? input.agent_key ?? "");
354
+ const number = await new LabsPhoneNumbersNamespace(this.sm).buyAndAssign({
355
+ ...(input.number ?? {}),
356
+ agentKey,
357
+ agentName: input.assistantName ?? input.assistant_name ?? input.name,
358
+ friendlyName: input.number?.friendlyName ?? input.number?.friendly_name ?? input.name,
359
+ style: "inbound",
360
+ presetKey: input.presetKey ?? input.preset_key ?? "general_intake_receptionist",
361
+ telephony: input.number?.telephony ?? { mode: "supafone_managed", provider: "supafone" },
362
+ });
363
+ return { ...agent, number };
364
+ }
365
+ /**
366
+ * Create an outbound agent, buy a Supafone-managed caller ID, and assign it.
367
+ * For sales teams this is the easiest path: Supafone owns telephony setup.
368
+ */
369
+ async createOutboundWithNumber(input) {
370
+ const agent = await this.createOutbound(input);
371
+ const agentKey = String(agent.agent?.agent_key ?? input.agentKey ?? input.agent_key ?? "");
372
+ const number = await new LabsPhoneNumbersNamespace(this.sm).buyAndAssign({
373
+ ...(input.number ?? {}),
374
+ agentKey,
375
+ agentName: input.assistantName ?? input.assistant_name ?? input.name,
376
+ friendlyName: input.number?.friendlyName ?? input.number?.friendly_name ?? input.name,
377
+ style: "outbound",
378
+ presetKey: input.presetKey ?? input.preset_key ?? "speed_to_lead_caller",
379
+ telephony: input.number?.telephony ?? { mode: "supafone_managed", provider: "supafone" },
380
+ });
381
+ return { ...agent, number };
382
+ }
383
+ /** List durable agents created in the Supafone account tied to this API key. */
384
+ list(opts = {}) {
385
+ const q = new URLSearchParams();
386
+ if (opts.agencyId)
387
+ q.set("agency_id", opts.agencyId);
388
+ if (opts.agentType)
389
+ q.set("agent_type", opts.agentType);
390
+ if (opts.style)
391
+ q.set("style", opts.style);
392
+ const suffix = q.toString() ? `?${q}` : "";
393
+ return this.sm.requestSupafoneApi("GET", `/api/v1supafone-labs/agents${suffix}`);
394
+ }
395
+ /** Fetch one durable agent by key. */
396
+ get(agentKey, opts = {}) {
397
+ const q = new URLSearchParams();
398
+ if (opts.agencyId)
399
+ q.set("agency_id", opts.agencyId);
400
+ if (opts.agentType)
401
+ q.set("agent_type", opts.agentType);
402
+ const suffix = q.toString() ? `?${q}` : "";
403
+ return this.sm.requestSupafoneApi("GET", `/api/v1supafone-labs/agents/${encodeURIComponent(agentKey)}${suffix}`);
404
+ }
405
+ }
406
+ class LabsPresetsNamespace {
407
+ sm;
408
+ constructor(sm) {
409
+ this.sm = sm;
410
+ }
411
+ /** Out-of-the-box multistage agent presets. */
412
+ list() {
413
+ return this.sm.requestSupafoneApi("GET", "/api/v1supafone-labs/presets");
414
+ }
415
+ }
416
+ class LabsToolsNamespace {
417
+ sm;
418
+ constructor(sm) {
419
+ this.sm = sm;
420
+ }
421
+ /** Built-in tools Supafone agents can use. */
422
+ list() {
423
+ return this.sm.requestSupafoneApi("GET", "/api/v1supafone-labs/tools");
424
+ }
425
+ }
426
+ class LabsVoicesNamespace {
427
+ sm;
428
+ constructor(sm) {
429
+ this.sm = sm;
430
+ }
431
+ /** Supafone-managed Ultravox, Cartesia, Inworld, and ElevenLabs-compatible voices. */
432
+ list(opts = {}) {
433
+ const q = new URLSearchParams();
434
+ if (opts.provider)
435
+ q.set("provider", opts.provider);
436
+ const suffix = q.toString() ? `?${q}` : "";
437
+ return this.sm.requestSupafoneApi("GET", `/api/v1supafone-labs/voices${suffix}`);
438
+ }
439
+ }
440
+ class LabsPhoneNumbersNamespace {
441
+ sm;
442
+ constructor(sm) {
443
+ this.sm = sm;
444
+ }
445
+ /** List numbers already owned by this Supafone account. */
446
+ list(opts = {}) {
447
+ const q = new URLSearchParams();
448
+ if (opts.agencyId)
449
+ q.set("agency_id", opts.agencyId);
450
+ if (opts.activeOnly !== undefined)
451
+ q.set("active_only", String(opts.activeOnly));
452
+ const suffix = q.toString() ? `?${q}` : "";
453
+ return this.sm.requestSupafoneApi("GET", `/api/v1supafone-labs/phone-numbers${suffix}`);
454
+ }
455
+ /** Search Supafone-managed inventory. This uses Supafone's master telephony account. */
456
+ search(opts = {}) {
457
+ return this.sm.requestSupafoneApi("POST", "/api/v1supafone-labs/phone-numbers/search", phoneNumberSearchPayload(opts));
458
+ }
459
+ /** Buy a Supafone-managed number. Developers do not need a Twilio account. */
460
+ buy(input) {
461
+ return this.sm.requestSupafoneApi("POST", "/api/v1supafone-labs/phone-numbers", phoneNumberProvisionPayload({
462
+ ...input,
463
+ telephony: input.telephony ?? { mode: "supafone_managed", provider: "supafone" },
464
+ }));
465
+ }
466
+ /** Attach an existing Supafone number to an inbound or outbound agent. */
467
+ assign(numberId, input = {}) {
468
+ return this.sm.requestSupafoneApi("POST", `/api/v1supafone-labs/phone-numbers/${encodeURIComponent(numberId)}/assign`, phoneNumberAssignPayload(input));
469
+ }
470
+ /**
471
+ * Search if needed, buy the first matching Supafone-managed number, and assign
472
+ * it to the supplied agent. This is the zero-Twilio-account happy path.
473
+ */
474
+ async buyAndAssign(input) {
475
+ let phoneNumber = input.phoneNumber ?? input.phone_number ?? "";
476
+ if (!phoneNumber) {
477
+ const found = await this.search({
478
+ ...(input.search ?? {}),
479
+ agencyId: input.agencyId ?? input.agency_id ?? input.search?.agencyId,
480
+ limit: input.search?.limit ?? 1,
481
+ });
482
+ phoneNumber = found.numbers[0]?.phone_number ?? "";
483
+ if (!phoneNumber) {
484
+ throw new SupafoneLabsError("No Supafone-managed phone numbers matched the search");
485
+ }
486
+ }
487
+ return this.buy({
488
+ ...input,
489
+ phoneNumber,
490
+ telephony: input.telephony ?? { mode: "supafone_managed", provider: "supafone" },
491
+ });
492
+ }
493
+ }
494
+ class LabsTelephonyNamespace {
495
+ sm;
496
+ constructor(sm) {
497
+ this.sm = sm;
498
+ }
499
+ /** Read the account telephony contract. Defaults to Supafone-managed. */
500
+ get(opts = {}) {
501
+ const q = new URLSearchParams();
502
+ if (opts.agencyId)
503
+ q.set("agency_id", opts.agencyId);
504
+ const suffix = q.toString() ? `?${q}` : "";
505
+ return this.sm.requestSupafoneApi("GET", `/api/v1supafone-labs/telephony${suffix}`);
506
+ }
507
+ /** Configure advanced BYOK telephony, or reset back to Supafone-managed. */
508
+ configure(input) {
509
+ return this.sm.requestSupafoneApi("PUT", "/api/v1supafone-labs/telephony", telephonyPayload(input));
510
+ }
511
+ /** Reset to the seamless default where Supafone buys and routes numbers. */
512
+ useSupafoneManaged(agencyId) {
513
+ return this.configure({ agencyId, mode: "supafone_managed", provider: "supafone" });
514
+ }
515
+ }
516
+ /**
517
+ * The agent builder. These methods are account/session-scoped — call
518
+ * `login(email, password)` first, or the gateway returns 401 "Log in first".
519
+ */
520
+ class BuilderNamespace {
521
+ sm;
522
+ constructor(sm) {
523
+ this.sm = sm;
524
+ }
525
+ /** One supervised builder turn: whisper + guided agent reply. */
526
+ chat(sessionId, messages) {
527
+ return this.sm.request("POST", "/v1/builder/chat", { session_id: sessionId, messages }, true);
528
+ }
529
+ /** End a test call: grades it and files a report for the optimizer. */
530
+ finish(sessionId, messages) {
531
+ return this.sm.request("POST", "/v1/builder/finish", { session_id: sessionId, messages }, true);
532
+ }
533
+ config() {
534
+ return this.sm.request("GET", "/v1/builder/config", undefined, true);
535
+ }
536
+ saveConfig(config) {
537
+ return this.sm.request("POST", "/v1/builder/config", config, true);
538
+ }
539
+ }
540
+ class QANamespace {
541
+ sm;
542
+ constructor(sm) {
543
+ this.sm = sm;
544
+ }
545
+ /**
546
+ * Run the adversarial QA suite, A/B (supervised vs unsupervised).
547
+ * Session-scoped — call login() first.
548
+ */
549
+ run(opts = {}) {
550
+ return this.sm.request("POST", "/v1/qa/run", { scenarios: opts.scenarios ?? [], turns: opts.turns ?? 2 }, true);
551
+ }
552
+ /** Past QA runs (works with the API key). */
553
+ history(agent = "builder", limit = 40) {
554
+ return this.sm.request("GET", `/v1/qa/runs?agent=${encodeURIComponent(agent)}&limit=${limit}`);
555
+ }
556
+ }
557
+ class OptimizerNamespace {
558
+ sm;
559
+ constructor(sm) {
560
+ this.sm = sm;
561
+ }
562
+ /** Improve the standing directive from accumulated call reports (OPRO-style). */
563
+ improve(agent = "builder") {
564
+ return this.sm.request("POST", "/v1/optimizer/improve", { agent });
565
+ }
566
+ /** Fetch the current standing directive. */
567
+ standing(agent = "builder") {
568
+ return this.sm.request("GET", `/v1/optimizer/standing?agent=${encodeURIComponent(agent)}`);
569
+ }
570
+ /** List the post-call reports behind the optimizer. */
571
+ reports(agent = "builder", limit = 40) {
572
+ return this.sm.request("GET", `/v1/optimizer/reports?agent=${encodeURIComponent(agent)}&limit=${limit}`);
573
+ }
574
+ }
575
+ function labsAgentPayload(input) {
576
+ return compact({
577
+ agency_id: input.agency_id ?? input.agencyId,
578
+ agent_key: input.agent_key ?? input.agentKey,
579
+ agent_type: input.agent_type ?? input.agentType,
580
+ style: input.agent_style ?? input.agentStyle ?? input.style,
581
+ name: input.name,
582
+ assistant_name: input.assistant_name ?? input.assistantName,
583
+ business_name: input.business_name ?? input.businessName,
584
+ industry: input.industry,
585
+ website_url: input.website_url ?? input.websiteUrl,
586
+ phone_number: input.phone_number ?? input.phoneNumber,
587
+ direction: input.direction,
588
+ preset_key: input.preset_key ?? input.presetKey,
589
+ runtime_mode: input.runtime_mode ?? input.runtimeMode,
590
+ goal: input.goal,
591
+ greeting: input.greeting,
592
+ system_prompt: input.system_prompt ?? input.systemPrompt,
593
+ language: input.language,
594
+ voice: input.voice ? voicePayload(input.voice) : undefined,
595
+ provider_keys: input.provider_keys ?? (input.providerKeys ? providerKeysPayload(input.providerKeys) : undefined),
596
+ byok: input.byok ? providerKeysPayload(input.byok) : undefined,
597
+ telephony: input.telephony ? telephonyPayload(input.telephony) : undefined,
598
+ tools: input.tools ? toolsPayload(input.tools) : undefined,
599
+ labs: input.labs ? labsPayload(input.labs) : undefined,
600
+ ultravox: input.ultravox ? ultravoxPayload(input.ultravox) : undefined,
601
+ voice_watcher: input.voice_watcher ?? input.voiceWatcher,
602
+ voice_watcher_model: input.voice_watcher_model ?? input.voiceWatcherModel,
603
+ metadata: input.metadata,
604
+ });
605
+ }
606
+ function telephonyPayload(input) {
607
+ return compact({
608
+ agency_id: input.agency_id ?? input.agencyId,
609
+ mode: input.mode,
610
+ provider: input.provider,
611
+ label: input.label,
612
+ credentials: input.credentials ? telephonyCredentialsPayload(input.credentials) : undefined,
613
+ metadata: input.metadata,
614
+ });
615
+ }
616
+ function telephonyCredentialsPayload(input) {
617
+ return compact({
618
+ account_sid: input.account_sid ?? input.accountSid,
619
+ auth_token: input.auth_token ?? input.authToken,
620
+ api_key: input.api_key ?? input.apiKey,
621
+ api_secret: input.api_secret ?? input.apiSecret,
622
+ auth_id: input.auth_id ?? input.authId,
623
+ connection_id: input.connection_id ?? input.connectionId,
624
+ from_number: input.from_number ?? input.fromNumber,
625
+ sip_trunk_uri: input.sip_trunk_uri ?? input.sipTrunkUri,
626
+ sip_host: input.sip_host ?? input.sipHost,
627
+ username: input.username,
628
+ password: input.password,
629
+ webhook_secret: input.webhook_secret ?? input.webhookSecret,
630
+ });
631
+ }
632
+ function phoneNumberSearchPayload(input) {
633
+ return compact({
634
+ agency_id: input.agency_id ?? input.agencyId,
635
+ country_code: input.country_code ?? input.countryCode,
636
+ area_code: input.area_code ?? input.areaCode,
637
+ postal_code: input.postal_code ?? input.postalCode,
638
+ zip_code: input.zip_code ?? input.zipCode,
639
+ contains: input.contains,
640
+ number_type: input.number_type ?? input.numberType,
641
+ limit: input.limit,
642
+ capabilities: input.capabilities,
643
+ });
644
+ }
645
+ function phoneNumberProvisionPayload(input) {
646
+ return compact({
647
+ agency_id: input.agency_id ?? input.agencyId,
648
+ phone_number: input.phone_number ?? input.phoneNumber,
649
+ friendly_name: input.friendly_name ?? input.friendlyName,
650
+ department_id: input.department_id ?? input.departmentId,
651
+ agent_key: input.agent_key ?? input.agentKey,
652
+ agent_id: input.agent_id ?? input.agentId,
653
+ agent_name: input.agent_name ?? input.agentName,
654
+ preset_key: input.preset_key ?? input.presetKey,
655
+ style: input.agent_style ?? input.agentStyle ?? input.style,
656
+ direction: input.direction,
657
+ telephony: input.telephony ? telephonyPayload(input.telephony) : undefined,
658
+ metadata: input.metadata,
659
+ });
660
+ }
661
+ function phoneNumberAssignPayload(input) {
662
+ return compact({
663
+ agency_id: input.agency_id ?? input.agencyId,
664
+ agent_key: input.agent_key ?? input.agentKey,
665
+ agent_id: input.agent_id ?? input.agentId,
666
+ agent_name: input.agent_name ?? input.agentName,
667
+ friendly_name: input.friendly_name ?? input.friendlyName,
668
+ preset_key: input.preset_key ?? input.presetKey,
669
+ style: input.agent_style ?? input.agentStyle ?? input.style,
670
+ direction: input.direction,
671
+ telephony: input.telephony ? telephonyPayload(input.telephony) : undefined,
672
+ metadata: input.metadata,
673
+ });
674
+ }
675
+ function voicePayload(input) {
676
+ return compact({
677
+ provider: input.provider,
678
+ voice_id: input.voice_id ?? input.voiceId,
679
+ model: input.model,
680
+ });
681
+ }
682
+ function providerKeysPayload(input) {
683
+ return compact({
684
+ ultravox: input.ultravox,
685
+ ultravox_api_key: input.ultravox_api_key ?? input.ultravoxApiKey,
686
+ elevenlabs: input.elevenlabs,
687
+ elevenlabs_api_key: input.elevenlabs_api_key ?? input.elevenlabsApiKey,
688
+ cartesia: input.cartesia,
689
+ cartesia_api_key: input.cartesia_api_key ?? input.cartesiaApiKey,
690
+ inworld: input.inworld,
691
+ inworld_api_key: input.inworld_api_key ?? input.inworldApiKey,
692
+ deepgram: input.deepgram,
693
+ deepgram_api_key: input.deepgram_api_key ?? input.deepgramApiKey,
694
+ });
695
+ }
696
+ function toolsPayload(input) {
697
+ return compact({
698
+ call_routing: input.call_routing ?? input.callRouting,
699
+ scheduling: input.scheduling,
700
+ sms: input.sms,
701
+ email: input.email,
702
+ intake_forms: input.intake_forms ?? input.intakeForms,
703
+ firm_knowledge: input.firm_knowledge ?? input.firmKnowledge,
704
+ existing_client_lookup: input.existing_client_lookup ?? input.existingClientLookup,
705
+ voicemail: input.voicemail,
706
+ emergency_escalation: input.emergency_escalation ?? input.emergencyEscalation,
707
+ custom_tools: input.custom_tools ?? input.customTools,
708
+ });
709
+ }
710
+ function labsPayload(input) {
711
+ return compact({
712
+ enabled: input.enabled,
713
+ voice_watcher: input.voice_watcher ?? input.voiceWatcher,
714
+ model: input.model,
715
+ label: input.label,
716
+ });
717
+ }
718
+ function ultravoxPayload(input) {
719
+ return compact({
720
+ model: input.model,
721
+ temperature: input.temperature,
722
+ medium: input.medium,
723
+ vadSettings: input.vadSettings ?? input.vad_settings,
724
+ speaker_first: input.speaker_first ?? input.speakerFirst,
725
+ firstSpeaker: input.firstSpeaker ?? input.first_speaker,
726
+ firstSpeakerSettings: input.firstSpeakerSettings ?? input.first_speaker_settings,
727
+ selectedTools: input.selectedTools ?? input.selected_tools,
728
+ initialMessages: input.initialMessages ?? input.initial_messages,
729
+ initialState: input.initialState ?? input.initial_state,
730
+ initialOutputMedium: input.initialOutputMedium ?? input.initial_output_medium,
731
+ joinTimeout: input.joinTimeout ?? input.join_timeout,
732
+ maxDuration: input.maxDuration ?? input.max_duration,
733
+ max_duration_seconds: input.max_duration_seconds ?? input.maxDurationSeconds,
734
+ timeExceededMessage: input.timeExceededMessage ?? input.time_exceeded_message,
735
+ inactivityMessages: input.inactivityMessages ?? input.inactivity_messages,
736
+ dataConnection: input.dataConnection ?? input.data_connection,
737
+ callbacks: input.callbacks,
738
+ metadata: input.metadata,
739
+ experimentalSettings: input.experimentalSettings ?? input.experimental_settings,
740
+ voiceOverrides: input.voiceOverrides ?? input.voice_overrides,
741
+ retentionPolicy: input.retentionPolicy ?? input.retention_policy,
742
+ callTemplate: input.callTemplate ?? input.call_template,
743
+ });
744
+ }
745
+ function compact(input) {
746
+ const out = {};
747
+ for (const [key, value] of Object.entries(input)) {
748
+ if (value !== undefined)
749
+ out[key] = value;
750
+ }
751
+ return out;
752
+ }
753
+ function safeJson(text) {
754
+ try {
755
+ return JSON.parse(text);
756
+ }
757
+ catch {
758
+ return { detail: text };
759
+ }
760
+ }
761
+ exports.default = SupafoneLabs;