supafone-labs 0.4.13 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -0
- package/dist/cjs/index.d.ts +156 -6
- package/dist/cjs/index.js +270 -6
- package/dist/index.d.ts +156 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +267 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +458 -5
package/dist/index.js
CHANGED
|
@@ -19,6 +19,127 @@
|
|
|
19
19
|
* number: { search: { areaCode: "415" } },
|
|
20
20
|
* });
|
|
21
21
|
*/
|
|
22
|
+
export const OUTBOUND_CALL_MODE_BOUNDS = {
|
|
23
|
+
maxDurationSeconds: { default: 180, min: 30, max: 900 },
|
|
24
|
+
maxKeypresses: { default: 12, min: 1, max: 64 },
|
|
25
|
+
repeatedMenuLimit: { default: 3, min: 1, max: 10 },
|
|
26
|
+
noProgressTimeoutSeconds: { default: 30, min: 5, max: 120 },
|
|
27
|
+
};
|
|
28
|
+
export const OUTBOUND_CALL_MODE_DEFAULTS = {
|
|
29
|
+
enabled: false,
|
|
30
|
+
autoDetect: true,
|
|
31
|
+
dtmfToolEnabled: true,
|
|
32
|
+
maxDurationSeconds: 180,
|
|
33
|
+
maxKeypresses: 12,
|
|
34
|
+
repeatedMenuLimit: 3,
|
|
35
|
+
noProgressTimeoutSeconds: 30,
|
|
36
|
+
humanDetectionEnabled: true,
|
|
37
|
+
resumeOnHuman: true,
|
|
38
|
+
observability: {
|
|
39
|
+
enabled: true,
|
|
40
|
+
includeTrigger: true,
|
|
41
|
+
includeTransitions: true,
|
|
42
|
+
includeTerminationReason: true,
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
const OUTBOUND_PROVIDER_PROFILE_BASE = {
|
|
46
|
+
contractSupported: true,
|
|
47
|
+
execution: "adapter_runtime_dependent",
|
|
48
|
+
capabilityPolicy: "fail_closed",
|
|
49
|
+
requiredCapabilities: ["dtmf", "state_persistence", "human_detection"],
|
|
50
|
+
};
|
|
51
|
+
export const OUTBOUND_CALL_MODE_PROVIDER_MATRIX = {
|
|
52
|
+
supafoneManaged: {
|
|
53
|
+
...OUTBOUND_PROVIDER_PROFILE_BASE,
|
|
54
|
+
transportFamily: "supafone_managed",
|
|
55
|
+
providers: ["supafone", "supafone_managed", "managed"],
|
|
56
|
+
},
|
|
57
|
+
twilio: {
|
|
58
|
+
...OUTBOUND_PROVIDER_PROFILE_BASE,
|
|
59
|
+
transportFamily: "twilio",
|
|
60
|
+
providers: ["twilio", "byo_twilio"],
|
|
61
|
+
},
|
|
62
|
+
telnyx: {
|
|
63
|
+
...OUTBOUND_PROVIDER_PROFILE_BASE,
|
|
64
|
+
transportFamily: "telnyx",
|
|
65
|
+
providers: ["telnyx", "byo_telnyx"],
|
|
66
|
+
},
|
|
67
|
+
plivo: {
|
|
68
|
+
...OUTBOUND_PROVIDER_PROFILE_BASE,
|
|
69
|
+
transportFamily: "plivo",
|
|
70
|
+
providers: ["plivo", "byo_plivo"],
|
|
71
|
+
},
|
|
72
|
+
signalWire: {
|
|
73
|
+
...OUTBOUND_PROVIDER_PROFILE_BASE,
|
|
74
|
+
transportFamily: "signalwire",
|
|
75
|
+
providers: ["signalwire", "signal_wire", "byo_signalwire"],
|
|
76
|
+
},
|
|
77
|
+
sipByoc: {
|
|
78
|
+
...OUTBOUND_PROVIDER_PROFILE_BASE,
|
|
79
|
+
transportFamily: "sip_byoc",
|
|
80
|
+
providers: ["sip", "custom_sip", "byo_sip", "byoc", "sip_byoc"],
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
export function outboundCallModeProviderProfile(provider) {
|
|
84
|
+
const normalized = String(provider ?? "").trim().toLowerCase();
|
|
85
|
+
for (const profile of Object.values(OUTBOUND_CALL_MODE_PROVIDER_MATRIX)) {
|
|
86
|
+
if (profile.providers.includes(normalized)) {
|
|
87
|
+
return { ...profile, providers: [...profile.providers] };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
contractSupported: false,
|
|
92
|
+
execution: "adapter_runtime_dependent",
|
|
93
|
+
capabilityPolicy: "fail_closed",
|
|
94
|
+
transportFamily: "unknown",
|
|
95
|
+
providers: normalized ? [normalized] : [],
|
|
96
|
+
requiredCapabilities: ["dtmf", "state_persistence", "human_detection"],
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
export function outboundCallModeReadiness(config, capabilities) {
|
|
100
|
+
const provider = String(capabilities?.provider ?? "");
|
|
101
|
+
const profile = outboundCallModeProviderProfile(provider);
|
|
102
|
+
const normalized = outboundCallModePayload(config ?? {});
|
|
103
|
+
if (normalized.enabled !== true) {
|
|
104
|
+
return {
|
|
105
|
+
ready: true,
|
|
106
|
+
status: "disabled",
|
|
107
|
+
provider,
|
|
108
|
+
transportFamily: profile.transportFamily,
|
|
109
|
+
requiredCapabilities: [],
|
|
110
|
+
missingCapabilities: [],
|
|
111
|
+
unsupportedCapabilities: [],
|
|
112
|
+
reasons: [],
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
const requiredCapabilities = [...normalized.required_capabilities];
|
|
116
|
+
const reported = {
|
|
117
|
+
dtmf: capabilities?.dtmf,
|
|
118
|
+
state_persistence: capabilities?.statePersistence,
|
|
119
|
+
human_detection: capabilities?.humanDetection,
|
|
120
|
+
observability: capabilities?.observability,
|
|
121
|
+
};
|
|
122
|
+
const missingCapabilities = requiredCapabilities.filter((key) => reported[key] === undefined);
|
|
123
|
+
const unsupportedCapabilities = requiredCapabilities.filter((key) => reported[key] === false);
|
|
124
|
+
const status = unsupportedCapabilities.length
|
|
125
|
+
? "unsupported"
|
|
126
|
+
: missingCapabilities.length
|
|
127
|
+
? "unknown"
|
|
128
|
+
: "ready";
|
|
129
|
+
const reasons = unsupportedCapabilities.length
|
|
130
|
+
? unsupportedCapabilities.map((key) => `Adapter reports ${key} is unsupported.`)
|
|
131
|
+
: missingCapabilities.map((key) => `Adapter did not report ${key} support.`);
|
|
132
|
+
return {
|
|
133
|
+
ready: status === "ready",
|
|
134
|
+
status,
|
|
135
|
+
provider,
|
|
136
|
+
transportFamily: profile.transportFamily,
|
|
137
|
+
requiredCapabilities,
|
|
138
|
+
missingCapabilities,
|
|
139
|
+
unsupportedCapabilities,
|
|
140
|
+
reasons,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
22
143
|
export class SupafoneLabsError extends Error {
|
|
23
144
|
status;
|
|
24
145
|
body;
|
|
@@ -763,18 +884,26 @@ class CampaignsNamespace {
|
|
|
763
884
|
const query = opts.accountId ? `?${new URLSearchParams({ account_id: opts.accountId })}` : "";
|
|
764
885
|
return this.sm.requestAccountApi("GET", `/api/v1/campaigns${query}`);
|
|
765
886
|
}
|
|
766
|
-
create(opts = {}) {
|
|
767
|
-
|
|
887
|
+
async create(opts = {}) {
|
|
888
|
+
const created = await this.sm.requestAccountApi("POST", "/api/v1/campaigns", compact({
|
|
768
889
|
name: opts.name ?? "New campaign",
|
|
769
890
|
goal: opts.goal ?? "book",
|
|
770
891
|
agent_id: opts.agentId,
|
|
771
892
|
account_id: opts.accountId,
|
|
772
893
|
}));
|
|
894
|
+
const settings = campaignSettingsPayload(opts.settings, opts.outboundCallMode);
|
|
895
|
+
if (!Object.keys(settings).length)
|
|
896
|
+
return created;
|
|
897
|
+
if (!created.campaign?.id) {
|
|
898
|
+
throw new SupafoneLabsError("Campaign was created but no campaign id was returned; outbound call mode was not saved.");
|
|
899
|
+
}
|
|
900
|
+
return this.update(created.campaign.id, { settings });
|
|
773
901
|
}
|
|
774
902
|
get(campaignId) {
|
|
775
903
|
return this.sm.requestAccountApi("GET", `/api/v1/campaigns/${encodeURIComponent(campaignId)}`);
|
|
776
904
|
}
|
|
777
905
|
update(campaignId, input) {
|
|
906
|
+
const settings = campaignSettingsPayload(input.settings, input.outboundCallMode);
|
|
778
907
|
const payload = compact({
|
|
779
908
|
name: input.name,
|
|
780
909
|
goal: input.goal,
|
|
@@ -782,10 +911,10 @@ class CampaignsNamespace {
|
|
|
782
911
|
email_subject: input.emailSubject,
|
|
783
912
|
email_body: input.emailBody,
|
|
784
913
|
cadence: input.cadence,
|
|
785
|
-
settings:
|
|
914
|
+
settings: Object.keys(settings).length ? settings : undefined,
|
|
786
915
|
});
|
|
787
916
|
if (!Object.keys(payload).length) {
|
|
788
|
-
throw new SupafoneLabsError("Nothing to update — pass name, goal, agentId, emailSubject, emailBody, cadence, or
|
|
917
|
+
throw new SupafoneLabsError("Nothing to update — pass name, goal, agentId, emailSubject, emailBody, cadence, settings, or outboundCallMode");
|
|
789
918
|
}
|
|
790
919
|
return this.sm.requestAccountApi("PUT", `/api/v1/campaigns/${encodeURIComponent(campaignId)}`, payload);
|
|
791
920
|
}
|
|
@@ -1112,6 +1241,38 @@ class LabsAgentsNamespace {
|
|
|
1112
1241
|
const suffix = q.toString() ? `?${q}` : "";
|
|
1113
1242
|
return this.sm.requestSupafoneApi("GET", `/api/v1/labs/agents/${encodeURIComponent(agentKey)}${suffix}`);
|
|
1114
1243
|
}
|
|
1244
|
+
/** Update an existing durable agent without changing omitted fields. */
|
|
1245
|
+
update(agentKey, input, opts = {}) {
|
|
1246
|
+
const q = new URLSearchParams();
|
|
1247
|
+
if (opts.agencyId)
|
|
1248
|
+
q.set("agency_id", opts.agencyId);
|
|
1249
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
1250
|
+
return this.sm.requestSupafoneApi("PATCH", `/api/v1/labs/agents/${encodeURIComponent(agentKey)}${suffix}`, labsAgentUpdatePayload(input));
|
|
1251
|
+
}
|
|
1252
|
+
/** Ask the managed server whether this agent is ready to activate. */
|
|
1253
|
+
readiness(agentKey, opts = {}) {
|
|
1254
|
+
const q = new URLSearchParams();
|
|
1255
|
+
if (opts.agencyId)
|
|
1256
|
+
q.set("agency_id", opts.agencyId);
|
|
1257
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
1258
|
+
return this.sm.requestSupafoneApi("GET", `/api/v1/labs/agents/${encodeURIComponent(agentKey)}/readiness${suffix}`);
|
|
1259
|
+
}
|
|
1260
|
+
/** Activate a ready hosted agent. */
|
|
1261
|
+
activate(agentKey, opts = {}) {
|
|
1262
|
+
const q = new URLSearchParams();
|
|
1263
|
+
if (opts.agencyId)
|
|
1264
|
+
q.set("agency_id", opts.agencyId);
|
|
1265
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
1266
|
+
return this.sm.requestSupafoneApi("POST", `/api/v1/labs/agents/${encodeURIComponent(agentKey)}/activate${suffix}`);
|
|
1267
|
+
}
|
|
1268
|
+
/** Pause a hosted agent without deleting it. */
|
|
1269
|
+
pause(agentKey, opts = {}) {
|
|
1270
|
+
const q = new URLSearchParams();
|
|
1271
|
+
if (opts.agencyId)
|
|
1272
|
+
q.set("agency_id", opts.agencyId);
|
|
1273
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
1274
|
+
return this.sm.requestSupafoneApi("POST", `/api/v1/labs/agents/${encodeURIComponent(agentKey)}/pause${suffix}`);
|
|
1275
|
+
}
|
|
1115
1276
|
/** Delete an agent. Optionally ask the backend to release assigned numbers. */
|
|
1116
1277
|
delete(agentKey, opts = {}) {
|
|
1117
1278
|
const q = new URLSearchParams();
|
|
@@ -1657,6 +1818,94 @@ class OptimizerNamespace {
|
|
|
1657
1818
|
return this.sm.request("GET", `/v1/optimizer/reports?agent=${encodeURIComponent(agent)}&limit=${limit}`);
|
|
1658
1819
|
}
|
|
1659
1820
|
}
|
|
1821
|
+
function outboundCallModePayload(config) {
|
|
1822
|
+
const enabled = outboundModeBoolean(config.enabled, false, "enabled");
|
|
1823
|
+
if (!enabled)
|
|
1824
|
+
return { version: 1, enabled: false };
|
|
1825
|
+
const observabilityInput = config.observability ?? {};
|
|
1826
|
+
if (typeof observabilityInput !== "object" || Array.isArray(observabilityInput)) {
|
|
1827
|
+
throw new SupafoneLabsError("outboundCallMode.observability must be an object");
|
|
1828
|
+
}
|
|
1829
|
+
if (observabilityInput.metadata !== undefined
|
|
1830
|
+
&& (typeof observabilityInput.metadata !== "object"
|
|
1831
|
+
|| observabilityInput.metadata === null
|
|
1832
|
+
|| Array.isArray(observabilityInput.metadata))) {
|
|
1833
|
+
throw new SupafoneLabsError("outboundCallMode.observability.metadata must be an object");
|
|
1834
|
+
}
|
|
1835
|
+
const observability = compact({
|
|
1836
|
+
enabled: outboundModeBoolean(observabilityInput.enabled, true, "observability.enabled"),
|
|
1837
|
+
include_trigger: outboundModeBoolean(observabilityInput.includeTrigger, true, "observability.includeTrigger"),
|
|
1838
|
+
include_transitions: outboundModeBoolean(observabilityInput.includeTransitions, true, "observability.includeTransitions"),
|
|
1839
|
+
include_termination_reason: outboundModeBoolean(observabilityInput.includeTerminationReason, true, "observability.includeTerminationReason"),
|
|
1840
|
+
metadata: observabilityInput.metadata,
|
|
1841
|
+
});
|
|
1842
|
+
const payload = {
|
|
1843
|
+
version: 1,
|
|
1844
|
+
enabled: true,
|
|
1845
|
+
initial_mode: "mission",
|
|
1846
|
+
ivr_mode: "dynamic",
|
|
1847
|
+
transport_scope: "provider_agnostic",
|
|
1848
|
+
capability_policy: "fail_closed",
|
|
1849
|
+
auto_detect: outboundModeBoolean(config.autoDetect, true, "autoDetect"),
|
|
1850
|
+
dtmf_tool_enabled: outboundModeBoolean(config.dtmfToolEnabled, true, "dtmfToolEnabled"),
|
|
1851
|
+
max_duration_seconds: outboundModeInteger(config.maxDurationSeconds, "maxDurationSeconds"),
|
|
1852
|
+
max_keypresses: outboundModeInteger(config.maxKeypresses, "maxKeypresses"),
|
|
1853
|
+
repeated_menu_limit: outboundModeInteger(config.repeatedMenuLimit, "repeatedMenuLimit"),
|
|
1854
|
+
no_progress_timeout_seconds: outboundModeInteger(config.noProgressTimeoutSeconds, "noProgressTimeoutSeconds"),
|
|
1855
|
+
human_detection_enabled: outboundModeBoolean(config.humanDetectionEnabled, true, "humanDetectionEnabled"),
|
|
1856
|
+
resume_on_human: outboundModeBoolean(config.resumeOnHuman, true, "resumeOnHuman"),
|
|
1857
|
+
observability,
|
|
1858
|
+
};
|
|
1859
|
+
payload.required_capabilities = outboundCallModeRequiredCapabilities(payload);
|
|
1860
|
+
return payload;
|
|
1861
|
+
}
|
|
1862
|
+
function outboundModeBoolean(value, fallback, key) {
|
|
1863
|
+
if (value === undefined)
|
|
1864
|
+
return fallback;
|
|
1865
|
+
if (typeof value !== "boolean") {
|
|
1866
|
+
throw new SupafoneLabsError(`outboundCallMode.${key} must be a boolean`);
|
|
1867
|
+
}
|
|
1868
|
+
return value;
|
|
1869
|
+
}
|
|
1870
|
+
function outboundModeInteger(value, key) {
|
|
1871
|
+
const bounds = OUTBOUND_CALL_MODE_BOUNDS[key];
|
|
1872
|
+
const normalized = value ?? bounds.default;
|
|
1873
|
+
if (!Number.isInteger(normalized)) {
|
|
1874
|
+
throw new SupafoneLabsError(`outboundCallMode.${key} must be an integer`);
|
|
1875
|
+
}
|
|
1876
|
+
if (normalized < bounds.min || normalized > bounds.max) {
|
|
1877
|
+
throw new SupafoneLabsError(`outboundCallMode.${key} must be between ${bounds.min} and ${bounds.max}`);
|
|
1878
|
+
}
|
|
1879
|
+
return normalized;
|
|
1880
|
+
}
|
|
1881
|
+
function outboundCallModeRequiredCapabilities(payload) {
|
|
1882
|
+
const required = ["state_persistence"];
|
|
1883
|
+
if (payload.dtmf_tool_enabled !== false)
|
|
1884
|
+
required.unshift("dtmf");
|
|
1885
|
+
if (payload.human_detection_enabled !== false || payload.resume_on_human !== false) {
|
|
1886
|
+
required.push("human_detection");
|
|
1887
|
+
}
|
|
1888
|
+
const observability = payload.observability;
|
|
1889
|
+
if (observability?.enabled === true)
|
|
1890
|
+
required.push("observability");
|
|
1891
|
+
return required;
|
|
1892
|
+
}
|
|
1893
|
+
function labsAgentMetadataPayload(input) {
|
|
1894
|
+
const metadata = { ...(input.metadata ?? {}) };
|
|
1895
|
+
const mode = input.outboundCallMode ?? input.outbound_call_mode;
|
|
1896
|
+
if (mode !== undefined)
|
|
1897
|
+
metadata.outbound_call_mode = outboundCallModePayload(mode);
|
|
1898
|
+
return Object.keys(metadata).length ? metadata : undefined;
|
|
1899
|
+
}
|
|
1900
|
+
function labsAgentUpdatePayload(input) {
|
|
1901
|
+
return labsAgentPayload(input);
|
|
1902
|
+
}
|
|
1903
|
+
function campaignSettingsPayload(settings, mode) {
|
|
1904
|
+
const payload = { ...(settings ?? {}) };
|
|
1905
|
+
if (mode !== undefined)
|
|
1906
|
+
payload.outbound_call_mode = outboundCallModePayload(mode);
|
|
1907
|
+
return payload;
|
|
1908
|
+
}
|
|
1660
1909
|
function labsAgentPayload(input) {
|
|
1661
1910
|
const fixedLanguage = input.preferred_language ?? input.preferredLanguage ?? input.language;
|
|
1662
1911
|
return compact({
|
|
@@ -1685,6 +1934,9 @@ function labsAgentPayload(input) {
|
|
|
1685
1934
|
greeting: input.greeting,
|
|
1686
1935
|
system_prompt: input.system_prompt ?? input.systemPrompt,
|
|
1687
1936
|
language: fixedLanguage,
|
|
1937
|
+
language_voice_routing: input.language_voice_routing ?? input.languageVoiceRouting,
|
|
1938
|
+
routing_languages: input.routing_languages ?? input.routingLanguages,
|
|
1939
|
+
language_profiles: languageProfilesPayload(input.language_profiles ?? input.languageProfiles),
|
|
1688
1940
|
voice: input.voice ? voicePayload(input.voice) : undefined,
|
|
1689
1941
|
voice_preference: voicePreferencePayload(input.voice_preference ?? input.voicePreference, fixedLanguage),
|
|
1690
1942
|
provider_keys: input.provider_keys ?? (input.providerKeys ? providerKeysPayload(input.providerKeys) : undefined),
|
|
@@ -1700,7 +1952,7 @@ function labsAgentPayload(input) {
|
|
|
1700
1952
|
ultravox: input.ultravox ? ultravoxPayload(input.ultravox) : undefined,
|
|
1701
1953
|
voice_watcher: input.voice_watcher ?? input.voiceWatcher,
|
|
1702
1954
|
voice_watcher_model: input.voice_watcher_model ?? input.voiceWatcherModel,
|
|
1703
|
-
metadata: input
|
|
1955
|
+
metadata: labsAgentMetadataPayload(input),
|
|
1704
1956
|
});
|
|
1705
1957
|
}
|
|
1706
1958
|
function hostedListQuery(opts) {
|
|
@@ -1928,6 +2180,16 @@ function voicePayload(input) {
|
|
|
1928
2180
|
model: input.model,
|
|
1929
2181
|
});
|
|
1930
2182
|
}
|
|
2183
|
+
function languageProfilesPayload(input) {
|
|
2184
|
+
if (!Array.isArray(input))
|
|
2185
|
+
return undefined;
|
|
2186
|
+
const profiles = input.map((profile) => compact({
|
|
2187
|
+
language: profile.language,
|
|
2188
|
+
language_hint: profile.language_hint ?? profile.languageHint,
|
|
2189
|
+
voice: profile.voice ? voicePayload(profile.voice) : undefined,
|
|
2190
|
+
}));
|
|
2191
|
+
return profiles.length ? profiles : undefined;
|
|
2192
|
+
}
|
|
1931
2193
|
function voicePreferencePayload(input, defaultLanguage) {
|
|
1932
2194
|
if (!input)
|
|
1933
2195
|
return undefined;
|