supafone-labs 0.6.2 → 0.7.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/dist/s2s.d.ts ADDED
@@ -0,0 +1,42 @@
1
+ import type { CreateLabsAgentRequest, GetLabsAgentOptions, LabsRealtimeSelection, SupafoneLabs } from "./index.js";
2
+ export type S2SProviderName = "ultravox" | "openai" | "google" | "xai" | "smallest";
3
+ export interface S2SModelOptions {
4
+ model?: string;
5
+ voice?: string;
6
+ }
7
+ export interface S2SOptions extends S2SModelOptions {
8
+ provider?: S2SProviderName;
9
+ }
10
+ /** Shared parent for Supafone-hosted S2S agents. Vendor keys stay on the server. */
11
+ export declare class SupafoneS2S {
12
+ protected readonly client: SupafoneLabs;
13
+ readonly provider: S2SProviderName;
14
+ private readonly selection;
15
+ constructor(client: SupafoneLabs, options?: S2SOptions);
16
+ /** A fresh wire selection; null explicitly restores the default Ultravox runtime. */
17
+ get realtime(): LabsRealtimeSelection | null;
18
+ /** Create through the normal hosted agent factory with this provider. */
19
+ create(input: Omit<CreateLabsAgentRequest, "realtime">): Promise<import("./index.js").CreateLabsAgentResponse>;
20
+ /** Change the saved selection for subsequent calls, preserving the agent and number. */
21
+ apply(agentKey: string, options?: GetLabsAgentOptions): Promise<import("./index.js").LabsAgentLifecycleResponse>;
22
+ /** Preview the saved agent. Call apply first when switching providers. */
23
+ testCall(agentKey: string, options?: GetLabsAgentOptions): Promise<import("./index.js").LabsBrowserTestSession>;
24
+ private checkAgentKey;
25
+ }
26
+ export declare class UltravoxS2S extends SupafoneS2S {
27
+ constructor(client: SupafoneLabs);
28
+ }
29
+ export declare class OpenAIS2S extends SupafoneS2S {
30
+ constructor(client: SupafoneLabs, options?: S2SModelOptions);
31
+ }
32
+ export declare class GeminiS2S extends SupafoneS2S {
33
+ constructor(client: SupafoneLabs, options?: S2SModelOptions);
34
+ }
35
+ export declare class GrokS2S extends SupafoneS2S {
36
+ constructor(client: SupafoneLabs, options?: S2SModelOptions);
37
+ }
38
+ /** Smallest AI Hydra: full-duplex audio, without transcript events. */
39
+ export declare class HydraS2S extends SupafoneS2S {
40
+ constructor(client: SupafoneLabs, options?: S2SModelOptions);
41
+ }
42
+ //# sourceMappingURL=s2s.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s2s.d.ts","sourceRoot":"","sources":["../src/s2s.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,sBAAsB,EACtB,mBAAmB,EACnB,qBAAqB,EACrB,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,KAAK,GAAG,UAAU,CAAC;AACpF,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AACD,MAAM,WAAW,UAAW,SAAQ,eAAe;IACjD,QAAQ,CAAC,EAAE,eAAe,CAAC;CAC5B;AAED,oFAAoF;AACpF,qBAAa,WAAW;IAIV,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,YAAY;IAHnD,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA+B;gBAE1B,MAAM,EAAE,YAAY,EAAE,OAAO,GAAE,UAAe;IAsB7E,qFAAqF;IACrF,IAAI,QAAQ,IAAI,qBAAqB,GAAG,IAAI,CAE3C;IAED,yEAAyE;IACzE,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,sBAAsB,EAAE,UAAU,CAAC;IAUtD,wFAAwF;IACxF,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAwB;IAKzD,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAwB;IAK5D,OAAO,CAAC,aAAa;CAGtB;AAED,qBAAa,WAAY,SAAQ,WAAW;gBAC9B,MAAM,EAAE,YAAY;CACjC;AACD,qBAAa,SAAU,SAAQ,WAAW;gBAC5B,MAAM,EAAE,YAAY,EAAE,OAAO,GAAE,eAAoB;CAGhE;AACD,qBAAa,SAAU,SAAQ,WAAW;gBAC5B,MAAM,EAAE,YAAY,EAAE,OAAO,GAAE,eAAoB;CAGhE;AACD,qBAAa,OAAQ,SAAQ,WAAW;gBAC1B,MAAM,EAAE,YAAY,EAAE,OAAO,GAAE,eAAoB;CAGhE;AACD,uEAAuE;AACvE,qBAAa,QAAS,SAAQ,WAAW;gBAC3B,MAAM,EAAE,YAAY,EAAE,OAAO,GAAE,eAAoB;CAGhE"}
package/dist/s2s.js ADDED
@@ -0,0 +1,81 @@
1
+ /** Shared parent for Supafone-hosted S2S agents. Vendor keys stay on the server. */
2
+ export class SupafoneS2S {
3
+ client;
4
+ provider;
5
+ selection;
6
+ constructor(client, options = {}) {
7
+ this.client = client;
8
+ const provider = options.provider ?? "ultravox";
9
+ if (!["ultravox", "openai", "google", "xai", "smallest"].includes(provider)) {
10
+ throw new Error("Unsupported Supafone S2S provider");
11
+ }
12
+ if (provider === "ultravox" && (options.model !== undefined || options.voice !== undefined)) {
13
+ throw new Error("Configure Ultravox voices through the agent's voice settings");
14
+ }
15
+ for (const field of ["model", "voice"]) {
16
+ const value = options[field];
17
+ if (value !== undefined && (typeof value !== "string" || !value.trim())) {
18
+ throw new Error(`${field} must be a non-empty string`);
19
+ }
20
+ }
21
+ this.provider = provider;
22
+ this.selection = provider === "ultravox" ? null : {
23
+ provider,
24
+ ...(options.model !== undefined ? { model: options.model.trim() } : {}),
25
+ ...(options.voice !== undefined ? { voice: options.voice.trim() } : {}),
26
+ };
27
+ }
28
+ /** A fresh wire selection; null explicitly restores the default Ultravox runtime. */
29
+ get realtime() {
30
+ return this.selection ? { ...this.selection } : null;
31
+ }
32
+ /** Create through the normal hosted agent factory with this provider. */
33
+ create(input) {
34
+ if ("realtime" in input) {
35
+ throw new Error("Set provider/model/voice on the S2S adapter, not create({realtime: ...})");
36
+ }
37
+ return this.client.labs.agents.create({
38
+ ...input,
39
+ ...(this.realtime ? { realtime: this.realtime } : {}),
40
+ });
41
+ }
42
+ /** Change the saved selection for subsequent calls, preserving the agent and number. */
43
+ apply(agentKey, options = {}) {
44
+ this.checkAgentKey(agentKey);
45
+ return this.client.labs.agents.update(agentKey, { realtime: this.realtime }, options);
46
+ }
47
+ /** Preview the saved agent. Call apply first when switching providers. */
48
+ testCall(agentKey, options = {}) {
49
+ this.checkAgentKey(agentKey);
50
+ return this.client.labs.agents.testCall(agentKey, options);
51
+ }
52
+ checkAgentKey(agentKey) {
53
+ if (typeof agentKey !== "string" || !agentKey.trim())
54
+ throw new Error("agentKey is required");
55
+ }
56
+ }
57
+ export class UltravoxS2S extends SupafoneS2S {
58
+ constructor(client) { super(client, { provider: "ultravox" }); }
59
+ }
60
+ export class OpenAIS2S extends SupafoneS2S {
61
+ constructor(client, options = {}) {
62
+ super(client, { ...options, provider: "openai" });
63
+ }
64
+ }
65
+ export class GeminiS2S extends SupafoneS2S {
66
+ constructor(client, options = {}) {
67
+ super(client, { ...options, provider: "google" });
68
+ }
69
+ }
70
+ export class GrokS2S extends SupafoneS2S {
71
+ constructor(client, options = {}) {
72
+ super(client, { ...options, provider: "xai" });
73
+ }
74
+ }
75
+ /** Smallest AI Hydra: full-duplex audio, without transcript events. */
76
+ export class HydraS2S extends SupafoneS2S {
77
+ constructor(client, options = {}) {
78
+ super(client, { ...options, provider: "smallest" });
79
+ }
80
+ }
81
+ //# sourceMappingURL=s2s.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s2s.js","sourceRoot":"","sources":["../src/s2s.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supafone-labs",
3
- "version": "0.6.2",
3
+ "version": "0.7.0",
4
4
  "description": "Build voice agents with one speech-to-speech harness and swappable models.",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
