supafone-labs 0.4.14 → 0.5.2

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/dist/cjs/index.js CHANGED
@@ -21,8 +21,131 @@
21
21
  * });
22
22
  */
23
23
  Object.defineProperty(exports, "__esModule", { value: true });
24
- exports.Supafone = exports.SupafoneLabs = exports.SupafoneLabsError = void 0;
24
+ exports.Supafone = exports.SupafoneLabs = exports.SupafoneLabsError = exports.OUTBOUND_CALL_MODE_PROVIDER_MATRIX = exports.OUTBOUND_CALL_MODE_DEFAULTS = exports.OUTBOUND_CALL_MODE_BOUNDS = void 0;
25
+ exports.outboundCallModeProviderProfile = outboundCallModeProviderProfile;
26
+ exports.outboundCallModeReadiness = outboundCallModeReadiness;
25
27
  exports.generateCallStages = generateCallStages;
28
+ exports.OUTBOUND_CALL_MODE_BOUNDS = {
29
+ maxDurationSeconds: { default: 180, min: 30, max: 900 },
30
+ maxKeypresses: { default: 12, min: 1, max: 64 },
31
+ repeatedMenuLimit: { default: 3, min: 1, max: 10 },
32
+ noProgressTimeoutSeconds: { default: 30, min: 5, max: 120 },
33
+ };
34
+ exports.OUTBOUND_CALL_MODE_DEFAULTS = {
35
+ enabled: false,
36
+ autoDetect: true,
37
+ dtmfToolEnabled: true,
38
+ maxDurationSeconds: 180,
39
+ maxKeypresses: 12,
40
+ repeatedMenuLimit: 3,
41
+ noProgressTimeoutSeconds: 30,
42
+ humanDetectionEnabled: true,
43
+ resumeOnHuman: true,
44
+ observability: {
45
+ enabled: true,
46
+ includeTrigger: true,
47
+ includeTransitions: true,
48
+ includeTerminationReason: true,
49
+ },
50
+ };
51
+ const OUTBOUND_PROVIDER_PROFILE_BASE = {
52
+ contractSupported: true,
53
+ execution: "adapter_runtime_dependent",
54
+ capabilityPolicy: "fail_closed",
55
+ requiredCapabilities: ["dtmf", "state_persistence", "human_detection"],
56
+ };
57
+ exports.OUTBOUND_CALL_MODE_PROVIDER_MATRIX = {
58
+ supafoneManaged: {
59
+ ...OUTBOUND_PROVIDER_PROFILE_BASE,
60
+ transportFamily: "supafone_managed",
61
+ providers: ["supafone", "supafone_managed", "managed"],
62
+ },
63
+ twilio: {
64
+ ...OUTBOUND_PROVIDER_PROFILE_BASE,
65
+ transportFamily: "twilio",
66
+ providers: ["twilio", "byo_twilio"],
67
+ },
68
+ telnyx: {
69
+ ...OUTBOUND_PROVIDER_PROFILE_BASE,
70
+ transportFamily: "telnyx",
71
+ providers: ["telnyx", "byo_telnyx"],
72
+ },
73
+ plivo: {
74
+ ...OUTBOUND_PROVIDER_PROFILE_BASE,
75
+ transportFamily: "plivo",
76
+ providers: ["plivo", "byo_plivo"],
77
+ },
78
+ signalWire: {
79
+ ...OUTBOUND_PROVIDER_PROFILE_BASE,
80
+ transportFamily: "signalwire",
81
+ providers: ["signalwire", "signal_wire", "byo_signalwire"],
82
+ },
83
+ sipByoc: {
84
+ ...OUTBOUND_PROVIDER_PROFILE_BASE,
85
+ transportFamily: "sip_byoc",
86
+ providers: ["sip", "custom_sip", "byo_sip", "byoc", "sip_byoc"],
87
+ },
88
+ };
89
+ function outboundCallModeProviderProfile(provider) {
90
+ const normalized = String(provider ?? "").trim().toLowerCase();
91
+ for (const profile of Object.values(exports.OUTBOUND_CALL_MODE_PROVIDER_MATRIX)) {
92
+ if (profile.providers.includes(normalized)) {
93
+ return { ...profile, providers: [...profile.providers] };
94
+ }
95
+ }
96
+ return {
97
+ contractSupported: false,
98
+ execution: "adapter_runtime_dependent",
99
+ capabilityPolicy: "fail_closed",
100
+ transportFamily: "unknown",
101
+ providers: normalized ? [normalized] : [],
102
+ requiredCapabilities: ["dtmf", "state_persistence", "human_detection"],
103
+ };
104
+ }
105
+ function outboundCallModeReadiness(config, capabilities) {
106
+ const provider = String(capabilities?.provider ?? "");
107
+ const profile = outboundCallModeProviderProfile(provider);
108
+ const normalized = outboundCallModePayload(config ?? {});
109
+ if (normalized.enabled !== true) {
110
+ return {
111
+ ready: true,
112
+ status: "disabled",
113
+ provider,
114
+ transportFamily: profile.transportFamily,
115
+ requiredCapabilities: [],
116
+ missingCapabilities: [],
117
+ unsupportedCapabilities: [],
118
+ reasons: [],
119
+ };
120
+ }
121
+ const requiredCapabilities = [...normalized.required_capabilities];
122
+ const reported = {
123
+ dtmf: capabilities?.dtmf,
124
+ state_persistence: capabilities?.statePersistence,
125
+ human_detection: capabilities?.humanDetection,
126
+ observability: capabilities?.observability,
127
+ };
128
+ const missingCapabilities = requiredCapabilities.filter((key) => reported[key] === undefined);
129
+ const unsupportedCapabilities = requiredCapabilities.filter((key) => reported[key] === false);
130
+ const status = unsupportedCapabilities.length
131
+ ? "unsupported"
132
+ : missingCapabilities.length
133
+ ? "unknown"
134
+ : "ready";
135
+ const reasons = unsupportedCapabilities.length
136
+ ? unsupportedCapabilities.map((key) => `Adapter reports ${key} is unsupported.`)
137
+ : missingCapabilities.map((key) => `Adapter did not report ${key} support.`);
138
+ return {
139
+ ready: status === "ready",
140
+ status,
141
+ provider,
142
+ transportFamily: profile.transportFamily,
143
+ requiredCapabilities,
144
+ missingCapabilities,
145
+ unsupportedCapabilities,
146
+ reasons,
147
+ };
148
+ }
26
149
  class SupafoneLabsError extends Error {
27
150
  status;
28
151
  body;
@@ -264,6 +387,33 @@ class SupafoneLabs {
264
387
  clearTimeout(timer);
265
388
  }
266
389
  }
