neoagent 2.3.1-beta.42 → 2.3.1-beta.43
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/package.json
CHANGED
|
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"42d3d75a56efe1a2e9902f52dc8006099c45d9
|
|
|
37
37
|
|
|
38
38
|
_flutter.loader.load({
|
|
39
39
|
serviceWorkerSettings: {
|
|
40
|
-
serviceWorkerVersion: "
|
|
40
|
+
serviceWorkerVersion: "555509852" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
|
|
41
41
|
}
|
|
42
42
|
});
|
|
@@ -40,9 +40,17 @@ const EMOJI_SPEECH_REGEX =
|
|
|
40
40
|
const WEARABLE_SAFE_AUDIO_FORMAT = Object.freeze({
|
|
41
41
|
responseFormat: 'wav',
|
|
42
42
|
mimeType: 'audio/wav',
|
|
43
|
+
streamResponseFormat: 'pcm',
|
|
44
|
+
streamMimeType: 'audio/wav',
|
|
45
|
+
pcmSampleRate: 24000,
|
|
46
|
+
pcmChannels: 1,
|
|
47
|
+
pcmBitsPerSample: 16,
|
|
43
48
|
deepgramEncoding: 'linear16',
|
|
44
49
|
deepgramContainer: 'wav',
|
|
50
|
+
deepgramStreamContainer: 'none',
|
|
45
51
|
});
|
|
52
|
+
const MIN_STREAM_PCM_CHUNK_BYTES = 24000;
|
|
53
|
+
const MAX_STREAM_PCM_CHUNK_BYTES = 48000;
|
|
46
54
|
|
|
47
55
|
function withTimeout(promise, timeoutMs, label) {
|
|
48
56
|
const normalizedTimeout = Number(timeoutMs);
|
|
@@ -198,10 +206,11 @@ async function fetchAudioStreamOrThrow(url, init, errorPrefix, defaultMimeType =
|
|
|
198
206
|
while (true) {
|
|
199
207
|
const { done, value } = await reader.read();
|
|
200
208
|
if (done) break;
|
|
201
|
-
if (value?.length)
|
|
209
|
+
if (value?.length) {
|
|
210
|
+
chunks.push(Buffer.from(value));
|
|
211
|
+
}
|
|
202
212
|
}
|
|
203
|
-
|
|
204
|
-
await onChunk({ audioBytes, mimeType });
|
|
213
|
+
await onChunk({ audioBytes: Buffer.concat(chunks), mimeType });
|
|
205
214
|
}
|
|
206
215
|
|
|
207
216
|
function guessExtFromMimeType(mimeType) {
|
|
@@ -262,6 +271,49 @@ function wrapPcmAsWav(audioBytes, format) {
|
|
|
262
271
|
return Buffer.concat([header, data]);
|
|
263
272
|
}
|
|
264
273
|
|
|
274
|
+
async function streamPcmAsWavChunks(readable, format, onChunk) {
|
|
275
|
+
const source = readable && typeof readable.getReader === 'function'
|
|
276
|
+
? readable
|
|
277
|
+
: null;
|
|
278
|
+
let pending = Buffer.alloc(0);
|
|
279
|
+
|
|
280
|
+
async function flushPending(force = false) {
|
|
281
|
+
while (pending.length >= MIN_STREAM_PCM_CHUNK_BYTES || (force && pending.length > 0)) {
|
|
282
|
+
const targetLength = force
|
|
283
|
+
? pending.length
|
|
284
|
+
: Math.min(pending.length, MAX_STREAM_PCM_CHUNK_BYTES);
|
|
285
|
+
const evenLength = targetLength - (targetLength % 2);
|
|
286
|
+
if (evenLength <= 0) return;
|
|
287
|
+
const pcmChunk = pending.subarray(0, evenLength);
|
|
288
|
+
pending = pending.subarray(evenLength);
|
|
289
|
+
await onChunk({
|
|
290
|
+
audioBytes: wrapPcmAsWav(pcmChunk, format),
|
|
291
|
+
mimeType: WEARABLE_SAFE_AUDIO_FORMAT.streamMimeType,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (source) {
|
|
297
|
+
const reader = source.getReader();
|
|
298
|
+
while (true) {
|
|
299
|
+
const { done, value } = await reader.read();
|
|
300
|
+
if (done) break;
|
|
301
|
+
if (value?.length) {
|
|
302
|
+
pending = Buffer.concat([pending, Buffer.from(value)]);
|
|
303
|
+
await flushPending(false);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
} else {
|
|
307
|
+
for await (const chunk of readable) {
|
|
308
|
+
if (chunk?.length) {
|
|
309
|
+
pending = Buffer.concat([pending, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)]);
|
|
310
|
+
await flushPending(false);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
await flushPending(true);
|
|
315
|
+
}
|
|
316
|
+
|
|
265
317
|
async function transcribeWithOpenAi(filePath, model, options = {}) {
|
|
266
318
|
const client = getOpenAiClient({
|
|
267
319
|
apiKey: typeof options.apiKey === 'string' ? options.apiKey.trim() : '',
|
|
@@ -380,8 +432,20 @@ async function streamWithOpenAi(text, model, voice, options = {}, onChunk) {
|
|
|
380
432
|
model: String(model || 'gpt-4o-mini-tts').trim() || 'gpt-4o-mini-tts',
|
|
381
433
|
voice: String(voice || 'alloy').trim() || 'alloy',
|
|
382
434
|
input: text,
|
|
383
|
-
response_format: useWearableSafeAudio ? WEARABLE_SAFE_AUDIO_FORMAT.
|
|
435
|
+
response_format: useWearableSafeAudio ? WEARABLE_SAFE_AUDIO_FORMAT.streamResponseFormat : 'mp3',
|
|
384
436
|
});
|
|
437
|
+
if (useWearableSafeAudio) {
|
|
438
|
+
await streamPcmAsWavChunks(
|
|
439
|
+
response.body,
|
|
440
|
+
{
|
|
441
|
+
bitsPerSample: WEARABLE_SAFE_AUDIO_FORMAT.pcmBitsPerSample,
|
|
442
|
+
sampleRate: WEARABLE_SAFE_AUDIO_FORMAT.pcmSampleRate,
|
|
443
|
+
channels: WEARABLE_SAFE_AUDIO_FORMAT.pcmChannels,
|
|
444
|
+
},
|
|
445
|
+
onChunk,
|
|
446
|
+
);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
385
449
|
const chunks = [];
|
|
386
450
|
for await (const chunk of response.body) {
|
|
387
451
|
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
@@ -427,7 +491,32 @@ async function streamWithDeepgram(text, model, options = {}, onChunk) {
|
|
|
427
491
|
});
|
|
428
492
|
if (useWearableSafeAudio) {
|
|
429
493
|
searchParams.set('encoding', WEARABLE_SAFE_AUDIO_FORMAT.deepgramEncoding);
|
|
430
|
-
searchParams.set('container', WEARABLE_SAFE_AUDIO_FORMAT.
|
|
494
|
+
searchParams.set('container', WEARABLE_SAFE_AUDIO_FORMAT.deepgramStreamContainer);
|
|
495
|
+
searchParams.set('sample_rate', String(WEARABLE_SAFE_AUDIO_FORMAT.pcmSampleRate));
|
|
496
|
+
const response = await fetch(
|
|
497
|
+
`https://api.deepgram.com/v1/speak?${searchParams.toString()}`,
|
|
498
|
+
{
|
|
499
|
+
method: 'POST',
|
|
500
|
+
headers: {
|
|
501
|
+
Authorization: `Token ${apiKey}`,
|
|
502
|
+
'Content-Type': 'application/json',
|
|
503
|
+
},
|
|
504
|
+
body: JSON.stringify({ text }),
|
|
505
|
+
},
|
|
506
|
+
);
|
|
507
|
+
if (!response.ok) {
|
|
508
|
+
await throwResponseError(response, 'Deepgram TTS stream failed');
|
|
509
|
+
}
|
|
510
|
+
await streamPcmAsWavChunks(
|
|
511
|
+
response.body,
|
|
512
|
+
{
|
|
513
|
+
bitsPerSample: WEARABLE_SAFE_AUDIO_FORMAT.pcmBitsPerSample,
|
|
514
|
+
sampleRate: WEARABLE_SAFE_AUDIO_FORMAT.pcmSampleRate,
|
|
515
|
+
channels: WEARABLE_SAFE_AUDIO_FORMAT.pcmChannels,
|
|
516
|
+
},
|
|
517
|
+
onChunk,
|
|
518
|
+
);
|
|
519
|
+
return;
|
|
431
520
|
}
|
|
432
521
|
await fetchAudioStreamOrThrow(
|
|
433
522
|
`https://api.deepgram.com/v1/speak?${searchParams.toString()}`,
|
|
@@ -546,6 +546,8 @@ class VoiceRuntimeManager {
|
|
|
546
546
|
provider: session.voiceSettings?.liveProvider,
|
|
547
547
|
model: session.voiceSettings?.liveTtsModel,
|
|
548
548
|
voice: session.voiceSettings?.liveVoice,
|
|
549
|
+
transport: 'wearable',
|
|
550
|
+
responseFormat: 'wav',
|
|
549
551
|
});
|
|
550
552
|
const spokenContent = sanitizeSpeechText(content);
|
|
551
553
|
let index = 0;
|
|
@@ -618,6 +620,8 @@ class VoiceRuntimeManager {
|
|
|
618
620
|
provider,
|
|
619
621
|
model: provider === voiceOptions.provider ? voiceOptions.model : null,
|
|
620
622
|
voice: provider === voiceOptions.provider ? voiceOptions.voice : null,
|
|
623
|
+
transport: voiceOptions.transport,
|
|
624
|
+
responseFormat: voiceOptions.responseFormat,
|
|
621
625
|
});
|
|
622
626
|
const runtime = provider === voiceOptions.provider
|
|
623
627
|
? {
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const path = require('path');
|
|
5
|
-
|
|
6
|
-
const { APP_DIR } = require('../../../runtime/paths');
|
|
7
|
-
|
|
3
|
+
const DEFAULT_GITHUB_REPOSITORY = 'NeoLabs-Systems/NeoAgent';
|
|
8
4
|
const DEFAULT_ASSET_NAME = 'neoagent-wearable-firmware.bin';
|
|
9
5
|
const MANIFEST_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
10
6
|
const manifestCache = new Map();
|
|
@@ -33,28 +29,10 @@ function parseRepositorySlug(value) {
|
|
|
33
29
|
return null;
|
|
34
30
|
}
|
|
35
31
|
|
|
36
|
-
function readPackageRepositorySlug() {
|
|
37
|
-
try {
|
|
38
|
-
const packageJsonPath = path.join(APP_DIR, 'package.json');
|
|
39
|
-
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
40
|
-
const repository = pkg?.repository;
|
|
41
|
-
if (typeof repository === 'string') {
|
|
42
|
-
return parseRepositorySlug(repository);
|
|
43
|
-
}
|
|
44
|
-
if (repository && typeof repository.url === 'string') {
|
|
45
|
-
return parseRepositorySlug(repository.url);
|
|
46
|
-
}
|
|
47
|
-
} catch {
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
32
|
function getGithubRepository() {
|
|
54
33
|
return (
|
|
55
34
|
parseRepositorySlug(process.env.NEOAGENT_WEARABLE_FIRMWARE_GITHUB_REPOSITORY)
|
|
56
|
-
||
|
|
57
|
-
|| readPackageRepositorySlug()
|
|
35
|
+
|| DEFAULT_GITHUB_REPOSITORY
|
|
58
36
|
);
|
|
59
37
|
}
|
|
60
38
|
|
|
@@ -359,6 +337,7 @@ async function resolveFirmwareManifest({
|
|
|
359
337
|
|
|
360
338
|
module.exports = {
|
|
361
339
|
DEFAULT_ASSET_NAME,
|
|
340
|
+
DEFAULT_GITHUB_REPOSITORY,
|
|
362
341
|
getFirmwareAssetName,
|
|
363
342
|
getGithubRepository,
|
|
364
343
|
normalizeChannel,
|
|
@@ -139,11 +139,7 @@ class WearableService {
|
|
|
139
139
|
const version = getVersionInfo();
|
|
140
140
|
const manifest = await resolveFirmwareManifest({
|
|
141
141
|
channel,
|
|
142
|
-
|
|
143
|
-
currentVersionOverride: toTrimmedString(process.env.NEOAGENT_WEARABLE_FIRMWARE_VERSION, 120),
|
|
144
|
-
releaseNotesUrlOverride: toTrimmedString(process.env.NEOAGENT_WEARABLE_FIRMWARE_RELEASE_NOTES_URL, 2000),
|
|
145
|
-
sha256Override: toTrimmedString(process.env.NEOAGENT_WEARABLE_FIRMWARE_SHA256, 256),
|
|
146
|
-
repositoryOverride: toTrimmedString(process.env.NEOAGENT_WEARABLE_FIRMWARE_GITHUB_REPOSITORY || process.env.GITHUB_REPOSITORY, 256),
|
|
142
|
+
repositoryOverride: toTrimmedString(process.env.NEOAGENT_WEARABLE_FIRMWARE_GITHUB_REPOSITORY, 256),
|
|
147
143
|
assetNameOverride: toTrimmedString(process.env.NEOAGENT_WEARABLE_FIRMWARE_ASSET_NAME, 128),
|
|
148
144
|
});
|
|
149
145
|
return {
|