@avakado.ai/schemas 1.1.32 → 1.1.33

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
@@ -123,3 +123,19 @@ await Subscription.createIndexes();
123
123
  ```
124
124
 
125
125
  That is why an index or enum change is a major version bump.
126
+
127
+ ### Realtime speech providers
128
+
129
+ Realtime agents may store `speechConfig` separately from the model's
130
+ `responseConfig`. Omit it for existing/native voice behavior, or explicitly use
131
+ `{ provider: "native" }`. An external voice is configured as follows:
132
+
133
+ ```js
134
+ speechConfig: {
135
+ provider: "elevenlabs",
136
+ voiceId: "your-voice-id",
137
+ voiceName: "My voice", // Optional display metadata
138
+ modelId: "eleven_flash_v2_5",
139
+ settings: { stability: 0.5, similarityBoost: 0.75 }
140
+ }
141
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@avakado.ai/schemas",
3
- "version": "1.1.32",
3
+ "version": "1.1.33",
4
4
  "description": "Canonical Mongo schemas, models, enums and index declarations shared by the Avakado services.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -83,6 +83,22 @@ const ModelConfigSchema = new Schema({
83
83
  const ResponseConfigSchema = new Schema({
84
84
  provider: { type: String, enum: [...Object.keys(ProviderConfig), 'elevenlabs', 'custom'], required: true },
85
85
  }, { _id: false, discriminatorKey: 'provider' });
