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 CHANGED
@@ -114,6 +114,44 @@ filtering, structured SecondMind directives, and the same configuration model
114
114
  across TypeScript and Python. Fixed language selection does not enable
115
115
  mid-call language or voice switching.
116
116
 
117
+ Agent Factory can opt into managed live language and matching-voice routing
118
+ with one field. It remains absent and disabled for existing agents:
119
+
120
+ ```ts
121
+ const agent = await supafone.labs.agents.createInbound({
122
+ agentKey: "bilingual-intake",
123
+ name: "Bilingual intake",
124
+ languageVoiceRouting: true,
125
+ routingLanguages: ["en-US", "es-MX"], // optional; defaults to English + Spanish
126
+ });
127
+ ```
128
+
129
+ The package sends only these preferences. Supafone's private hosted runtime
130
+ selects compatible live-catalog voices and performs the in-call transition.
131
+ The first configured language owns the opening, and a non-English primary
132
+ greeting is translated during provisioning. See
133
+ [Live Language and Voice Routing](../gitbook/live-language-voice-routing.md).
134
+
135
+ Outbound agents can opt into temporary, bounded phone-tree navigation without
136
+ changing their normal campaign objective:
137
+
138
+ ```ts
139
+ await supafone.labs.agents.createOutbound({
140
+ name: "Benefits verification",
141
+ outboundCallMode: {
142
+ enabled: true,
143
+ maxDurationSeconds: 180,
144
+ maxKeypresses: 12,
145
+ },
146
+ });
147
+ ```
148
+
149
+ The SDK stores this provider-neutral contract and exposes fail-closed adapter
150
+ readiness checks. Supafone-managed, Twilio, Telnyx, Plivo, SignalWire, and
151
+ SIP/BYOC use the same public shape; private detection and DTMF execution stay in
152
+ the managed runtime. See
153
+ [Outbound IVR Call Mode](../docs/outbound-ivr-call-mode.md).
154
+
117
155
  BYOK is advanced and split into three independent lanes:
118
156
 
119
157
  | Lane | Examples |
@@ -210,6 +210,13 @@ export interface LabsVoiceSelection {
210
210
  voice_id?: string;
211
211
  model?: string;
212
212
  }
213
+ /** Public Agent Factory preference for one routed language. */
214
+ export interface LabsLanguageVoiceProfile {
215
+ language: string;
216
+ languageHint?: string;
217
+ language_hint?: string;
218
+ voice?: LabsVoiceSelection;
219
+ }
213
220
  export type LabsVoiceGender = "female" | "male" | "neutral";
