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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neoagent",
3
- "version": "2.3.1-beta.42",
3
+ "version": "2.3.1-beta.43",
4
4
  "description": "Proactive personal AI agent with no limits",
5
5
  "license": "MIT",
6
6
  "main": "server/index.js",
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"42d3d75a56efe1a2e9902f52dc8006099c45d9
37
37
 
38
38
  _flutter.loader.load({
39
39
  serviceWorkerSettings: {
40
- serviceWorkerVersion: "380195857" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
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) chunks.push(Buffer.from(value));
209
+ if (value?.length) {
210
+ chunks.push(Buffer.from(value));
211
+ }
202
212
  }
203
- const audioBytes = Buffer.concat(chunks);
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.responseFormat : 'mp3',
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.deepgramContainer);
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 fs = require('fs');
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
- || parseRepositorySlug(process.env.GITHUB_REPOSITORY)
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
- downloadUrlOverride: toTrimmedString(process.env.NEOAGENT_WEARABLE_FIRMWARE_DOWNLOAD_URL, 2000),
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 {