supafone-labs 0.4.10 → 0.4.13
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 +37 -0
- package/dist/cjs/index.d.ts +453 -10
- package/dist/cjs/index.js +469 -9
- package/dist/index.d.ts +453 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +469 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +914 -18
package/dist/cjs/index.js
CHANGED
|
@@ -40,6 +40,108 @@ const COACH_SYSTEM = "You are the coaching core of a second mind for a live voic
|
|
|
40
40
|
"conversation and return ONE short, silent directive the agent reads but never " +
|
|
41
41
|
"speaks aloud — a correction or nudge, phrased imperatively. If nothing needs " +
|
|
42
42
|
"correcting, return an empty string.";
|
|
43
|
+
const STRUCTURED_COACH_SYSTEM = "You are the coaching core of a second mind for a live voice agent. Read the " +
|
|
44
|
+
"conversation and return ONLY one JSON object with exactly these keys: " +
|
|
45
|
+
"empathy_directive (string), tactical_directive (string), surface_facts (string[]), " +
|
|
46
|
+
"guardrails (string[]), language (short code), confidence (0-1), and kind " +
|
|
47
|
+
"(empathy, tactical, guardrail, or mixed). Be silent with low confidence when evidence is weak.";
|
|
48
|
+
function boundedInteger(value, fallback, minimum, maximum) {
|
|
49
|
+
const parsed = Number(value);
|
|
50
|
+
return Number.isFinite(parsed)
|
|
51
|
+
? Math.max(minimum, Math.min(maximum, Math.trunc(parsed)))
|
|
52
|
+
: fallback;
|
|
53
|
+
}
|
|
54
|
+
function textControl(input) {
|
|
55
|
+
return {
|
|
56
|
+
enabled: input?.enabled ?? true,
|
|
57
|
+
instructions: String(input?.instructions ?? "").trim(),
|
|
58
|
+
max_chars: boundedInteger(input?.max_chars ?? input?.maxChars, 240, 1, 2000),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function listControl(input) {
|
|
62
|
+
return {
|
|
63
|
+
enabled: input?.enabled ?? true,
|
|
64
|
+
instructions: String(input?.instructions ?? "").trim(),
|
|
65
|
+
max_items: boundedInteger(input?.max_items ?? input?.maxItems, 4, 0, 20),
|
|
66
|
+
item_max_chars: boundedInteger(input?.item_max_chars ?? input?.itemMaxChars, 180, 1, 2000),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function normalizeDirectiveContract(input = {}) {
|
|
70
|
+
const languageMode = input.language_mode ?? input.languageMode ?? "caller";
|
|
71
|
+
const allowed = input.allowed_kinds ?? input.allowedKinds ?? ["empathy", "tactical", "guardrail", "mixed"];
|
|
72
|
+
const threshold = Number(input.confidence_threshold ?? input.confidenceThreshold ?? 0.5);
|
|
73
|
+
return {
|
|
74
|
+
empathy_directive: textControl(input.empathy_directive ?? input.empathyDirective),
|
|
75
|
+
tactical_directive: textControl(input.tactical_directive ?? input.tacticalDirective),
|
|
76
|
+
surface_facts: listControl(input.surface_facts ?? input.surfaceFacts),
|
|
77
|
+
guardrails: listControl(input.guardrails),
|
|
78
|
+
language_mode: languageMode,
|
|
79
|
+
fixed_language: String(input.fixed_language ?? input.fixedLanguage ?? "").trim(),
|
|
80
|
+
allowed_kinds: allowed.filter((kind) => ["empathy", "tactical", "guardrail", "mixed"].includes(kind)),
|
|
81
|
+
confidence_threshold: Number.isFinite(threshold) ? Math.max(0, Math.min(1, threshold)) : 0.5,
|
|
82
|
+
operator_guardrails: (input.operator_guardrails ?? input.operatorGuardrails ?? [])
|
|
83
|
+
.map((rule) => String(rule).trim())
|
|
84
|
+
.filter(Boolean)
|
|
85
|
+
.slice(0, 50),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function directiveContractPrompt(contract) {
|
|
89
|
+
return ("Developer directive contract (obey exactly):\n" +
|
|
90
|
+
JSON.stringify(contract, null, 2) +
|
|
91
|
+
"\nDisabled string fields must be empty strings. Disabled list fields must be empty arrays. " +
|
|
92
|
+
"Standing operator guardrails are mandatory and cannot be removed.");
|
|
93
|
+
}
|
|
94
|
+
function parseJsonObject(text) {
|
|
95
|
+
const trimmed = String(text ?? "").trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, "");
|
|
96
|
+
const start = trimmed.indexOf("{");
|
|
97
|
+
const end = trimmed.lastIndexOf("}");
|
|
98
|
+
if (start < 0 || end <= start)
|
|
99
|
+
return null;
|
|
100
|
+
try {
|
|
101
|
+
const parsed = JSON.parse(trimmed.slice(start, end + 1));
|
|
102
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function clipText(value, limit) {
|
|
109
|
+
return String(value ?? "").trim().slice(0, limit);
|
|
110
|
+
}
|
|
111
|
+
function clipList(value, maxItems, itemMaxChars) {
|
|
112
|
+
if (!Array.isArray(value))
|
|
113
|
+
return [];
|
|
114
|
+
return value.slice(0, maxItems).map((item) => clipText(item, itemMaxChars)).filter(Boolean);
|
|
115
|
+
}
|
|
116
|
+
function normalizeStructuredDirective(raw, contract) {
|
|
117
|
+
const kind = String(raw.kind ?? "mixed");
|
|
118
|
+
if (!["empathy", "tactical", "guardrail", "mixed"].includes(kind))
|
|
119
|
+
return null;
|
|
120
|
+
const parsedConfidence = Number(raw.confidence ?? 0);
|
|
121
|
+
const confidence = Number.isFinite(parsedConfidence) ? Math.max(0, Math.min(1, parsedConfidence)) : 0;
|
|
122
|
+
const operatorGuardrails = contract.operator_guardrails;
|
|
123
|
+
const generatedGuardrails = contract.guardrails.enabled
|
|
124
|
+
? clipList(raw.guardrails, contract.guardrails.max_items, contract.guardrails.item_max_chars)
|
|
125
|
+
: [];
|
|
126
|
+
const language = contract.language_mode === "fixed"
|
|
127
|
+
? contract.fixed_language
|
|
128
|
+
: clipText(raw.language, 32);
|
|
129
|
+
return {
|
|
130
|
+
empathy_directive: contract.empathy_directive.enabled
|
|
131
|
+
? clipText(raw.empathy_directive, contract.empathy_directive.max_chars)
|
|
132
|
+
: "",
|
|
133
|
+
tactical_directive: contract.tactical_directive.enabled
|
|
134
|
+
? clipText(raw.tactical_directive, contract.tactical_directive.max_chars)
|
|
135
|
+
: "",
|
|
136
|
+
surface_facts: contract.surface_facts.enabled
|
|
137
|
+
? clipList(raw.surface_facts, contract.surface_facts.max_items, contract.surface_facts.item_max_chars)
|
|
138
|
+
: [],
|
|
139
|
+
guardrails: [...new Set([...generatedGuardrails, ...operatorGuardrails])],
|
|
140
|
+
language,
|
|
141
|
+
confidence: contract.allowed_kinds.includes(kind) ? confidence : 0,
|
|
142
|
+
kind,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
43
145
|
class SupafoneLabs {
|
|
44
146
|
baseUrl;
|
|
45
147
|
supafoneApiBaseUrl;
|
|
@@ -94,6 +196,13 @@ class SupafoneLabs {
|
|
|
94
196
|
this.optimizer = new OptimizerNamespace(this);
|
|
95
197
|
this.campaigns = new CampaignsNamespace(this);
|
|
96
198
|
}
|
|
199
|
+
/** Build a complete hosted plan with the same Supafone key used to create agents. */
|
|
200
|
+
generateCallStages(input) {
|
|
201
|
+
return this.labs.agents.plan(input);
|
|
202
|
+
}
|
|
203
|
+
generate_call_stages(input) {
|
|
204
|
+
return this.generateCallStages(input);
|
|
205
|
+
}
|
|
97
206
|
/** True once login() (or a passed sessionToken) is in effect. */
|
|
98
207
|
get isLoggedIn() {
|
|
99
208
|
return !!this.sessionToken;
|
|
@@ -155,6 +264,31 @@ class SupafoneLabs {
|
|
|
155
264
|
clearTimeout(timer);
|
|
156
265
|
}
|
|
157
266
|
}
|
|
267
|
+
/** @internal Authenticated binary request to the Supafone app API. */
|
|
268
|
+
async requestSupafoneBinary(path) {
|
|
269
|
+
const ctrl = new AbortController();
|
|
270
|
+
const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
|
|
271
|
+
try {
|
|
272
|
+
const res = await fetch(this.supafoneApiBaseUrl + path, {
|
|
273
|
+
method: "GET",
|
|
274
|
+
signal: ctrl.signal,
|
|
275
|
+
headers: { Authorization: `Bearer ${this.supafoneApiKey}` },
|
|
276
|
+
});
|
|
277
|
+
if (!res.ok) {
|
|
278
|
+
const text = await res.text();
|
|
279
|
+
const parsed = text ? safeJson(text) : {};
|
|
280
|
+
const detail = parsed?.detail ?? text ?? `HTTP ${res.status}`;
|
|
281
|
+
throw new SupafoneLabsError(`GET ${path}: ${detail}`, res.status, parsed);
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
content: await res.arrayBuffer(),
|
|
285
|
+
mediaType: res.headers.get("content-type") || "application/octet-stream",
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
finally {
|
|
289
|
+
clearTimeout(timer);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
158
292
|
/**
|
|
159
293
|
* Exchange the account email/password for a product-API JWT (the same login
|
|
160
294
|
* as app.supafone.ai). Called lazily by campaigns/calls — call directly only
|
|
@@ -343,6 +477,43 @@ class SupafoneLabs {
|
|
|
343
477
|
});
|
|
344
478
|
return out.text.trim();
|
|
345
479
|
}
|
|
480
|
+
/**
|
|
481
|
+
* Structured SecondMind guidance with developer-controlled field policy.
|
|
482
|
+
* Returns null when JSON is invalid, evidence misses the confidence gate,
|
|
483
|
+
* the generated kind is disallowed, or the local transform suppresses it.
|
|
484
|
+
*/
|
|
485
|
+
async whisperStructured(transcript, opts = {}) {
|
|
486
|
+
const contract = normalizeDirectiveContract(opts.directive_contract ?? opts.directiveContract ?? {});
|
|
487
|
+
const operatorRules = [opts.guardrails, ...contract.operator_guardrails]
|
|
488
|
+
.map((rule) => String(rule ?? "").trim())
|
|
489
|
+
.filter(Boolean);
|
|
490
|
+
const system = [
|
|
491
|
+
STRUCTURED_COACH_SYSTEM,
|
|
492
|
+
directiveContractPrompt(contract),
|
|
493
|
+
operatorRules.length ? `Operator rules:\n${operatorRules.join("\n")}` : "",
|
|
494
|
+
].filter(Boolean).join("\n\n");
|
|
495
|
+
const out = await this.oracle({
|
|
496
|
+
model: opts.model,
|
|
497
|
+
maxTokens: opts.maxTokens ?? 320,
|
|
498
|
+
...(opts.temperature !== undefined ? { temperature: opts.temperature } : {}),
|
|
499
|
+
messages: [
|
|
500
|
+
{ role: "system", content: system },
|
|
501
|
+
{ role: "user", content: transcript },
|
|
502
|
+
],
|
|
503
|
+
});
|
|
504
|
+
const parsed = parseJsonObject(out.text);
|
|
505
|
+
let directive = parsed ? normalizeStructuredDirective(parsed, contract) : null;
|
|
506
|
+
if (directive && opts.transform) {
|
|
507
|
+
directive = await opts.transform(directive);
|
|
508
|
+
}
|
|
509
|
+
if (!directive || directive.confidence < contract.confidence_threshold)
|
|
510
|
+
return null;
|
|
511
|
+
return directive;
|
|
512
|
+
}
|
|
513
|
+
/** Alias that reads naturally in applications building their own watcher loop. */
|
|
514
|
+
directive(transcript, opts = {}) {
|
|
515
|
+
return this.whisperStructured(transcript, opts);
|
|
516
|
+
}
|
|
346
517
|
/** Hosted TTS — returns raw audio bytes (WAV/PCM per voice). */
|
|
347
518
|
async tts(text, voice = "supafone-labs-calm-en") {
|
|
348
519
|
const ctrl = new AbortController();
|
|
@@ -773,23 +944,31 @@ class CampaignsNamespace {
|
|
|
773
944
|
class LabsNamespace {
|
|
774
945
|
sm;
|
|
775
946
|
agents;
|
|
947
|
+
billing;
|
|
776
948
|
presets;
|
|
777
949
|
tools;
|
|
778
950
|
voices;
|
|
951
|
+
runtime;
|
|
779
952
|
phoneNumbers;
|
|
780
953
|
telephony;
|
|
781
954
|
calls;
|
|
955
|
+
activity;
|
|
956
|
+
plans;
|
|
782
957
|
recordings;
|
|
783
958
|
transcripts;
|
|
784
959
|
constructor(sm) {
|
|
785
960
|
this.sm = sm;
|
|
786
961
|
this.agents = new LabsAgentsNamespace(sm);
|
|
962
|
+
this.billing = new LabsBillingNamespace(sm);
|
|
787
963
|
this.presets = new LabsPresetsNamespace(sm);
|
|
788
964
|
this.tools = new LabsToolsNamespace(sm);
|
|
789
965
|
this.voices = new LabsVoicesNamespace(sm);
|
|
966
|
+
this.runtime = new LabsRuntimeNamespace(sm);
|
|
790
967
|
this.phoneNumbers = new LabsPhoneNumbersNamespace(sm);
|
|
791
968
|
this.telephony = new LabsTelephonyNamespace(sm);
|
|
792
969
|
this.calls = new LabsCallsNamespace(sm);
|
|
970
|
+
this.activity = new LabsActivityNamespace(sm);
|
|
971
|
+
this.plans = new LabsPlansNamespace(sm);
|
|
793
972
|
this.recordings = new LabsRecordingsNamespace(sm);
|
|
794
973
|
this.transcripts = new LabsTranscriptsNamespace(sm);
|
|
795
974
|
}
|
|
@@ -798,6 +977,38 @@ class LabsNamespace {
|
|
|
798
977
|
return this.sm.requestSupafoneApi("GET", "/api/v1/labs/capabilities");
|
|
799
978
|
}
|
|
800
979
|
}
|
|
980
|
+
class LabsBillingNamespace {
|
|
981
|
+
sm;
|
|
982
|
+
constructor(sm) {
|
|
983
|
+
this.sm = sm;
|
|
984
|
+
}
|
|
985
|
+
/** Start hosted Stripe Checkout. MCP callers should render checkout_url as a link. */
|
|
986
|
+
checkout(input = {}) {
|
|
987
|
+
return this.sm.request("POST", "/v1/billing/checkout", compact({
|
|
988
|
+
kind: input.kind ?? "plan",
|
|
989
|
+
plan_key: input.plan_key ?? input.planKey,
|
|
990
|
+
number_strategy: input.number_strategy ?? input.numberStrategy,
|
|
991
|
+
phone_number: input.phone_number ?? input.phoneNumber,
|
|
992
|
+
quantity: input.quantity,
|
|
993
|
+
success_url: input.success_url ?? input.successUrl,
|
|
994
|
+
cancel_url: input.cancel_url ?? input.cancelUrl,
|
|
995
|
+
}));
|
|
996
|
+
}
|
|
997
|
+
status(checkoutSessionId) {
|
|
998
|
+
if (!checkoutSessionId?.trim())
|
|
999
|
+
throw new SupafoneLabsError("checkoutSessionId is required");
|
|
1000
|
+
return this.sm.request("GET", `/v1/billing/checkout/${encodeURIComponent(checkoutSessionId)}`);
|
|
1001
|
+
}
|
|
1002
|
+
portal() {
|
|
1003
|
+
return this.sm.request("POST", "/v1/billing/portal", {});
|
|
1004
|
+
}
|
|
1005
|
+
createCheckout(input = {}) {
|
|
1006
|
+
return this.checkout(input);
|
|
1007
|
+
}
|
|
1008
|
+
getCheckout(checkoutSessionId) {
|
|
1009
|
+
return this.status(checkoutSessionId);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
801
1012
|
class LabsAgentsNamespace {
|
|
802
1013
|
sm;
|
|
803
1014
|
constructor(sm) {
|
|
@@ -807,6 +1018,13 @@ class LabsAgentsNamespace {
|
|
|
807
1018
|
create(input) {
|
|
808
1019
|
return this.sm.requestSupafoneApi("POST", "/api/v1/labs/agents", labsAgentPayload(this.withVoiceWatcher(input)));
|
|
809
1020
|
}
|
|
1021
|
+
/** Preview the exact validated call-plan contract the Supafone runtime executes. */
|
|
1022
|
+
plan(input) {
|
|
1023
|
+
return this.sm.requestSupafoneApi("POST", "/api/v1/labs/agent-plans", stagePlanPayload(input));
|
|
1024
|
+
}
|
|
1025
|
+
generateCallStages(input) {
|
|
1026
|
+
return this.plan(input);
|
|
1027
|
+
}
|
|
810
1028
|
/** Default the agent onto the client's Voice Watcher setting (live supervision
|
|
811
1029
|
* + QA + scoring) unless the caller set it explicitly; mirror into
|
|
812
1030
|
* labs.voice_watcher when a labs block exists. Never overwrites a caller value. */
|
|
@@ -939,14 +1157,138 @@ class LabsVoicesNamespace {
|
|
|
939
1157
|
constructor(sm) {
|
|
940
1158
|
this.sm = sm;
|
|
941
1159
|
}
|
|
942
|
-
/**
|
|
1160
|
+
/** Live normalized catalog from every connected/managed TTS provider. */
|
|
943
1161
|
list(opts = {}) {
|
|
944
1162
|
const q = new URLSearchParams();
|
|
945
1163
|
if (opts.provider)
|
|
946
1164
|
q.set("provider", opts.provider);
|
|
1165
|
+
if (opts.search)
|
|
1166
|
+
q.set("search", opts.search);
|
|
1167
|
+
if (opts.language)
|
|
1168
|
+
q.set("language", opts.language);
|
|
1169
|
+
const compatibleLanguage = opts.compatible_language ?? opts.compatibleLanguage;
|
|
1170
|
+
if (compatibleLanguage)
|
|
1171
|
+
q.set("compatible_language", compatibleLanguage);
|
|
1172
|
+
if (opts.gender)
|
|
1173
|
+
q.set("gender", opts.gender);
|
|
1174
|
+
const voiceType = opts.voice_type ?? opts.voiceType;
|
|
1175
|
+
if (voiceType)
|
|
1176
|
+
q.set("voice_type", voiceType);
|
|
1177
|
+
if (opts.model)
|
|
1178
|
+
q.set("model", opts.model);
|
|
1179
|
+
const runtimeProvider = opts.runtime_provider ?? opts.runtimeProvider;
|
|
1180
|
+
if (runtimeProvider)
|
|
1181
|
+
q.set("runtime_provider", runtimeProvider);
|
|
1182
|
+
const configuredOnly = opts.configured_only ?? opts.configuredOnly;
|
|
1183
|
+
if (configuredOnly !== undefined)
|
|
1184
|
+
q.set("configured_only", String(configuredOnly));
|
|
1185
|
+
if (opts.cursor !== undefined)
|
|
1186
|
+
q.set("cursor", String(opts.cursor));
|
|
1187
|
+
if (opts.limit !== undefined)
|
|
1188
|
+
q.set("limit", String(opts.limit));
|
|
1189
|
+
const agencyId = opts.agency_id ?? opts.agencyId;
|
|
1190
|
+
if (agencyId)
|
|
1191
|
+
q.set("agency_id", agencyId);
|
|
947
1192
|
const suffix = q.toString() ? `?${q}` : "";
|
|
948
1193
|
return this.sm.requestSupafoneApi("GET", `/api/v1/labs/voices${suffix}`);
|
|
949
1194
|
}
|
|
1195
|
+
/** Provider/model language limits and their Ultravox-compatible intersection. */
|
|
1196
|
+
capabilities() {
|
|
1197
|
+
return this.sm.requestSupafoneApi("GET", "/api/v1/labs/voices/capabilities");
|
|
1198
|
+
}
|
|
1199
|
+
/** Page through the complete normalized catalog. */
|
|
1200
|
+
async listAll(opts = {}) {
|
|
1201
|
+
const pageSize = Math.max(1, Math.min(opts.pageSize ?? 250, 250));
|
|
1202
|
+
const maxPages = Math.max(1, opts.maxPages ?? 100);
|
|
1203
|
+
let cursor = 0;
|
|
1204
|
+
let pages = 0;
|
|
1205
|
+
let first;
|
|
1206
|
+
const voices = [];
|
|
1207
|
+
const seen = new Set();
|
|
1208
|
+
while (cursor !== null && pages < maxPages) {
|
|
1209
|
+
if (seen.has(cursor))
|
|
1210
|
+
throw new SupafoneLabsError("Voice catalog returned a repeated cursor");
|
|
1211
|
+
seen.add(cursor);
|
|
1212
|
+
const page = await this.list({ ...opts, cursor, limit: pageSize });
|
|
1213
|
+
first ??= page;
|
|
1214
|
+
voices.push(...page.voices);
|
|
1215
|
+
cursor = page.next_cursor ?? null;
|
|
1216
|
+
pages += 1;
|
|
1217
|
+
}
|
|
1218
|
+
if (cursor !== null) {
|
|
1219
|
+
throw new SupafoneLabsError(`Voice catalog exceeded maxPages=${maxPages}`);
|
|
1220
|
+
}
|
|
1221
|
+
return {
|
|
1222
|
+
...(first ?? { total: 0, providers: [] }),
|
|
1223
|
+
voices,
|
|
1224
|
+
total: first?.total ?? voices.length,
|
|
1225
|
+
cursor: 0,
|
|
1226
|
+
next_cursor: null,
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
/** Rank real current voices from a plain-language description. */
|
|
1230
|
+
recommend(opts) {
|
|
1231
|
+
if (!opts.description?.trim()) {
|
|
1232
|
+
throw new SupafoneLabsError("description is required — describe the voice you want");
|
|
1233
|
+
}
|
|
1234
|
+
return this.sm.requestSupafoneApi("POST", "/api/v1/labs/voices/recommend", compact({
|
|
1235
|
+
agency_id: opts.agency_id ?? opts.agencyId,
|
|
1236
|
+
description: opts.description.trim(),
|
|
1237
|
+
language: opts.language,
|
|
1238
|
+
provider: opts.provider,
|
|
1239
|
+
gender: opts.gender,
|
|
1240
|
+
voice_type: opts.voice_type ?? opts.voiceType,
|
|
1241
|
+
model: opts.model,
|
|
1242
|
+
configured_only: opts.configured_only ?? opts.configuredOnly,
|
|
1243
|
+
premium: opts.premium,
|
|
1244
|
+
limit: opts.limit,
|
|
1245
|
+
}));
|
|
1246
|
+
}
|
|
1247
|
+
/** Download an authenticated voice preview. */
|
|
1248
|
+
preview(voiceId, opts = {}) {
|
|
1249
|
+
if (!voiceId.trim())
|
|
1250
|
+
throw new SupafoneLabsError("voiceId is required");
|
|
1251
|
+
const q = new URLSearchParams({ voice: voiceId.trim() });
|
|
1252
|
+
const agencyId = opts.agency_id ?? opts.agencyId;
|
|
1253
|
+
if (agencyId)
|
|
1254
|
+
q.set("agency_id", agencyId);
|
|
1255
|
+
return this.sm.requestSupafoneBinary(`/api/v1/labs/voices/preview?${q}`);
|
|
1256
|
+
}
|
|
1257
|
+
/** Convert a catalog row into the exact Agent Factory voice field. */
|
|
1258
|
+
selection(voice) {
|
|
1259
|
+
if (!voice?.id)
|
|
1260
|
+
throw new SupafoneLabsError("A catalog voice with id is required");
|
|
1261
|
+
return compact({
|
|
1262
|
+
provider: voice.provider_key || voice.source,
|
|
1263
|
+
voiceId: voice.id,
|
|
1264
|
+
model: voice.model || undefined,
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
class LabsRuntimeNamespace {
|
|
1269
|
+
sm;
|
|
1270
|
+
constructor(sm) {
|
|
1271
|
+
this.sm = sm;
|
|
1272
|
+
}
|
|
1273
|
+
get(opts = {}) {
|
|
1274
|
+
const q = new URLSearchParams();
|
|
1275
|
+
const agencyId = opts.agency_id ?? opts.agencyId;
|
|
1276
|
+
if (agencyId)
|
|
1277
|
+
q.set("agency_id", agencyId);
|
|
1278
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
1279
|
+
return this.sm.requestSupafoneApi("GET", `/api/v1/labs/runtime${suffix}`);
|
|
1280
|
+
}
|
|
1281
|
+
configure(input) {
|
|
1282
|
+
const credentials = input.credentials ?? {};
|
|
1283
|
+
return this.sm.requestSupafoneApi("PUT", "/api/v1/labs/runtime", compact({
|
|
1284
|
+
agency_id: input.agency_id ?? input.agencyId,
|
|
1285
|
+
provider: input.provider ?? "ultravox",
|
|
1286
|
+
credentials: compact({
|
|
1287
|
+
api_key: credentials.api_key ?? credentials.apiKey,
|
|
1288
|
+
base_url: credentials.base_url ?? credentials.baseUrl,
|
|
1289
|
+
}),
|
|
1290
|
+
}));
|
|
1291
|
+
}
|
|
950
1292
|
}
|
|
951
1293
|
class LabsCallsNamespace {
|
|
952
1294
|
sm;
|
|
@@ -960,11 +1302,61 @@ class LabsCallsNamespace {
|
|
|
960
1302
|
}
|
|
961
1303
|
get(callId, opts = {}) {
|
|
962
1304
|
const q = new URLSearchParams();
|
|
963
|
-
|
|
964
|
-
|
|
1305
|
+
const agencyId = opts.agency_id ?? opts.agencyId;
|
|
1306
|
+
if (agencyId)
|
|
1307
|
+
q.set("agency_id", agencyId);
|
|
965
1308
|
const suffix = q.toString() ? `?${q}` : "";
|
|
966
1309
|
return this.sm.requestSupafoneApi("GET", `/api/v1/labs/calls/${encodeURIComponent(callId)}${suffix}`);
|
|
967
1310
|
}
|
|
1311
|
+
delete(callId, opts = {}) {
|
|
1312
|
+
const q = new URLSearchParams();
|
|
1313
|
+
const agencyId = opts.agency_id ?? opts.agencyId;
|
|
1314
|
+
if (agencyId)
|
|
1315
|
+
q.set("agency_id", agencyId);
|
|
1316
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
1317
|
+
return this.sm.requestSupafoneApi("DELETE", `/api/v1/labs/calls/${encodeURIComponent(callId)}${suffix}`);
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
class LabsActivityNamespace {
|
|
1321
|
+
sm;
|
|
1322
|
+
constructor(sm) {
|
|
1323
|
+
this.sm = sm;
|
|
1324
|
+
}
|
|
1325
|
+
list(opts = {}) {
|
|
1326
|
+
const q = new URLSearchParams();
|
|
1327
|
+
const accountId = opts.account_id ?? opts.accountId ?? opts.agency_id ?? opts.agencyId;
|
|
1328
|
+
if (accountId)
|
|
1329
|
+
q.set("account_id", accountId);
|
|
1330
|
+
const eventType = opts.event_type ?? opts.eventType;
|
|
1331
|
+
if (eventType)
|
|
1332
|
+
q.set("event_type", eventType);
|
|
1333
|
+
const resourceType = opts.resource_type ?? opts.resourceType;
|
|
1334
|
+
if (resourceType)
|
|
1335
|
+
q.set("resource_type", resourceType);
|
|
1336
|
+
const resourceId = opts.resource_id ?? opts.resourceId;
|
|
1337
|
+
if (resourceId)
|
|
1338
|
+
q.set("resource_id", resourceId);
|
|
1339
|
+
if (opts.limit !== undefined)
|
|
1340
|
+
q.set("limit", String(opts.limit));
|
|
1341
|
+
if (opts.offset !== undefined)
|
|
1342
|
+
q.set("offset", String(opts.offset));
|
|
1343
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
1344
|
+
return this.sm.requestSupafoneApi("GET", `/api/v1/labs/activity${suffix}`);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
class LabsPlansNamespace {
|
|
1348
|
+
sm;
|
|
1349
|
+
constructor(sm) {
|
|
1350
|
+
this.sm = sm;
|
|
1351
|
+
}
|
|
1352
|
+
list(opts = {}) {
|
|
1353
|
+
const activity = new LabsActivityNamespace(this.sm);
|
|
1354
|
+
return activity.list({
|
|
1355
|
+
...opts,
|
|
1356
|
+
eventType: "studio.plan.created",
|
|
1357
|
+
resourceType: "studio_plan",
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
968
1360
|
}
|
|
969
1361
|
class LabsRecordingsNamespace {
|
|
970
1362
|
sm;
|
|
@@ -1036,10 +1428,27 @@ class LabsPhoneNumbersNamespace {
|
|
|
1036
1428
|
search(opts = {}) {
|
|
1037
1429
|
return this.sm.requestSupafoneApi("POST", "/api/v1/labs/phone-numbers/search", phoneNumberSearchPayload(opts));
|
|
1038
1430
|
}
|
|
1039
|
-
/**
|
|
1431
|
+
/**
|
|
1432
|
+
* Buy a managed number. Paid strategies return a hosted Checkout link first;
|
|
1433
|
+
* call again with billingCheckoutSessionId after Checkout reports paid.
|
|
1434
|
+
*/
|
|
1040
1435
|
buy(input) {
|
|
1436
|
+
const strategy = input.number_strategy ?? input.numberStrategy ?? (input.premium ? "premium" : "default_pool");
|
|
1437
|
+
const checkoutSessionId = input.billing_checkout_session_id ?? input.billingCheckoutSessionId;
|
|
1438
|
+
if ((strategy === "dedicated" || strategy === "premium") && !checkoutSessionId) {
|
|
1439
|
+
const phoneNumber = input.phone_number ?? input.phoneNumber;
|
|
1440
|
+
if (!phoneNumber) {
|
|
1441
|
+
return Promise.reject(new SupafoneLabsError("phoneNumber is required before starting number Checkout"));
|
|
1442
|
+
}
|
|
1443
|
+
return this.sm.labs.billing.checkout({
|
|
1444
|
+
kind: "number_addon",
|
|
1445
|
+
numberStrategy: strategy,
|
|
1446
|
+
phoneNumber,
|
|
1447
|
+
});
|
|
1448
|
+
}
|
|
1041
1449
|
return this.sm.requestSupafoneApi("POST", "/api/v1/labs/phone-numbers", phoneNumberProvisionPayload({
|
|
1042
1450
|
...input,
|
|
1451
|
+
numberStrategy: strategy,
|
|
1043
1452
|
telephony: input.telephony ?? { mode: "supafone_managed", provider: "supafone" },
|
|
1044
1453
|
}));
|
|
1045
1454
|
}
|
|
@@ -1256,12 +1665,14 @@ class OptimizerNamespace {
|
|
|
1256
1665
|
}
|
|
1257
1666
|
}
|
|
1258
1667
|
function labsAgentPayload(input) {
|
|
1668
|
+
const fixedLanguage = input.preferred_language ?? input.preferredLanguage ?? input.language;
|
|
1259
1669
|
return compact({
|
|
1260
1670
|
agency_id: input.agency_id ?? input.agencyId,
|
|
1261
1671
|
agent_key: input.agent_key ?? input.agentKey,
|
|
1262
1672
|
agent_type: input.agent_type ?? input.agentType,
|
|
1263
1673
|
style: input.agent_style ?? input.agentStyle ?? input.style,
|
|
1264
1674
|
name: input.name,
|
|
1675
|
+
description: input.description,
|
|
1265
1676
|
assistant_name: input.assistant_name ?? input.assistantName,
|
|
1266
1677
|
business_name: input.business_name ?? input.businessName,
|
|
1267
1678
|
industry: input.industry,
|
|
@@ -1274,11 +1685,15 @@ function labsAgentPayload(input) {
|
|
|
1274
1685
|
preset_key: input.preset_key ?? input.presetKey,
|
|
1275
1686
|
runtime_mode: input.runtime_mode ?? input.runtimeMode,
|
|
1276
1687
|
call_stages: callStagesPayload(input),
|
|
1688
|
+
stage_generation: input.stage_generation ?? input.stageGeneration,
|
|
1689
|
+
stage_count: input.stage_count ?? input.stageCount,
|
|
1690
|
+
stage_detail: input.stage_detail ?? input.stageDetail,
|
|
1277
1691
|
goal: input.goal,
|
|
1278
1692
|
greeting: input.greeting,
|
|
1279
1693
|
system_prompt: input.system_prompt ?? input.systemPrompt,
|
|
1280
|
-
language:
|
|
1694
|
+
language: fixedLanguage,
|
|
1281
1695
|
voice: input.voice ? voicePayload(input.voice) : undefined,
|
|
1696
|
+
voice_preference: voicePreferencePayload(input.voice_preference ?? input.voicePreference, fixedLanguage),
|
|
1282
1697
|
provider_keys: input.provider_keys ?? (input.providerKeys ? providerKeysPayload(input.providerKeys) : undefined),
|
|
1283
1698
|
byok: input.byok ? byokPayload(input.byok) : undefined,
|
|
1284
1699
|
telephony: input.telephony ? telephonyPayload(input.telephony) : undefined,
|
|
@@ -1297,13 +1712,16 @@ function labsAgentPayload(input) {
|
|
|
1297
1712
|
}
|
|
1298
1713
|
function hostedListQuery(opts) {
|
|
1299
1714
|
const q = new URLSearchParams();
|
|
1300
|
-
|
|
1301
|
-
|
|
1715
|
+
const agencyId = opts.agency_id ?? opts.agencyId;
|
|
1716
|
+
if (agencyId)
|
|
1717
|
+
q.set("agency_id", agencyId);
|
|
1302
1718
|
const agentKey = opts.agent_key ?? opts.agentKey;
|
|
1303
1719
|
if (agentKey)
|
|
1304
1720
|
q.set("agent_key", agentKey);
|
|
1305
1721
|
if (opts.limit !== undefined)
|
|
1306
1722
|
q.set("limit", String(opts.limit));
|
|
1723
|
+
if (opts.offset !== undefined)
|
|
1724
|
+
q.set("offset", String(opts.offset));
|
|
1307
1725
|
return q;
|
|
1308
1726
|
}
|
|
1309
1727
|
function telephonyPayload(input) {
|
|
@@ -1375,6 +1793,7 @@ function phoneNumberProvisionPayload(input) {
|
|
|
1375
1793
|
number_strategy: input.number_strategy ?? input.numberStrategy,
|
|
1376
1794
|
number_pool: input.number_pool ?? input.numberPool,
|
|
1377
1795
|
premium: input.premium,
|
|
1796
|
+
billing_checkout_session_id: input.billing_checkout_session_id ?? input.billingCheckoutSessionId,
|
|
1378
1797
|
style: input.agent_style ?? input.agentStyle ?? input.style,
|
|
1379
1798
|
direction: input.direction,
|
|
1380
1799
|
telephony: input.telephony ? telephonyPayload(input.telephony) : undefined,
|
|
@@ -1412,8 +1831,31 @@ function callStagesPayload(input) {
|
|
|
1412
1831
|
if (Array.isArray(explicit))
|
|
1413
1832
|
return explicit.map(callStagePayload);
|
|
1414
1833
|
if (explicit === false || auto === false)
|
|
1415
|
-
return
|
|
1416
|
-
|
|
1834
|
+
return false;
|
|
1835
|
+
if (explicit === "oracle" || explicit === "template" || explicit === "off")
|
|
1836
|
+
return explicit;
|
|
1837
|
+
// Omitted means the private Supafone API generates and compiles the plan.
|
|
1838
|
+
return undefined;
|
|
1839
|
+
}
|
|
1840
|
+
function stagePlanPayload(input) {
|
|
1841
|
+
return compact({
|
|
1842
|
+
agency_id: input.agency_id ?? input.agencyId,
|
|
1843
|
+
description: input.description,
|
|
1844
|
+
name: input.name,
|
|
1845
|
+
assistant_name: input.assistant_name ?? input.assistantName,
|
|
1846
|
+
business_name: input.business_name ?? input.businessName,
|
|
1847
|
+
industry: input.industry,
|
|
1848
|
+
direction: input.direction ?? input.agent_style ?? input.agentStyle ?? input.style,
|
|
1849
|
+
goal: input.goal,
|
|
1850
|
+
system_prompt: input.system_prompt ?? input.systemPrompt,
|
|
1851
|
+
tools: input.tools ? toolsPayload(input.tools) : undefined,
|
|
1852
|
+
call_stages: Array.isArray(input.call_stages ?? input.callStages ?? input.stages)
|
|
1853
|
+
? (input.call_stages ?? input.callStages ?? input.stages)
|
|
1854
|
+
: undefined,
|
|
1855
|
+
stage_generation: input.stage_generation ?? input.stageGeneration,
|
|
1856
|
+
stage_count: input.stage_count ?? input.stageCount,
|
|
1857
|
+
stage_detail: input.stage_detail ?? input.stageDetail,
|
|
1858
|
+
});
|
|
1417
1859
|
}
|
|
1418
1860
|
function callStagePayload(input) {
|
|
1419
1861
|
return compact({
|
|
@@ -1423,10 +1865,14 @@ function callStagePayload(input) {
|
|
|
1423
1865
|
instructions: input.instructions,
|
|
1424
1866
|
exit_criteria: input.exit_criteria ?? input.exitCriteria,
|
|
1425
1867
|
tools: input.tools,
|
|
1868
|
+
temperature: input.temperature,
|
|
1869
|
+
next_stages: input.next_stages ?? input.nextStages,
|
|
1426
1870
|
metadata: input.metadata,
|
|
1427
1871
|
});
|
|
1428
1872
|
}
|
|
1429
1873
|
function generateCallStages(input) {
|
|
1874
|
+
// Offline compatibility template. New code should use
|
|
1875
|
+
// client.generateCallStages(...) for the hosted, executable plan.
|
|
1430
1876
|
const direction = String(input.direction ?? input.agent_style ?? input.agentStyle ?? input.style ?? "inbound").toLowerCase();
|
|
1431
1877
|
const haystack = [
|
|
1432
1878
|
input.name,
|
|
@@ -1489,6 +1935,20 @@ function voicePayload(input) {
|
|
|
1489
1935
|
model: input.model,
|
|
1490
1936
|
});
|
|
1491
1937
|
}
|
|
1938
|
+
function voicePreferencePayload(input, defaultLanguage) {
|
|
1939
|
+
if (!input)
|
|
1940
|
+
return undefined;
|
|
1941
|
+
return compact({
|
|
1942
|
+
description: input.description,
|
|
1943
|
+
language: input.language ?? defaultLanguage,
|
|
1944
|
+
provider: input.provider,
|
|
1945
|
+
gender: input.gender,
|
|
1946
|
+
voice_type: input.voice_type ?? input.voiceType,
|
|
1947
|
+
model: input.model,
|
|
1948
|
+
configured_only: input.configured_only ?? input.configuredOnly,
|
|
1949
|
+
premium: input.premium,
|
|
1950
|
+
});
|
|
1951
|
+
}
|
|
1492
1952
|
function providerKeysPayload(input) {
|
|
1493
1953
|
return compact({
|
|
1494
1954
|
ultravox: input.ultravox,
|