@absolutejs/voice 0.0.22-beta.632 → 0.0.22-beta.634
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/core/types.d.ts +34 -0
- package/dist/index.js +46 -14
- package/dist/manifest.d.ts +2 -0
- package/dist/manifest.js +8865 -0
- package/dist/manifest.json +385 -0
- package/dist/testing/index.js +49 -15
- package/package.json +17 -4
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
{
|
|
2
|
+
"contract": 1,
|
|
3
|
+
"identity": {
|
|
4
|
+
"accent": "#f43f5e",
|
|
5
|
+
"category": "voice",
|
|
6
|
+
"description": "Self-hosted voice assistant runtime for Elysia: browser and phone-call sessions over WebSockets, pluggable speech-to-text (`voice/stt`), text-to-speech (`voice/tts`), and speech-to-speech (`voice/realtime`) vendor adapters, turn detection, barge-in, greetings, reconnect recovery, session stores (`voice/session-store`), traces, evals, and live-monitor surfaces — the orchestration hosted voice platforms keep behind their dashboards, inside your own server.",
|
|
7
|
+
"docsUrl": "https://github.com/absolutejs/voice",
|
|
8
|
+
"name": "@absolutejs/voice",
|
|
9
|
+
"tagline": "Let people talk to your site and get spoken answers."
|
|
10
|
+
},
|
|
11
|
+
"implements": [
|
|
12
|
+
{
|
|
13
|
+
"contract": "voice/session-store",
|
|
14
|
+
"factory": "createVoiceMemoryStore",
|
|
15
|
+
"from": "@absolutejs/voice",
|
|
16
|
+
"title": "In memory (development only — calls are forgotten on restart)",
|
|
17
|
+
"wiring": {
|
|
18
|
+
"code": "createVoiceMemoryStore()",
|
|
19
|
+
"imports": [
|
|
20
|
+
{
|
|
21
|
+
"from": "@absolutejs/voice",
|
|
22
|
+
"names": [
|
|
23
|
+
"createVoiceMemoryStore"
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"contract": "voice/session-store",
|
|
31
|
+
"factory": "createVoiceSQLiteSessionStore",
|
|
32
|
+
"from": "@absolutejs/voice",
|
|
33
|
+
"settings": {
|
|
34
|
+
"type": "object",
|
|
35
|
+
"required": [
|
|
36
|
+
"path"
|
|
37
|
+
],
|
|
38
|
+
"properties": {
|
|
39
|
+
"path": {
|
|
40
|
+
"description": "Database file on this machine where call sessions are kept. Created if missing.",
|
|
41
|
+
"examples": [
|
|
42
|
+
"./var/voice-sessions.sqlite"
|
|
43
|
+
],
|
|
44
|
+
"title": "Database file",
|
|
45
|
+
"type": "string"
|
|
46
|
+
},
|
|
47
|
+
"tableName": {
|
|
48
|
+
"description": "Table the sessions are stored in.",
|
|
49
|
+
"title": "Table name",
|
|
50
|
+
"type": "string"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"title": "SQLite file on this machine",
|
|
55
|
+
"wiring": {
|
|
56
|
+
"code": "createVoiceSQLiteSessionStore(${settings})",
|
|
57
|
+
"imports": [
|
|
58
|
+
{
|
|
59
|
+
"from": "@absolutejs/voice",
|
|
60
|
+
"names": [
|
|
61
|
+
"createVoiceSQLiteSessionStore"
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"contract": "voice/session-store",
|
|
69
|
+
"factory": "createVoicePostgresSessionStore",
|
|
70
|
+
"from": "@absolutejs/voice",
|
|
71
|
+
"requires": {
|
|
72
|
+
"env": [
|
|
73
|
+
{
|
|
74
|
+
"description": "Postgres connection string (call sessions are stored here)",
|
|
75
|
+
"example": "postgres://user:pass@host/db",
|
|
76
|
+
"key": "DATABASE_URL",
|
|
77
|
+
"secret": true
|
|
78
|
+
}
|
|
79
|
+
],
|
|
80
|
+
"services": [
|
|
81
|
+
{
|
|
82
|
+
"description": "Stores call sessions durably",
|
|
83
|
+
"id": "postgres"
|
|
84
|
+
}
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
"settings": {
|
|
88
|
+
"type": "object",
|
|
89
|
+
"properties": {
|
|
90
|
+
"schemaName": {
|
|
91
|
+
"description": "Postgres schema the table lives in.",
|
|
92
|
+
"title": "Schema name",
|
|
93
|
+
"type": "string"
|
|
94
|
+
},
|
|
95
|
+
"tableName": {
|
|
96
|
+
"description": "Table the sessions are stored in.",
|
|
97
|
+
"title": "Table name",
|
|
98
|
+
"type": "string"
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"title": "Postgres",
|
|
103
|
+
"wiring": {
|
|
104
|
+
"code": "createVoicePostgresSessionStore({ connectionString: ${env.DATABASE_URL} ?? \"\", ...${settings} })",
|
|
105
|
+
"imports": [
|
|
106
|
+
{
|
|
107
|
+
"from": "@absolutejs/voice",
|
|
108
|
+
"names": [
|
|
109
|
+
"createVoicePostgresSessionStore"
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"contract": "voice/session-store",
|
|
117
|
+
"factory": "createVoiceFileSessionStore",
|
|
118
|
+
"from": "@absolutejs/voice",
|
|
119
|
+
"settings": {
|
|
120
|
+
"type": "object",
|
|
121
|
+
"required": [
|
|
122
|
+
"directory"
|
|
123
|
+
],
|
|
124
|
+
"properties": {
|
|
125
|
+
"directory": {
|
|
126
|
+
"description": "Folder on this machine where each call session is kept as a JSON file. Created if missing.",
|
|
127
|
+
"examples": [
|
|
128
|
+
"./var/voice-sessions"
|
|
129
|
+
],
|
|
130
|
+
"title": "Storage folder",
|
|
131
|
+
"type": "string"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
"title": "JSON files on this machine",
|
|
136
|
+
"wiring": {
|
|
137
|
+
"code": "createVoiceFileSessionStore(${settings})",
|
|
138
|
+
"imports": [
|
|
139
|
+
{
|
|
140
|
+
"from": "@absolutejs/voice",
|
|
141
|
+
"names": [
|
|
142
|
+
"createVoiceFileSessionStore"
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
],
|
|
149
|
+
"requires": {
|
|
150
|
+
"peers": [
|
|
151
|
+
{
|
|
152
|
+
"name": "elysia",
|
|
153
|
+
"range": ">= 1.4.18",
|
|
154
|
+
"reason": "plugin host"
|
|
155
|
+
}
|
|
156
|
+
]
|
|
157
|
+
},
|
|
158
|
+
"settings": {
|
|
159
|
+
"type": "object",
|
|
160
|
+
"properties": {
|
|
161
|
+
"backchannelBargeInGuard": {
|
|
162
|
+
"description": "Keep the assistant talking through listening cues like \"mm-hm\" or \"right\" instead of treating them as interruptions.",
|
|
163
|
+
"title": "Ignore listening cues",
|
|
164
|
+
"type": "boolean"
|
|
165
|
+
},
|
|
166
|
+
"greeting": {
|
|
167
|
+
"description": "Spoken by the assistant the moment a call connects, before the caller says anything.",
|
|
168
|
+
"examples": [
|
|
169
|
+
"Hi! How can I help you today?"
|
|
170
|
+
],
|
|
171
|
+
"title": "Opening greeting",
|
|
172
|
+
"type": "string"
|
|
173
|
+
},
|
|
174
|
+
"normalizeNumbers": {
|
|
175
|
+
"description": "Rewrite spoken numbers to digits in transcripts (\"ten million\" becomes \"10 million\") so downstream systems read them consistently.",
|
|
176
|
+
"title": "Write numbers as digits",
|
|
177
|
+
"type": "boolean"
|
|
178
|
+
},
|
|
179
|
+
"path": {
|
|
180
|
+
"default": "/voice/realtime",
|
|
181
|
+
"description": "The WebSocket route callers connect to.",
|
|
182
|
+
"title": "Call route",
|
|
183
|
+
"type": "string"
|
|
184
|
+
},
|
|
185
|
+
"preset": {
|
|
186
|
+
"description": "Tuned bundle of turn-taking and latency defaults for the kind of calls you take — casual chat, guided intake, dictation, noisy rooms, or phone lines.",
|
|
187
|
+
"title": "Conversation preset",
|
|
188
|
+
"anyOf": [
|
|
189
|
+
{
|
|
190
|
+
"const": "default",
|
|
191
|
+
"type": "string"
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"const": "chat",
|
|
195
|
+
"type": "string"
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"const": "guided-intake",
|
|
199
|
+
"type": "string"
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"const": "dictation",
|
|
203
|
+
"type": "string"
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"const": "noisy-room",
|
|
207
|
+
"type": "string"
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"const": "pstn-balanced",
|
|
211
|
+
"type": "string"
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
"const": "pstn-fast",
|
|
215
|
+
"type": "string"
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"const": "reliability",
|
|
219
|
+
"type": "string"
|
|
220
|
+
}
|
|
221
|
+
]
|
|
222
|
+
},
|
|
223
|
+
"resumeGreeting": {
|
|
224
|
+
"description": "Spoken when a dropped call reconnects, so the caller knows they are back.",
|
|
225
|
+
"examples": [
|
|
226
|
+
"Sorry, we got cut off — where were we?"
|
|
227
|
+
],
|
|
228
|
+
"title": "Reconnect greeting",
|
|
229
|
+
"type": "string"
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
"slots": {
|
|
234
|
+
"realtime": {
|
|
235
|
+
"configPath": "realtime",
|
|
236
|
+
"contract": "voice/realtime",
|
|
237
|
+
"description": "One speech-to-speech model that listens and talks in a single stream — an alternative to picking separate speech-to-text and text-to-speech services.",
|
|
238
|
+
"known": [
|
|
239
|
+
"@absolutejs/voice-openai",
|
|
240
|
+
"@absolutejs/voice-gemini"
|
|
241
|
+
]
|
|
242
|
+
},
|
|
243
|
+
"session": {
|
|
244
|
+
"configPath": "session",
|
|
245
|
+
"contract": "voice/session-store",
|
|
246
|
+
"description": "Where each call's transcript and state is kept while it runs (and after, for review)",
|
|
247
|
+
"known": [
|
|
248
|
+
"@absolutejs/voice#memory",
|
|
249
|
+
"@absolutejs/voice#sqlite",
|
|
250
|
+
"@absolutejs/voice#postgres",
|
|
251
|
+
"@absolutejs/voice#file"
|
|
252
|
+
],
|
|
253
|
+
"required": true
|
|
254
|
+
},
|
|
255
|
+
"stt": {
|
|
256
|
+
"configPath": "stt",
|
|
257
|
+
"contract": "voice/stt",
|
|
258
|
+
"description": "The service that turns the caller's speech into text. Not needed when a speech-to-speech (realtime) adapter is chosen instead.",
|
|
259
|
+
"known": [
|
|
260
|
+
"@absolutejs/voice-deepgram",
|
|
261
|
+
"@absolutejs/voice-assemblyai",
|
|
262
|
+
"@absolutejs/voice-gladia",
|
|
263
|
+
"@absolutejs/voice-soniox",
|
|
264
|
+
"@absolutejs/voice-speechmatics",
|
|
265
|
+
"@absolutejs/voice-azure",
|
|
266
|
+
"@absolutejs/voice-google-speech",
|
|
267
|
+
"@absolutejs/voice-openai-whisper"
|
|
268
|
+
]
|
|
269
|
+
},
|
|
270
|
+
"tts": {
|
|
271
|
+
"configPath": "tts",
|
|
272
|
+
"contract": "voice/tts",
|
|
273
|
+
"description": "The service that speaks the assistant's replies out loud. Not needed when a speech-to-speech (realtime) adapter is chosen instead.",
|
|
274
|
+
"known": [
|
|
275
|
+
"@absolutejs/voice-elevenlabs",
|
|
276
|
+
"@absolutejs/voice-cartesia",
|
|
277
|
+
"@absolutejs/voice-azure",
|
|
278
|
+
"@absolutejs/voice-lmnt",
|
|
279
|
+
"@absolutejs/voice-playht",
|
|
280
|
+
"@absolutejs/voice-rime",
|
|
281
|
+
"@absolutejs/voice-smallest",
|
|
282
|
+
"@absolutejs/voice-neets"
|
|
283
|
+
]
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
"wiring": [
|
|
287
|
+
{
|
|
288
|
+
"description": "Pick a speech-to-text and a text-to-speech service, choose where call sessions are stored, and write the onTurn handler — the assistant's brain.",
|
|
289
|
+
"id": "default",
|
|
290
|
+
"server": {
|
|
291
|
+
"code": "// Every call's transcript and state lives in the session store.\nconst voiceSessions = ${slot.session};\n\n// Mount with .use(voiceAssistant); callers connect over WebSocket at `path`.\nconst voiceAssistant = voice({\n\tonTurn: async (session, turn) => {\n\t\t// TODO: generate the assistant's reply (call your LLM here).\n\t\treturn { assistantText: \"You said: \" + turn.text };\n\t},\n\tsession: voiceSessions,\n\tstt: ${slot.stt},\n\ttts: ${slot.tts},\n\t...${settings}\n});",
|
|
292
|
+
"imports": [
|
|
293
|
+
{
|
|
294
|
+
"from": "@absolutejs/voice",
|
|
295
|
+
"names": [
|
|
296
|
+
"voice"
|
|
297
|
+
]
|
|
298
|
+
}
|
|
299
|
+
],
|
|
300
|
+
"placement": "module-scope"
|
|
301
|
+
},
|
|
302
|
+
"title": "Voice assistant with separate STT + TTS"
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
"description": "Use one speech-to-speech model (OpenAI Realtime, Gemini Live) that listens and talks in a single stream instead of separate STT and TTS services.",
|
|
306
|
+
"id": "realtime",
|
|
307
|
+
"server": {
|
|
308
|
+
"code": "// Every call's transcript and state lives in the session store.\nconst voiceSessions = ${slot.session};\n\n// Mount with .use(voiceAssistant); callers connect over WebSocket at `path`.\nconst voiceAssistant = voice({\n\tonTurn: async (session, turn) => {\n\t\t// TODO: generate the assistant's reply (call your LLM here).\n\t\treturn { assistantText: \"You said: \" + turn.text };\n\t},\n\trealtime: ${slot.realtime},\n\tsession: voiceSessions,\n\t...${settings}\n});",
|
|
309
|
+
"imports": [
|
|
310
|
+
{
|
|
311
|
+
"from": "@absolutejs/voice",
|
|
312
|
+
"names": [
|
|
313
|
+
"voice"
|
|
314
|
+
]
|
|
315
|
+
}
|
|
316
|
+
],
|
|
317
|
+
"placement": "module-scope"
|
|
318
|
+
},
|
|
319
|
+
"title": "Voice assistant with a speech-to-speech model"
|
|
320
|
+
}
|
|
321
|
+
],
|
|
322
|
+
"tools": {
|
|
323
|
+
"delete_session": {
|
|
324
|
+
"annotations": {
|
|
325
|
+
"destructiveHint": true,
|
|
326
|
+
"idempotentHint": true
|
|
327
|
+
},
|
|
328
|
+
"description": "Delete one call session (its transcript and state) by id. Deleting an unknown id succeeds.",
|
|
329
|
+
"input": {
|
|
330
|
+
"type": "object",
|
|
331
|
+
"required": [
|
|
332
|
+
"id"
|
|
333
|
+
],
|
|
334
|
+
"properties": {
|
|
335
|
+
"id": {
|
|
336
|
+
"minLength": 1,
|
|
337
|
+
"type": "string"
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
"kind": "runtime"
|
|
342
|
+
},
|
|
343
|
+
"get_session": {
|
|
344
|
+
"annotations": {
|
|
345
|
+
"readOnlyHint": true
|
|
346
|
+
},
|
|
347
|
+
"description": "Fetch one call session by id: status, timing, and the committed conversation turns (caller text and assistant reply), most recent last.",
|
|
348
|
+
"input": {
|
|
349
|
+
"type": "object",
|
|
350
|
+
"required": [
|
|
351
|
+
"id"
|
|
352
|
+
],
|
|
353
|
+
"properties": {
|
|
354
|
+
"id": {
|
|
355
|
+
"minLength": 1,
|
|
356
|
+
"type": "string"
|
|
357
|
+
},
|
|
358
|
+
"maxTurns": {
|
|
359
|
+
"maximum": 200,
|
|
360
|
+
"minimum": 1,
|
|
361
|
+
"type": "integer"
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
"kind": "runtime"
|
|
366
|
+
},
|
|
367
|
+
"list_sessions": {
|
|
368
|
+
"annotations": {
|
|
369
|
+
"readOnlyHint": true
|
|
370
|
+
},
|
|
371
|
+
"description": "List call sessions with status, creation time, last activity, and turn count — most recently active first.",
|
|
372
|
+
"input": {
|
|
373
|
+
"type": "object",
|
|
374
|
+
"properties": {
|
|
375
|
+
"limit": {
|
|
376
|
+
"maximum": 200,
|
|
377
|
+
"minimum": 1,
|
|
378
|
+
"type": "integer"
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
},
|
|
382
|
+
"kind": "runtime"
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
package/dist/testing/index.js
CHANGED
|
@@ -6408,7 +6408,10 @@ var createEmptyCurrentTurn = () => ({
|
|
|
6408
6408
|
silenceStartedAt: undefined,
|
|
6409
6409
|
transcripts: []
|
|
6410
6410
|
});
|
|
6411
|
-
var cloneTranscript = (transcript) => ({
|
|
6411
|
+
var cloneTranscript = (transcript) => ({
|
|
6412
|
+
...transcript,
|
|
6413
|
+
words: transcript.words?.map((word) => ({ ...word }))
|
|
6414
|
+
});
|
|
6412
6415
|
var encodeBase64 = (chunk) => Buffer2.from(chunk).toString("base64");
|
|
6413
6416
|
var countWords2 = (text) => text.trim().split(/\s+/).filter(Boolean).length;
|
|
6414
6417
|
var normalizeText2 = (text) => text.trim().replace(/\s+/g, " ");
|
|
@@ -6453,9 +6456,12 @@ var calculateMeanConfidence = (transcripts) => {
|
|
|
6453
6456
|
}
|
|
6454
6457
|
return sum / total;
|
|
6455
6458
|
};
|
|
6459
|
+
var collectTranscriptWords = (transcripts) => transcripts.flatMap((transcript) => transcript.words ?? []);
|
|
6460
|
+
var collectWordConfidences = (transcripts) => collectTranscriptWords(transcripts).map((word) => word.confidence).filter((confidence) => typeof confidence === "number" && Number.isFinite(confidence));
|
|
6456
6461
|
var createTurnQuality = (transcripts, source, fallbackUsed, fallbackDiagnostics, correctionDiagnostics, costEstimate) => {
|
|
6457
6462
|
const sampledTranscripts = transcripts.filter((transcript) => typeof transcript.confidence === "number");
|
|
6458
6463
|
const confidenceSampleCount = sampledTranscripts.length;
|
|
6464
|
+
const wordConfidences = collectWordConfidences(transcripts);
|
|
6459
6465
|
return {
|
|
6460
6466
|
averageConfidence: confidenceSampleCount > 0 ? sampledTranscripts.reduce((sum, transcript) => sum + transcript.confidence, 0) / confidenceSampleCount : undefined,
|
|
6461
6467
|
confidenceSampleCount,
|
|
@@ -6466,7 +6472,9 @@ var createTurnQuality = (transcripts, source, fallbackUsed, fallbackDiagnostics,
|
|
|
6466
6472
|
finalTranscriptCount: transcripts.filter((transcript) => transcript.isFinal).length,
|
|
6467
6473
|
partialTranscriptCount: transcripts.filter((transcript) => !transcript.isFinal).length,
|
|
6468
6474
|
selectedTranscriptCount: transcripts.length,
|
|
6469
|
-
source
|
|
6475
|
+
source,
|
|
6476
|
+
lowestWordConfidence: wordConfidences.length > 0 ? Math.min(...wordConfidences) : undefined,
|
|
6477
|
+
wordConfidenceSampleCount: wordConfidences.length
|
|
6470
6478
|
};
|
|
6471
6479
|
};
|
|
6472
6480
|
var createTurnCostEstimate = (input) => {
|
|
@@ -6483,20 +6491,38 @@ var createTurnCostEstimate = (input) => {
|
|
|
6483
6491
|
};
|
|
6484
6492
|
};
|
|
6485
6493
|
var normalizeCorrectionText = (text) => normalizeText2(text);
|
|
6486
|
-
var
|
|
6494
|
+
var evaluateFallbackNeed = (candidate, config) => {
|
|
6487
6495
|
const trimmed = normalizeText2(candidate.text);
|
|
6488
6496
|
const wordCount = countWords2(trimmed);
|
|
6497
|
+
const averageConfidence = calculateMeanConfidence(candidate.transcripts);
|
|
6498
|
+
const words = collectTranscriptWords(candidate.transcripts);
|
|
6499
|
+
const wordConfidences = collectWordConfidences(candidate.transcripts);
|
|
6500
|
+
const lowestWordConfidence = wordConfidences.length > 0 ? Math.min(...wordConfidences) : undefined;
|
|
6501
|
+
const resolvedCandidate = {
|
|
6502
|
+
averageConfidence,
|
|
6503
|
+
lowestWordConfidence,
|
|
6504
|
+
text: candidate.text,
|
|
6505
|
+
transcripts: candidate.transcripts,
|
|
6506
|
+
wordCount,
|
|
6507
|
+
words
|
|
6508
|
+
};
|
|
6489
6509
|
if (config.trigger === "always") {
|
|
6490
|
-
return true;
|
|
6510
|
+
return { needed: true, reason: "always" };
|
|
6491
6511
|
}
|
|
6492
|
-
if (config.trigger === "empty-turn") {
|
|
6493
|
-
return
|
|
6512
|
+
if (wordCount < config.minTextLength && (config.trigger === "empty-turn" || config.trigger === "empty-or-low-confidence")) {
|
|
6513
|
+
return { needed: true, reason: "empty-turn" };
|
|
6494
6514
|
}
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6515
|
+
if (config.riskPolicy?.(resolvedCandidate)) {
|
|
6516
|
+
return { needed: true, reason: "risk-policy" };
|
|
6517
|
+
}
|
|
6518
|
+
if (config.wordConfidenceThreshold !== undefined && lowestWordConfidence !== undefined && lowestWordConfidence < config.wordConfidenceThreshold) {
|
|
6519
|
+
return { needed: true, reason: "word-confidence" };
|
|
6520
|
+
}
|
|
6521
|
+
const checksConfidence = config.trigger === "low-confidence" || config.trigger === "empty-or-low-confidence";
|
|
6522
|
+
if (checksConfidence && averageConfidence > 0 && averageConfidence < config.confidenceThreshold) {
|
|
6523
|
+
return { needed: true, reason: "transcript-confidence" };
|
|
6498
6524
|
}
|
|
6499
|
-
return
|
|
6525
|
+
return { needed: false, reason: undefined };
|
|
6500
6526
|
};
|
|
6501
6527
|
var selectBetterTurnText = (candidate, fallback) => {
|
|
6502
6528
|
if (!fallback.text) {
|
|
@@ -6615,7 +6641,9 @@ var createVoiceSession = (options) => {
|
|
|
6615
6641
|
minTextLength: options.sttFallback.minTextLength ?? DEFAULT_FALLBACK_MIN_TEXT_LENGTH,
|
|
6616
6642
|
replayWindowMs: options.sttFallback.replayWindowMs ?? DEFAULT_FALLBACK_REPLAY_MS,
|
|
6617
6643
|
settleMs: options.sttFallback.settleMs ?? DEFAULT_FALLBACK_SETTLE_MS,
|
|
6618
|
-
trigger: options.sttFallback.trigger ?? "empty-or-low-confidence"
|
|
6644
|
+
trigger: options.sttFallback.trigger ?? "empty-or-low-confidence",
|
|
6645
|
+
wordConfidenceThreshold: options.sttFallback.wordConfidenceThreshold,
|
|
6646
|
+
riskPolicy: options.sttFallback.riskPolicy
|
|
6619
6647
|
} : undefined;
|
|
6620
6648
|
const appendTrace = async (input) => {
|
|
6621
6649
|
await options.trace?.append({
|
|
@@ -7762,7 +7790,8 @@ var createVoiceSession = (options) => {
|
|
|
7762
7790
|
text: primaryText,
|
|
7763
7791
|
transcripts: primaryTranscripts
|
|
7764
7792
|
};
|
|
7765
|
-
|
|
7793
|
+
const fallbackNeed = evaluateFallbackNeed(candidate, sttFallback);
|
|
7794
|
+
if (!fallbackNeed.needed) {
|
|
7766
7795
|
return null;
|
|
7767
7796
|
}
|
|
7768
7797
|
fallbackAttemptsForCurrentTurn += 1;
|
|
@@ -7889,7 +7918,8 @@ var createVoiceSession = (options) => {
|
|
|
7889
7918
|
primaryWordCount: primaryCandidate.wordCount,
|
|
7890
7919
|
selected: selection.winner.text === fallbackCandidate.text,
|
|
7891
7920
|
selectionReason: selection.reason,
|
|
7892
|
-
trigger: sttFallback.trigger
|
|
7921
|
+
trigger: sttFallback.trigger,
|
|
7922
|
+
triggerReason: fallbackNeed.reason
|
|
7893
7923
|
};
|
|
7894
7924
|
if (selection.winner.text === primaryCandidate.text) {
|
|
7895
7925
|
return {
|
|
@@ -10251,7 +10281,9 @@ var resolveBenchmarkFallbackConfig = (config) => {
|
|
|
10251
10281
|
minTextLength: config.minTextLength ?? 2,
|
|
10252
10282
|
replayWindowMs: config.replayWindowMs ?? 8000,
|
|
10253
10283
|
settleMs: config.settleMs ?? 220,
|
|
10254
|
-
trigger: config.trigger ?? "empty-or-low-confidence"
|
|
10284
|
+
trigger: config.trigger ?? "empty-or-low-confidence",
|
|
10285
|
+
wordConfidenceThreshold: config.wordConfidenceThreshold,
|
|
10286
|
+
riskPolicy: config.riskPolicy
|
|
10255
10287
|
};
|
|
10256
10288
|
};
|
|
10257
10289
|
var chunkAudio2 = (audio, bytesPerChunk) => {
|
|
@@ -14426,7 +14458,9 @@ var resolveSTTFallbackConfig = (config) => {
|
|
|
14426
14458
|
minTextLength: config.minTextLength ?? 2,
|
|
14427
14459
|
replayWindowMs: config.replayWindowMs ?? 8000,
|
|
14428
14460
|
settleMs: config.settleMs ?? 220,
|
|
14429
|
-
trigger: config.trigger ?? "empty-or-low-confidence"
|
|
14461
|
+
trigger: config.trigger ?? "empty-or-low-confidence",
|
|
14462
|
+
wordConfidenceThreshold: config.wordConfidenceThreshold,
|
|
14463
|
+
riskPolicy: config.riskPolicy
|
|
14430
14464
|
};
|
|
14431
14465
|
};
|
|
14432
14466
|
var normalizePhraseHints = (hints) => (hints ?? []).map((hint) => ({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@absolutejs/voice",
|
|
3
|
-
"version": "0.0.22-beta.
|
|
3
|
+
"version": "0.0.22-beta.634",
|
|
4
4
|
"description": "Voice primitives and Elysia plugin for AbsoluteJS",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"license": "BSL-1.1",
|
|
20
20
|
"author": "Alex Kahn",
|
|
21
21
|
"scripts": {
|
|
22
|
-
"build": "rm -rf dist && bun build ./src/index.ts ./src/client/index.ts ./src/react/index.ts ./src/vue/index.ts ./src/svelte/index.ts ./src/angular/index.ts ./src/testing/index.ts ./src/drizzle/index.ts --outdir dist --target bun --external elysia --external react --external vue --external @angular/core --external @absolutejs/absolute --external @absolutejs/ai --external @absolutejs/media --external drizzle-orm && bun build ./src/client/htmxBootstrap.ts --outdir dist/client --target browser --format esm && bun build ./src/embed/index.ts --outfile dist/embed/voice-widget.js --target browser --format iife --minify && bun build ./src/embed/index.ts --outdir dist/embed --target browser --format esm && tsc --emitDeclarationOnly --project tsconfig.json",
|
|
22
|
+
"build": "rm -rf dist && bun build ./src/index.ts ./src/manifest.ts ./src/client/index.ts ./src/react/index.ts ./src/vue/index.ts ./src/svelte/index.ts ./src/angular/index.ts ./src/testing/index.ts ./src/drizzle/index.ts --outdir dist --root ./src --target bun --external elysia --external react --external vue --external @angular/core --external @absolutejs/absolute --external @absolutejs/ai --external @absolutejs/media --external drizzle-orm && bun build ./src/client/htmxBootstrap.ts --outdir dist/client --target browser --format esm && bun build ./src/embed/index.ts --outfile dist/embed/voice-widget.js --target browser --format iife --minify && bun build ./src/embed/index.ts --outdir dist/embed --target browser --format esm && tsc --emitDeclarationOnly --project tsconfig.json && absolute-manifest emit",
|
|
23
23
|
"config": "absolute config",
|
|
24
24
|
"format": "absolute prettier --write",
|
|
25
25
|
"knip": "knip",
|
|
@@ -75,7 +75,12 @@
|
|
|
75
75
|
"import": "./dist/embed/index.js",
|
|
76
76
|
"types": "./dist/embed/index.d.ts"
|
|
77
77
|
},
|
|
78
|
-
"./embed/voice-widget.js": "./dist/embed/voice-widget.js"
|
|
78
|
+
"./embed/voice-widget.js": "./dist/embed/voice-widget.js",
|
|
79
|
+
"./manifest": {
|
|
80
|
+
"import": "./dist/manifest.js",
|
|
81
|
+
"types": "./dist/manifest.d.ts"
|
|
82
|
+
},
|
|
83
|
+
"./manifest.json": "./dist/manifest.json"
|
|
79
84
|
},
|
|
80
85
|
"typesVersions": {
|
|
81
86
|
"*": {
|
|
@@ -102,6 +107,9 @@
|
|
|
102
107
|
],
|
|
103
108
|
"embed": [
|
|
104
109
|
"dist/embed/index.d.ts"
|
|
110
|
+
],
|
|
111
|
+
"manifest": [
|
|
112
|
+
"dist/manifest.d.ts"
|
|
105
113
|
]
|
|
106
114
|
}
|
|
107
115
|
},
|
|
@@ -132,7 +140,9 @@
|
|
|
132
140
|
}
|
|
133
141
|
},
|
|
134
142
|
"dependencies": {
|
|
135
|
-
"@absolutejs/
|
|
143
|
+
"@absolutejs/manifest": "^0.1.0",
|
|
144
|
+
"@absolutejs/media": "0.0.1-beta.19",
|
|
145
|
+
"@sinclair/typebox": "^0.34.0"
|
|
136
146
|
},
|
|
137
147
|
"devDependencies": {
|
|
138
148
|
"@absolutejs/absolute": "0.19.0-beta.1009",
|
|
@@ -157,5 +167,8 @@
|
|
|
157
167
|
"typescript": "^5.9.3",
|
|
158
168
|
"typescript-eslint": "^8.57.2",
|
|
159
169
|
"vue": "3.5.27"
|
|
170
|
+
},
|
|
171
|
+
"absolutejs": {
|
|
172
|
+
"manifestContract": 1
|
|
160
173
|
}
|
|
161
174
|
}
|