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