86
+ const SpeechConfigSchema = new Schema({
87
+ provider: { type: String, enum: ['native', 'elevenlabs'], required: true },
88
+ }, { _id: false, discriminatorKey: 'provider', strict: 'throw' });
89
+ SpeechConfigSchema.discriminator('native', new Schema({}, { _id: false, strict: 'throw' }));
90
+ SpeechConfigSchema.discriminator('elevenlabs', new Schema({
91
+ voiceId: { type: String, required: true, trim: true },
92
+ voiceName: { type: String, trim: true },
93
+ modelId: { type: String, required: true, trim: true },
94
+ settings: {
95
+ type: new Schema({
96
+ stability: { type: Number, min: 0, max: 1 },
97
+ similarityBoost: { type: Number, min: 0, max: 1 },
98
+ }, { _id: false, strict: 'throw' }),
99
+ default: undefined,
100
+ },
101
+ }, { _id: false, strict: 'throw' }));
86
102
  const RealtimeConfigSchema = new Schema({
87
103
  modality: {
88
104
  type: String, required: true,
@@ -95,7 +111,9 @@ const RealtimeConfigSchema = new Schema({
95
111
  message: props => `Invalid modality "${props.value}" for this provider`,
96
112
  }
97
113
  },
98
- responseConfig: ResponseConfigSchema
114
+ responseConfig: ResponseConfigSchema,
115
+ // Missing on existing agents means native speech; no migration required.
116
+ speechConfig: { type: SpeechConfigSchema, default: undefined },
99
117
  }, { _id: false });
100
118
  const responseConfigPath = RealtimeConfigSchema.path('responseConfig');
101
119
  responseConfigPath.discriminator('openai', new Schema({
package/types.d.ts CHANGED
@@ -46,6 +46,7 @@ export interface AgentModel {
46
46
  personalInfo?: { name?: string; description?: string; avatar?: string; systemPrompt?: { role?: string; objective?: string; instructionsAndWorkflow?: string; constraintsAndRules?: string; }; };
47
47
  modality?: string;
48
48
  responseConfig?: { provider: "openai" | "gemini" | "anthropic" | "elevenlabs" | "custom"; };
49
+ speechConfig?: { provider: "native"; } | { voiceId: string; voiceName?: string; modelId: string; settings?: { stability?: number; similarityBoost?: number; }; provider: "elevenlabs"; };
49
50
  }
50
51
 
51
52
  export interface Amount {
package/src/media.test.js DELETED
@@ -1,85 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import { MediaPlanError, negotiateCall, selectChannelMedia } from "./media.js";
4
-
5
- test("exotel 8 kHz pcm is resampled to openai 24 kHz", () => {
6
- const plan = negotiateCall({ channelName: "Exotel", provider: "openai" });
7
- assert.deepEqual(plan.channel, { encoding: "pcm16", rate: 8000 });
8
- assert.deepEqual(plan.provider.input, { encoding: "pcm16", rate: 24000 });
9
- assert.deepEqual(plan.provider.output, { encoding: "pcm16", rate: 24000 });
10
- assert.equal(plan.uplink, "pcm16:8000->pcm16:24000");
11
- assert.equal(plan.downlink, "pcm16:24000->pcm16:8000");
12
- });
13
-
14
- test("exotel 16 kHz pcm is resampled to openai 24 kHz", () => {
15
- const plan = negotiateCall({
16
- channelName: "exotel",
17
- channelConfig: { media: { encoding: "pcm16", rate: 16000 } },
18
- provider: "openai",
19
- });
20
- assert.equal(plan.channel.rate, 16000);
21
- assert.equal(plan.provider.input.rate, 24000);
22
- assert.equal(plan.uplink, "pcm16:16000->pcm16:24000");
23
- assert.equal(plan.downlink, "pcm16:24000->pcm16:16000");
24
- });
25
-
26
- test("exotel 8 kHz and gemini bridge 16 kHz in and 24 kHz out", () => {
27
- const plan = negotiateCall({ channelName: "exotel", provider: "gemini" });
28
- assert.deepEqual(plan.provider.input, { encoding: "pcm16", rate: 16000 });
29
- assert.deepEqual(plan.provider.output, { encoding: "pcm16", rate: 24000 });
30
- assert.equal(plan.uplink, "pcm16:8000->pcm16:16000");
31
- assert.equal(plan.downlink, "pcm16:24000->pcm16:8000");
32
- });
33
-
34
- test("exotel 16 kHz into gemini only downsamples the model output", () => {
35
- const plan = negotiateCall({
36
- channelName: "exotel",
37
- channelConfig: { media: { encoding: "pcm16", rate: 16000 } },
38
- provider: "google",
39
- });
40
- assert.equal(plan.uplink, "identity");
41
- assert.equal(plan.downlink, "pcm16:24000->pcm16:16000");
42
- });
43
-
44
- test("exotel 24 kHz into gemini only downsamples the caller audio", () => {
45
- const plan = negotiateCall({
46
- channelName: "exotel",
47
- channelConfig: { media: { encoding: "pcm16", rate: 24000 } },
48
- provider: "gemini",
49
- });
50
- assert.equal(plan.uplink, "pcm16:24000->pcm16:16000");
51
- assert.equal(plan.downlink, "identity");
52
- });
53
-
54
- test("whatsapp and twilio default to g711 ulaw and match openai", () => {
55
- for (const channelName of ["Whatsapp", "twilio", "tataTele"]) {
56
- const plan = negotiateCall({ channelName, provider: "openai" });
57
- assert.deepEqual(plan.channel, { encoding: "g711_ulaw", rate: 8000 });
58
- assert.equal(plan.uplink, "identity");
59
- assert.equal(plan.downlink, "identity");
60
- }
61
- });
62
-
63
- test("whatsapp ulaw into gemini uses the registered converters", () => {
64
- const plan = negotiateCall({ channelName: "whatsapp", provider: "gemini" });
65
- assert.equal(plan.uplink, "g711_ulaw:8000->pcm16:16000");
66
- assert.equal(plan.downlink, "pcm16:24000->g711_ulaw:8000");
67
- });
68
-
69
- test("a wire outside the channel menu is rejected", () => {
70
- assert.throws(
71
- () => selectChannelMedia("exotel", { encoding: "pcm16", rate: 48000 }),
72
- MediaPlanError
73
- );
74
- });
75
-
76
- test("alaw into gemini fails until a converter exists", () => {
77
- assert.throws(
78
- () => negotiateCall({
79
- channelName: "whatsapp",
80
- channelConfig: { media: { encoding: "g711_alaw", rate: 8000 } },
81
- provider: "gemini",
82
- }),
83
- /No converter/
84
- );
85
- });