390
+ /** @internal Multipart upload to the Supafone product API with the Labs key. */
391
+ async requestSupafoneUpload(path, file, filename) {
392
+ const ctrl = new AbortController();
393
+ const timer = setTimeout(() => ctrl.abort(), Math.max(this.timeoutMs, 120_000));
394
+ try {
395
+ const safeFilename = filename.replace(/["\\\r\n]/g, "").slice(0, 120) || "document.txt";
396
+ const blob = file instanceof Blob ? file : new Blob([file]);
397
+ const form = new FormData();
398
+ form.append("file", blob, safeFilename);
399
+ const res = await fetch(this.supafoneApiBaseUrl + path, {
400
+ method: "POST",
401
+ signal: ctrl.signal,
402
+ headers: { Authorization: `Bearer ${this.supafoneApiKey}` },
403
+ body: form,
404
+ });
405
+ const text = await res.text();
406
+ const parsed = text ? safeJson(text) : {};
407
+ if (!res.ok) {
408
+ const detail = parsed?.detail ?? text ?? `HTTP ${res.status}`;
409
+ throw new SupafoneLabsError(`POST ${path}: ${detail}`, res.status, parsed);
410
+ }
411
+ return parsed;
412
+ }
413
+ finally {
414
+ clearTimeout(timer);
415
+ }
416
+ }
267
417
  /** @internal Authenticated binary request to the Supafone app API. */
268
418
  async requestSupafoneBinary(path) {
269
419
  const ctrl = new AbortController();
@@ -770,18 +920,26 @@ class CampaignsNamespace {
770
920
  const query = opts.accountId ? `?${new URLSearchParams({ account_id: opts.accountId })}` : "";
771
921
  return this.sm.requestAccountApi("GET", `/api/v1/campaigns${query}`);
772
922
  }
773
- create(opts = {}) {
774
- return this.sm.requestAccountApi("POST", "/api/v1/campaigns", compact({
923
+ async create(opts = {}) {
924
+ const created = await this.sm.requestAccountApi("POST", "/api/v1/campaigns", compact({
775
925
  name: opts.name ?? "New campaign",
776
926
  goal: opts.goal ?? "book",
777
927
  agent_id: opts.agentId,
778
928
  account_id: opts.accountId,
779
929
  }));
930
+ const settings = campaignSettingsPayload(opts.settings, opts.outboundCallMode);
931
+ if (!Object.keys(settings).length)
932
+ return created;
933
+ if (!created.campaign?.id) {
934
+ throw new SupafoneLabsError("Campaign was created but no campaign id was returned; outbound call mode was not saved.");
935
+ }
936
+ return this.update(created.campaign.id, { settings });
780
937
  }
781
938
  get(campaignId) {
782
939
  return this.sm.requestAccountApi("GET", `/api/v1/campaigns/${encodeURIComponent(campaignId)}`);
783
940
  }
784
941
  update(campaignId, input) {
942
+ const settings = campaignSettingsPayload(input.settings, input.outboundCallMode);
785
943
  const payload = compact({
786
944
  name: input.name,
787
945
  goal: input.goal,
@@ -789,10 +947,10 @@ class CampaignsNamespace {
789
947
  email_subject: input.emailSubject,
790
948
  email_body: input.emailBody,
791
949
  cadence: input.cadence,
792
- settings: input.settings,
950
+ settings: Object.keys(settings).length ? settings : undefined,
793
951
  });
794
952
  if (!Object.keys(payload).length) {
795
- throw new SupafoneLabsError("Nothing to update — pass name, goal, agentId, emailSubject, emailBody, cadence, or settings");
953
+ throw new SupafoneLabsError("Nothing to update — pass name, goal, agentId, emailSubject, emailBody, cadence, settings, or outboundCallMode");
796
954
  }
797
955
  return this.sm.requestAccountApi("PUT", `/api/v1/campaigns/${encodeURIComponent(campaignId)}`, payload);
798
956
  }
@@ -1119,6 +1277,66 @@ class LabsAgentsNamespace {
1119
1277
  const suffix = q.toString() ? `?${q}` : "";
1120
1278
  return this.sm.requestSupafoneApi("GET", `/api/v1/labs/agents/${encodeURIComponent(agentKey)}${suffix}`);
1121
1279
  }
1280
+ /** Update an existing durable agent without changing omitted fields. */
1281
+ update(agentKey, input, opts = {}) {
1282
+ const q = new URLSearchParams();
1283
+ if (opts.agencyId)
1284
+ q.set("agency_id", opts.agencyId);
1285
+ const suffix = q.toString() ? `?${q}` : "";
1286
+ return this.sm.requestSupafoneApi("PATCH", `/api/v1/labs/agents/${encodeURIComponent(agentKey)}${suffix}`, labsAgentUpdatePayload(input));
1287
+ }
1288
+ /** Scrape a website and rebuild this agent's managed knowledge corpus. */
1289
+ syncKnowledge(agentId, input = {}) {
1290
+ return this.sm.requestSupafoneApi("POST", `/api/v1/agents/${encodeURIComponent(agentId)}/sync-knowledge`, { url: input.url ?? input.website_url ?? input.websiteUrl ?? "" });
1291
+ }
1292
+ /** Remove website knowledge while preserving uploaded documents and notes. */
1293
+ detachWebsiteKnowledge(agentId) {
1294
+ return this.sm.requestSupafoneApi("DELETE", `/api/v1/agents/${encodeURIComponent(agentId)}/knowledge/website`);
1295
+ }
1296
+ /** Ask the managed server whether this agent is ready to activate. */
1297
+ readiness(agentKey, opts = {}) {
1298
+ const q = new URLSearchParams();
1299
+ if (opts.agencyId)
1300
+ q.set("agency_id", opts.agencyId);
1301
+ const suffix = q.toString() ? `?${q}` : "";
1302
+ return this.sm.requestSupafoneApi("GET", `/api/v1/labs/agents/${encodeURIComponent(agentKey)}/readiness${suffix}`);
1303
+ }
1304
+ /** Activate a ready hosted agent. */
1305
+ activate(agentKey, opts = {}) {
1306
+ const q = new URLSearchParams();
1307
+ if (opts.agencyId)
1308
+ q.set("agency_id", opts.agencyId);
1309
+ const suffix = q.toString() ? `?${q}` : "";
1310
+ return this.sm.requestSupafoneApi("POST", `/api/v1/labs/agents/${encodeURIComponent(agentKey)}/activate${suffix}`);
1311
+ }
1312
+ /** Pause a hosted agent without deleting it. */
1313
+ pause(agentKey, opts = {}) {
1314
+ const q = new URLSearchParams();
1315
+ if (opts.agencyId)
1316
+ q.set("agency_id", opts.agencyId);
1317
+ const suffix = q.toString() ? `?${q}` : "";
1318
+ return this.sm.requestSupafoneApi("POST", `/api/v1/labs/agents/${encodeURIComponent(agentKey)}/pause${suffix}`);
1319
+ }
1320
+ /** Start the native provider-neutral browser voice session for this agent. */
1321
+ startWebRtcCall(agentId) {
1322
+ return this.sm.startWebRtcCall({ agentId });
1323
+ }
1324
+ /** Rebuild retrieval data from the agent's already saved knowledge sources. */
1325
+ reindexKnowledge(agentId) {
1326
+ return this.sm.requestSupafoneApi("POST", `/api/v1/agents/${encodeURIComponent(agentId)}/knowledge/reindex`);
1327
+ }
1328
+ /** Upload and index a txt, md, csv, or PDF knowledge document. */
1329
+ uploadKnowledgeDocument(agentId, file, filename) {
1330
+ return this.sm.requestSupafoneUpload(`/api/v1/agents/${encodeURIComponent(agentId)}/knowledge/upload`, file, filename);
1331
+ }
1332
+ /** Delete one document and rebuild the agent's managed corpus. */
1333
+ deleteKnowledgeDocument(agentId, documentId) {
1334
+ return this.sm.requestSupafoneApi("DELETE", `/api/v1/agents/${encodeURIComponent(agentId)}/knowledge/docs/${encodeURIComponent(documentId)}`);
1335
+ }
1336
+ /** Ask a grounded question against the same corpus used during calls. */
1337
+ chatKnowledge(agentId, message, history = []) {
1338
+ return this.sm.requestSupafoneApi("POST", `/api/v1/agents/${encodeURIComponent(agentId)}/knowledge-chat`, { message, history });
1339
+ }
1122
1340
  /** Delete an agent. Optionally ask the backend to release assigned numbers. */
1123
1341
  delete(agentKey, opts = {}) {
1124
1342
  const q = new URLSearchParams();
@@ -1664,6 +1882,94 @@ class OptimizerNamespace {
1664
1882
  return this.sm.request("GET", `/v1/optimizer/reports?agent=${encodeURIComponent(agent)}&limit=${limit}`);
1665
1883
  }
1666
1884
  }
1885
+ function outboundCallModePayload(config) {
1886
+ const enabled = outboundModeBoolean(config.enabled, false, "enabled");
1887
+ if (!enabled)
1888
+ return { version: 1, enabled: false };
1889
+ const observabilityInput = config.observability ?? {};
1890
+ if (typeof observabilityInput !== "object" || Array.isArray(observabilityInput)) {
1891
+ throw new SupafoneLabsError("outboundCallMode.observability must be an object");
1892
+ }
1893
+ if (observabilityInput.metadata !== undefined
1894
+ && (typeof observabilityInput.metadata !== "object"
1895
+ || observabilityInput.metadata === null
1896
+ || Array.isArray(observabilityInput.metadata))) {
1897
+ throw new SupafoneLabsError("outboundCallMode.observability.metadata must be an object");
1898
+ }
1899
+ const observability = compact({
1900
+ enabled: outboundModeBoolean(observabilityInput.enabled, true, "observability.enabled"),
1901
+ include_trigger: outboundModeBoolean(observabilityInput.includeTrigger, true, "observability.includeTrigger"),
1902
+ include_transitions: outboundModeBoolean(observabilityInput.includeTransitions, true, "observability.includeTransitions"),
1903
+ include_termination_reason: outboundModeBoolean(observabilityInput.includeTerminationReason, true, "observability.includeTerminationReason"),
1904
+ metadata: observabilityInput.metadata,
1905
+ });
1906
+ const payload = {
1907
+ version: 1,
1908
+ enabled: true,
1909
+ initial_mode: "mission",
1910
+ ivr_mode: "dynamic",
1911
+ transport_scope: "provider_agnostic",
1912
+ capability_policy: "fail_closed",
1913
+ auto_detect: outboundModeBoolean(config.autoDetect, true, "autoDetect"),
1914
+ dtmf_tool_enabled: outboundModeBoolean(config.dtmfToolEnabled, true, "dtmfToolEnabled"),
1915
+ max_duration_seconds: outboundModeInteger(config.maxDurationSeconds, "maxDurationSeconds"),
1916
+ max_keypresses: outboundModeInteger(config.maxKeypresses, "maxKeypresses"),
1917
+ repeated_menu_limit: outboundModeInteger(config.repeatedMenuLimit, "repeatedMenuLimit"),
1918
+ no_progress_timeout_seconds: outboundModeInteger(config.noProgressTimeoutSeconds, "noProgressTimeoutSeconds"),
1919
+ human_detection_enabled: outboundModeBoolean(config.humanDetectionEnabled, true, "humanDetectionEnabled"),
1920
+ resume_on_human: outboundModeBoolean(config.resumeOnHuman, true, "resumeOnHuman"),
1921
+ observability,
1922
+ };
1923
+ payload.required_capabilities = outboundCallModeRequiredCapabilities(payload);
1924
+ return payload;
1925
+ }
1926
+ function outboundModeBoolean(value, fallback, key) {
1927
+ if (value === undefined)
1928
+ return fallback;
1929
+ if (typeof value !== "boolean") {
1930
+ throw new SupafoneLabsError(`outboundCallMode.${key} must be a boolean`);
1931
+ }
1932
+ return value;
1933
+ }
1934
+ function outboundModeInteger(value, key) {
1935
+ const bounds = exports.OUTBOUND_CALL_MODE_BOUNDS[key];
1936
+ const normalized = value ?? bounds.default;
1937
+ if (!Number.isInteger(normalized)) {
1938
+ throw new SupafoneLabsError(`outboundCallMode.${key} must be an integer`);
1939
+ }
1940
+ if (normalized < bounds.min || normalized > bounds.max) {
1941
+ throw new SupafoneLabsError(`outboundCallMode.${key} must be between ${bounds.min} and ${bounds.max}`);
1942
+ }
1943
+ return normalized;
1944
+ }
1945
+ function outboundCallModeRequiredCapabilities(payload) {
1946
+ const required = ["state_persistence"];
1947
+ if (payload.dtmf_tool_enabled !== false)
1948
+ required.unshift("dtmf");
1949
+ if (payload.human_detection_enabled !== false || payload.resume_on_human !== false) {
1950
+ required.push("human_detection");
1951
+ }
1952
+ const observability = payload.observability;
1953
+ if (observability?.enabled === true)
1954
+ required.push("observability");
1955
+ return required;
1956
+ }
1957
+ function labsAgentMetadataPayload(input) {
1958
+ const metadata = { ...(input.metadata ?? {}) };
1959
+ const mode = input.outboundCallMode ?? input.outbound_call_mode;
1960
+ if (mode !== undefined)
1961
+ metadata.outbound_call_mode = outboundCallModePayload(mode);
1962
+ return Object.keys(metadata).length ? metadata : undefined;
1963
+ }
1964
+ function labsAgentUpdatePayload(input) {
1965
+ return labsAgentPayload(input);
1966
+ }
1967
+ function campaignSettingsPayload(settings, mode) {
1968
+ const payload = { ...(settings ?? {}) };
1969
+ if (mode !== undefined)
1970
+ payload.outbound_call_mode = outboundCallModePayload(mode);
1971
+ return payload;
1972
+ }
1667
1973
  function labsAgentPayload(input) {
1668
1974
  const fixedLanguage = input.preferred_language ?? input.preferredLanguage ?? input.language;
1669
1975
  return compact({
@@ -1710,7 +2016,7 @@ function labsAgentPayload(input) {
1710
2016
  ultravox: input.ultravox ? ultravoxPayload(input.ultravox) : undefined,
1711
2017
  voice_watcher: input.voice_watcher ?? input.voiceWatcher,
1712
2018
  voice_watcher_model: input.voice_watcher_model ?? input.voiceWatcherModel,
1713
- metadata: input.metadata,
2019
+ metadata: labsAgentMetadataPayload(input),
1714
2020
  });
1715
2021
  }
1716
2022
  function hostedListQuery(opts) {
@@ -1941,6 +2247,9 @@ function voicePayload(input) {
1941
2247
  function languageProfilesPayload(input) {
1942
2248
  if (!Array.isArray(input))
1943
2249
  return undefined;
2250
+ if (input.length > 4) {
2251
+ throw new SupafoneLabsError("languageProfiles supports at most four profiles");
2252
+ }
1944
2253
  const profiles = input.map((profile) => compact({
1945
2254
  language: profile.language,
1946
2255
  language_hint: profile.language_hint ?? profile.languageHint,
package/dist/index.d.ts CHANGED
@@ -257,16 +257,37 @@ export interface LabsVoiceProviderTraits {
257
257
  is_public?: boolean | null;
258
258
  is_owner?: boolean | null;
259
259
  }
260
+ /** Stable provider presentation metadata returned by the hosted catalog. */
261
+ export interface LabsVoiceProviderBrand {
262
+ /** Canonical provider key used for filtering and grouping. */
263
+ key: string;
264
+ /** Human-readable provider/company name. */
265
+ name: string;
266
+ /** Stable local asset slug when Supafone has an official provider mark. */
267
+ logo_slug?: string | null;
268
+ /** Absolute, browser-ready provider logo URL. */
269
+ logo_url?: string | null;
270
+ /** Provider's official product or company website. */
271
+ website_url?: string | null;
272
+ }
260
273
  export interface LabsVoiceCatalogItem {
261
274
  /** Stable runtime reference, including provider prefix when required. */
262
275
  id: string;
263
276
  voice_id: string;
264
277
  /** Human-readable provider display name. */
265
278
  provider: string;
279
+ /** Canonical brand display name for the synthesis provider. */
280
+ provider_label?: string;
281
+ /** Browser-ready official provider mark, or null for an unknown provider. */
282
+ provider_logo_url?: string | null;
283
+ /** Branding for the provider that renders the voice. */
284
+ provider_brand?: LabsVoiceProviderBrand;
266
285
  /** Canonical SDK/runtime provider key. */
267
286
  provider_key: string;
268
287
  /** Runtime adapter used to place the voice on an Ultravox call. */
269
288
  runtime_provider_key: string;
289
+ /** Branding for the realtime runtime, which can differ from synthesis. */
290
+ runtime_provider_brand?: LabsVoiceProviderBrand;
270
291
  /** Actual TTS engine behind the voice (can differ for Ultravox built-ins). */
271
292
  synthesis_provider_key: string;
272
293
  /** Raw identifier used by the provider API. */
@@ -622,6 +643,95 @@ export interface LabsUltravoxRuntime {
622
643
  sip?: LabsCustomSipConfig;
623
644
  [extra: string]: unknown;
624
645
  }
646
+ export interface OutboundCallModeObservability {
647
+ enabled?: boolean;
648
+ includeTrigger?: boolean;
649
+ includeTransitions?: boolean;
650
+ includeTerminationReason?: boolean;
651
+ metadata?: Record<string, unknown>;
652
+ }
653
+ /** Public, provider-neutral controls for temporary outbound IVR navigation. */
654
+ export interface OutboundCallModeConfig {
655
+ enabled?: boolean;
656
+ autoDetect?: boolean;
657
+ dtmfToolEnabled?: boolean;
658
+ maxDurationSeconds?: number;
659
+ maxKeypresses?: number;
660
+ repeatedMenuLimit?: number;
661
+ noProgressTimeoutSeconds?: number;
662
+ humanDetectionEnabled?: boolean;
663
+ resumeOnHuman?: boolean;
664
+ observability?: OutboundCallModeObservability;
665
+ }
666
+ /** Capabilities positively reported by the active telephony/runtime adapter. */
667
+ export interface OutboundCallModeCapabilities {
668
+ provider?: string;
669
+ dtmf?: boolean;
670
+ statePersistence?: boolean;
671
+ humanDetection?: boolean;
672
+ observability?: boolean;
673
+ metadata?: Record<string, unknown>;
674
+ }
675
+ export interface OutboundCallModeReadiness {
676
+ ready: boolean;
677
+ status: "disabled" | "ready" | "unknown" | "unsupported";
678
+ provider: string;
679
+ transportFamily: string;
680
+ requiredCapabilities: string[];
681
+ missingCapabilities: string[];
682
+ unsupportedCapabilities: string[];
683
+ reasons: string[];
684
+ }
685
+ export declare const OUTBOUND_CALL_MODE_BOUNDS: {
686
+ readonly maxDurationSeconds: {
687
+ readonly default: 180;
688
+ readonly min: 30;
689
+ readonly max: 900;
690
+ };
691
+ readonly maxKeypresses: {
692
+ readonly default: 12;
693
+ readonly min: 1;
694
+ readonly max: 64;
695
+ };
696
+ readonly repeatedMenuLimit: {
697
+ readonly default: 3;
698
+ readonly min: 1;
699
+ readonly max: 10;
700
+ };
701
+ readonly noProgressTimeoutSeconds: {
702
+ readonly default: 30;
703
+ readonly min: 5;
704
+ readonly max: 120;
705
+ };
706
+ };
707
+ export declare const OUTBOUND_CALL_MODE_DEFAULTS: {
708
+ readonly enabled: false;
709
+ readonly autoDetect: true;
710
+ readonly dtmfToolEnabled: true;
711
+ readonly maxDurationSeconds: 180;
712
+ readonly maxKeypresses: 12;
713
+ readonly repeatedMenuLimit: 3;
714
+ readonly noProgressTimeoutSeconds: 30;
715
+ readonly humanDetectionEnabled: true;
716
+ readonly resumeOnHuman: true;
717
+ readonly observability: {
718
+ readonly enabled: true;
719
+ readonly includeTrigger: true;
720
+ readonly includeTransitions: true;
721
+ readonly includeTerminationReason: true;
722
+ };
723
+ };
724
+ export interface OutboundCallModeProviderProfile {
725
+ contractSupported: boolean;
726
+ execution: "adapter_runtime_dependent";
727
+ capabilityPolicy: "fail_closed";
728
+ transportFamily: string;
729
+ providers: readonly string[];
730
+ requiredCapabilities: readonly string[];
731
+ }
732
+ export declare const OUTBOUND_CALL_MODE_PROVIDER_MATRIX: Record<string, OutboundCallModeProviderProfile>;
733
+ export declare function outboundCallModeProviderProfile(provider: string): OutboundCallModeProviderProfile;
734
+ export declare function outboundCallModeReadiness(config?: OutboundCallModeConfig, capabilities?: OutboundCallModeCapabilities): OutboundCallModeReadiness;
625
735
  export interface CreateLabsAgentRequest {
626
736
  agencyId?: string;
627
737
  agency_id?: string;
@@ -706,6 +816,9 @@ export interface CreateLabsAgentRequest {
706
816
  voice_watcher?: boolean;
707
817
  voiceWatcherModel?: string;
708
818
  voice_watcher_model?: string;
819
+ /** Opt-in outbound IVR mode. Execution depends on adapter-reported readiness. */
820
+ outboundCallMode?: OutboundCallModeConfig;
821
+ outbound_call_mode?: OutboundCallModeConfig;
709
822
  metadata?: Record<string, unknown>;
710
823
  }
711
824
  export interface ListLabsAgentsOptions {
@@ -728,6 +841,41 @@ export interface DeleteLabsAgentResponse {
728
841
  released_numbers?: unknown[];
729
842
  [extra: string]: unknown;
730
843
  }
844
+ export type LabsAgentLifecycle = "draft" | "active" | "paused" | "archived" | string;
845
+ export interface LabsAgentReadiness {
846
+ ready: boolean;
847
+ status?: string;
848
+ checks?: Array<Record<string, unknown>>;
849
+ [extra: string]: unknown;
850
+ }
851
+ export type UpdateLabsAgentRequest = Partial<CreateLabsAgentRequest>;
852
+ export interface LabsAgentLifecycleResponse {
853
+ success?: boolean;
854
+ agent?: LabsAgentResponse;
855
+ lifecycle?: LabsAgentLifecycle;
856
+ readiness?: LabsAgentReadiness;
857
+ [extra: string]: unknown;
858
+ }
859
+ export interface LabsKnowledgeSyncRequest {
860
+ websiteUrl?: string;
861
+ website_url?: string;
862
+ url?: string;
863
+ }
864
+ export interface LabsKnowledgeSyncResponse {
865
+ agent: LabsAgentResponse;
866
+ chunks_indexed?: number;
867
+ scraped_ok?: boolean;
868
+ last_error?: string | null;
869
+ website_detached?: boolean;
870
+ }
871
+ export interface LabsKnowledgeChatTurn {
872
+ role: "user" | "assistant";
873
+ content: string;
874
+ }
875
+ export interface LabsKnowledgeChatResponse {
876
+ reply: string;
877
+ sources: string[];
878
+ }
731
879
  export interface LabsCapabilitiesResponse {
732
880
  product: string;
733
881
  api_namespace: string;
@@ -778,7 +926,10 @@ export interface LabsVoiceListResponse {
778
926
  configured: boolean;
779
927
  voice_count: number;
780
928
  error?: string | null;
929
+ brand?: LabsVoiceProviderBrand;
781
930
  }>;
931
+ /** Distinct synthesis-provider brands represented in the complete result. */
932
+ provider_brands?: LabsVoiceProviderBrand[];
782
933
  provider_accounts?: Record<string, unknown>;
783
934
  errors?: Record<string, string>;
784
935
  catalog?: {
@@ -1068,6 +1219,8 @@ export interface LabsAgentResponse {
1068
1219
  display_name?: string;
1069
1220
  runtime_mode?: string;
1070
1221
  preset_key?: string;
1222
+ status?: string;
1223
+ lifecycle?: LabsAgentLifecycle;
1071
1224
  profile?: Record<string, unknown>;
1072
1225
  runtime?: Record<string, unknown>;
1073
1226
  [extra: string]: unknown;
@@ -1100,6 +1253,9 @@ export interface ListLabsAgentsResponse {
1100
1253
  }
1101
1254
  export interface GetLabsAgentResponse {
1102
1255
  agent: LabsAgentResponse;
1256
+ runtime?: Record<string, unknown>;
1257
+ widget?: Record<string, unknown>;
1258
+ readiness?: LabsAgentReadiness;
1103
1259
  }
1104
1260
  export interface STTResult {
1105
1261
  transcript: string;
@@ -1394,6 +1550,8 @@ export declare class SupafoneLabs {
1394
1550
  request<T>(method: string, path: string, body?: unknown, useSession?: boolean): Promise<T>;
1395
1551
  /** @internal Authenticated JSON request to the Supafone app API (`/api/v1/labs/*`). */
1396
1552
  requestSupafoneApi<T>(method: string, path: string, body?: unknown): Promise<T>;
1553
+ /** @internal Multipart upload to the Supafone product API with the Labs key. */
1554
+ requestSupafoneUpload<T>(path: string, file: Uint8Array | ArrayBuffer | Blob, filename: string): Promise<T>;
1397
1555
  /** @internal Authenticated binary request to the Supafone app API. */
1398
1556
  requestSupafoneBinary(path: string): Promise<LabsVoicePreview>;
1399
1557
  /**
@@ -1625,6 +1783,14 @@ export interface CampaignLiveView {
1625
1783
  portal_url: string;
1626
1784
  stats?: Record<string, unknown> | null;
1627
1785
  }
1786
+ export interface CampaignCreateInput {
1787
+ name?: string;
1788
+ goal?: string;
1789
+ agentId?: string;
1790
+ accountId?: string;
1791
+ settings?: Record<string, unknown>;
1792
+ outboundCallMode?: OutboundCallModeConfig;
1793
+ }
1628
1794
  export interface CampaignUpdateInput {
1629
1795
  name?: string;
1630
1796
  goal?: string;
@@ -1636,6 +1802,7 @@ export interface CampaignUpdateInput {
1636
1802
  delay_hours: number;
1637
1803
  }[];
1638
1804
  settings?: Record<string, unknown>;
1805
+ outboundCallMode?: OutboundCallModeConfig;
1639
1806
  }
1640
1807
  export interface BrandScanResult {
1641
1808
  url: string;
@@ -1699,12 +1866,7 @@ declare class CampaignsNamespace {
1699
1866
  }): Promise<{
1700
1867
  campaigns: CampaignSummary[];
1701
1868
  }>;
1702
- create(opts?: {
1703
- name?: string;
1704
- goal?: string;
1705
- agentId?: string;
1706
- accountId?: string;
1707
- }): Promise<{
1869
+ create(opts?: CampaignCreateInput): Promise<{
1708
1870
  campaign: CampaignSummary;
1709
1871
  }>;
1710
1872
  get(campaignId: string): Promise<{
@@ -1912,6 +2074,32 @@ declare class LabsAgentsNamespace {
1912
2074
  list(opts?: ListLabsAgentsOptions): Promise<ListLabsAgentsResponse>;
1913
2075
  /** Fetch one durable agent by key. */
1914
2076
  get(agentKey: string, opts?: GetLabsAgentOptions): Promise<GetLabsAgentResponse>;
2077
+ /** Update an existing durable agent without changing omitted fields. */
2078
+ update(agentKey: string, input: UpdateLabsAgentRequest, opts?: GetLabsAgentOptions): Promise<LabsAgentLifecycleResponse>;
2079
+ /** Scrape a website and rebuild this agent's managed knowledge corpus. */
2080
+ syncKnowledge(agentId: string, input?: LabsKnowledgeSyncRequest): Promise<LabsKnowledgeSyncResponse>;
2081
+ /** Remove website knowledge while preserving uploaded documents and notes. */
2082
+ detachWebsiteKnowledge(agentId: string): Promise<LabsKnowledgeSyncResponse>;
2083
+ /** Ask the managed server whether this agent is ready to activate. */
2084
+ readiness(agentKey: string, opts?: GetLabsAgentOptions): Promise<LabsAgentReadiness>;
2085
+ /** Activate a ready hosted agent. */
2086
+ activate(agentKey: string, opts?: GetLabsAgentOptions): Promise<LabsAgentLifecycleResponse>;
2087
+ /** Pause a hosted agent without deleting it. */
2088
+ pause(agentKey: string, opts?: GetLabsAgentOptions): Promise<LabsAgentLifecycleResponse>;
2089
+ /** Start the native provider-neutral browser voice session for this agent. */
2090
+ startWebRtcCall(agentId: string): Promise<WebRtcCallResult>;
2091
+ /** Rebuild retrieval data from the agent's already saved knowledge sources. */
2092
+ reindexKnowledge(agentId: string): Promise<Record<string, unknown>>;
2093
+ /** Upload and index a txt, md, csv, or PDF knowledge document. */
2094
+ uploadKnowledgeDocument(agentId: string, file: Uint8Array | ArrayBuffer | Blob, filename: string): Promise<{
2095
+ agent: LabsAgentResponse;
2096
+ }>;
2097
+ /** Delete one document and rebuild the agent's managed corpus. */
2098
+ deleteKnowledgeDocument(agentId: string, documentId: string): Promise<{
2099
+ agent: LabsAgentResponse;
2100
+ }>;
2101
+ /** Ask a grounded question against the same corpus used during calls. */
2102
+ chatKnowledge(agentId: string, message: string, history?: LabsKnowledgeChatTurn[]): Promise<LabsKnowledgeChatResponse>;
1915
2103
  /** Delete an agent. Optionally ask the backend to release assigned numbers. */
1916
2104
  delete(agentKey: string, opts?: DeleteLabsAgentOptions): Promise<DeleteLabsAgentResponse>;
1917
2105
  }