package/src/index.ts CHANGED
@@ -1,3 +1,6 @@
1
+ export { SupafoneS2S, UltravoxS2S, OpenAIS2S, GeminiS2S, GrokS2S, HydraS2S } from "./s2s.js";
2
+ export type { S2SProviderName, S2SModelOptions, S2SOptions } from "./s2s.js";
3
+
1
4
  /**
2
5
  * Supafone Labs — the agent framework behind Supafone.
3
6
  *
@@ -221,9 +224,70 @@ export interface LabsCallStage {
221
224
  temperature?: number;
222
225
  nextStages?: string[];
223
226
  next_stages?: string[];
227
+ /** Server-enforced exit gates, independent of conversational exit criteria. */
228
+ requirements?: LabsStageRequirements;
229
+ role?: string;
230
+ specialistId?: string;
231
+ specialist_id?: string;
224
232
  metadata?: Record<string, unknown>;
225
233
  }
226
234
 
235
+ export interface LabsStageRequirements {
236
+ requiredFields?: string[];
237
+ required_fields?: string[];
238
+ successfulTools?: string[];
239
+ successful_tools?: string[];
240
+ }
241
+
242
+ export interface LabsManagerConfig {
243
+ enabled?: boolean;
244
+ /** Reuse the existing encrypted Supervisor profile, or use managed reasoning. */
245
+ reasoning?: "managed" | "supervisor";
246
+ model?: "supafone-manager" | "supafone-manager-pro";
247
+ maxTasks?: number;
248
+ max_tasks?: number;
249
+ maxParallel?: number;
250
+ max_parallel?: number;
251
+ timeoutSeconds?: number;
252
+ timeout_seconds?: number;
253
+ }
254
+
255
+ export interface LabsSpecialist {
256
+ id: string;
257
+ role?: string;
258
+ label?: string;
259
+ description?: string;
260
+ instructions?: string;
261
+ enabled?: boolean;
262
+ stageKeys?: string[];
263
+ stage_keys?: string[];
264
+ tools?: string[];
265
+ }
266
+
267
+ export interface LabsAgentTeam {
268
+ enabled?: boolean;
269
+ routingMode?: "auto" | "sequential";
270
+ routing_mode?: "auto" | "sequential";
271
+ fallbackMemberId?: string;
272
+ fallback_member_id?: string;
273
+ members?: LabsSpecialist[];
274
+ routingRules?: Array<{ intent: string; member_id: string; priority?: string; action?: string }>;
275
+ routing_rules?: Array<{ intent: string; member_id: string; priority?: string; action?: string }>;
276
+ }
277
+
278
+ export interface LabsRuntimeRoutingConfig {
279
+ enabled?: boolean;
280
+ allowedModels?: LabsRealtimeSelection[];
281
+ allowed_models?: LabsRealtimeSelection[];
282
+ /** Server-supported range is 1–3 handoffs per call. */
283
+ maxHandoffs?: number;
284
+ max_handoffs?: number;
285
+ recoverOnDisconnect?: boolean;
286
+ recover_on_disconnect?: boolean;
287
+ allowedLanguages?: string[];
288
+ allowed_languages?: string[];
289
+ }
290
+
227
291
  export type LabsStageGeneration = "managed" | "template" | "off" | "oracle";