214
221
  export interface LabsVoiceLanguage {
215
222
  code: string;
@@ -615,6 +622,95 @@ export interface LabsUltravoxRuntime {
615
622
  sip?: LabsCustomSipConfig;
616
623
  [extra: string]: unknown;
617
624
  }
625
+ export interface OutboundCallModeObservability {
626
+ enabled?: boolean;
627
+ includeTrigger?: boolean;
628
+ includeTransitions?: boolean;
629
+ includeTerminationReason?: boolean;
630
+ metadata?: Record<string, unknown>;
631
+ }
632
+ /** Public, provider-neutral controls for temporary outbound IVR navigation. */
633
+ export interface OutboundCallModeConfig {
634
+ enabled?: boolean;
635
+ autoDetect?: boolean;
636
+ dtmfToolEnabled?: boolean;
637
+ maxDurationSeconds?: number;
638
+ maxKeypresses?: number;
639
+ repeatedMenuLimit?: number;
640
+ noProgressTimeoutSeconds?: number;
641
+ humanDetectionEnabled?: boolean;
642
+ resumeOnHuman?: boolean;
643
+ observability?: OutboundCallModeObservability;
644
+ }
645
+ /** Capabilities positively reported by the active telephony/runtime adapter. */
646
+ export interface OutboundCallModeCapabilities {
647
+ provider?: string;
648
+ dtmf?: boolean;
649
+ statePersistence?: boolean;
650
+ humanDetection?: boolean;
651
+ observability?: boolean;
652
+ metadata?: Record<string, unknown>;
653
+ }
654
+ export interface OutboundCallModeReadiness {
655
+ ready: boolean;
656
+ status: "disabled" | "ready" | "unknown" | "unsupported";
657
+ provider: string;
658
+ transportFamily: string;
659
+ requiredCapabilities: string[];
660
+ missingCapabilities: string[];
661
+ unsupportedCapabilities: string[];
662
+ reasons: string[];
663
+ }
664
+ export declare const OUTBOUND_CALL_MODE_BOUNDS: {
665
+ readonly maxDurationSeconds: {
666
+ readonly default: 180;
667
+ readonly min: 30;
668
+ readonly max: 900;
669
+ };
670
+ readonly maxKeypresses: {
671
+ readonly default: 12;
672
+ readonly min: 1;
673
+ readonly max: 64;
674
+ };
675
+ readonly repeatedMenuLimit: {
676
+ readonly default: 3;
677
+ readonly min: 1;
678
+ readonly max: 10;
679
+ };
680
+ readonly noProgressTimeoutSeconds: {
681
+ readonly default: 30;
682
+ readonly min: 5;
683
+ readonly max: 120;
684
+ };
685
+ };
686
+ export declare const OUTBOUND_CALL_MODE_DEFAULTS: {
687
+ readonly enabled: false;
688
+ readonly autoDetect: true;
689
+ readonly dtmfToolEnabled: true;
690
+ readonly maxDurationSeconds: 180;
691
+ readonly maxKeypresses: 12;
692
+ readonly repeatedMenuLimit: 3;
693
+ readonly noProgressTimeoutSeconds: 30;
694
+ readonly humanDetectionEnabled: true;
695
+ readonly resumeOnHuman: true;
696
+ readonly observability: {
697
+ readonly enabled: true;
698
+ readonly includeTrigger: true;
699
+ readonly includeTransitions: true;
700
+ readonly includeTerminationReason: true;
701
+ };
702
+ };
703
+ export interface OutboundCallModeProviderProfile {
704
+ contractSupported: boolean;
705
+ execution: "adapter_runtime_dependent";
706
+ capabilityPolicy: "fail_closed";
707
+ transportFamily: string;
708
+ providers: readonly string[];
709
+ requiredCapabilities: readonly string[];
710
+ }
711
+ export declare const OUTBOUND_CALL_MODE_PROVIDER_MATRIX: Record<string, OutboundCallModeProviderProfile>;
712
+ export declare function outboundCallModeProviderProfile(provider: string): OutboundCallModeProviderProfile;
713
+ export declare function outboundCallModeReadiness(config?: OutboundCallModeConfig, capabilities?: OutboundCallModeCapabilities): OutboundCallModeReadiness;
618
714
  export interface CreateLabsAgentRequest {
619
715
  agencyId?: string;
620
716
  agency_id?: string;
@@ -668,6 +764,15 @@ export interface CreateLabsAgentRequest {
668
764
  language?: string;
669
765
  preferredLanguage?: string;
670
766
  preferred_language?: string;
767
+ /** Opt in to managed live language and matching-voice routing. Defaults to false. */
768
+ languageVoiceRouting?: boolean;
769
+ language_voice_routing?: boolean;
770
+ /** Optional ordered language list. The first language controls the greeting. */
771
+ routingLanguages?: string[];
772
+ routing_languages?: string[];
773
+ /** Optional per-language voice preferences. Supports two to four profiles. */
774
+ languageProfiles?: LabsLanguageVoiceProfile[];
775
+ language_profiles?: LabsLanguageVoiceProfile[];
671
776
  voice?: LabsVoiceSelection;
672
777
  /** Resolve a real current catalog voice from this plain-language preference. */
673
778
  voicePreference?: LabsVoicePreference;
@@ -690,6 +795,9 @@ export interface CreateLabsAgentRequest {
690
795
  voice_watcher?: boolean;
691
796
  voiceWatcherModel?: string;
692
797
  voice_watcher_model?: string;
798
+ /** Opt-in outbound IVR mode. Execution depends on adapter-reported readiness. */
799
+ outboundCallMode?: OutboundCallModeConfig;
800
+ outbound_call_mode?: OutboundCallModeConfig;
693
801
  metadata?: Record<string, unknown>;
694
802
  }
695
803
  export interface ListLabsAgentsOptions {
@@ -712,6 +820,21 @@ export interface DeleteLabsAgentResponse {
712
820
  released_numbers?: unknown[];
713
821
  [extra: string]: unknown;
714
822
  }
823
+ export type LabsAgentLifecycle = "draft" | "active" | "paused" | "archived" | string;
824
+ export interface LabsAgentReadiness {
825
+ ready: boolean;
826
+ status?: string;
827
+ checks?: Array<Record<string, unknown>>;
828
+ [extra: string]: unknown;
829
+ }
830
+ export type UpdateLabsAgentRequest = Partial<CreateLabsAgentRequest>;
831
+ export interface LabsAgentLifecycleResponse {
832
+ success?: boolean;
833
+ agent?: LabsAgentResponse;
834
+ lifecycle?: LabsAgentLifecycle;
835
+ readiness?: LabsAgentReadiness;
836
+ [extra: string]: unknown;
837
+ }
715
838
  export interface LabsCapabilitiesResponse {
716
839
  product: string;
717
840
  api_namespace: string;
@@ -1052,6 +1175,8 @@ export interface LabsAgentResponse {
1052
1175
  display_name?: string;
1053
1176
  runtime_mode?: string;
1054
1177
  preset_key?: string;
1178
+ status?: string;
1179
+ lifecycle?: LabsAgentLifecycle;
1055
1180
  profile?: Record<string, unknown>;
1056
1181
  runtime?: Record<string, unknown>;
1057
1182
  [extra: string]: unknown;
@@ -1066,6 +1191,16 @@ export interface CreateLabsAgentResponse {
1066
1191
  [extra: string]: unknown;
1067
1192
  };
1068
1193
  call_plan?: LabsCallPlan;
1194
+ language_voice_routing?: {
1195
+ enabled: boolean;
1196
+ voice_routing_enabled: boolean;
1197
+ profiles: Array<Record<string, unknown>>;
1198
+ greeting_translation?: {
1199
+ language: string;
1200
+ language_hint: string;
1201
+ status: "not_needed" | "translated";
1202
+ };
1203
+ };
1069
1204
  [extra: string]: unknown;
1070
1205
  }
1071
1206
  export interface ListLabsAgentsResponse {
@@ -1074,6 +1209,9 @@ export interface ListLabsAgentsResponse {
1074
1209
  }
1075
1210
  export interface GetLabsAgentResponse {
1076
1211
  agent: LabsAgentResponse;
1212
+ runtime?: Record<string, unknown>;
1213
+ widget?: Record<string, unknown>;
1214
+ readiness?: LabsAgentReadiness;
1077
1215
  }
1078
1216
  export interface STTResult {
1079
1217
  transcript: string;
@@ -1599,6 +1737,14 @@ export interface CampaignLiveView {
1599
1737
  portal_url: string;
1600
1738
  stats?: Record<string, unknown> | null;
1601
1739
  }
1740
+ export interface CampaignCreateInput {
1741
+ name?: string;
1742
+ goal?: string;
1743
+ agentId?: string;
1744
+ accountId?: string;
1745
+ settings?: Record<string, unknown>;
1746
+ outboundCallMode?: OutboundCallModeConfig;
1747
+ }
1602
1748
  export interface CampaignUpdateInput {
1603
1749
  name?: string;
1604
1750
  goal?: string;
@@ -1610,6 +1756,7 @@ export interface CampaignUpdateInput {
1610
1756
  delay_hours: number;
1611
1757
  }[];
1612
1758
  settings?: Record<string, unknown>;
1759
+ outboundCallMode?: OutboundCallModeConfig;
1613
1760
  }
1614
1761
  export interface BrandScanResult {
1615
1762
  url: string;
@@ -1673,12 +1820,7 @@ declare class CampaignsNamespace {
1673
1820
  }): Promise<{
1674
1821
  campaigns: CampaignSummary[];
1675
1822
  }>;
1676
- create(opts?: {
1677
- name?: string;
1678
- goal?: string;
1679
- agentId?: string;
1680
- accountId?: string;
1681
- }): Promise<{
1823
+ create(opts?: CampaignCreateInput): Promise<{
1682
1824
  campaign: CampaignSummary;
1683
1825
  }>;
1684
1826
  get(campaignId: string): Promise<{
@@ -1886,6 +2028,14 @@ declare class LabsAgentsNamespace {
1886
2028
  list(opts?: ListLabsAgentsOptions): Promise<ListLabsAgentsResponse>;
1887
2029
  /** Fetch one durable agent by key. */
1888
2030
  get(agentKey: string, opts?: GetLabsAgentOptions): Promise<GetLabsAgentResponse>;
2031
+ /** Update an existing durable agent without changing omitted fields. */
2032
+ update(agentKey: string, input: UpdateLabsAgentRequest, opts?: GetLabsAgentOptions): Promise<LabsAgentLifecycleResponse>;
2033
+ /** Ask the managed server whether this agent is ready to activate. */
2034
+ readiness(agentKey: string, opts?: GetLabsAgentOptions): Promise<LabsAgentReadiness>;
2035
+ /** Activate a ready hosted agent. */
2036
+ activate(agentKey: string, opts?: GetLabsAgentOptions): Promise<LabsAgentLifecycleResponse>;
2037
+ /** Pause a hosted agent without deleting it. */
2038
+ pause(agentKey: string, opts?: GetLabsAgentOptions): Promise<LabsAgentLifecycleResponse>;
1889
2039
  /** Delete an agent. Optionally ask the backend to release assigned numbers. */
1890
2040
  delete(agentKey: string, opts?: DeleteLabsAgentOptions): Promise<DeleteLabsAgentResponse>;
1891
2041
  }
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;
@@ -770,18 +893,26 @@ class CampaignsNamespace {
770
893
  const query = opts.accountId ? `?${new URLSearchParams({ account_id: opts.accountId })}` : "";
771
894
  return this.sm.requestAccountApi("GET", `/api/v1/campaigns${query}`);
772
895
  }
773
- create(opts = {}) {
774
- return this.sm.requestAccountApi("POST", "/api/v1/campaigns", compact({
896
+ async create(opts = {}) {
897
+ const created = await this.sm.requestAccountApi("POST", "/api/v1/campaigns", compact({
775
898
  name: opts.name ?? "New campaign",
776
899
  goal: opts.goal ?? "book",
777
900
  agent_id: opts.agentId,
778
901
  account_id: opts.accountId,
779
902
  }));
903
+ const settings = campaignSettingsPayload(opts.settings, opts.outboundCallMode);
904
+ if (!Object.keys(settings).length)
905
+ return created;
906
+ if (!created.campaign?.id) {
907
+ throw new SupafoneLabsError("Campaign was created but no campaign id was returned; outbound call mode was not saved.");
908
+ }
909
+ return this.update(created.campaign.id, { settings });
780
910
  }
781
911
  get(campaignId) {
782
912
  return this.sm.requestAccountApi("GET", `/api/v1/campaigns/${encodeURIComponent(campaignId)}`);
783
913
  }
784
914
  update(campaignId, input) {
915
+ const settings = campaignSettingsPayload(input.settings, input.outboundCallMode);
785
916
  const payload = compact({
786
917
  name: input.name,
787
918
  goal: input.goal,
@@ -789,10 +920,10 @@ class CampaignsNamespace {
789
920
  email_subject: input.emailSubject,
790
921
  email_body: input.emailBody,
791
922
  cadence: input.cadence,
792
- settings: input.settings,
923
+ settings: Object.keys(settings).length ? settings : undefined,
793
924
  });
794
925
  if (!Object.keys(payload).length) {
795
- throw new SupafoneLabsError("Nothing to update — pass name, goal, agentId, emailSubject, emailBody, cadence, or settings");
926
+ throw new SupafoneLabsError("Nothing to update — pass name, goal, agentId, emailSubject, emailBody, cadence, settings, or outboundCallMode");
796
927
  }
797
928
  return this.sm.requestAccountApi("PUT", `/api/v1/campaigns/${encodeURIComponent(campaignId)}`, payload);
798
929
  }
@@ -1119,6 +1250,38 @@ class LabsAgentsNamespace {
1119
1250
  const suffix = q.toString() ? `?${q}` : "";
1120
1251
  return this.sm.requestSupafoneApi("GET", `/api/v1/labs/agents/${encodeURIComponent(agentKey)}${suffix}`);
1121
1252
  }
1253
+ /** Update an existing durable agent without changing omitted fields. */
1254
+ update(agentKey, input, opts = {}) {
1255
+ const q = new URLSearchParams();
1256
+ if (opts.agencyId)
1257
+ q.set("agency_id", opts.agencyId);
1258
+ const suffix = q.toString() ? `?${q}` : "";
1259
+ return this.sm.requestSupafoneApi("PATCH", `/api/v1/labs/agents/${encodeURIComponent(agentKey)}${suffix}`, labsAgentUpdatePayload(input));
1260
+ }
1261
+ /** Ask the managed server whether this agent is ready to activate. */
1262
+ readiness(agentKey, opts = {}) {
1263
+ const q = new URLSearchParams();
1264
+ if (opts.agencyId)
1265
+ q.set("agency_id", opts.agencyId);
1266
+ const suffix = q.toString() ? `?${q}` : "";
1267
+ return this.sm.requestSupafoneApi("GET", `/api/v1/labs/agents/${encodeURIComponent(agentKey)}/readiness${suffix}`);
1268
+ }
1269
+ /** Activate a ready hosted agent. */
1270
+ activate(agentKey, opts = {}) {
1271
+ const q = new URLSearchParams();
1272
+ if (opts.agencyId)
1273
+ q.set("agency_id", opts.agencyId);
1274
+ const suffix = q.toString() ? `?${q}` : "";
1275
+ return this.sm.requestSupafoneApi("POST", `/api/v1/labs/agents/${encodeURIComponent(agentKey)}/activate${suffix}`);
1276
+ }
1277
+ /** Pause a hosted agent without deleting it. */
1278
+ pause(agentKey, opts = {}) {
1279
+ const q = new URLSearchParams();
1280
+ if (opts.agencyId)
1281
+ q.set("agency_id", opts.agencyId);
1282
+ const suffix = q.toString() ? `?${q}` : "";
1283
+ return this.sm.requestSupafoneApi("POST", `/api/v1/labs/agents/${encodeURIComponent(agentKey)}/pause${suffix}`);
1284
+ }
1122
1285
  /** Delete an agent. Optionally ask the backend to release assigned numbers. */
1123
1286
  delete(agentKey, opts = {}) {
1124
1287
  const q = new URLSearchParams();
@@ -1664,6 +1827,94 @@ class OptimizerNamespace {
1664
1827
  return this.sm.request("GET", `/v1/optimizer/reports?agent=${encodeURIComponent(agent)}&limit=${limit}`);
1665
1828
  }
1666
1829
  }
1830
+ function outboundCallModePayload(config) {
1831
+ const enabled = outboundModeBoolean(config.enabled, false, "enabled");
1832
+ if (!enabled)
1833
+ return { version: 1, enabled: false };
1834
+ const observabilityInput = config.observability ?? {};
1835
+ if (typeof observabilityInput !== "object" || Array.isArray(observabilityInput)) {
1836
+ throw new SupafoneLabsError("outboundCallMode.observability must be an object");
1837
+ }
1838
+ if (observabilityInput.metadata !== undefined
1839
+ && (typeof observabilityInput.metadata !== "object"
1840
+ || observabilityInput.metadata === null
1841
+ || Array.isArray(observabilityInput.metadata))) {
1842
+ throw new SupafoneLabsError("outboundCallMode.observability.metadata must be an object");
1843
+ }
1844
+ const observability = compact({
1845
+ enabled: outboundModeBoolean(observabilityInput.enabled, true, "observability.enabled"),
1846
+ include_trigger: outboundModeBoolean(observabilityInput.includeTrigger, true, "observability.includeTrigger"),
1847
+ include_transitions: outboundModeBoolean(observabilityInput.includeTransitions, true, "observability.includeTransitions"),
1848
+ include_termination_reason: outboundModeBoolean(observabilityInput.includeTerminationReason, true, "observability.includeTerminationReason"),
1849
+ metadata: observabilityInput.metadata,
1850
+ });
1851
+ const payload = {
1852
+ version: 1,
1853
+ enabled: true,
1854
+ initial_mode: "mission",
1855
+ ivr_mode: "dynamic",
1856
+ transport_scope: "provider_agnostic",
1857
+ capability_policy: "fail_closed",
1858
+ auto_detect: outboundModeBoolean(config.autoDetect, true, "autoDetect"),
1859
+ dtmf_tool_enabled: outboundModeBoolean(config.dtmfToolEnabled, true, "dtmfToolEnabled"),
1860
+ max_duration_seconds: outboundModeInteger(config.maxDurationSeconds, "maxDurationSeconds"),
1861
+ max_keypresses: outboundModeInteger(config.maxKeypresses, "maxKeypresses"),
1862
+ repeated_menu_limit: outboundModeInteger(config.repeatedMenuLimit, "repeatedMenuLimit"),
1863
+ no_progress_timeout_seconds: outboundModeInteger(config.noProgressTimeoutSeconds, "noProgressTimeoutSeconds"),
1864
+ human_detection_enabled: outboundModeBoolean(config.humanDetectionEnabled, true, "humanDetectionEnabled"),
1865
+ resume_on_human: outboundModeBoolean(config.resumeOnHuman, true, "resumeOnHuman"),
1866
+ observability,
1867
+ };
1868
+ payload.required_capabilities = outboundCallModeRequiredCapabilities(payload);
1869
+ return payload;
1870
+ }
1871
+ function outboundModeBoolean(value, fallback, key) {
1872
+ if (value === undefined)
1873
+ return fallback;
1874
+ if (typeof value !== "boolean") {
1875
+ throw new SupafoneLabsError(`outboundCallMode.${key} must be a boolean`);
1876
+ }
1877
+ return value;
1878
+ }
1879
+ function outboundModeInteger(value, key) {
1880
+ const bounds = exports.OUTBOUND_CALL_MODE_BOUNDS[key];
1881
+ const normalized = value ?? bounds.default;
1882
+ if (!Number.isInteger(normalized)) {
1883
+ throw new SupafoneLabsError(`outboundCallMode.${key} must be an integer`);
1884
+ }
1885
+ if (normalized < bounds.min || normalized > bounds.max) {
1886
+ throw new SupafoneLabsError(`outboundCallMode.${key} must be between ${bounds.min} and ${bounds.max}`);
1887
+ }
1888
+ return normalized;
1889
+ }
1890
+ function outboundCallModeRequiredCapabilities(payload) {
1891
+ const required = ["state_persistence"];
1892
+ if (payload.dtmf_tool_enabled !== false)
1893
+ required.unshift("dtmf");
1894
+ if (payload.human_detection_enabled !== false || payload.resume_on_human !== false) {
1895
+ required.push("human_detection");
1896
+ }
1897
+ const observability = payload.observability;
1898
+ if (observability?.enabled === true)
1899
+ required.push("observability");
1900
+ return required;
1901
+ }
1902
+ function labsAgentMetadataPayload(input) {
1903
+ const metadata = { ...(input.metadata ?? {}) };
1904
+ const mode = input.outboundCallMode ?? input.outbound_call_mode;
1905
+ if (mode !== undefined)
1906
+ metadata.outbound_call_mode = outboundCallModePayload(mode);
1907
+ return Object.keys(metadata).length ? metadata : undefined;
1908
+ }
1909
+ function labsAgentUpdatePayload(input) {
1910
+ return labsAgentPayload(input);
1911
+ }
1912
+ function campaignSettingsPayload(settings, mode) {
1913
+ const payload = { ...(settings ?? {}) };
1914
+ if (mode !== undefined)
1915
+ payload.outbound_call_mode = outboundCallModePayload(mode);
1916
+ return payload;
1917
+ }
1667
1918
  function labsAgentPayload(input) {
1668
1919
  const fixedLanguage = input.preferred_language ?? input.preferredLanguage ?? input.language;
1669
1920
  return compact({
@@ -1692,6 +1943,9 @@ function labsAgentPayload(input) {
1692
1943
  greeting: input.greeting,
1693
1944
  system_prompt: input.system_prompt ?? input.systemPrompt,
1694
1945
  language: fixedLanguage,
1946
+ language_voice_routing: input.language_voice_routing ?? input.languageVoiceRouting,
1947
+ routing_languages: input.routing_languages ?? input.routingLanguages,
1948
+ language_profiles: languageProfilesPayload(input.language_profiles ?? input.languageProfiles),
1695
1949
  voice: input.voice ? voicePayload(input.voice) : undefined,
1696
1950
  voice_preference: voicePreferencePayload(input.voice_preference ?? input.voicePreference, fixedLanguage),
1697
1951
  provider_keys: input.provider_keys ?? (input.providerKeys ? providerKeysPayload(input.providerKeys) : undefined),
@@ -1707,7 +1961,7 @@ function labsAgentPayload(input) {
1707
1961
  ultravox: input.ultravox ? ultravoxPayload(input.ultravox) : undefined,
1708
1962
  voice_watcher: input.voice_watcher ?? input.voiceWatcher,
1709
1963
  voice_watcher_model: input.voice_watcher_model ?? input.voiceWatcherModel,
1710
- metadata: input.metadata,
1964
+ metadata: labsAgentMetadataPayload(input),
1711
1965
  });
1712
1966
  }
1713
1967
  function hostedListQuery(opts) {
@@ -1935,6 +2189,16 @@ function voicePayload(input) {
1935
2189
  model: input.model,
1936
2190
  });
1937
2191
  }
2192
+ function languageProfilesPayload(input) {
2193
+ if (!Array.isArray(input))
2194
+ return undefined;
2195
+ const profiles = input.map((profile) => compact({
2196
+ language: profile.language,
2197
+ language_hint: profile.language_hint ?? profile.languageHint,
2198
+ voice: profile.voice ? voicePayload(profile.voice) : undefined,
2199
+ }));
2200
+ return profiles.length ? profiles : undefined;
2201
+ }
1938
2202
  function voicePreferencePayload(input, defaultLanguage) {
1939
2203
  if (!input)
1940
2204
  return undefined;