supafone-labs 0.5.2 → 0.5.4

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/src/index.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * A dependency-free TypeScript client for creating hosted Supafone agents
5
5
  * through the Supafone API, including managed phone numbers, voices, stages,
6
- * tools, recordings, transcripts, widgets, and Supafone Pro watcher. It also
6
+ * tools, recordings, transcripts, widgets, and Supafone Supervisor. It also
7
7
  * includes the Labs cloud sidecar oracle, hosted TTS/STT, live multilingual
8
8
  * transcription, telemetry, agent builder, and objective-driven optimizer.
9
9
  *
@@ -54,15 +54,13 @@ export interface SupafoneLabsOptions {
54
54
  * fall back to the plain zero-billed report.
55
55
  */
56
56
  postCallAnalysis?: boolean;
57
- /**
58
- * Run provisioned agents under Supafone's Voice Watcher framework — live
59
- * supervision, QA, and call scoring (default true). Set false for a raw
60
- * agent with no watcher. `voice_watcher` (snake) and the deprecated `labs`
61
- * are accepted aliases, resolved voiceWatcher > voice_watcher > labs > true.
62
- */
57
+ /** Enable Supafone Supervisor live guidance, QA, and scoring (default true). */
58
+ supervisor?: boolean;
59
+ /** @deprecated Use supervisor. */
63
60
  voiceWatcher?: boolean;
61
+ /** @deprecated Use supervisor. */
64
62
  voice_watcher?: boolean;
65
- /** @deprecated alias for voiceWatcher. */
63
+ /** @deprecated Use supervisor. */
66
64
  labs?: boolean;
67
65
  }
68
66
 
@@ -93,7 +91,10 @@ export interface WhisperOptions {
93
91
  temperature?: number;
94
92
  }
95
93
 
96
- export type SecondMindDirectiveKind = "empathy" | "tactical" | "guardrail" | "mixed";
94
+ export type SupervisorDirectiveKind = "empathy" | "tactical" | "guardrail" | "mixed";
95
+
96
+ /** @deprecated Use SupervisorDirectiveKind. */
97
+ export type SecondMindDirectiveKind = SupervisorDirectiveKind;
97
98
 