228
292
 
229
293
  export interface LabsCallPlan {
@@ -385,7 +449,7 @@ export interface LabsVoicePreference {
385
449
 
386
450
  /** Direct speech-to-speech configuration for browser previews and carrier phone calls. */
387
451
  export interface LabsRealtimeSelection {
388
- provider: "openai" | "google" | "xai";
452
+ provider: "openai" | "google" | "xai" | "smallest";
389
453
  model?: string;
390
454
  voice?: string;
391
455
  }
@@ -406,6 +470,17 @@ export interface LabsBrowserTestSession {
406
470
  token?: string | null;
407
471
  input_sample_rate?: number;
408
472
  output_sample_rate?: number;
473
+ capabilities?: {
474
+ stages?: boolean;
475
+ tools?: boolean;
476
+ transcripts?: boolean;
477
+ recording?: boolean;
478
+ supervisor?: boolean;
479
+ transfer?: boolean;
480
+ mutable_instructions?: boolean;
481
+ stage_update_mode?: string;
482
+ languages?: string[];
483
+ };
409
484
  };
410
485
  }
411
486
 
@@ -483,6 +558,9 @@ export interface LabsProviderKeys {
483
558
  xai?: string;
484
559
  xaiApiKey?: string;
485
560
  xai_api_key?: string;
561
+ smallest?: string;
562
+ smallestApiKey?: string;
563
+ smallest_api_key?: string;
486
564
  [extra: string]: unknown;
487
565
  }
488
566
 
@@ -675,6 +753,8 @@ export interface LabsEmailConfig {
675
753
 
676
754
  export interface LabsRecordingConfig {
677
755
  enabled?: boolean;
756
+ /** Request post-call transcription of the recording where configured. */
757
+ transcribe?: boolean;
678
758
  recordAudio?: boolean;
679
759
  record_audio?: boolean;
680
760
  consentRequired?: boolean;
@@ -1027,6 +1107,17 @@ export interface CreateLabsAgentRequest {
1027
1107
  presetKey?: string;
1028
1108
  preset_key?: string;
1029
1109
  realtime?: LabsRealtimeSelection;
1110
+ manager?: boolean | LabsManagerConfig;
1111
+ agentTeam?: LabsAgentTeam;
1112
+ agent_team?: LabsAgentTeam;
1113
+ runtimeRouting?: LabsRuntimeRoutingConfig;
1114
+ runtime_routing?: LabsRuntimeRoutingConfig;
1115
+ captureFields?: string[];
1116
+ capture_fields?: string[];
1117
+ timezone?: string;
1118
+ businessTimezone?: string;
1119
+ timeAwareness?: boolean;
1120
+ time_awareness?: boolean;
1030
1121
  runtimeMode?: LabsRuntimeMode;
1031
1122
  runtime_mode?: LabsRuntimeMode;
1032
1123
  /** Defaults to Supafone's hosted planner; model credentials remain server-side. */
@@ -1069,7 +1160,7 @@ export interface CreateLabsAgentRequest {
1069
1160
  customSip?: LabsCustomSipConfig;
1070
1161
  custom_sip?: LabsCustomSipConfig;
1071
1162
  sip?: LabsCustomSipConfig;
1072
- recording?: LabsRecordingConfig;
1163
+ recording?: boolean | LabsRecordingConfig;
1073
1164
  transcription?: LabsTranscriptionConfig;
1074
1165
  artifacts?: LabsArtifactsConfig;
1075
1166
  compliance?: Record<string, unknown>;
@@ -3343,7 +3434,7 @@ class LabsBillingNamespace {
3343
3434
  class LabsAgentsNamespace {
3344
3435
  constructor(private sm: SupafoneLabs) {}
3345
3436
 
3346
- /** Spawn a durable hosted Supafone agent backed by Ultravox and Supafone-managed providers. */
3437
+ /** Create a hosted Supafone agent with the default Ultravox runtime or a selected native S2S model. */
3347
3438
  create(input: CreateLabsAgentRequest): Promise<CreateLabsAgentResponse> {
3348
3439
  return this.sm.requestSupafoneApi<CreateLabsAgentResponse>(
3349
3440
  "POST",
@@ -4346,6 +4437,40 @@ function outboundCallModeRequiredCapabilities(payload: Record<string, unknown>):
4346
4437
  return required;
4347
4438
  }
4348
4439
 
4440
+ function managerPayload(input?: boolean | LabsManagerConfig): boolean | Record<string, unknown> | undefined {
4441
+ if (typeof input !== "object" || input === null) return input;
4442
+ return compact({
4443
+ enabled: input.enabled, reasoning: input.reasoning, model: input.model,
4444
+ max_tasks: input.max_tasks ?? input.maxTasks,
4445
+ max_parallel: input.max_parallel ?? input.maxParallel,
4446
+ timeout_seconds: input.timeout_seconds ?? input.timeoutSeconds,
4447
+ });
4448
+ }
4449
+
4450
+ function agentTeamPayload(input?: LabsAgentTeam): Record<string, unknown> | undefined {
4451
+ if (!input) return undefined;
4452
+ return compact({
4453
+ enabled: input.enabled, routing_mode: input.routing_mode ?? input.routingMode,
4454
+ fallback_member_id: input.fallback_member_id ?? input.fallbackMemberId,
4455
+ routing_rules: input.routing_rules ?? input.routingRules,
4456
+ members: input.members?.map((member) => compact({
4457
+ id: member.id, role: member.role, label: member.label, description: member.description,
4458
+ instructions: member.instructions, enabled: member.enabled, tools: member.tools,
4459
+ stage_keys: member.stage_keys ?? member.stageKeys,
4460
+ })),
4461
+ });
4462
+ }
4463
+
4464
+ function runtimeRoutingPayload(input?: LabsRuntimeRoutingConfig): Record<string, unknown> | undefined {
4465
+ if (!input) return undefined;
4466
+ return compact({
4467
+ enabled: input.enabled, allowed_models: input.allowed_models ?? input.allowedModels,
4468
+ max_handoffs: input.max_handoffs ?? input.maxHandoffs,
4469
+ recover_on_disconnect: input.recover_on_disconnect ?? input.recoverOnDisconnect,
4470
+ allowed_languages: input.allowed_languages ?? input.allowedLanguages,
4471
+ });
4472
+ }
4473
+
4349
4474
  function labsAgentMetadataPayload(input: CreateLabsAgentRequest): Record<string, unknown> | undefined {
4350
4475
  const metadata = { ...(input.metadata ?? {}) };
4351
4476
  const mode = input.outboundCallMode ?? input.outbound_call_mode;
@@ -4390,6 +4515,12 @@ function labsAgentPayload(input: CreateLabsAgentRequest): Record<string, unknown
4390
4515
  preset_key: input.preset_key ?? input.presetKey,
4391
4516
  runtime_mode: input.runtime_mode ?? input.runtimeMode,
4392
4517
  realtime: input.realtime,
4518
+ manager: managerPayload(input.manager),
4519
+ agent_team: agentTeamPayload(input.agent_team ?? input.agentTeam),
4520
+ runtime_routing: runtimeRoutingPayload(input.runtime_routing ?? input.runtimeRouting),
4521
+ capture_fields: input.capture_fields ?? input.captureFields,
4522
+ timezone: input.timezone ?? input.businessTimezone,
4523
+ time_awareness: input.time_awareness ?? input.timeAwareness,
4393
4524
  call_stages: callStagesPayload(input),
4394
4525
  stage_generation: input.stage_generation ?? input.stageGeneration,
4395
4526
  stage_count: input.stage_count ?? input.stageCount,
@@ -4410,7 +4541,7 @@ function labsAgentPayload(input: CreateLabsAgentRequest): Record<string, unknown
4410
4541
  byok: input.byok ? byokPayload(input.byok) : undefined,
4411
4542
  telephony: input.telephony ? telephonyPayload(input.telephony) : undefined,
4412
4543
  custom_sip: customSipPayload(input.custom_sip ?? input.customSip ?? input.sip),
4413
- recording: input.recording ? recordingPayload(input.recording) : undefined,
4544
+ recording: input.recording !== undefined ? recordingPayload(input.recording) : undefined,
4414
4545
  transcription: input.transcription ? transcriptionPayload(input.transcription) : undefined,
4415
4546
  artifacts: input.artifacts ? artifactsPayload(input.artifacts) : undefined,
4416
4547
  compliance: input.compliance,
@@ -4558,8 +4689,6 @@ function callStagesPayload(
4558
4689
  if (explicit === false || auto === false) return false;
4559
4690
  if (explicit === "managed" || explicit === "template" || explicit === "off") return explicit;
4560
4691
  if (explicit === "oracle") return "managed";
4561
- // The S2S backend owns its validated intake → booking → confirmation flow.
4562
- if (input.realtime) return undefined;
4563
4692
  // Omitted means the private Supafone API generates and compiles the plan.
4564
4693
  return undefined;
4565
4694
  }
@@ -4579,8 +4708,7 @@ function stagePlanPayload(
4579
4708
  system_prompt: input.system_prompt ?? input.systemPrompt,
4580
4709
  tools: input.tools ? toolsPayload(input.tools) : undefined,
4581
4710
  call_stages: Array.isArray(input.call_stages ?? input.callStages ?? input.stages)
4582
- ? (input.call_stages ?? input.callStages ?? input.stages)
4583
- : undefined,
4711
+ ? callStagesPayload(input) : undefined,
4584
4712
  stage_generation: input.stage_generation ?? input.stageGeneration,
4585
4713
  stage_count: input.stage_count ?? input.stageCount,
4586
4714
  stage_detail: input.stage_detail ?? input.stageDetail,
@@ -4597,6 +4725,12 @@ function callStagePayload(input: LabsCallStage): Record<string, unknown> {
4597
4725
  tools: input.tools,
4598
4726
  temperature: input.temperature,
4599
4727
  next_stages: input.next_stages ?? input.nextStages,
4728
+ requirements: input.requirements ? compact({
4729
+ required_fields: input.requirements.required_fields ?? input.requirements.requiredFields,
4730
+ successful_tools: input.requirements.successful_tools ?? input.requirements.successfulTools,
4731
+ }) : undefined,
4732
+ role: input.role,
4733
+ specialist_id: input.specialist_id ?? input.specialistId,
4600
4734
  metadata: input.metadata,
4601
4735
  });
4602
4736
  }
@@ -4757,6 +4891,8 @@ function providerKeysPayload(input: LabsProviderKeys): Record<string, unknown> {
4757
4891
  openai_api_key: input.openai_api_key ?? input.openaiApiKey,
4758
4892
  xai: input.xai,
4759
4893
  xai_api_key: input.xai_api_key ?? input.xaiApiKey,
4894
+ smallest: input.smallest,
4895
+ smallest_api_key: input.smallest_api_key ?? input.smallestApiKey,
4760
4896
  });
4761
4897
  }
4762
4898
 
@@ -4837,9 +4973,11 @@ function emailPayload(input: LabsEmailConfig): Record<string, unknown> {
4837
4973
  });
4838
4974
  }
4839
4975
 
4840
- function recordingPayload(input: LabsRecordingConfig): Record<string, unknown> {
4976
+ function recordingPayload(input: LabsRecordingConfig | boolean): Record<string, unknown> | boolean {
4977
+ if (typeof input === "boolean") return input;
4841
4978
  return compact({
4842
4979
  enabled: input.enabled,
4980
+ transcribe: input.transcribe,
4843
4981
  record_audio: input.record_audio ?? input.recordAudio,
4844
4982
  consent_required: input.consent_required ?? input.consentRequired,
4845
4983
  announcement: input.announcement,
package/src/s2s.ts ADDED
@@ -0,0 +1,100 @@
1
+ import type {
2
+ CreateLabsAgentRequest,
3
+ GetLabsAgentOptions,
4
+ LabsRealtimeSelection,
5
+ SupafoneLabs,
6
+ } from "./index.js";
7
+
8
+ export type S2SProviderName = "ultravox" | "openai" | "google" | "xai" | "smallest";
9
+ export interface S2SModelOptions {
10
+ model?: string;
11
+ voice?: string;
12
+ }
13
+ export interface S2SOptions extends S2SModelOptions {
14
+ provider?: S2SProviderName;
15
+ }
16
+
17
+ /** Shared parent for Supafone-hosted S2S agents. Vendor keys stay on the server. */
18
+ export class SupafoneS2S {
19
+ readonly provider: S2SProviderName;
20
+ private readonly selection: LabsRealtimeSelection | null;
21
+
22
+ constructor(protected readonly client: SupafoneLabs, options: S2SOptions = {}) {
23
+ const provider = options.provider ?? "ultravox";
24
+ if (!["ultravox", "openai", "google", "xai", "smallest"].includes(provider)) {
25
+ throw new Error("Unsupported Supafone S2S provider");
26
+ }
27
+ if (provider === "ultravox" && (options.model !== undefined || options.voice !== undefined)) {
28
+ throw new Error("Configure Ultravox voices through the agent's voice settings");
29
+ }
30
+ for (const field of ["model", "voice"] as const) {
31
+ const value = options[field];
32
+ if (value !== undefined && (typeof value !== "string" || !value.trim())) {
33
+ throw new Error(`${field} must be a non-empty string`);
34
+ }
35
+ }
36
+ this.provider = provider;
37
+ this.selection = provider === "ultravox" ? null : {
38
+ provider,
39
+ ...(options.model !== undefined ? { model: options.model.trim() } : {}),
40
+ ...(options.voice !== undefined ? { voice: options.voice.trim() } : {}),
41
+ };
42
+ }
43
+
44
+ /** A fresh wire selection; null explicitly restores the default Ultravox runtime. */
45
+ get realtime(): LabsRealtimeSelection | null {
46
+ return this.selection ? { ...this.selection } : null;
47
+ }
48
+
49
+ /** Create through the normal hosted agent factory with this provider. */
50
+ create(input: Omit<CreateLabsAgentRequest, "realtime">) {
51
+ if ("realtime" in input) {
52
+ throw new Error("Set provider/model/voice on the S2S adapter, not create({realtime: ...})");
53
+ }
54
+ return this.client.labs.agents.create({
55
+ ...input,
56
+ ...(this.realtime ? { realtime: this.realtime } : {}),
57
+ });
58
+ }
59
+
60
+ /** Change the saved selection for subsequent calls, preserving the agent and number. */
61
+ apply(agentKey: string, options: GetLabsAgentOptions = {}) {
62
+ this.checkAgentKey(agentKey);
63
+ return this.client.labs.agents.update(agentKey, { realtime: this.realtime }, options);
64
+ }
65
+
66
+ /** Preview the saved agent. Call apply first when switching providers. */
67
+ testCall(agentKey: string, options: GetLabsAgentOptions = {}) {
68
+ this.checkAgentKey(agentKey);
69
+ return this.client.labs.agents.testCall(agentKey, options);
70
+ }
71
+
72
+ private checkAgentKey(agentKey: string): void {
73
+ if (typeof agentKey !== "string" || !agentKey.trim()) throw new Error("agentKey is required");
74
+ }
75
+ }
76
+
77
+ export class UltravoxS2S extends SupafoneS2S {
78
+ constructor(client: SupafoneLabs) { super(client, { provider: "ultravox" }); }
79
+ }
80
+ export class OpenAIS2S extends SupafoneS2S {
81
+ constructor(client: SupafoneLabs, options: S2SModelOptions = {}) {
82
+ super(client, { ...options, provider: "openai" });
83
+ }
84
+ }
85
+ export class GeminiS2S extends SupafoneS2S {
86
+ constructor(client: SupafoneLabs, options: S2SModelOptions = {}) {
87
+ super(client, { ...options, provider: "google" });
88
+ }
89
+ }
90
+ export class GrokS2S extends SupafoneS2S {
91
+ constructor(client: SupafoneLabs, options: S2SModelOptions = {}) {
92
+ super(client, { ...options, provider: "xai" });
93
+ }
94
+ }
95
+ /** Smallest AI Hydra: full-duplex audio, without transcript events. */
96
+ export class HydraS2S extends SupafoneS2S {
97
+ constructor(client: SupafoneLabs, options: S2SModelOptions = {}) {
98
+ super(client, { ...options, provider: "smallest" });
99
+ }
100
+ }