zilmate 1.1.0 → 1.3.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/.env.example +6 -0
- package/README.md +38 -3
- package/dist/agents/manager.d.ts +133 -0
- package/dist/agents/manager.d.ts.map +1 -1
- package/dist/agents/manager.js +23 -0
- package/dist/agents/manager.js.map +1 -1
- package/dist/cli/camera.d.ts +6 -0
- package/dist/cli/camera.d.ts.map +1 -0
- package/dist/cli/camera.js +27 -0
- package/dist/cli/camera.js.map +1 -0
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +14 -4
- package/dist/cli/interactive.js.map +1 -1
- package/dist/cli/menu.d.ts.map +1 -1
- package/dist/cli/menu.js +14 -0
- package/dist/cli/menu.js.map +1 -1
- package/dist/cli/setup.d.ts +5 -0
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +146 -12
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/voice.d.ts +3 -0
- package/dist/cli/voice.d.ts.map +1 -1
- package/dist/cli/voice.js +135 -4
- package/dist/cli/voice.js.map +1 -1
- package/dist/cli/welcome.d.ts.map +1 -1
- package/dist/cli/welcome.js +1 -0
- package/dist/cli/welcome.js.map +1 -1
- package/dist/config/env.d.ts +2 -0
- package/dist/config/env.d.ts.map +1 -1
- package/dist/config/env.js +2 -0
- package/dist/config/env.js.map +1 -1
- package/dist/config/models.d.ts +1 -0
- package/dist/config/models.d.ts.map +1 -1
- package/dist/config/models.js +1 -0
- package/dist/config/models.js.map +1 -1
- package/dist/index.js +104 -3
- package/dist/index.js.map +1 -1
- package/dist/tools/desktop.tool.d.ts +58 -0
- package/dist/tools/desktop.tool.d.ts.map +1 -0
- package/dist/tools/desktop.tool.js +433 -0
- package/dist/tools/desktop.tool.js.map +1 -0
- package/dist/tools/filesystem.tool.d.ts +93 -0
- package/dist/tools/filesystem.tool.d.ts.map +1 -0
- package/dist/tools/filesystem.tool.js +303 -0
- package/dist/tools/filesystem.tool.js.map +1 -0
- package/dist/voice/cascade.d.ts +15 -0
- package/dist/voice/cascade.d.ts.map +1 -0
- package/dist/voice/cascade.js +306 -0
- package/dist/voice/cascade.js.map +1 -0
- package/dist/voice/deepgram.d.ts +9 -16
- package/dist/voice/deepgram.d.ts.map +1 -1
- package/dist/voice/deepgram.js +66 -16
- package/dist/voice/deepgram.js.map +1 -1
- package/dist/voice/terminal.d.ts +27 -0
- package/dist/voice/terminal.d.ts.map +1 -0
- package/dist/voice/terminal.js +448 -0
- package/dist/voice/terminal.js.map +1 -0
- package/dist/voice/types.d.ts +2 -0
- package/dist/voice/types.d.ts.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { tool } from 'ai';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { copyFile, mkdir, readdir, readFile, rename, stat, unlink, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { requestConfirmation } from '../runtime/confirm.js';
|
|
7
|
+
import { emitProgress } from '../runtime/progress.js';
|
|
8
|
+
import { readJson, writeJson } from '../memory/local-store.js';
|
|
9
|
+
const watchFile = 'filesystem-watch.json';
|
|
10
|
+
const defaultMaxReadBytes = 180_000;
|
|
11
|
+
const ignoredDirectoryNames = new Set(['.git', 'node_modules', 'dist', '.next', '.npm-cache', '.zilo-manager', 'outputs']);
|
|
12
|
+
const sensitiveNamePattern = /(^\.env(?:\..*)?$|\.pem$|\.key$|\.p12$|\.pfx$|id_rsa|id_dsa|credentials|secrets?|token)/i;
|
|
13
|
+
function allowedRoots() {
|
|
14
|
+
const configured = (process.env.ZILMATE_FILE_ROOTS || '')
|
|
15
|
+
.split(path.delimiter)
|
|
16
|
+
.map((item) => item.trim())
|
|
17
|
+
.filter(Boolean);
|
|
18
|
+
return [process.cwd(), ...configured].map((root) => path.resolve(root));
|
|
19
|
+
}
|
|
20
|
+
function insideRoot(resolved, root) {
|
|
21
|
+
return resolved === root || resolved.startsWith(`${root}${path.sep}`);
|
|
22
|
+
}
|
|
23
|
+
function assertSafePath(inputPath, options = {}) {
|
|
24
|
+
const resolved = path.resolve(inputPath);
|
|
25
|
+
const roots = allowedRoots();
|
|
26
|
+
if (!roots.some((root) => insideRoot(resolved, root))) {
|
|
27
|
+
throw new Error(`Path is outside allowed ZilMate file roots: ${resolved}`);
|
|
28
|
+
}
|
|
29
|
+
const parts = resolved.split(/[\\/]/);
|
|
30
|
+
if (parts.some((part) => ignoredDirectoryNames.has(part))) {
|
|
31
|
+
throw new Error(`Path is inside an ignored directory: ${resolved}`);
|
|
32
|
+
}
|
|
33
|
+
if (!options.allowSensitive && parts.some((part) => sensitiveNamePattern.test(part))) {
|
|
34
|
+
throw new Error(`Path looks sensitive and cannot be accessed by file tools: ${resolved}`);
|
|
35
|
+
}
|
|
36
|
+
return resolved;
|
|
37
|
+
}
|
|
38
|
+
function relativeDisplay(resolved) {
|
|
39
|
+
const root = allowedRoots().find((item) => insideRoot(resolved, item));
|
|
40
|
+
return root ? path.relative(root, resolved) || '.' : resolved;
|
|
41
|
+
}
|
|
42
|
+
async function confirmFileAction(action, details) {
|
|
43
|
+
return requestConfirmation({
|
|
44
|
+
toolkitSlug: 'ZILMATE',
|
|
45
|
+
toolSlug: 'FILESYSTEM',
|
|
46
|
+
action,
|
|
47
|
+
access: 'Write',
|
|
48
|
+
targetTools: ['ZILMATE_FILESYSTEM'],
|
|
49
|
+
details,
|
|
50
|
+
summary: details.join('; '),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
async function walk(rootPath, options = {}) {
|
|
54
|
+
const maxDepth = options.maxDepth ?? 4;
|
|
55
|
+
const maxEntries = options.maxEntries ?? 200;
|
|
56
|
+
const results = [];
|
|
57
|
+
async function visit(current, depth) {
|
|
58
|
+
if (results.length >= maxEntries)
|
|
59
|
+
return;
|
|
60
|
+
const info = await stat(current);
|
|
61
|
+
const type = info.isDirectory() ? 'directory' : 'file';
|
|
62
|
+
results.push({
|
|
63
|
+
path: relativeDisplay(current),
|
|
64
|
+
absolutePath: current,
|
|
65
|
+
type,
|
|
66
|
+
size: info.size,
|
|
67
|
+
modifiedAt: info.mtime.toISOString(),
|
|
68
|
+
});
|
|
69
|
+
if (!info.isDirectory() || depth >= maxDepth)
|
|
70
|
+
return;
|
|
71
|
+
const entries = await readdir(current, { withFileTypes: true });
|
|
72
|
+
for (const entry of entries) {
|
|
73
|
+
if (results.length >= maxEntries)
|
|
74
|
+
return;
|
|
75
|
+
if (ignoredDirectoryNames.has(entry.name) || sensitiveNamePattern.test(entry.name))
|
|
76
|
+
continue;
|
|
77
|
+
await visit(path.join(current, entry.name), depth + 1);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
await visit(rootPath, 0);
|
|
81
|
+
return results;
|
|
82
|
+
}
|
|
83
|
+
async function snapshot(rootPath, maxDepth = 4) {
|
|
84
|
+
const entries = await walk(rootPath, { maxDepth, maxEntries: 1000 });
|
|
85
|
+
return Object.fromEntries(entries.map((entry) => [
|
|
86
|
+
entry.path,
|
|
87
|
+
{
|
|
88
|
+
size: entry.size,
|
|
89
|
+
modifiedAt: new Date(entry.modifiedAt).getTime(),
|
|
90
|
+
type: entry.type,
|
|
91
|
+
},
|
|
92
|
+
]));
|
|
93
|
+
}
|
|
94
|
+
function summarizeText(content) {
|
|
95
|
+
const cleaned = content.replace(/\s+/g, ' ').trim();
|
|
96
|
+
const sentences = cleaned.match(/[^.!?\n]+[.!?]?/g) ?? [];
|
|
97
|
+
const preview = sentences.slice(0, 8).join(' ').slice(0, 2400);
|
|
98
|
+
const words = cleaned ? cleaned.split(/\s+/).length : 0;
|
|
99
|
+
return {
|
|
100
|
+
characters: content.length,
|
|
101
|
+
words,
|
|
102
|
+
summary: preview || cleaned.slice(0, 1200),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
export const fileSystemTools = {
|
|
106
|
+
searchFiles: tool({
|
|
107
|
+
description: 'Search file and folder names, and optionally text content, inside allowed ZilMate file roots. Skips sensitive files, node_modules, .git, dist, and caches.',
|
|
108
|
+
inputSchema: z.object({
|
|
109
|
+
query: z.string().min(1),
|
|
110
|
+
root: z.string().optional().describe('Folder to search. Defaults to the current working directory.'),
|
|
111
|
+
includeContent: z.boolean().optional(),
|
|
112
|
+
maxDepth: z.number().int().min(0).max(8).optional(),
|
|
113
|
+
maxResults: z.number().int().min(1).max(200).optional(),
|
|
114
|
+
}),
|
|
115
|
+
execute: async ({ query, root, includeContent, maxDepth, maxResults }) => {
|
|
116
|
+
const searchRoot = assertSafePath(root || process.cwd());
|
|
117
|
+
emitProgress({ type: 'search:start', label: 'Searching files', detail: query });
|
|
118
|
+
const entries = await walk(searchRoot, { maxDepth: maxDepth ?? 5, maxEntries: 1000 });
|
|
119
|
+
const normalized = query.toLowerCase();
|
|
120
|
+
const matches = [];
|
|
121
|
+
for (const entry of entries) {
|
|
122
|
+
if (matches.length >= (maxResults ?? 50))
|
|
123
|
+
break;
|
|
124
|
+
const nameMatch = entry.path.toLowerCase().includes(normalized);
|
|
125
|
+
let contentMatch = false;
|
|
126
|
+
let excerpt = '';
|
|
127
|
+
if (!nameMatch && includeContent && entry.type === 'file' && entry.size <= defaultMaxReadBytes) {
|
|
128
|
+
try {
|
|
129
|
+
const content = await readFile(entry.absolutePath, 'utf8');
|
|
130
|
+
const index = content.toLowerCase().indexOf(normalized);
|
|
131
|
+
contentMatch = index >= 0;
|
|
132
|
+
if (contentMatch)
|
|
133
|
+
excerpt = content.slice(Math.max(0, index - 120), index + query.length + 220);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
contentMatch = false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (nameMatch || contentMatch)
|
|
140
|
+
matches.push({ ...entry, match: nameMatch ? 'name' : 'content', excerpt });
|
|
141
|
+
}
|
|
142
|
+
emitProgress({ type: 'search:end', label: 'File search complete', detail: `${matches.length} result${matches.length === 1 ? '' : 's'}` });
|
|
143
|
+
return matches;
|
|
144
|
+
},
|
|
145
|
+
}),
|
|
146
|
+
readFile: tool({
|
|
147
|
+
description: 'Read a text file inside allowed ZilMate file roots. Sensitive files such as .env, keys, credentials, and tokens are blocked.',
|
|
148
|
+
inputSchema: z.object({
|
|
149
|
+
path: z.string().min(1),
|
|
150
|
+
maxBytes: z.number().int().min(1000).max(500_000).optional(),
|
|
151
|
+
}),
|
|
152
|
+
execute: async ({ path: filePath, maxBytes }) => {
|
|
153
|
+
const resolved = assertSafePath(filePath);
|
|
154
|
+
const info = await stat(resolved);
|
|
155
|
+
if (!info.isFile())
|
|
156
|
+
throw new Error('Path is not a file.');
|
|
157
|
+
const limit = maxBytes ?? defaultMaxReadBytes;
|
|
158
|
+
const content = await readFile(resolved, 'utf8');
|
|
159
|
+
const truncated = Buffer.byteLength(content, 'utf8') > limit;
|
|
160
|
+
emitProgress({ type: 'fetch:end', label: 'File read', detail: relativeDisplay(resolved) });
|
|
161
|
+
return {
|
|
162
|
+
path: relativeDisplay(resolved),
|
|
163
|
+
size: info.size,
|
|
164
|
+
modifiedAt: info.mtime.toISOString(),
|
|
165
|
+
truncated,
|
|
166
|
+
content: truncated ? content.slice(0, limit) : content,
|
|
167
|
+
};
|
|
168
|
+
},
|
|
169
|
+
}),
|
|
170
|
+
writeFile: tool({
|
|
171
|
+
description: 'Write or append text to a file inside allowed ZilMate file roots. Requires user confirmation.',
|
|
172
|
+
inputSchema: z.object({
|
|
173
|
+
path: z.string().min(1),
|
|
174
|
+
content: z.string(),
|
|
175
|
+
mode: z.enum(['overwrite', 'append']).optional(),
|
|
176
|
+
}),
|
|
177
|
+
execute: async ({ path: filePath, content, mode }) => {
|
|
178
|
+
const resolved = assertSafePath(filePath);
|
|
179
|
+
const approved = await confirmFileAction(mode === 'append' ? 'Append file' : 'Write file', [
|
|
180
|
+
`Path: ${relativeDisplay(resolved)}`,
|
|
181
|
+
`Mode: ${mode ?? 'overwrite'}`,
|
|
182
|
+
`Bytes: ${Buffer.byteLength(content, 'utf8')}`,
|
|
183
|
+
]);
|
|
184
|
+
if (!approved)
|
|
185
|
+
throw new Error('Blocked file write. Ask the user to approve writing this file.');
|
|
186
|
+
await mkdir(path.dirname(resolved), { recursive: true });
|
|
187
|
+
await writeFile(resolved, content, { encoding: 'utf8', flag: mode === 'append' ? 'a' : 'w' });
|
|
188
|
+
emitProgress({ type: 'tool:end', label: 'File written', detail: relativeDisplay(resolved) });
|
|
189
|
+
return { path: relativeDisplay(resolved), bytes: Buffer.byteLength(content, 'utf8'), mode: mode ?? 'overwrite' };
|
|
190
|
+
},
|
|
191
|
+
}),
|
|
192
|
+
createFolder: tool({
|
|
193
|
+
description: 'Create a folder inside allowed ZilMate file roots. Requires user confirmation.',
|
|
194
|
+
inputSchema: z.object({ path: z.string().min(1) }),
|
|
195
|
+
execute: async ({ path: folderPath }) => {
|
|
196
|
+
const resolved = assertSafePath(folderPath);
|
|
197
|
+
const approved = await confirmFileAction('Create folder', [`Path: ${relativeDisplay(resolved)}`]);
|
|
198
|
+
if (!approved)
|
|
199
|
+
throw new Error('Blocked folder creation. Ask the user to approve creating this folder.');
|
|
200
|
+
await mkdir(resolved, { recursive: true });
|
|
201
|
+
emitProgress({ type: 'tool:end', label: 'Folder created', detail: relativeDisplay(resolved) });
|
|
202
|
+
return { path: relativeDisplay(resolved), created: true };
|
|
203
|
+
},
|
|
204
|
+
}),
|
|
205
|
+
moveCopyRename: tool({
|
|
206
|
+
description: 'Move, copy, or rename a file/folder inside allowed ZilMate file roots. Requires user confirmation.',
|
|
207
|
+
inputSchema: z.object({
|
|
208
|
+
operation: z.enum(['move', 'copy', 'rename']),
|
|
209
|
+
from: z.string().min(1),
|
|
210
|
+
to: z.string().min(1),
|
|
211
|
+
overwrite: z.boolean().optional(),
|
|
212
|
+
}),
|
|
213
|
+
execute: async ({ operation, from, to, overwrite }) => {
|
|
214
|
+
const source = assertSafePath(from);
|
|
215
|
+
const target = assertSafePath(to);
|
|
216
|
+
if (!existsSync(source))
|
|
217
|
+
throw new Error('Source path does not exist.');
|
|
218
|
+
if (existsSync(target) && !overwrite)
|
|
219
|
+
throw new Error('Target already exists. Set overwrite=true if the user explicitly approves replacing it.');
|
|
220
|
+
const approved = await confirmFileAction(operation, [`From: ${relativeDisplay(source)}`, `To: ${relativeDisplay(target)}`, `Overwrite: ${overwrite ? 'yes' : 'no'}`]);
|
|
221
|
+
if (!approved)
|
|
222
|
+
throw new Error(`Blocked ${operation}. Ask the user to approve this file operation.`);
|
|
223
|
+
await mkdir(path.dirname(target), { recursive: true });
|
|
224
|
+
if (existsSync(target) && overwrite)
|
|
225
|
+
await unlink(target);
|
|
226
|
+
if (operation === 'copy') {
|
|
227
|
+
await copyFile(source, target);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
await rename(source, target);
|
|
231
|
+
}
|
|
232
|
+
emitProgress({ type: 'tool:end', label: `File ${operation} complete`, detail: relativeDisplay(target) });
|
|
233
|
+
return { operation, from: relativeDisplay(source), to: relativeDisplay(target) };
|
|
234
|
+
},
|
|
235
|
+
}),
|
|
236
|
+
summarizeDocument: tool({
|
|
237
|
+
description: 'Read and summarize a text-like document inside allowed ZilMate file roots. Supports plain text, markdown, JSON, CSV, and code files.',
|
|
238
|
+
inputSchema: z.object({
|
|
239
|
+
path: z.string().min(1),
|
|
240
|
+
maxBytes: z.number().int().min(1000).max(500_000).optional(),
|
|
241
|
+
}),
|
|
242
|
+
execute: async ({ path: filePath, maxBytes }) => {
|
|
243
|
+
const resolved = assertSafePath(filePath);
|
|
244
|
+
const content = await readFile(resolved, 'utf8');
|
|
245
|
+
const limit = maxBytes ?? 300_000;
|
|
246
|
+
const sliced = content.length > limit ? content.slice(0, limit) : content;
|
|
247
|
+
const summary = summarizeText(sliced);
|
|
248
|
+
emitProgress({ type: 'fetch:end', label: 'Document summarized', detail: relativeDisplay(resolved) });
|
|
249
|
+
return { path: relativeDisplay(resolved), truncated: content.length > limit, ...summary };
|
|
250
|
+
},
|
|
251
|
+
}),
|
|
252
|
+
watchFolderChanges: tool({
|
|
253
|
+
description: 'Snapshot a folder and report changes since the previous snapshot. This is a local compare-watch, not a background daemon.',
|
|
254
|
+
inputSchema: z.object({
|
|
255
|
+
path: z.string().optional(),
|
|
256
|
+
watchName: z.string().min(1).max(80).optional(),
|
|
257
|
+
reset: z.boolean().optional(),
|
|
258
|
+
maxDepth: z.number().int().min(0).max(8).optional(),
|
|
259
|
+
}),
|
|
260
|
+
execute: async ({ path: folderPath, watchName, reset, maxDepth }) => {
|
|
261
|
+
const root = assertSafePath(folderPath || process.cwd());
|
|
262
|
+
const key = watchName || relativeDisplay(root) || 'default';
|
|
263
|
+
const store = await readJson(watchFile, {});
|
|
264
|
+
const current = await snapshot(root, maxDepth ?? 4);
|
|
265
|
+
const previous = reset ? undefined : store[key];
|
|
266
|
+
store[key] = current;
|
|
267
|
+
await writeJson(watchFile, store);
|
|
268
|
+
if (!previous)
|
|
269
|
+
return { watchName: key, baselineSaved: true, added: [], changed: [], removed: [] };
|
|
270
|
+
const added = Object.keys(current).filter((item) => !previous[item]);
|
|
271
|
+
const removed = Object.keys(previous).filter((item) => !current[item]);
|
|
272
|
+
const changed = Object.keys(current).filter((item) => previous[item] && (previous[item].size !== current[item].size || previous[item].modifiedAt !== current[item].modifiedAt));
|
|
273
|
+
return { watchName: key, baselineSaved: true, added, changed, removed };
|
|
274
|
+
},
|
|
275
|
+
}),
|
|
276
|
+
findDuplicateLargeFiles: tool({
|
|
277
|
+
description: 'Find large files and likely duplicate files by size inside allowed ZilMate file roots. Skips sensitive files and dependency/build folders.',
|
|
278
|
+
inputSchema: z.object({
|
|
279
|
+
root: z.string().optional(),
|
|
280
|
+
minSizeBytes: z.number().int().min(1).optional(),
|
|
281
|
+
maxDepth: z.number().int().min(0).max(8).optional(),
|
|
282
|
+
maxResults: z.number().int().min(1).max(200).optional(),
|
|
283
|
+
}),
|
|
284
|
+
execute: async ({ root, minSizeBytes, maxDepth, maxResults }) => {
|
|
285
|
+
const searchRoot = assertSafePath(root || process.cwd());
|
|
286
|
+
const entries = (await walk(searchRoot, { maxDepth: maxDepth ?? 6, maxEntries: 2000 })).filter((entry) => entry.type === 'file');
|
|
287
|
+
const minSize = minSizeBytes ?? 1_000_000;
|
|
288
|
+
const large = entries
|
|
289
|
+
.filter((entry) => entry.size >= minSize)
|
|
290
|
+
.sort((a, b) => b.size - a.size)
|
|
291
|
+
.slice(0, maxResults ?? 50);
|
|
292
|
+
const bySize = new Map();
|
|
293
|
+
for (const entry of entries)
|
|
294
|
+
bySize.set(entry.size, [...(bySize.get(entry.size) ?? []), entry]);
|
|
295
|
+
const duplicates = [...bySize.entries()]
|
|
296
|
+
.filter(([, items]) => items.length > 1)
|
|
297
|
+
.map(([size, items]) => ({ size, files: items.map((item) => item.path) }))
|
|
298
|
+
.slice(0, maxResults ?? 50);
|
|
299
|
+
return { large, duplicateSizeGroups: duplicates };
|
|
300
|
+
},
|
|
301
|
+
}),
|
|
302
|
+
};
|
|
303
|
+
//# sourceMappingURL=filesystem.tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filesystem.tool.js","sourceRoot":"","sources":["../../src/tools/filesystem.tool.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ZilMateVoiceEvent } from './types.js';
|
|
2
|
+
export type CascadedVoiceSessionOptions = {
|
|
3
|
+
sessionId?: string;
|
|
4
|
+
audio: AsyncIterable<Buffer | Uint8Array>;
|
|
5
|
+
onEvent?: (event: ZilMateVoiceEvent) => void;
|
|
6
|
+
onAudio?: (chunk: Uint8Array) => void;
|
|
7
|
+
onUserTranscript: (text: string) => Promise<string | void> | string | void;
|
|
8
|
+
};
|
|
9
|
+
export declare function speakWithDeepgram(text: string, options?: Pick<CascadedVoiceSessionOptions, 'onEvent' | 'onAudio'>): Promise<{
|
|
10
|
+
audioBytes: number;
|
|
11
|
+
}>;
|
|
12
|
+
export declare function startCascadedVoiceSession(options: CascadedVoiceSessionOptions): Promise<{
|
|
13
|
+
sessionId: string;
|
|
14
|
+
}>;
|
|
15
|
+
//# sourceMappingURL=cascade.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cascade.d.ts","sourceRoot":"","sources":["../../src/voice/cascade.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AA2CpD,MAAM,MAAM,2BAA2B,GAAG;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,aAAa,CAAC,MAAM,GAAG,UAAU,CAAC,CAAC;IAC1C,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAC7C,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACtC,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC;CAC5E,CAAC;AAiIF,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,IAAI,CAAC,2BAA2B,EAAE,SAAS,GAAG,SAAS,CAAM;;GAsB3H;AAwFD,wBAAsB,yBAAyB,CAAC,OAAO,EAAE,2BAA2B;;GAwEnF"}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { requireDeepgram } from '../config/env.js';
|
|
3
|
+
import { getVoiceConfig } from './deepgram.js';
|
|
4
|
+
const dynamicImport = new Function('specifier', 'return import(specifier)');
|
|
5
|
+
function now() {
|
|
6
|
+
return new Date().toISOString();
|
|
7
|
+
}
|
|
8
|
+
function emit(options, event) {
|
|
9
|
+
options.onEvent?.(event);
|
|
10
|
+
}
|
|
11
|
+
async function loadClient() {
|
|
12
|
+
const module = await dynamicImport('@deepgram/sdk');
|
|
13
|
+
return module.createClient(requireDeepgram());
|
|
14
|
+
}
|
|
15
|
+
async function loadWebSocket() {
|
|
16
|
+
const module = await dynamicImport('ws');
|
|
17
|
+
const WebSocket = module.default || module.WebSocket;
|
|
18
|
+
if (!WebSocket)
|
|
19
|
+
throw new Error('The ws package is required for Deepgram Flux v2 streaming.');
|
|
20
|
+
return WebSocket;
|
|
21
|
+
}
|
|
22
|
+
function transcriptText(data) {
|
|
23
|
+
const record = data && typeof data === 'object' ? data : {};
|
|
24
|
+
if (typeof record.transcript === 'string')
|
|
25
|
+
return record.transcript.trim();
|
|
26
|
+
const channel = record.channel && typeof record.channel === 'object' ? record.channel : {};
|
|
27
|
+
const alternatives = Array.isArray(channel.alternatives) ? channel.alternatives : [];
|
|
28
|
+
const first = alternatives[0] && typeof alternatives[0] === 'object' ? alternatives[0] : {};
|
|
29
|
+
return typeof first.transcript === 'string' ? first.transcript.trim() : '';
|
|
30
|
+
}
|
|
31
|
+
function isFinalTurn(data) {
|
|
32
|
+
const record = data && typeof data === 'object' ? data : {};
|
|
33
|
+
if (record.type === 'TurnInfo')
|
|
34
|
+
return record.event === 'EndOfTurn';
|
|
35
|
+
return record.is_final === true || record.speech_final === true;
|
|
36
|
+
}
|
|
37
|
+
function listenOptions() {
|
|
38
|
+
const config = getVoiceConfig();
|
|
39
|
+
const options = {
|
|
40
|
+
model: config.listenModel,
|
|
41
|
+
encoding: 'linear16',
|
|
42
|
+
sample_rate: 16000,
|
|
43
|
+
};
|
|
44
|
+
if (!config.listenModel.startsWith('flux-')) {
|
|
45
|
+
options.channels = 1;
|
|
46
|
+
options.interim_results = true;
|
|
47
|
+
options.smart_format = true;
|
|
48
|
+
options.language = config.language;
|
|
49
|
+
}
|
|
50
|
+
else if (config.listenModel === 'flux-general-multi' && config.languageHints.length > 0) {
|
|
51
|
+
options.language_hint = config.languageHints;
|
|
52
|
+
}
|
|
53
|
+
return options;
|
|
54
|
+
}
|
|
55
|
+
function listenEndpoint() {
|
|
56
|
+
const config = getVoiceConfig();
|
|
57
|
+
return config.listenModel.startsWith('flux-') ? '/v2/listen' : undefined;
|
|
58
|
+
}
|
|
59
|
+
function speakOptions() {
|
|
60
|
+
const config = getVoiceConfig();
|
|
61
|
+
return {
|
|
62
|
+
model: config.ttsModel,
|
|
63
|
+
encoding: 'linear16',
|
|
64
|
+
sample_rate: 24000,
|
|
65
|
+
container: 'none',
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function listenForTtsAudio(speak, options) {
|
|
69
|
+
let audioBytes = 0;
|
|
70
|
+
let flushed = false;
|
|
71
|
+
let opened = false;
|
|
72
|
+
let resolveOpen;
|
|
73
|
+
let rejectOpen;
|
|
74
|
+
const openedPromise = new Promise((resolve, reject) => {
|
|
75
|
+
resolveOpen = resolve;
|
|
76
|
+
rejectOpen = reject;
|
|
77
|
+
});
|
|
78
|
+
speak.on('Open', () => {
|
|
79
|
+
opened = true;
|
|
80
|
+
resolveOpen?.();
|
|
81
|
+
emit(options, { type: 'status', label: 'TTS socket opened', timestamp: now() });
|
|
82
|
+
});
|
|
83
|
+
speak.on('Close', () => emit(options, { type: 'status', label: 'TTS socket closed', timestamp: now() }));
|
|
84
|
+
speak.on('Error', (error) => {
|
|
85
|
+
const message = error instanceof Error ? error.message : JSON.stringify(error);
|
|
86
|
+
rejectOpen?.(new Error(message));
|
|
87
|
+
emit(options, { type: 'error', message, timestamp: now() });
|
|
88
|
+
});
|
|
89
|
+
speak.on('Warning', (warning) => emit(options, { type: 'status', label: 'TTS warning', detail: JSON.stringify(warning), timestamp: now() }));
|
|
90
|
+
speak.on('Flushed', () => {
|
|
91
|
+
flushed = true;
|
|
92
|
+
emit(options, { type: 'status', label: 'TTS audio flushed', detail: `${audioBytes} bytes`, timestamp: now() });
|
|
93
|
+
});
|
|
94
|
+
speak.on('Audio', (chunk) => {
|
|
95
|
+
if (chunk instanceof Uint8Array) {
|
|
96
|
+
audioBytes += chunk.byteLength;
|
|
97
|
+
options.onAudio?.(chunk);
|
|
98
|
+
emit(options, { type: 'audio', bytes: chunk.byteLength, timestamp: now() });
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
return {
|
|
102
|
+
get audioBytes() {
|
|
103
|
+
return audioBytes;
|
|
104
|
+
},
|
|
105
|
+
get flushed() {
|
|
106
|
+
return flushed;
|
|
107
|
+
},
|
|
108
|
+
waitForOpen: async () => {
|
|
109
|
+
if (opened)
|
|
110
|
+
return;
|
|
111
|
+
await Promise.race([
|
|
112
|
+
openedPromise,
|
|
113
|
+
new Promise((_resolve, reject) => {
|
|
114
|
+
setTimeout(() => reject(new Error('Timed out waiting for Deepgram TTS socket to open.')), 10_000);
|
|
115
|
+
}),
|
|
116
|
+
]);
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
export async function speakWithDeepgram(text, options = {}) {
|
|
121
|
+
const deepgram = await loadClient();
|
|
122
|
+
const speak = deepgram.speak.live(speakOptions());
|
|
123
|
+
const state = listenForTtsAudio(speak, options);
|
|
124
|
+
await state.waitForOpen();
|
|
125
|
+
speak.sendText?.(text);
|
|
126
|
+
speak.flush?.();
|
|
127
|
+
await new Promise((resolve, reject) => {
|
|
128
|
+
const started = Date.now();
|
|
129
|
+
const timer = setInterval(() => {
|
|
130
|
+
if (state.flushed && Date.now() - started > 500) {
|
|
131
|
+
clearInterval(timer);
|
|
132
|
+
resolve();
|
|
133
|
+
}
|
|
134
|
+
else if (Date.now() - started > 15_000) {
|
|
135
|
+
clearInterval(timer);
|
|
136
|
+
reject(new Error(`Timed out waiting for Deepgram TTS audio. Received ${state.audioBytes} bytes.`));
|
|
137
|
+
}
|
|
138
|
+
}, 100);
|
|
139
|
+
});
|
|
140
|
+
speak.requestClose?.();
|
|
141
|
+
speak.disconnect();
|
|
142
|
+
return { audioBytes: state.audioBytes };
|
|
143
|
+
}
|
|
144
|
+
function appendQuery(url, key, value) {
|
|
145
|
+
if (Array.isArray(value)) {
|
|
146
|
+
for (const item of value)
|
|
147
|
+
url.searchParams.append(key, String(item));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
151
|
+
url.searchParams.set(key, String(value));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
async function createFluxListenConnection(options) {
|
|
155
|
+
const WebSocket = await loadWebSocket();
|
|
156
|
+
const url = new URL('/v2/listen', 'wss://api.deepgram.com');
|
|
157
|
+
for (const [key, value] of Object.entries(options))
|
|
158
|
+
appendQuery(url, key, value);
|
|
159
|
+
const handlers = new Map();
|
|
160
|
+
const buffered = [];
|
|
161
|
+
let open = false;
|
|
162
|
+
let closed = false;
|
|
163
|
+
const emitLocal = (event, data) => {
|
|
164
|
+
for (const handler of handlers.get(event) || []) {
|
|
165
|
+
void handler(data);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
const socket = new WebSocket(url.toString(), undefined, {
|
|
169
|
+
headers: {
|
|
170
|
+
Authorization: `Token ${requireDeepgram()}`,
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
socket.on('open', () => {
|
|
174
|
+
open = true;
|
|
175
|
+
emitLocal('open', undefined);
|
|
176
|
+
while (buffered.length > 0)
|
|
177
|
+
socket.send(buffered.shift());
|
|
178
|
+
});
|
|
179
|
+
socket.on('close', (code, reason) => {
|
|
180
|
+
closed = true;
|
|
181
|
+
emitLocal('close', { code, reason: Buffer.isBuffer(reason) ? reason.toString('utf8') : String(reason || '') });
|
|
182
|
+
});
|
|
183
|
+
socket.on('unexpected-response', (_request, response) => {
|
|
184
|
+
const statusCode = response && typeof response === 'object' && 'statusCode' in response
|
|
185
|
+
? response.statusCode
|
|
186
|
+
: undefined;
|
|
187
|
+
const statusMessage = response && typeof response === 'object' && 'statusMessage' in response
|
|
188
|
+
? response.statusMessage
|
|
189
|
+
: undefined;
|
|
190
|
+
emitLocal('error', new Error(`Deepgram rejected Flux websocket${statusCode ? ` (${statusCode}${statusMessage ? ` ${statusMessage}` : ''})` : ''}. Check DEEPGRAM_API_KEY access to Flux v2.`));
|
|
191
|
+
});
|
|
192
|
+
socket.on('error', (error) => {
|
|
193
|
+
emitLocal('error', error);
|
|
194
|
+
});
|
|
195
|
+
socket.on('message', (message) => {
|
|
196
|
+
const text = Buffer.isBuffer(message) ? message.toString('utf8') : String(message);
|
|
197
|
+
try {
|
|
198
|
+
const data = JSON.parse(text);
|
|
199
|
+
emitLocal('Message', data);
|
|
200
|
+
if (typeof data.type === 'string')
|
|
201
|
+
emitLocal(data.type, data);
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
emitLocal('error', new Error(`Unable to parse Flux message: ${text.slice(0, 200)}`));
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
return {
|
|
208
|
+
on: (event, handler) => {
|
|
209
|
+
handlers.set(event, [...(handlers.get(event) || []), handler]);
|
|
210
|
+
},
|
|
211
|
+
send: (chunk) => {
|
|
212
|
+
if (closed)
|
|
213
|
+
return;
|
|
214
|
+
if (open)
|
|
215
|
+
socket.send(chunk);
|
|
216
|
+
else
|
|
217
|
+
buffered.push(chunk);
|
|
218
|
+
},
|
|
219
|
+
keepAlive: () => {
|
|
220
|
+
if (closed || !open)
|
|
221
|
+
return;
|
|
222
|
+
socket.ping?.();
|
|
223
|
+
},
|
|
224
|
+
requestClose: () => {
|
|
225
|
+
if (!closed)
|
|
226
|
+
socket.send(JSON.stringify({ type: 'CloseStream' }));
|
|
227
|
+
},
|
|
228
|
+
disconnect: () => {
|
|
229
|
+
if (!closed)
|
|
230
|
+
socket.close();
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
export async function startCascadedVoiceSession(options) {
|
|
235
|
+
const sessionId = options.sessionId || `voice_${randomUUID()}`;
|
|
236
|
+
const config = getVoiceConfig();
|
|
237
|
+
const deepgram = await loadClient();
|
|
238
|
+
const listen = config.listenModel.startsWith('flux-')
|
|
239
|
+
? await createFluxListenConnection(listenOptions())
|
|
240
|
+
: deepgram.listen.live(listenOptions(), listenEndpoint());
|
|
241
|
+
const speak = deepgram.speak.live(speakOptions());
|
|
242
|
+
const ttsState = listenForTtsAudio(speak, options);
|
|
243
|
+
let pendingTranscript = '';
|
|
244
|
+
let answering = Promise.resolve();
|
|
245
|
+
listen.on('open', () => emit(options, { type: 'status', label: 'Flux listening', detail: getVoiceConfig().listenModel, timestamp: now() }));
|
|
246
|
+
listen.on('close', (data) => {
|
|
247
|
+
const record = data && typeof data === 'object' ? data : {};
|
|
248
|
+
const code = typeof record.code === 'number' ? record.code : undefined;
|
|
249
|
+
const reason = typeof record.reason === 'string' ? record.reason : '';
|
|
250
|
+
const detail = code ? `${code}${reason ? ` ${reason}` : ''}` : undefined;
|
|
251
|
+
emit(options, {
|
|
252
|
+
type: 'status',
|
|
253
|
+
label: 'Flux socket closed',
|
|
254
|
+
...(detail ? { detail } : {}),
|
|
255
|
+
timestamp: now(),
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
listen.on('error', (error) => emit(options, { type: 'error', message: error instanceof Error ? error.message : JSON.stringify(error), timestamp: now() }));
|
|
259
|
+
listen.on('Connected', () => emit(options, { type: 'status', label: 'Flux connected', detail: getVoiceConfig().listenModel, timestamp: now() }));
|
|
260
|
+
listen.on('SpeechStarted', () => {
|
|
261
|
+
speak.clear?.();
|
|
262
|
+
emit(options, { type: 'status', label: 'User started speaking', detail: 'barge-in enabled', timestamp: now() });
|
|
263
|
+
});
|
|
264
|
+
const onTurn = (data) => {
|
|
265
|
+
const text = transcriptText(data);
|
|
266
|
+
if (!text)
|
|
267
|
+
return;
|
|
268
|
+
pendingTranscript = text;
|
|
269
|
+
if (!isFinalTurn(data))
|
|
270
|
+
return;
|
|
271
|
+
const finalText = pendingTranscript;
|
|
272
|
+
pendingTranscript = '';
|
|
273
|
+
emit(options, { type: 'transcript', role: 'user', text: finalText, final: true, timestamp: now() });
|
|
274
|
+
answering = answering.then(async () => {
|
|
275
|
+
const reply = await options.onUserTranscript(finalText);
|
|
276
|
+
if (!reply || typeof reply !== 'string')
|
|
277
|
+
return;
|
|
278
|
+
emit(options, { type: 'transcript', role: 'assistant', text: reply, final: true, timestamp: now() });
|
|
279
|
+
await ttsState.waitForOpen();
|
|
280
|
+
speak.sendText?.(reply);
|
|
281
|
+
speak.flush?.();
|
|
282
|
+
}).catch((error) => {
|
|
283
|
+
emit(options, { type: 'error', message: error instanceof Error ? error.message : String(error), timestamp: now() });
|
|
284
|
+
});
|
|
285
|
+
};
|
|
286
|
+
listen.on('Results', onTurn);
|
|
287
|
+
listen.on('TurnInfo', onTurn);
|
|
288
|
+
const keepAlive = setInterval(() => {
|
|
289
|
+
listen.keepAlive?.();
|
|
290
|
+
}, 5000);
|
|
291
|
+
try {
|
|
292
|
+
for await (const chunk of options.audio) {
|
|
293
|
+
listen.send(chunk);
|
|
294
|
+
}
|
|
295
|
+
await answering;
|
|
296
|
+
}
|
|
297
|
+
finally {
|
|
298
|
+
clearInterval(keepAlive);
|
|
299
|
+
listen.requestClose?.();
|
|
300
|
+
speak.requestClose?.();
|
|
301
|
+
listen.disconnect();
|
|
302
|
+
speak.disconnect();
|
|
303
|
+
}
|
|
304
|
+
return { sessionId };
|
|
305
|
+
}
|
|
306
|
+
//# sourceMappingURL=cascade.js.map
|