98
99
  export interface DirectiveTextControl {
99
100
  enabled?: boolean;
@@ -111,7 +112,7 @@ export interface DirectiveListControl {
111
112
  item_max_chars?: number;
112
113
  }
113
114
 
114
- /** Serializable controls for each field generated by SecondMind. */
115
+ /** Serializable controls for each field generated by Supafone Supervisor. */
115
116
  export interface DirectiveContract {
116
117
  empathyDirective?: DirectiveTextControl;
117
118
  empathy_directive?: DirectiveTextControl;
@@ -124,31 +125,34 @@ export interface DirectiveContract {
124
125
  language_mode?: "caller" | "model" | "fixed";
125
126
  fixedLanguage?: string;
126
127
  fixed_language?: string;
127
- allowedKinds?: SecondMindDirectiveKind[];
128
- allowed_kinds?: SecondMindDirectiveKind[];
128
+ allowedKinds?: SupervisorDirectiveKind[];
129
+ allowed_kinds?: SupervisorDirectiveKind[];
129
130
  confidenceThreshold?: number;
130
131
  confidence_threshold?: number;
131
132
  operatorGuardrails?: string[];
132
133
  operator_guardrails?: string[];
133
134
  }
134
135
 
135
- export interface SecondMindDirective {
136
+ export interface SupervisorDirective {
136
137
  empathy_directive: string;
137
138
  tactical_directive: string;
138
139
  surface_facts: string[];
139
140
  guardrails: string[];
140
141
  language: string;
141
142
  confidence: number;
142
- kind: SecondMindDirectiveKind;
143
+ kind: SupervisorDirectiveKind;
143
144
  }
144
145
 
146
+ /** @deprecated Use SupervisorDirective. */
147
+ export type SecondMindDirective = SupervisorDirective;
148
+
145
149
  export interface StructuredWhisperOptions extends WhisperOptions {
146
150
  directiveContract?: DirectiveContract;
147
151
  directive_contract?: DirectiveContract;
148
152
  /** Local final say: revise or suppress the generated directive before use. */
149
153
  transform?: (
150
- directive: SecondMindDirective,
151
- ) => SecondMindDirective | null | Promise<SecondMindDirective | null>;
154
+ directive: SupervisorDirective,
155
+ ) => SupervisorDirective | null | Promise<SupervisorDirective | null>;
152
156
  }
153
157
 
154
158
  export interface Balance {
@@ -574,8 +578,60 @@ export interface LabsToolsConfig {
574
578
  voicemail?: boolean;
575
579
  emergencyEscalation?: boolean;
576
580
  emergency_escalation?: boolean;
577
- customTools?: Array<Record<string, unknown>>;
578
- custom_tools?: Array<Record<string, unknown>>;
581
+ /** Makes the native DTMF tool available for bounded phone-tree navigation. */
582
+ ivrNavigation?: boolean;
583
+ ivr_navigation?: boolean;
584
+ /** Executable customer-owned HTTPS tools, proxied by Supafone at call time. */
585
+ customTools?: LabsCustomToolDefinition[];
586
+ custom_tools?: LabsCustomToolDefinition[];
587
+ }
588
+
589
+ export type LabsCustomToolParameterType = "string" | "number" | "integer" | "boolean";
590
+
591
+ export interface LabsCustomToolParameter {
592
+ name: string;
593
+ description?: string;
594
+ type?: LabsCustomToolParameterType;
595
+ required?: boolean;
596
+ }
597
+
598
+ export interface LabsCustomToolDefinition {
599
+ /** Stable id returned by Supafone. Omit it when creating a tool. */
600
+ id?: string;
601
+ /** Lowercase model tool name, such as lookup_order. */
602
+ name: string;
603
+ description?: string;
604
+ /** HTTPS endpoint called by Supafone's guarded server-side proxy. */
605
+ url: string;
606
+ /** Optional secret header name. Defaults to Authorization: Bearer <apiKey>. */
607
+ header?: string;
608
+ apiKey?: string;
609
+ api_key?: string;
610
+ /** Set false on update to remove a previously stored secret. */
611
+ apiKeySet?: boolean;
612
+ api_key_set?: boolean;
613
+ params?: LabsCustomToolParameter[];
614
+ parameters?: LabsCustomToolParameter[];
615
+ /** Optional call-stage keys where this tool is available. Empty means every stage. */
616
+ stages?: string[];
617
+ allowedStages?: string[];
618
+ allowed_stages?: string[];
619
+ }
620
+
621
+ export interface LabsEmailConfig {
622
+ enabled?: boolean;
623
+ fromEmail?: string;
624
+ from_email?: string;
625
+ smtpHost?: string;
626
+ smtp_host?: string;
627
+ smtpPort?: number;
628
+ smtp_port?: number;
629
+ smtpUser?: string;
630
+ smtp_user?: string;
631
+ /** Encrypted by Supafone and never returned. */
632
+ smtpPassword?: string;
633
+ smtp_password?: string;
634
+ smtp_pass?: string;
579
635
  }
580
636
 
581
637
  export interface LabsRecordingConfig {
@@ -618,9 +674,12 @@ export interface LabsArtifactsConfig {
618
674
  metadata?: Record<string, unknown>;
619
675
  }
620
676
 
621
- export interface LabsWatcherConfig {
677
+ export interface LabsSupervisorConfig {
622
678
  enabled?: boolean;
679
+ supervisor?: boolean;
680
+ /** @deprecated Use supervisor. */
623
681
  voiceWatcher?: boolean;
682
+ /** @deprecated Use supervisor. */
624
683
  voice_watcher?: boolean;
625
684
  apiKey?: string;
626
685
  api_key?: string;
@@ -636,6 +695,9 @@ export interface LabsWatcherConfig {
636
695
  label?: string;
637
696
  }
638
697
 
698
+ /** @deprecated Use LabsSupervisorConfig. */
699
+ export type LabsWatcherConfig = LabsSupervisorConfig;
700
+
639
701
  export interface LabsUltravoxRuntime {
640
702
  model?: string;
641
703
  temperature?: number;
@@ -948,9 +1010,14 @@ export interface CreateLabsAgentRequest {
948
1010
  artifacts?: LabsArtifactsConfig;
949
1011
  compliance?: Record<string, unknown>;
950
1012
  tools?: LabsToolsConfig;
951
- labs?: LabsWatcherConfig;
1013
+ /** Optional per-agent SMTP sender used by the callable send_email tool. */
1014
+ email?: LabsEmailConfig;
1015
+ labs?: LabsSupervisorConfig;
952
1016
  ultravox?: LabsUltravoxRuntime;
1017
+ supervisor?: boolean;
1018
+ /** @deprecated Use supervisor. */
953
1019
  voiceWatcher?: boolean;
1020
+ /** @deprecated Use supervisor. */
954
1021
  voice_watcher?: boolean;
955
1022
  voiceWatcherModel?: string;
956
1023
  voice_watcher_model?: string;
@@ -1722,13 +1789,13 @@ const DEFAULT_BASE = "https://api.labs.supafone.ai";
1722
1789
  const DEFAULT_SUPAFONE_API_BASE = "https://api.supafone.ai";
1723
1790
 
1724
1791
  const COACH_SYSTEM =
1725
- "You are the coaching core of a second mind for a live voice agent. Read the " +
1792
+ "You are the coaching core of a supervisor for a live voice agent. Read the " +
1726
1793
  "conversation and return ONE short, silent directive the agent reads but never " +
1727
1794
  "speaks aloud — a correction or nudge, phrased imperatively. If nothing needs " +
1728
1795
  "correcting, return an empty string.";
1729
1796
 
1730
1797
  const STRUCTURED_COACH_SYSTEM =
1731
- "You are the coaching core of a second mind for a live voice agent. Read the " +
1798
+ "You are the coaching core of a supervisor for a live voice agent. Read the " +
1732
1799
  "conversation and return ONLY one JSON object with exactly these keys: " +
1733
1800
  "empathy_directive (string), tactical_directive (string), surface_facts (string[]), " +
1734
1801
  "guardrails (string[]), language (short code), confidence (0-1), and kind " +
@@ -1747,7 +1814,7 @@ interface NormalizedDirectiveContract {
1747
1814
  };
1748
1815
  language_mode: "caller" | "model" | "fixed";
1749
1816
  fixed_language: string;
1750
- allowed_kinds: SecondMindDirectiveKind[];
1817
+ allowed_kinds: SupervisorDirectiveKind[];
1751
1818
  confidence_threshold: number;
1752
1819
  operator_guardrails: string[];
1753
1820
  }
@@ -1787,7 +1854,7 @@ function normalizeDirectiveContract(input: DirectiveContract = {}): NormalizedDi
1787
1854
  guardrails: listControl(input.guardrails),
1788
1855
  language_mode: languageMode,
1789
1856
  fixed_language: String(input.fixed_language ?? input.fixedLanguage ?? "").trim(),
1790
- allowed_kinds: allowed.filter((kind): kind is SecondMindDirectiveKind =>
1857
+ allowed_kinds: allowed.filter((kind): kind is SupervisorDirectiveKind =>
1791
1858
  ["empathy", "tactical", "guardrail", "mixed"].includes(kind),
1792
1859
  ),
1793
1860
  confidence_threshold: Number.isFinite(threshold) ? Math.max(0, Math.min(1, threshold)) : 0.5,
@@ -1832,8 +1899,8 @@ function clipList(value: unknown, maxItems: number, itemMaxChars: number): strin
1832
1899
  function normalizeStructuredDirective(
1833
1900
  raw: Record<string, unknown>,
1834
1901
  contract: NormalizedDirectiveContract,
1835
- ): SecondMindDirective | null {
1836
- const kind = String(raw.kind ?? "mixed") as SecondMindDirectiveKind;
1902
+ ): SupervisorDirective | null {
1903
+ const kind = String(raw.kind ?? "mixed") as SupervisorDirectiveKind;
1837
1904
  if (!["empathy", "tactical", "guardrail", "mixed"].includes(kind)) return null;
1838
1905
  const parsedConfidence = Number(raw.confidence ?? 0);
1839
1906
  const confidence = Number.isFinite(parsedConfidence) ? Math.max(0, Math.min(1, parsedConfidence)) : 0;
@@ -1869,7 +1936,9 @@ export class SupafoneLabs {
1869
1936
  private readonly supafoneApiKey: string;
1870
1937
  private readonly timeoutMs: number;
1871
1938
  private readonly postCallAnalysis: boolean;
1872
- /** Client default for the Voice Watcher framework (supervision + QA + scoring). */
1939
+ /** Client default for Supafone Supervisor (live guidance + QA + scoring). */
1940
+ readonly supervisor: boolean;
1941
+ /** @deprecated Use supervisor. */
1873
1942
  readonly voiceWatcher: boolean;
1874
1943
  private sessionToken?: string;
1875
1944
  private accountToken?: string;
@@ -1898,8 +1967,9 @@ export class SupafoneLabs {
1898
1967
  this.appUrl = (opts.appUrl ?? "https://app.supafone.ai").replace(/\/$/, "");
1899
1968
  this.timeoutMs = opts.timeoutMs ?? 30_000;
1900
1969
  this.postCallAnalysis = opts.postCallAnalysis ?? false;
1901
- // Precedence: explicit voiceWatcher > voice_watcher (snake) > labs (deprecated) > true.
1902
- this.voiceWatcher = opts.voiceWatcher ?? opts.voice_watcher ?? opts.labs ?? true;
1970
+ // Precedence: supervisor > legacy aliases > true.
1971
+ this.supervisor = opts.supervisor ?? opts.voiceWatcher ?? opts.voice_watcher ?? opts.labs ?? true;
1972
+ this.voiceWatcher = this.supervisor;
1903
1973
  this.sessionToken = opts.sessionToken;
1904
1974
  this.accountToken = opts.accountToken;
1905
1975
  this.accountEmail = opts.accountEmail;
@@ -2259,14 +2329,14 @@ export class SupafoneLabs {
2259
2329
  }
2260
2330
 
2261
2331
  /**
2262
- * Structured SecondMind guidance with developer-controlled field policy.
2332
+ * Structured Supafone Supervisor guidance with developer-controlled field policy.
2263
2333
  * Returns null when JSON is invalid, evidence misses the confidence gate,
2264
2334
  * the generated kind is disallowed, or the local transform suppresses it.
2265
2335
  */
2266
2336
  async whisperStructured(
2267
2337
  transcript: string,
2268
2338
  opts: StructuredWhisperOptions = {},
2269
- ): Promise<SecondMindDirective | null> {
2339
+ ): Promise<SupervisorDirective | null> {
2270
2340
  const contract = normalizeDirectiveContract(
2271
2341
  opts.directive_contract ?? opts.directiveContract ?? {},
2272
2342
  );
@@ -2300,7 +2370,7 @@ export class SupafoneLabs {
2300
2370
  directive(
2301
2371
  transcript: string,
2302
2372
  opts: StructuredWhisperOptions = {},
2303
- ): Promise<SecondMindDirective | null> {
2373
+ ): Promise<SupervisorDirective | null> {
2304
2374
  return this.whisperStructured(transcript, opts);
2305
2375
  }
2306
2376
 
@@ -3097,7 +3167,7 @@ class LabsAgentsNamespace {
3097
3167
  return this.sm.requestSupafoneApi<CreateLabsAgentResponse>(
3098
3168
  "POST",
3099
3169
  "/api/v1/labs/agents",
3100
- labsAgentPayload(this.withVoiceWatcher(input)),
3170
+ labsAgentPayload(this.withSupervisor(input)),
3101
3171
  );
3102
3172
  }
3103
3173
 
@@ -3114,17 +3184,16 @@ class LabsAgentsNamespace {
3114
3184
  return this.plan(input);
3115
3185
  }
3116
3186
 
3117
- /** Default the agent onto the client's Voice Watcher setting (live supervision
3118
- * + QA + scoring) unless the caller set it explicitly; mirror into
3119
- * labs.voice_watcher when a labs block exists. Never overwrites a caller value. */
3120
- private withVoiceWatcher(input: CreateLabsAgentRequest): CreateLabsAgentRequest {
3187
+ /** Default the agent onto the client's Supafone Supervisor setting unless the
3188
+ * caller set it explicitly. Legacy wire fields remain unchanged. */
3189
+ private withSupervisor(input: CreateLabsAgentRequest): CreateLabsAgentRequest {
3121
3190
  const out: CreateLabsAgentRequest = { ...input };
3122
- if (out.voice_watcher === undefined && out.voiceWatcher === undefined) {
3123
- out.voice_watcher = this.sm.voiceWatcher;
3191
+ if (out.supervisor === undefined && out.voice_watcher === undefined && out.voiceWatcher === undefined) {
3192
+ out.supervisor = this.sm.supervisor;
3124
3193
  }
3125
3194
  const labs = out.labs;
3126
- if (labs && labs.voice_watcher === undefined && labs.voiceWatcher === undefined) {
3127
- out.labs = { ...labs, voice_watcher: this.sm.voiceWatcher };
3195
+ if (labs && labs.supervisor === undefined && labs.voice_watcher === undefined && labs.voiceWatcher === undefined) {
3196
+ out.labs = { ...labs, supervisor: this.sm.supervisor };
3128
3197
  }
3129
3198
  return out;
3130
3199
  }
@@ -4099,9 +4168,10 @@ function labsAgentPayload(input: CreateLabsAgentRequest): Record<string, unknown
4099
4168
  artifacts: input.artifacts ? artifactsPayload(input.artifacts) : undefined,
4100
4169
  compliance: input.compliance,
4101
4170
  tools: input.tools ? toolsPayload(input.tools) : undefined,
4171
+ email: input.email ? emailPayload(input.email) : undefined,
4102
4172
  labs: input.labs ? labsPayload(input.labs) : undefined,
4103
4173
  ultravox: input.ultravox ? ultravoxPayload(input.ultravox) : undefined,
4104
- voice_watcher: input.voice_watcher ?? input.voiceWatcher,
4174
+ voice_watcher: input.supervisor ?? input.voice_watcher ?? input.voiceWatcher,
4105
4175
  voice_watcher_model: input.voice_watcher_model ?? input.voiceWatcherModel,
4106
4176
  metadata: labsAgentMetadataPayload(input),
4107
4177
  });
@@ -4490,10 +4560,22 @@ function toolsPayload(input: LabsToolsConfig): Record<string, unknown> {
4490
4560
  existing_client_lookup: input.existing_client_lookup ?? input.existingClientLookup,
4491
4561
  voicemail: input.voicemail,
4492
4562
  emergency_escalation: input.emergency_escalation ?? input.emergencyEscalation,
4563
+ ivr_navigation: input.ivr_navigation ?? input.ivrNavigation,
4493
4564
  custom_tools: input.custom_tools ?? input.customTools,
4494
4565
  });
4495
4566
  }
4496
4567
 
4568
+ function emailPayload(input: LabsEmailConfig): Record<string, unknown> {
4569
+ return compact({
4570
+ enabled: input.enabled,
4571
+ from_email: input.from_email ?? input.fromEmail,
4572
+ smtp_host: input.smtp_host ?? input.smtpHost,
4573
+ smtp_port: input.smtp_port ?? input.smtpPort,
4574
+ smtp_user: input.smtp_user ?? input.smtpUser,
4575
+ smtp_pass: input.smtp_pass ?? input.smtp_password ?? input.smtpPassword,
4576
+ });
4577
+ }
4578
+
4497
4579
  function recordingPayload(input: LabsRecordingConfig): Record<string, unknown> {
4498
4580
  return compact({
4499
4581
  enabled: input.enabled,
@@ -4533,10 +4615,10 @@ function artifactsPayload(input: LabsArtifactsConfig): Record<string, unknown> {
4533
4615
  });
4534
4616
  }
4535
4617
 
4536
- function labsPayload(input: LabsWatcherConfig): Record<string, unknown> {
4618
+ function labsPayload(input: LabsSupervisorConfig): Record<string, unknown> {
4537
4619
  return compact({
4538
4620
  enabled: input.enabled,
4539
- voice_watcher: input.voice_watcher ?? input.voiceWatcher,
4621
+ voice_watcher: input.supervisor ?? input.voice_watcher ?? input.voiceWatcher,
4540
4622
  api_key: input.api_key ?? input.apiKey,
4541
4623
  model: input.model,
4542
4624
  mode: input.mode,