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/README.md +24 -0
- package/dist/cjs/index.d.ts +194 -6
- package/dist/cjs/index.js +315 -6
- package/dist/index.d.ts +194 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +312 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +569 -5
package/src/index.ts
CHANGED
|
@@ -287,16 +287,38 @@ export interface LabsVoiceProviderTraits {
|
|
|
287
287
|
is_owner?: boolean | null;
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
+
/** Stable provider presentation metadata returned by the hosted catalog. */
|
|
291
|
+
export interface LabsVoiceProviderBrand {
|
|
292
|
+
/** Canonical provider key used for filtering and grouping. */
|
|
293
|
+
key: string;
|
|
294
|
+
/** Human-readable provider/company name. */
|
|
295
|
+
name: string;
|
|
296
|
+
/** Stable local asset slug when Supafone has an official provider mark. */
|
|
297
|
+
logo_slug?: string | null;
|
|
298
|
+
/** Absolute, browser-ready provider logo URL. */
|
|
299
|
+
logo_url?: string | null;
|
|
300
|
+
/** Provider's official product or company website. */
|
|
301
|
+
website_url?: string | null;
|
|
302
|
+
}
|
|
303
|
+
|
|
290
304
|
export interface LabsVoiceCatalogItem {
|
|
291
305
|
/** Stable runtime reference, including provider prefix when required. */
|
|
292
306
|
id: string;
|
|
293
307
|
voice_id: string;
|
|
294
308
|
/** Human-readable provider display name. */
|
|
295
309
|
provider: string;
|
|
310
|
+
/** Canonical brand display name for the synthesis provider. */
|
|
311
|
+
provider_label?: string;
|
|
312
|
+
/** Browser-ready official provider mark, or null for an unknown provider. */
|
|
313
|
+
provider_logo_url?: string | null;
|
|
314
|
+
/** Branding for the provider that renders the voice. */
|
|
315
|
+
provider_brand?: LabsVoiceProviderBrand;
|
|
296
316
|
/** Canonical SDK/runtime provider key. */
|
|
297
317
|
provider_key: string;
|
|
298
318
|
/** Runtime adapter used to place the voice on an Ultravox call. */
|
|
299
319
|
runtime_provider_key: string;
|
|
320
|
+
/** Branding for the realtime runtime, which can differ from synthesis. */
|
|
321
|
+
runtime_provider_brand?: LabsVoiceProviderBrand;
|
|
300
322
|
/** Actual TTS engine behind the voice (can differ for Ultravox built-ins). */
|
|
301
323
|
synthesis_provider_key: string;
|
|
302
324
|
/** Raw identifier used by the provider API. */
|
|
@@ -662,6 +684,192 @@ export interface LabsUltravoxRuntime {
|
|
|
662
684
|
[extra: string]: unknown;
|
|
663
685
|
}
|
|
664
686
|
|
|
687
|
+
export interface OutboundCallModeObservability {
|
|
688
|
+
enabled?: boolean;
|
|
689
|
+
includeTrigger?: boolean;
|
|
690
|
+
includeTransitions?: boolean;
|
|
691
|
+
includeTerminationReason?: boolean;
|
|
692
|
+
metadata?: Record<string, unknown>;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/** Public, provider-neutral controls for temporary outbound IVR navigation. */
|
|
696
|
+
export interface OutboundCallModeConfig {
|
|
697
|
+
enabled?: boolean;
|
|
698
|
+
autoDetect?: boolean;
|
|
699
|
+
dtmfToolEnabled?: boolean;
|
|
700
|
+
maxDurationSeconds?: number;
|
|
701
|
+
maxKeypresses?: number;
|
|
702
|
+
repeatedMenuLimit?: number;
|
|
703
|
+
noProgressTimeoutSeconds?: number;
|
|
704
|
+
humanDetectionEnabled?: boolean;
|
|
705
|
+
resumeOnHuman?: boolean;
|
|
706
|
+
observability?: OutboundCallModeObservability;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/** Capabilities positively reported by the active telephony/runtime adapter. */
|
|
710
|
+
export interface OutboundCallModeCapabilities {
|
|
711
|
+
provider?: string;
|
|
712
|
+
dtmf?: boolean;
|
|
713
|
+
statePersistence?: boolean;
|
|
714
|
+
humanDetection?: boolean;
|
|
715
|
+
observability?: boolean;
|
|
716
|
+
metadata?: Record<string, unknown>;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
export interface OutboundCallModeReadiness {
|
|
720
|
+
ready: boolean;
|
|
721
|
+
status: "disabled" | "ready" | "unknown" | "unsupported";
|
|
722
|
+
provider: string;
|
|
723
|
+
transportFamily: string;
|
|
724
|
+
requiredCapabilities: string[];
|
|
725
|
+
missingCapabilities: string[];
|
|
726
|
+
unsupportedCapabilities: string[];
|
|
727
|
+
reasons: string[];
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
export const OUTBOUND_CALL_MODE_BOUNDS = {
|
|
731
|
+
maxDurationSeconds: { default: 180, min: 30, max: 900 },
|
|
732
|
+
maxKeypresses: { default: 12, min: 1, max: 64 },
|
|
733
|
+
repeatedMenuLimit: { default: 3, min: 1, max: 10 },
|
|
734
|
+
noProgressTimeoutSeconds: { default: 30, min: 5, max: 120 },
|
|
735
|
+
} as const;
|
|
736
|
+
|
|
737
|
+
export const OUTBOUND_CALL_MODE_DEFAULTS = {
|
|
738
|
+
enabled: false,
|
|
739
|
+
autoDetect: true,
|
|
740
|
+
dtmfToolEnabled: true,
|
|
741
|
+
maxDurationSeconds: 180,
|
|
742
|
+
maxKeypresses: 12,
|
|
743
|
+
repeatedMenuLimit: 3,
|
|
744
|
+
noProgressTimeoutSeconds: 30,
|
|
745
|
+
humanDetectionEnabled: true,
|
|
746
|
+
resumeOnHuman: true,
|
|
747
|
+
observability: {
|
|
748
|
+
enabled: true,
|
|
749
|
+
includeTrigger: true,
|
|
750
|
+
includeTransitions: true,
|
|
751
|
+
includeTerminationReason: true,
|
|
752
|
+
},
|
|
753
|
+
} as const;
|
|
754
|
+
|
|
755
|
+
export interface OutboundCallModeProviderProfile {
|
|
756
|
+
contractSupported: boolean;
|
|
757
|
+
execution: "adapter_runtime_dependent";
|
|
758
|
+
capabilityPolicy: "fail_closed";
|
|
759
|
+
transportFamily: string;
|
|
760
|
+
providers: readonly string[];
|
|
761
|
+
requiredCapabilities: readonly string[];
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
const OUTBOUND_PROVIDER_PROFILE_BASE = {
|
|
765
|
+
contractSupported: true,
|
|
766
|
+
execution: "adapter_runtime_dependent",
|
|
767
|
+
capabilityPolicy: "fail_closed",
|
|
768
|
+
requiredCapabilities: ["dtmf", "state_persistence", "human_detection"],
|
|
769
|
+
} as const;
|
|
770
|
+
|
|
771
|
+
export const OUTBOUND_CALL_MODE_PROVIDER_MATRIX: Record<
|
|
772
|
+
string,
|
|
773
|
+
OutboundCallModeProviderProfile
|
|
774
|
+
> = {
|
|
775
|
+
supafoneManaged: {
|
|
776
|
+
...OUTBOUND_PROVIDER_PROFILE_BASE,
|
|
777
|
+
transportFamily: "supafone_managed",
|
|
778
|
+
providers: ["supafone", "supafone_managed", "managed"],
|
|
779
|
+
},
|
|
780
|
+
twilio: {
|
|
781
|
+
...OUTBOUND_PROVIDER_PROFILE_BASE,
|
|
782
|
+
transportFamily: "twilio",
|
|
783
|
+
providers: ["twilio", "byo_twilio"],
|
|
784
|
+
},
|
|
785
|
+
telnyx: {
|
|
786
|
+
...OUTBOUND_PROVIDER_PROFILE_BASE,
|
|
787
|
+
transportFamily: "telnyx",
|
|
788
|
+
providers: ["telnyx", "byo_telnyx"],
|
|
789
|
+
},
|
|
790
|
+
plivo: {
|
|
791
|
+
...OUTBOUND_PROVIDER_PROFILE_BASE,
|
|
792
|
+
transportFamily: "plivo",
|
|
793
|
+
providers: ["plivo", "byo_plivo"],
|
|
794
|
+
},
|
|
795
|
+
signalWire: {
|
|
796
|
+
...OUTBOUND_PROVIDER_PROFILE_BASE,
|
|
797
|
+
transportFamily: "signalwire",
|
|
798
|
+
providers: ["signalwire", "signal_wire", "byo_signalwire"],
|
|
799
|
+
},
|
|
800
|
+
sipByoc: {
|
|
801
|
+
...OUTBOUND_PROVIDER_PROFILE_BASE,
|
|
802
|
+
transportFamily: "sip_byoc",
|
|
803
|
+
providers: ["sip", "custom_sip", "byo_sip", "byoc", "sip_byoc"],
|
|
804
|
+
},
|
|
805
|
+
};
|
|
806
|
+
|
|
807
|
+
export function outboundCallModeProviderProfile(provider: string): OutboundCallModeProviderProfile {
|
|
808
|
+
const normalized = String(provider ?? "").trim().toLowerCase();
|
|
809
|
+
for (const profile of Object.values(OUTBOUND_CALL_MODE_PROVIDER_MATRIX)) {
|
|
810
|
+
if (profile.providers.includes(normalized)) {
|
|
811
|
+
return { ...profile, providers: [...profile.providers] };
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
return {
|
|
815
|
+
contractSupported: false,
|
|
816
|
+
execution: "adapter_runtime_dependent",
|
|
817
|
+
capabilityPolicy: "fail_closed",
|
|
818
|
+
transportFamily: "unknown",
|
|
819
|
+
providers: normalized ? [normalized] : [],
|
|
820
|
+
requiredCapabilities: ["dtmf", "state_persistence", "human_detection"],
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
export function outboundCallModeReadiness(
|
|
825
|
+
config?: OutboundCallModeConfig,
|
|
826
|
+
capabilities?: OutboundCallModeCapabilities,
|
|
827
|
+
): OutboundCallModeReadiness {
|
|
828
|
+
const provider = String(capabilities?.provider ?? "");
|
|
829
|
+
const profile = outboundCallModeProviderProfile(provider);
|
|
830
|
+
const normalized = outboundCallModePayload(config ?? {});
|
|
831
|
+
if (normalized.enabled !== true) {
|
|
832
|
+
return {
|
|
833
|
+
ready: true,
|
|
834
|
+
status: "disabled",
|
|
835
|
+
provider,
|
|
836
|
+
transportFamily: profile.transportFamily,
|
|
837
|
+
requiredCapabilities: [],
|
|
838
|
+
missingCapabilities: [],
|
|
839
|
+
unsupportedCapabilities: [],
|
|
840
|
+
reasons: [],
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
const requiredCapabilities = [...(normalized.required_capabilities as string[])];
|
|
845
|
+
const reported: Record<string, boolean | undefined> = {
|
|
846
|
+
dtmf: capabilities?.dtmf,
|
|
847
|
+
state_persistence: capabilities?.statePersistence,
|
|
848
|
+
human_detection: capabilities?.humanDetection,
|
|
849
|
+
observability: capabilities?.observability,
|
|
850
|
+
};
|
|
851
|
+
const missingCapabilities = requiredCapabilities.filter((key) => reported[key] === undefined);
|
|
852
|
+
const unsupportedCapabilities = requiredCapabilities.filter((key) => reported[key] === false);
|
|
853
|
+
const status = unsupportedCapabilities.length
|
|
854
|
+
? "unsupported"
|
|
855
|
+
: missingCapabilities.length
|
|
856
|
+
? "unknown"
|
|
857
|
+
: "ready";
|
|
858
|
+
const reasons = unsupportedCapabilities.length
|
|
859
|
+
? unsupportedCapabilities.map((key) => `Adapter reports ${key} is unsupported.`)
|
|
860
|
+
: missingCapabilities.map((key) => `Adapter did not report ${key} support.`);
|
|
861
|
+
return {
|
|
862
|
+
ready: status === "ready",
|
|
863
|
+
status,
|
|
864
|
+
provider,
|
|
865
|
+
transportFamily: profile.transportFamily,
|
|
866
|
+
requiredCapabilities,
|
|
867
|
+
missingCapabilities,
|
|
868
|
+
unsupportedCapabilities,
|
|
869
|
+
reasons,
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
|
|
665
873
|
export interface CreateLabsAgentRequest {
|
|
666
874
|
agencyId?: string;
|
|
667
875
|
agency_id?: string;
|
|
@@ -746,6 +954,9 @@ export interface CreateLabsAgentRequest {
|
|
|
746
954
|
voice_watcher?: boolean;
|
|
747
955
|
voiceWatcherModel?: string;
|
|
748
956
|
voice_watcher_model?: string;
|
|
957
|
+
/** Opt-in outbound IVR mode. Execution depends on adapter-reported readiness. */
|
|
958
|
+
outboundCallMode?: OutboundCallModeConfig;
|
|
959
|
+
outbound_call_mode?: OutboundCallModeConfig;
|
|
749
960
|
metadata?: Record<string, unknown>;
|
|
750
961
|
}
|
|
751
962
|
|
|
@@ -772,6 +983,49 @@ export interface DeleteLabsAgentResponse {
|
|
|
772
983
|
[extra: string]: unknown;
|
|
773
984
|
}
|
|
774
985
|
|
|
986
|
+
export type LabsAgentLifecycle = "draft" | "active" | "paused" | "archived" | string;
|
|
987
|
+
|
|
988
|
+
export interface LabsAgentReadiness {
|
|
989
|
+
ready: boolean;
|
|
990
|
+
status?: string;
|
|
991
|
+
checks?: Array<Record<string, unknown>>;
|
|
992
|
+
[extra: string]: unknown;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
export type UpdateLabsAgentRequest = Partial<CreateLabsAgentRequest>;
|
|
996
|
+
|
|
997
|
+
export interface LabsAgentLifecycleResponse {
|
|
998
|
+
success?: boolean;
|
|
999
|
+
agent?: LabsAgentResponse;
|
|
1000
|
+
lifecycle?: LabsAgentLifecycle;
|
|
1001
|
+
readiness?: LabsAgentReadiness;
|
|
1002
|
+
[extra: string]: unknown;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
export interface LabsKnowledgeSyncRequest {
|
|
1006
|
+
websiteUrl?: string;
|
|
1007
|
+
website_url?: string;
|
|
1008
|
+
url?: string;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
export interface LabsKnowledgeSyncResponse {
|
|
1012
|
+
agent: LabsAgentResponse;
|
|
1013
|
+
chunks_indexed?: number;
|
|
1014
|
+
scraped_ok?: boolean;
|
|
1015
|
+
last_error?: string | null;
|
|
1016
|
+
website_detached?: boolean;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
export interface LabsKnowledgeChatTurn {
|
|
1020
|
+
role: "user" | "assistant";
|
|
1021
|
+
content: string;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
export interface LabsKnowledgeChatResponse {
|
|
1025
|
+
reply: string;
|
|
1026
|
+
sources: string[];
|
|
1027
|
+
}
|
|
1028
|
+
|
|
775
1029
|
export interface LabsCapabilitiesResponse {
|
|
776
1030
|
product: string;
|
|
777
1031
|
api_namespace: string;
|
|
@@ -826,7 +1080,10 @@ export interface LabsVoiceListResponse {
|
|
|
826
1080
|
configured: boolean;
|
|
827
1081
|
voice_count: number;
|
|
828
1082
|
error?: string | null;
|
|
1083
|
+
brand?: LabsVoiceProviderBrand;
|
|
829
1084
|
}>;
|
|
1085
|
+
/** Distinct synthesis-provider brands represented in the complete result. */
|
|
1086
|
+
provider_brands?: LabsVoiceProviderBrand[];
|
|
830
1087
|
provider_accounts?: Record<string, unknown>;
|
|
831
1088
|
errors?: Record<string, string>;
|
|
832
1089
|
catalog?: {
|
|
@@ -1151,6 +1408,8 @@ export interface LabsAgentResponse {
|
|
|
1151
1408
|
display_name?: string;
|
|
1152
1409
|
runtime_mode?: string;
|
|
1153
1410
|
preset_key?: string;
|
|
1411
|
+
status?: string;
|
|
1412
|
+
lifecycle?: LabsAgentLifecycle;
|
|
1154
1413
|
profile?: Record<string, unknown>;
|
|
1155
1414
|
runtime?: Record<string, unknown>;
|
|
1156
1415
|
[extra: string]: unknown;
|
|
@@ -1186,6 +1445,9 @@ export interface ListLabsAgentsResponse {
|
|
|
1186
1445
|
|
|
1187
1446
|
export interface GetLabsAgentResponse {
|
|
1188
1447
|
agent: LabsAgentResponse;
|
|
1448
|
+
runtime?: Record<string, unknown>;
|
|
1449
|
+
widget?: Record<string, unknown>;
|
|
1450
|
+
readiness?: LabsAgentReadiness;
|
|
1189
1451
|
}
|
|
1190
1452
|
|
|
1191
1453
|
export interface STTResult {
|
|
@@ -1729,6 +1991,37 @@ export class SupafoneLabs {
|
|
|
1729
1991
|
}
|
|
1730
1992
|
}
|
|
1731
1993
|
|
|
1994
|
+
/** @internal Multipart upload to the Supafone product API with the Labs key. */
|
|
1995
|
+
async requestSupafoneUpload<T>(
|
|
1996
|
+
path: string,
|
|
1997
|
+
file: Uint8Array | ArrayBuffer | Blob,
|
|
1998
|
+
filename: string,
|
|
1999
|
+
): Promise<T> {
|
|
2000
|
+
const ctrl = new AbortController();
|
|
2001
|
+
const timer = setTimeout(() => ctrl.abort(), Math.max(this.timeoutMs, 120_000));
|
|
2002
|
+
try {
|
|
2003
|
+
const safeFilename = filename.replace(/["\\\r\n]/g, "").slice(0, 120) || "document.txt";
|
|
2004
|
+
const blob = file instanceof Blob ? file : new Blob([file as BlobPart]);
|
|
2005
|
+
const form = new FormData();
|
|
2006
|
+
form.append("file", blob, safeFilename);
|
|
2007
|
+
const res = await fetch(this.supafoneApiBaseUrl + path, {
|
|
2008
|
+
method: "POST",
|
|
2009
|
+
signal: ctrl.signal,
|
|
2010
|
+
headers: { Authorization: `Bearer ${this.supafoneApiKey}` },
|
|
2011
|
+
body: form,
|
|
2012
|
+
});
|
|
2013
|
+
const text = await res.text();
|
|
2014
|
+
const parsed = text ? safeJson(text) : {};
|
|
2015
|
+
if (!res.ok) {
|
|
2016
|
+
const detail = (parsed as { detail?: string })?.detail ?? text ?? `HTTP ${res.status}`;
|
|
2017
|
+
throw new SupafoneLabsError(`POST ${path}: ${detail}`, res.status, parsed);
|
|
2018
|
+
}
|
|
2019
|
+
return parsed as T;
|
|
2020
|
+
} finally {
|
|
2021
|
+
clearTimeout(timer);
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
|
|
1732
2025
|
/** @internal Authenticated binary request to the Supafone app API. */
|
|
1733
2026
|
async requestSupafoneBinary(path: string): Promise<LabsVoicePreview> {
|
|
1734
2027
|
const ctrl = new AbortController();
|
|
@@ -2371,6 +2664,15 @@ export interface CampaignLiveView {
|
|
|
2371
2664
|
stats?: Record<string, unknown> | null;
|
|
2372
2665
|
}
|
|
2373
2666
|
|
|
2667
|
+
export interface CampaignCreateInput {
|
|
2668
|
+
name?: string;
|
|
2669
|
+
goal?: string;
|
|
2670
|
+
agentId?: string;
|
|
2671
|
+
accountId?: string;
|
|
2672
|
+
settings?: Record<string, unknown>;
|
|
2673
|
+
outboundCallMode?: OutboundCallModeConfig;
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2374
2676
|
export interface CampaignUpdateInput {
|
|
2375
2677
|
name?: string;
|
|
2376
2678
|
goal?: string;
|
|
@@ -2379,6 +2681,7 @@ export interface CampaignUpdateInput {
|
|
|
2379
2681
|
emailBody?: string;
|
|
2380
2682
|
cadence?: { channel: "voice" | "email"; delay_hours: number }[];
|
|
2381
2683
|
settings?: Record<string, unknown>;
|
|
2684
|
+
outboundCallMode?: OutboundCallModeConfig;
|
|
2382
2685
|
}
|
|
2383
2686
|
|
|
2384
2687
|
export interface BrandScanResult {
|
|
@@ -2443,13 +2746,21 @@ class CampaignsNamespace {
|
|
|
2443
2746
|
return this.sm.requestAccountApi("GET", `/api/v1/campaigns${query}`);
|
|
2444
2747
|
}
|
|
2445
2748
|
|
|
2446
|
-
create(opts:
|
|
2447
|
-
|
|
2749
|
+
async create(opts: CampaignCreateInput = {}): Promise<{ campaign: CampaignSummary }> {
|
|
2750
|
+
const created = await this.sm.requestAccountApi<{ campaign: CampaignSummary }>("POST", "/api/v1/campaigns", compact({
|
|
2448
2751
|
name: opts.name ?? "New campaign",
|
|
2449
2752
|
goal: opts.goal ?? "book",
|
|
2450
2753
|
agent_id: opts.agentId,
|
|
2451
2754
|
account_id: opts.accountId,
|
|
2452
2755
|
}));
|
|
2756
|
+
const settings = campaignSettingsPayload(opts.settings, opts.outboundCallMode);
|
|
2757
|
+
if (!Object.keys(settings).length) return created;
|
|
2758
|
+
if (!created.campaign?.id) {
|
|
2759
|
+
throw new SupafoneLabsError(
|
|
2760
|
+
"Campaign was created but no campaign id was returned; outbound call mode was not saved.",
|
|
2761
|
+
);
|
|
2762
|
+
}
|
|
2763
|
+
return this.update(created.campaign.id, { settings });
|
|
2453
2764
|
}
|
|
2454
2765
|
|
|
2455
2766
|
get(campaignId: string): Promise<{ campaign: CampaignSummary }> {
|
|
@@ -2457,6 +2768,7 @@ class CampaignsNamespace {
|
|
|
2457
2768
|
}
|
|
2458
2769
|
|
|
2459
2770
|
update(campaignId: string, input: CampaignUpdateInput): Promise<{ campaign: CampaignSummary }> {
|
|
2771
|
+
const settings = campaignSettingsPayload(input.settings, input.outboundCallMode);
|
|
2460
2772
|
const payload = compact({
|
|
2461
2773
|
name: input.name,
|
|
2462
2774
|
goal: input.goal,
|
|
@@ -2464,10 +2776,12 @@ class CampaignsNamespace {
|
|
|
2464
2776
|
email_subject: input.emailSubject,
|
|
2465
2777
|
email_body: input.emailBody,
|
|
2466
2778
|
cadence: input.cadence,
|
|
2467
|
-
settings:
|
|
2779
|
+
settings: Object.keys(settings).length ? settings : undefined,
|
|
2468
2780
|
});
|
|
2469
2781
|
if (!Object.keys(payload as Record<string, unknown>).length) {
|
|
2470
|
-
throw new SupafoneLabsError(
|
|
2782
|
+
throw new SupafoneLabsError(
|
|
2783
|
+
"Nothing to update — pass name, goal, agentId, emailSubject, emailBody, cadence, settings, or outboundCallMode",
|
|
2784
|
+
);
|
|
2471
2785
|
}
|
|
2472
2786
|
return this.sm.requestAccountApi("PUT", `/api/v1/campaigns/${encodeURIComponent(campaignId)}`, payload);
|
|
2473
2787
|
}
|
|
@@ -2903,6 +3217,131 @@ class LabsAgentsNamespace {
|
|
|
2903
3217
|
);
|
|
2904
3218
|
}
|
|
2905
3219
|
|
|
3220
|
+
/** Update an existing durable agent without changing omitted fields. */
|
|
3221
|
+
update(
|
|
3222
|
+
agentKey: string,
|
|
3223
|
+
input: UpdateLabsAgentRequest,
|
|
3224
|
+
opts: GetLabsAgentOptions = {},
|
|
3225
|
+
): Promise<LabsAgentLifecycleResponse> {
|
|
3226
|
+
const q = new URLSearchParams();
|
|
3227
|
+
if (opts.agencyId) q.set("agency_id", opts.agencyId);
|
|
3228
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
3229
|
+
return this.sm.requestSupafoneApi<LabsAgentLifecycleResponse>(
|
|
3230
|
+
"PATCH",
|
|
3231
|
+
`/api/v1/labs/agents/${encodeURIComponent(agentKey)}${suffix}`,
|
|
3232
|
+
labsAgentUpdatePayload(input),
|
|
3233
|
+
);
|
|
3234
|
+
}
|
|
3235
|
+
|
|
3236
|
+
/** Scrape a website and rebuild this agent's managed knowledge corpus. */
|
|
3237
|
+
syncKnowledge(
|
|
3238
|
+
agentId: string,
|
|
3239
|
+
input: LabsKnowledgeSyncRequest = {},
|
|
3240
|
+
): Promise<LabsKnowledgeSyncResponse> {
|
|
3241
|
+
return this.sm.requestSupafoneApi<LabsKnowledgeSyncResponse>(
|
|
3242
|
+
"POST",
|
|
3243
|
+
`/api/v1/agents/${encodeURIComponent(agentId)}/sync-knowledge`,
|
|
3244
|
+
{ url: input.url ?? input.website_url ?? input.websiteUrl ?? "" },
|
|
3245
|
+
);
|
|
3246
|
+
}
|
|
3247
|
+
|
|
3248
|
+
/** Remove website knowledge while preserving uploaded documents and notes. */
|
|
3249
|
+
detachWebsiteKnowledge(agentId: string): Promise<LabsKnowledgeSyncResponse> {
|
|
3250
|
+
return this.sm.requestSupafoneApi<LabsKnowledgeSyncResponse>(
|
|
3251
|
+
"DELETE",
|
|
3252
|
+
`/api/v1/agents/${encodeURIComponent(agentId)}/knowledge/website`,
|
|
3253
|
+
);
|
|
3254
|
+
}
|
|
3255
|
+
|
|
3256
|
+
/** Ask the managed server whether this agent is ready to activate. */
|
|
3257
|
+
readiness(agentKey: string, opts: GetLabsAgentOptions = {}): Promise<LabsAgentReadiness> {
|
|
3258
|
+
const q = new URLSearchParams();
|
|
3259
|
+
if (opts.agencyId) q.set("agency_id", opts.agencyId);
|
|
3260
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
3261
|
+
return this.sm.requestSupafoneApi<LabsAgentReadiness>(
|
|
3262
|
+
"GET",
|
|
3263
|
+
`/api/v1/labs/agents/${encodeURIComponent(agentKey)}/readiness${suffix}`,
|
|
3264
|
+
);
|
|
3265
|
+
}
|
|
3266
|
+
|
|
3267
|
+
/** Activate a ready hosted agent. */
|
|
3268
|
+
activate(
|
|
3269
|
+
agentKey: string,
|
|
3270
|
+
opts: GetLabsAgentOptions = {},
|
|
3271
|
+
): Promise<LabsAgentLifecycleResponse> {
|
|
3272
|
+
const q = new URLSearchParams();
|
|
3273
|
+
if (opts.agencyId) q.set("agency_id", opts.agencyId);
|
|
3274
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
3275
|
+
return this.sm.requestSupafoneApi<LabsAgentLifecycleResponse>(
|
|
3276
|
+
"POST",
|
|
3277
|
+
`/api/v1/labs/agents/${encodeURIComponent(agentKey)}/activate${suffix}`,
|
|
3278
|
+
);
|
|
3279
|
+
}
|
|
3280
|
+
|
|
3281
|
+
/** Pause a hosted agent without deleting it. */
|
|
3282
|
+
pause(
|
|
3283
|
+
agentKey: string,
|
|
3284
|
+
opts: GetLabsAgentOptions = {},
|
|
3285
|
+
): Promise<LabsAgentLifecycleResponse> {
|
|
3286
|
+
const q = new URLSearchParams();
|
|
3287
|
+
if (opts.agencyId) q.set("agency_id", opts.agencyId);
|
|
3288
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
3289
|
+
return this.sm.requestSupafoneApi<LabsAgentLifecycleResponse>(
|
|
3290
|
+
"POST",
|
|
3291
|
+
`/api/v1/labs/agents/${encodeURIComponent(agentKey)}/pause${suffix}`,
|
|
3292
|
+
);
|
|
3293
|
+
}
|
|
3294
|
+
|
|
3295
|
+
/** Start the native provider-neutral browser voice session for this agent. */
|
|
3296
|
+
startWebRtcCall(agentId: string): Promise<WebRtcCallResult> {
|
|
3297
|
+
return this.sm.startWebRtcCall({ agentId });
|
|
3298
|
+
}
|
|
3299
|
+
|
|
3300
|
+
/** Rebuild retrieval data from the agent's already saved knowledge sources. */
|
|
3301
|
+
reindexKnowledge(agentId: string): Promise<Record<string, unknown>> {
|
|
3302
|
+
return this.sm.requestSupafoneApi(
|
|
3303
|
+
"POST",
|
|
3304
|
+
`/api/v1/agents/${encodeURIComponent(agentId)}/knowledge/reindex`,
|
|
3305
|
+
);
|
|
3306
|
+
}
|
|
3307
|
+
|
|
3308
|
+
/** Upload and index a txt, md, csv, or PDF knowledge document. */
|
|
3309
|
+
uploadKnowledgeDocument(
|
|
3310
|
+
agentId: string,
|
|
3311
|
+
file: Uint8Array | ArrayBuffer | Blob,
|
|
3312
|
+
filename: string,
|
|
3313
|
+
): Promise<{ agent: LabsAgentResponse }> {
|
|
3314
|
+
return this.sm.requestSupafoneUpload(
|
|
3315
|
+
`/api/v1/agents/${encodeURIComponent(agentId)}/knowledge/upload`,
|
|
3316
|
+
file,
|
|
3317
|
+
filename,
|
|
3318
|
+
);
|
|
3319
|
+
}
|
|
3320
|
+
|
|
3321
|
+
/** Delete one document and rebuild the agent's managed corpus. */
|
|
3322
|
+
deleteKnowledgeDocument(
|
|
3323
|
+
agentId: string,
|
|
3324
|
+
documentId: string,
|
|
3325
|
+
): Promise<{ agent: LabsAgentResponse }> {
|
|
3326
|
+
return this.sm.requestSupafoneApi(
|
|
3327
|
+
"DELETE",
|
|
3328
|
+
`/api/v1/agents/${encodeURIComponent(agentId)}/knowledge/docs/${encodeURIComponent(documentId)}`,
|
|
3329
|
+
);
|
|
3330
|
+
}
|
|
3331
|
+
|
|
3332
|
+
/** Ask a grounded question against the same corpus used during calls. */
|
|
3333
|
+
chatKnowledge(
|
|
3334
|
+
agentId: string,
|
|
3335
|
+
message: string,
|
|
3336
|
+
history: LabsKnowledgeChatTurn[] = [],
|
|
3337
|
+
): Promise<LabsKnowledgeChatResponse> {
|
|
3338
|
+
return this.sm.requestSupafoneApi(
|
|
3339
|
+
"POST",
|
|
3340
|
+
`/api/v1/agents/${encodeURIComponent(agentId)}/knowledge-chat`,
|
|
3341
|
+
{ message, history },
|
|
3342
|
+
);
|
|
3343
|
+
}
|
|
3344
|
+
|
|
2906
3345
|
/** Delete an agent. Optionally ask the backend to release assigned numbers. */
|
|
2907
3346
|
delete(agentKey: string, opts: DeleteLabsAgentOptions = {}): Promise<DeleteLabsAgentResponse> {
|
|
2908
3347
|
const q = new URLSearchParams();
|
|
@@ -3493,6 +3932,128 @@ class OptimizerNamespace {
|
|
|
3493
3932
|
}
|
|
3494
3933
|
}
|
|
3495
3934
|
|
|
3935
|
+
function outboundCallModePayload(config: OutboundCallModeConfig): Record<string, unknown> {
|
|
3936
|
+
const enabled = outboundModeBoolean(config.enabled, false, "enabled");
|
|
3937
|
+
if (!enabled) return { version: 1, enabled: false };
|
|
3938
|
+
|
|
3939
|
+
const observabilityInput = config.observability ?? {};
|
|
3940
|
+
if (typeof observabilityInput !== "object" || Array.isArray(observabilityInput)) {
|
|
3941
|
+
throw new SupafoneLabsError("outboundCallMode.observability must be an object");
|
|
3942
|
+
}
|
|
3943
|
+
if (
|
|
3944
|
+
observabilityInput.metadata !== undefined
|
|
3945
|
+
&& (typeof observabilityInput.metadata !== "object"
|
|
3946
|
+
|| observabilityInput.metadata === null
|
|
3947
|
+
|| Array.isArray(observabilityInput.metadata))
|
|
3948
|
+
) {
|
|
3949
|
+
throw new SupafoneLabsError("outboundCallMode.observability.metadata must be an object");
|
|
3950
|
+
}
|
|
3951
|
+
const observability = compact({
|
|
3952
|
+
enabled: outboundModeBoolean(observabilityInput.enabled, true, "observability.enabled"),
|
|
3953
|
+
include_trigger: outboundModeBoolean(
|
|
3954
|
+
observabilityInput.includeTrigger,
|
|
3955
|
+
true,
|
|
3956
|
+
"observability.includeTrigger",
|
|
3957
|
+
),
|
|
3958
|
+
include_transitions: outboundModeBoolean(
|
|
3959
|
+
observabilityInput.includeTransitions,
|
|
3960
|
+
true,
|
|
3961
|
+
"observability.includeTransitions",
|
|
3962
|
+
),
|
|
3963
|
+
include_termination_reason: outboundModeBoolean(
|
|
3964
|
+
observabilityInput.includeTerminationReason,
|
|
3965
|
+
true,
|
|
3966
|
+
"observability.includeTerminationReason",
|
|
3967
|
+
),
|
|
3968
|
+
metadata: observabilityInput.metadata,
|
|
3969
|
+
});
|
|
3970
|
+
const payload: Record<string, unknown> = {
|
|
3971
|
+
version: 1,
|
|
3972
|
+
enabled: true,
|
|
3973
|
+
initial_mode: "mission",
|
|
3974
|
+
ivr_mode: "dynamic",
|
|
3975
|
+
transport_scope: "provider_agnostic",
|
|
3976
|
+
capability_policy: "fail_closed",
|
|
3977
|
+
auto_detect: outboundModeBoolean(config.autoDetect, true, "autoDetect"),
|
|
3978
|
+
dtmf_tool_enabled: outboundModeBoolean(config.dtmfToolEnabled, true, "dtmfToolEnabled"),
|
|
3979
|
+
max_duration_seconds: outboundModeInteger(
|
|
3980
|
+
config.maxDurationSeconds,
|
|
3981
|
+
"maxDurationSeconds",
|
|
3982
|
+
),
|
|
3983
|
+
max_keypresses: outboundModeInteger(config.maxKeypresses, "maxKeypresses"),
|
|
3984
|
+
repeated_menu_limit: outboundModeInteger(config.repeatedMenuLimit, "repeatedMenuLimit"),
|
|
3985
|
+
no_progress_timeout_seconds: outboundModeInteger(
|
|
3986
|
+
config.noProgressTimeoutSeconds,
|
|
3987
|
+
"noProgressTimeoutSeconds",
|
|
3988
|
+
),
|
|
3989
|
+
human_detection_enabled: outboundModeBoolean(
|
|
3990
|
+
config.humanDetectionEnabled,
|
|
3991
|
+
true,
|
|
3992
|
+
"humanDetectionEnabled",
|
|
3993
|
+
),
|
|
3994
|
+
resume_on_human: outboundModeBoolean(config.resumeOnHuman, true, "resumeOnHuman"),
|
|
3995
|
+
observability,
|
|
3996
|
+
};
|
|
3997
|
+
payload.required_capabilities = outboundCallModeRequiredCapabilities(payload);
|
|
3998
|
+
return payload;
|
|
3999
|
+
}
|
|
4000
|
+
|
|
4001
|
+
function outboundModeBoolean(value: boolean | undefined, fallback: boolean, key: string): boolean {
|
|
4002
|
+
if (value === undefined) return fallback;
|
|
4003
|
+
if (typeof value !== "boolean") {
|
|
4004
|
+
throw new SupafoneLabsError(`outboundCallMode.${key} must be a boolean`);
|
|
4005
|
+
}
|
|
4006
|
+
return value;
|
|
4007
|
+
}
|
|
4008
|
+
|
|
4009
|
+
function outboundModeInteger(
|
|
4010
|
+
value: number | undefined,
|
|
4011
|
+
key: keyof typeof OUTBOUND_CALL_MODE_BOUNDS,
|
|
4012
|
+
): number {
|
|
4013
|
+
const bounds = OUTBOUND_CALL_MODE_BOUNDS[key];
|
|
4014
|
+
const normalized = value ?? bounds.default;
|
|
4015
|
+
if (!Number.isInteger(normalized)) {
|
|
4016
|
+
throw new SupafoneLabsError(`outboundCallMode.${key} must be an integer`);
|
|
4017
|
+
}
|
|
4018
|
+
if (normalized < bounds.min || normalized > bounds.max) {
|
|
4019
|
+
throw new SupafoneLabsError(
|
|
4020
|
+
`outboundCallMode.${key} must be between ${bounds.min} and ${bounds.max}`,
|
|
4021
|
+
);
|
|
4022
|
+
}
|
|
4023
|
+
return normalized;
|
|
4024
|
+
}
|
|
4025
|
+
|
|
4026
|
+
function outboundCallModeRequiredCapabilities(payload: Record<string, unknown>): string[] {
|
|
4027
|
+
const required = ["state_persistence"];
|
|
4028
|
+
if (payload.dtmf_tool_enabled !== false) required.unshift("dtmf");
|
|
4029
|
+
if (payload.human_detection_enabled !== false || payload.resume_on_human !== false) {
|
|
4030
|
+
required.push("human_detection");
|
|
4031
|
+
}
|
|
4032
|
+
const observability = payload.observability as Record<string, unknown> | undefined;
|
|
4033
|
+
if (observability?.enabled === true) required.push("observability");
|
|
4034
|
+
return required;
|
|
4035
|
+
}
|
|
4036
|
+
|
|
4037
|
+
function labsAgentMetadataPayload(input: CreateLabsAgentRequest): Record<string, unknown> | undefined {
|
|
4038
|
+
const metadata = { ...(input.metadata ?? {}) };
|
|
4039
|
+
const mode = input.outboundCallMode ?? input.outbound_call_mode;
|
|
4040
|
+
if (mode !== undefined) metadata.outbound_call_mode = outboundCallModePayload(mode);
|
|
4041
|
+
return Object.keys(metadata).length ? metadata : undefined;
|
|
4042
|
+
}
|
|
4043
|
+
|
|
4044
|
+
function labsAgentUpdatePayload(input: UpdateLabsAgentRequest): Record<string, unknown> {
|
|
4045
|
+
return labsAgentPayload(input as CreateLabsAgentRequest);
|
|
4046
|
+
}
|
|
4047
|
+
|
|
4048
|
+
function campaignSettingsPayload(
|
|
4049
|
+
settings?: Record<string, unknown>,
|
|
4050
|
+
mode?: OutboundCallModeConfig,
|
|
4051
|
+
): Record<string, unknown> {
|
|
4052
|
+
const payload = { ...(settings ?? {}) };
|
|
4053
|
+
if (mode !== undefined) payload.outbound_call_mode = outboundCallModePayload(mode);
|
|
4054
|
+
return payload;
|
|
4055
|
+
}
|
|
4056
|
+
|
|
3496
4057
|
function labsAgentPayload(input: CreateLabsAgentRequest): Record<string, unknown> {
|
|
3497
4058
|
const fixedLanguage = input.preferred_language ?? input.preferredLanguage ?? input.language;
|
|
3498
4059
|
return compact({
|
|
@@ -3542,7 +4103,7 @@ function labsAgentPayload(input: CreateLabsAgentRequest): Record<string, unknown
|
|
|
3542
4103
|
ultravox: input.ultravox ? ultravoxPayload(input.ultravox) : undefined,
|
|
3543
4104
|
voice_watcher: input.voice_watcher ?? input.voiceWatcher,
|
|
3544
4105
|
voice_watcher_model: input.voice_watcher_model ?? input.voiceWatcherModel,
|
|
3545
|
-
metadata: input
|
|
4106
|
+
metadata: labsAgentMetadataPayload(input),
|
|
3546
4107
|
});
|
|
3547
4108
|
}
|
|
3548
4109
|
|
|
@@ -3795,6 +4356,9 @@ function languageProfilesPayload(
|
|
|
3795
4356
|
input?: LabsLanguageVoiceProfile[],
|
|
3796
4357
|
): Record<string, unknown>[] | undefined {
|
|
3797
4358
|
if (!Array.isArray(input)) return undefined;
|
|
4359
|
+
if (input.length > 4) {
|
|
4360
|
+
throw new SupafoneLabsError("languageProfiles supports at most four profiles");
|
|
4361
|
+
}
|
|
3798
4362
|
const profiles = input.map((profile) => compact({
|
|
3799
4363
|
language: profile.language,
|
|
3800
4364
|
language_hint: profile.language_hint ?? profile.languageHint,
|