@absolutejs/voice 0.0.22-beta.632 → 0.0.22-beta.633

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.
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.632",
3
+ "version": "0.0.22-beta.633",
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/media": "0.0.1-beta.19"
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
  }