neoagent 2.3.1-beta.4 → 2.3.1-beta.41
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 +45 -0
- package/docs/capabilities.md +2 -2
- package/docs/configuration.md +12 -5
- package/docs/hardware.md +1 -1
- package/docs/integrations.md +2 -3
- package/flutter_app/.metadata +42 -0
- package/flutter_app/README.md +21 -0
- package/flutter_app/analysis_options.yaml +32 -0
- package/flutter_app/android/app/build.gradle.kts +109 -0
- package/flutter_app/android/app/src/debug/AndroidManifest.xml +7 -0
- package/flutter_app/android/app/src/main/AndroidManifest.xml +147 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/MainActivity.kt +747 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthConnectGateway.kt +280 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncNotifications.kt +113 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncPayload.kt +57 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncScheduler.kt +78 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncWorker.kt +253 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/PermissionsRationaleActivity.kt +46 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingBootReceiver.kt +21 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingForegroundService.kt +586 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingStateStore.kt +78 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingUploadClient.kt +104 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/AiHomeWidgetProvider.kt +457 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/AiWidgetStore.kt +194 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/VoiceLaunchWidgetProvider.kt +67 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetConfigActivity.kt +228 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetSyncScheduler.kt +72 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetSyncWorker.kt +186 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetTaskRunWorker.kt +210 -0
- package/flutter_app/android/app/src/main/res/drawable/launch_background.xml +12 -0
- package/flutter_app/android/app/src/main/res/drawable/neoagent_ai_widget_bg.xml +11 -0
- package/flutter_app/android/app/src/main/res/drawable/neoagent_ai_widget_task_bg.xml +8 -0
- package/flutter_app/android/app/src/main/res/drawable-v21/launch_background.xml +12 -0
- package/flutter_app/android/app/src/main/res/layout/neoagent_ai_widget.xml +138 -0
- package/flutter_app/android/app/src/main/res/layout/neoagent_ai_widget_task_row.xml +52 -0
- package/flutter_app/android/app/src/main/res/layout/neoagent_voice_widget.xml +49 -0
- package/flutter_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/values/strings.xml +12 -0
- package/flutter_app/android/app/src/main/res/values/styles.xml +18 -0
- package/flutter_app/android/app/src/main/res/values-night/styles.xml +18 -0
- package/flutter_app/android/app/src/main/res/xml/file_paths.xml +6 -0
- package/flutter_app/android/app/src/main/res/xml/neoagent_ai_widget_info.xml +12 -0
- package/flutter_app/android/app/src/main/res/xml/neoagent_voice_widget_info.xml +12 -0
- package/flutter_app/android/app/src/profile/AndroidManifest.xml +7 -0
- package/flutter_app/android/build.gradle.kts +24 -0
- package/flutter_app/android/ci-release.keystore +0 -0
- package/flutter_app/android/gradle/wrapper/gradle-wrapper.properties +5 -0
- package/flutter_app/android/gradle.properties +3 -0
- package/flutter_app/android/key.properties +4 -0
- package/flutter_app/android/settings.gradle.kts +26 -0
- package/flutter_app/assets/branding/app_icon_1024.png +0 -0
- package/flutter_app/assets/branding/app_icon_128.png +0 -0
- package/flutter_app/assets/branding/app_icon_192.png +0 -0
- package/flutter_app/assets/branding/app_icon_256.png +0 -0
- package/flutter_app/assets/branding/app_icon_32.png +0 -0
- package/flutter_app/assets/branding/app_icon_512.png +0 -0
- package/flutter_app/assets/branding/app_icon_64.png +0 -0
- package/flutter_app/assets/branding/tray_icon_template.png +0 -0
- package/flutter_app/lib/features/location/location_service.dart +119 -0
- package/flutter_app/lib/features/notifications/notification_interceptor.dart +97 -0
- package/flutter_app/lib/main.dart +99 -0
- package/flutter_app/lib/main_account_settings.dart +1250 -0
- package/flutter_app/lib/main_admin.dart +886 -0
- package/flutter_app/lib/main_app_shell.dart +1682 -0
- package/flutter_app/lib/main_chat.dart +3352 -0
- package/flutter_app/lib/main_controller.dart +6781 -0
- package/flutter_app/lib/main_devices.dart +2301 -0
- package/flutter_app/lib/main_integrations.dart +1129 -0
- package/flutter_app/lib/main_launcher.dart +959 -0
- package/flutter_app/lib/main_launcher_entry.dart +5 -0
- package/flutter_app/lib/main_models.dart +3546 -0
- package/flutter_app/lib/main_navigation.dart +193 -0
- package/flutter_app/lib/main_operations.dart +4851 -0
- package/flutter_app/lib/main_recordings.dart +870 -0
- package/flutter_app/lib/main_runtime.dart +806 -0
- package/flutter_app/lib/main_settings.dart +2024 -0
- package/flutter_app/lib/main_shared.dart +2861 -0
- package/flutter_app/lib/main_theme.dart +204 -0
- package/flutter_app/lib/main_voice_assistant.dart +957 -0
- package/flutter_app/lib/src/android_apk_drop_zone.dart +32 -0
- package/flutter_app/lib/src/android_apk_drop_zone_stub.dart +16 -0
- package/flutter_app/lib/src/android_apk_drop_zone_web.dart +348 -0
- package/flutter_app/lib/src/android_app_installer.dart +22 -0
- package/flutter_app/lib/src/android_app_installer_io.dart +122 -0
- package/flutter_app/lib/src/android_app_installer_stub.dart +21 -0
- package/flutter_app/lib/src/android_launcher_bridge.dart +239 -0
- package/flutter_app/lib/src/app_launch_bridge.dart +29 -0
- package/flutter_app/lib/src/app_release_updater.dart +511 -0
- package/flutter_app/lib/src/backend_client.dart +1833 -0
- package/flutter_app/lib/src/desktop_companion.dart +2 -0
- package/flutter_app/lib/src/desktop_companion_actions.dart +586 -0
- package/flutter_app/lib/src/desktop_companion_io.dart +538 -0
- package/flutter_app/lib/src/desktop_companion_stub.dart +59 -0
- package/flutter_app/lib/src/desktop_native_bridge.dart +91 -0
- package/flutter_app/lib/src/desktop_screen_capture.dart +21 -0
- package/flutter_app/lib/src/desktop_screen_capture_io.dart +142 -0
- package/flutter_app/lib/src/desktop_screen_capture_stub.dart +12 -0
- package/flutter_app/lib/src/diagnostics_logger.dart +119 -0
- package/flutter_app/lib/src/health_bridge.dart +136 -0
- package/flutter_app/lib/src/live_voice_capture.dart +85 -0
- package/flutter_app/lib/src/messaging_access_summary.dart +46 -0
- package/flutter_app/lib/src/network/app_http_client.dart +53 -0
- package/flutter_app/lib/src/network/app_http_client_factory.dart +6 -0
- package/flutter_app/lib/src/network/app_http_client_io.dart +138 -0
- package/flutter_app/lib/src/network/app_http_client_stub.dart +3 -0
- package/flutter_app/lib/src/network/app_http_client_web.dart +94 -0
- package/flutter_app/lib/src/oauth_launcher.dart +33 -0
- package/flutter_app/lib/src/oauth_launcher_io.dart +77 -0
- package/flutter_app/lib/src/oauth_launcher_stub.dart +33 -0
- package/flutter_app/lib/src/oauth_launcher_web.dart +107 -0
- package/flutter_app/lib/src/recording_bridge.dart +232 -0
- package/flutter_app/lib/src/recording_bridge_io.dart +1019 -0
- package/flutter_app/lib/src/recording_bridge_stub.dart +120 -0
- package/flutter_app/lib/src/recording_bridge_web.dart +689 -0
- package/flutter_app/lib/src/recording_payloads.dart +86 -0
- package/flutter_app/lib/src/theme/palette.dart +81 -0
- package/flutter_app/lib/src/widget_bridge.dart +49 -0
- package/flutter_app/linux/CMakeLists.txt +128 -0
- package/flutter_app/linux/flutter/CMakeLists.txt +88 -0
- package/flutter_app/linux/flutter/generated_plugin_registrant.cc +43 -0
- package/flutter_app/linux/flutter/generated_plugin_registrant.h +15 -0
- package/flutter_app/linux/flutter/generated_plugins.cmake +31 -0
- package/flutter_app/linux/runner/CMakeLists.txt +26 -0
- package/flutter_app/linux/runner/main.cc +6 -0
- package/flutter_app/linux/runner/my_application.cc +144 -0
- package/flutter_app/linux/runner/my_application.h +18 -0
- package/flutter_app/linux/runner/resources/app_icon.png +0 -0
- package/flutter_app/macos/Flutter/Flutter-Debug.xcconfig +2 -0
- package/flutter_app/macos/Flutter/Flutter-Release.xcconfig +2 -0
- package/flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +40 -0
- package/flutter_app/macos/Podfile +42 -0
- package/flutter_app/macos/Podfile.lock +87 -0
- package/flutter_app/macos/Runner/AppDelegate.swift +576 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +68 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png +0 -0
- package/flutter_app/macos/Runner/Base.lproj/MainMenu.xib +342 -0
- package/flutter_app/macos/Runner/Configs/AppInfo.xcconfig +14 -0
- package/flutter_app/macos/Runner/Configs/Debug.xcconfig +2 -0
- package/flutter_app/macos/Runner/Configs/Release.xcconfig +2 -0
- package/flutter_app/macos/Runner/Configs/Warnings.xcconfig +13 -0
- package/flutter_app/macos/Runner/DebugProfile.entitlements +16 -0
- package/flutter_app/macos/Runner/Info.plist +36 -0
- package/flutter_app/macos/Runner/MainFlutterWindow.swift +19 -0
- package/flutter_app/macos/Runner/Release.entitlements +12 -0
- package/flutter_app/macos/Runner.xcodeproj/project.pbxproj +801 -0
- package/flutter_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/flutter_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +99 -0
- package/flutter_app/macos/Runner.xcworkspace/contents.xcworkspacedata +10 -0
- package/flutter_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/flutter_app/macos/RunnerTests/RunnerTests.swift +12 -0
- package/flutter_app/patch_strings.py +12 -0
- package/flutter_app/pubspec.lock +1088 -0
- package/flutter_app/pubspec.yaml +53 -0
- package/flutter_app/test/messaging_access_summary_test.dart +22 -0
- package/flutter_app/test/recording_payloads_test.dart +53 -0
- package/flutter_app/third_party/desktop_audio_capture/LICENSE +21 -0
- package/flutter_app/third_party/desktop_audio_capture/README.md +262 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/audio_capture.dart +65 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/config/mic_audio_config.dart +153 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/config/system_adudio_config.dart +110 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/mic/mic_audio_capture.dart +461 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/model/audio_status.dart +91 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/model/decibel_data.dart +106 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/model/input_device_type.dart +219 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/system/system_audio_capture.dart +336 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/CMakeLists.txt +101 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/audio_capture_plugin.cc +692 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/audio_capture_plugin.h +35 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/mic_capture_plugin.h +36 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/audio_capture_plugin.h +32 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/mic_capture_plugin.h +32 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/mic_capture_plugin.cc +878 -0
- package/flutter_app/third_party/desktop_audio_capture/macos/Classes/AudioCapturePlugin.swift +27 -0
- package/flutter_app/third_party/desktop_audio_capture/macos/Classes/MicCapturePlugin.swift +1172 -0
- package/flutter_app/third_party/desktop_audio_capture/macos/Classes/SystemCapturePlugin.swift +655 -0
- package/flutter_app/third_party/desktop_audio_capture/macos/Resources/PrivacyInfo.xcprivacy +12 -0
- package/flutter_app/third_party/desktop_audio_capture/macos/desktop_audio_capture.podspec +30 -0
- package/flutter_app/third_party/desktop_audio_capture/pubspec.yaml +87 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/CMakeLists.txt +105 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.cpp +80 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.h +31 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin_c_api.cpp +12 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/include/audio_capture/audio_capture_plugin_c_api.h +23 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/include/desktop_audio_capture/audio_capture_plugin.h +25 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.cpp +1117 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.h +115 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.cpp +777 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.h +87 -0
- package/flutter_app/third_party/flutter_secure_storage_linux/linux/CMakeLists.txt +30 -0
- package/flutter_app/third_party/flutter_secure_storage_linux/linux/flutter_secure_storage_linux_plugin.cc +215 -0
- package/flutter_app/third_party/flutter_secure_storage_linux/linux/include/flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h +27 -0
- package/flutter_app/third_party/flutter_secure_storage_linux/pubspec.yaml +20 -0
- package/flutter_app/tool/generate_desktop_branding.py +219 -0
- package/flutter_app/web/favicon.png +0 -0
- package/flutter_app/web/favicon.svg +12 -0
- package/flutter_app/web/icons/Icon-192.png +0 -0
- package/flutter_app/web/icons/Icon-512.png +0 -0
- package/flutter_app/web/icons/Icon-maskable-192.png +0 -0
- package/flutter_app/web/icons/Icon-maskable-512.png +0 -0
- package/flutter_app/web/index.html +39 -0
- package/flutter_app/web/manifest.json +35 -0
- package/flutter_app/windows/CMakeLists.txt +108 -0
- package/flutter_app/windows/flutter/CMakeLists.txt +109 -0
- package/flutter_app/windows/flutter/generated_plugin_registrant.cc +47 -0
- package/flutter_app/windows/flutter/generated_plugin_registrant.h +15 -0
- package/flutter_app/windows/flutter/generated_plugins.cmake +35 -0
- package/flutter_app/windows/runner/CMakeLists.txt +41 -0
- package/flutter_app/windows/runner/Runner.rc +121 -0
- package/flutter_app/windows/runner/flutter_window.cpp +533 -0
- package/flutter_app/windows/runner/flutter_window.h +37 -0
- package/flutter_app/windows/runner/main.cpp +53 -0
- package/flutter_app/windows/runner/resource.h +16 -0
- package/flutter_app/windows/runner/resources/app_icon.ico +0 -0
- package/flutter_app/windows/runner/runner.exe.manifest +14 -0
- package/flutter_app/windows/runner/utils.cpp +65 -0
- package/flutter_app/windows/runner/utils.h +19 -0
- package/flutter_app/windows/runner/win32_window.cpp +299 -0
- package/flutter_app/windows/runner/win32_window.h +102 -0
- package/lib/install_helpers.js +31 -0
- package/lib/manager.js +227 -6
- package/package.json +3 -1
- package/server/db/database.js +110 -0
- package/server/http/middleware.js +55 -2
- package/server/http/routes.js +1 -0
- package/server/index.js +3 -0
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/NOTICES +1 -1
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/canvaskit/wimp.wasm +0 -0
- package/server/public/flutter_bootstrap.js +2 -2
- package/server/public/main.dart.js +74324 -73132
- package/server/routes/integrations.js +108 -1
- package/server/routes/memory.js +11 -2
- package/server/{http/routes → routes}/screenHistory.js +2 -2
- package/server/routes/settings.js +75 -2
- package/server/{http/routes → routes}/triggers.js +2 -2
- package/server/routes/wearable.js +67 -0
- package/server/services/ai/models.js +30 -0
- package/server/services/ai/providers/githubCopilot.js +97 -0
- package/server/services/ai/providers/openai.js +2 -1
- package/server/services/ai/providers/openaiCodex.js +31 -0
- package/server/services/ai/settings.js +20 -0
- package/server/services/ai/toolSelector.js +14 -1
- package/server/services/ai/tools.js +77 -4
- package/server/services/desktop/screenRecorder.js +65 -9
- package/server/services/integrations/env.js +5 -0
- package/server/services/integrations/figma/provider.js +1 -0
- package/server/services/integrations/github/common.js +106 -0
- package/server/services/integrations/github/provider.js +499 -0
- package/server/services/integrations/github/repos.js +1124 -0
- package/server/services/integrations/google/provider.js +1 -0
- package/server/services/integrations/home_assistant/provider.js +325 -26
- package/server/services/integrations/manager.js +88 -12
- package/server/services/integrations/microsoft/provider.js +1 -0
- package/server/services/integrations/oauth_provider.js +25 -8
- package/server/services/integrations/provider_config_store.js +85 -0
- package/server/services/integrations/registry.js +4 -0
- package/server/services/integrations/spotify/provider.js +1 -0
- package/server/services/integrations/trello/provider.js +842 -0
- package/server/services/manager.js +46 -1
- package/server/services/mcp/client.js +120 -23
- package/server/services/memory/manager.js +39 -2
- package/server/services/messaging/access_policy.js +10 -0
- package/server/services/messaging/manager.js +49 -0
- package/server/services/messaging/meshtastic.js +260 -0
- package/server/services/messaging/meshtastic_env.js +100 -0
- package/server/services/messaging/meshtastic_protocol.js +476 -0
- package/server/services/messaging/meshtastic_tcp_transport.js +25 -0
- package/server/services/tasks/runtime.js +1 -1
- package/server/services/voice/openaiClient.js +4 -1
- package/server/services/voice/openaiSpeech.js +6 -1
- package/server/services/voice/providers.js +52 -12
- package/server/services/voice/runtimeManager.js +136 -19
- package/server/services/voice/turnRunner.js +29 -9
- package/server/services/wearable/firmware_manifest.js +370 -0
- package/server/services/wearable/gateway.js +350 -0
- package/server/services/wearable/protocol.js +45 -0
- package/server/services/wearable/service.js +244 -0
- package/server/utils/local_secrets.js +56 -0
- package/server/utils/logger.js +37 -9
|
@@ -6,6 +6,7 @@ const { AGENT_DATA_DIR } = require('../../../runtime/paths');
|
|
|
6
6
|
const { getOpenAiClient } = require('./openaiClient');
|
|
7
7
|
const { synthesizeSpeechBuffer } = require('./openaiSpeech');
|
|
8
8
|
const { transcribeChunkWithDeepgram } = require('../recordings/deepgram');
|
|
9
|
+
const { decryptLocalValue } = require('../../utils/local_secrets');
|
|
9
10
|
|
|
10
11
|
const DEFAULT_STT_PROVIDER = 'openai';
|
|
11
12
|
const DEFAULT_TTS_PROVIDER = 'openai';
|
|
@@ -36,6 +37,12 @@ const DEFAULT_GEMINI_TRANSCRIPTION_PROMPT =
|
|
|
36
37
|
'Transcribe this audio verbatim. Return only the transcript text.';
|
|
37
38
|
const EMOJI_SPEECH_REGEX =
|
|
38
39
|
/[\p{Extended_Pictographic}\p{Emoji_Presentation}\p{Regional_Indicator}\u200D\uFE0F\u20E3]/gu;
|
|
40
|
+
const WEARABLE_SAFE_AUDIO_FORMAT = Object.freeze({
|
|
41
|
+
responseFormat: 'wav',
|
|
42
|
+
mimeType: 'audio/wav',
|
|
43
|
+
deepgramEncoding: 'linear16',
|
|
44
|
+
deepgramContainer: 'wav',
|
|
45
|
+
});
|
|
39
46
|
|
|
40
47
|
function withTimeout(promise, timeoutMs, label) {
|
|
41
48
|
const normalizedTimeout = Number(timeoutMs);
|
|
@@ -94,7 +101,7 @@ function resolveApiKey(candidates = []) {
|
|
|
94
101
|
const snake = lower.replace(/[^a-z0-9]+/g, '_');
|
|
95
102
|
const variants = [key, lower, snake];
|
|
96
103
|
for (const variant of variants) {
|
|
97
|
-
const value = keys[variant];
|
|
104
|
+
const value = decryptLocalValue(keys[variant]);
|
|
98
105
|
if (typeof value === 'string' && value.trim()) {
|
|
99
106
|
return value.trim();
|
|
100
107
|
}
|
|
@@ -138,9 +145,16 @@ function normalizeVoiceSynthesisOptions(options = {}) {
|
|
|
138
145
|
provider,
|
|
139
146
|
model: resolveTtsModel(provider, options.model),
|
|
140
147
|
voice: resolveTtsVoice(provider, options.voice),
|
|
148
|
+
responseFormat: String(options.responseFormat || '').trim().toLowerCase(),
|
|
149
|
+
transport: String(options.transport || '').trim().toLowerCase(),
|
|
141
150
|
};
|
|
142
151
|
}
|
|
143
152
|
|
|
153
|
+
function resolveWearableSafeAudioOptions(options = {}) {
|
|
154
|
+
return String(options.transport || '').trim().toLowerCase() === 'wearable'
|
|
155
|
+
|| String(options.responseFormat || '').trim().toLowerCase() === 'wav';
|
|
156
|
+
}
|
|
157
|
+
|
|
144
158
|
function requireApiKey(settingLabel, candidates = []) {
|
|
145
159
|
const apiKey = resolveApiKey(candidates);
|
|
146
160
|
if (!apiKey) {
|
|
@@ -341,10 +355,15 @@ async function synthesizeWithOpenAi(text, model, voice, options = {}) {
|
|
|
341
355
|
if (!client) {
|
|
342
356
|
throw new Error('OpenAI TTS is selected but OPENAI_API_KEY is not configured.');
|
|
343
357
|
}
|
|
344
|
-
const
|
|
358
|
+
const useWearableSafeAudio = resolveWearableSafeAudioOptions(options);
|
|
359
|
+
const audioBytes = await synthesizeSpeechBuffer(client, text, {
|
|
360
|
+
model,
|
|
361
|
+
voice,
|
|
362
|
+
responseFormat: useWearableSafeAudio ? WEARABLE_SAFE_AUDIO_FORMAT.responseFormat : 'mp3',
|
|
363
|
+
});
|
|
345
364
|
return {
|
|
346
365
|
audioBytes,
|
|
347
|
-
mimeType: 'audio/mpeg',
|
|
366
|
+
mimeType: useWearableSafeAudio ? WEARABLE_SAFE_AUDIO_FORMAT.mimeType : 'audio/mpeg',
|
|
348
367
|
};
|
|
349
368
|
}
|
|
350
369
|
|
|
@@ -356,25 +375,37 @@ async function streamWithOpenAi(text, model, voice, options = {}, onChunk) {
|
|
|
356
375
|
if (!client) {
|
|
357
376
|
throw new Error('OpenAI TTS is selected but OPENAI_API_KEY is not configured.');
|
|
358
377
|
}
|
|
378
|
+
const useWearableSafeAudio = resolveWearableSafeAudioOptions(options);
|
|
359
379
|
const response = await client.audio.speech.create({
|
|
360
380
|
model: String(model || 'gpt-4o-mini-tts').trim() || 'gpt-4o-mini-tts',
|
|
361
381
|
voice: String(voice || 'alloy').trim() || 'alloy',
|
|
362
382
|
input: text,
|
|
363
|
-
response_format: 'mp3',
|
|
383
|
+
response_format: useWearableSafeAudio ? WEARABLE_SAFE_AUDIO_FORMAT.responseFormat : 'mp3',
|
|
364
384
|
});
|
|
365
385
|
const chunks = [];
|
|
366
386
|
for await (const chunk of response.body) {
|
|
367
387
|
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
368
388
|
}
|
|
369
389
|
const audioBytes = Buffer.concat(chunks);
|
|
370
|
-
await onChunk({
|
|
390
|
+
await onChunk({
|
|
391
|
+
audioBytes,
|
|
392
|
+
mimeType: useWearableSafeAudio ? WEARABLE_SAFE_AUDIO_FORMAT.mimeType : 'audio/mpeg',
|
|
393
|
+
});
|
|
371
394
|
}
|
|
372
395
|
|
|
373
|
-
async function synthesizeWithDeepgram(text, model) {
|
|
396
|
+
async function synthesizeWithDeepgram(text, model, options = {}) {
|
|
374
397
|
const apiKey = requireApiKey('Deepgram TTS', ['DEEPGRAM_API_KEY']);
|
|
398
|
+
const useWearableSafeAudio = resolveWearableSafeAudioOptions(options);
|
|
399
|
+
const searchParams = new URLSearchParams({
|
|
400
|
+
model,
|
|
401
|
+
});
|
|
402
|
+
if (useWearableSafeAudio) {
|
|
403
|
+
searchParams.set('encoding', WEARABLE_SAFE_AUDIO_FORMAT.deepgramEncoding);
|
|
404
|
+
searchParams.set('container', WEARABLE_SAFE_AUDIO_FORMAT.deepgramContainer);
|
|
405
|
+
}
|
|
375
406
|
|
|
376
407
|
return fetchAudioOrThrow(
|
|
377
|
-
`https://api.deepgram.com/v1/speak
|
|
408
|
+
`https://api.deepgram.com/v1/speak?${searchParams.toString()}`,
|
|
378
409
|
{
|
|
379
410
|
method: 'POST',
|
|
380
411
|
headers: {
|
|
@@ -384,13 +415,22 @@ async function synthesizeWithDeepgram(text, model) {
|
|
|
384
415
|
body: JSON.stringify({ text }),
|
|
385
416
|
},
|
|
386
417
|
'Deepgram TTS request failed',
|
|
418
|
+
useWearableSafeAudio ? WEARABLE_SAFE_AUDIO_FORMAT.mimeType : 'audio/mpeg',
|
|
387
419
|
);
|
|
388
420
|
}
|
|
389
421
|
|
|
390
|
-
async function streamWithDeepgram(text, model, onChunk) {
|
|
422
|
+
async function streamWithDeepgram(text, model, options = {}, onChunk) {
|
|
391
423
|
const apiKey = requireApiKey('Deepgram TTS', ['DEEPGRAM_API_KEY']);
|
|
424
|
+
const useWearableSafeAudio = resolveWearableSafeAudioOptions(options);
|
|
425
|
+
const searchParams = new URLSearchParams({
|
|
426
|
+
model,
|
|
427
|
+
});
|
|
428
|
+
if (useWearableSafeAudio) {
|
|
429
|
+
searchParams.set('encoding', WEARABLE_SAFE_AUDIO_FORMAT.deepgramEncoding);
|
|
430
|
+
searchParams.set('container', WEARABLE_SAFE_AUDIO_FORMAT.deepgramContainer);
|
|
431
|
+
}
|
|
392
432
|
await fetchAudioStreamOrThrow(
|
|
393
|
-
`https://api.deepgram.com/v1/speak
|
|
433
|
+
`https://api.deepgram.com/v1/speak?${searchParams.toString()}`,
|
|
394
434
|
{
|
|
395
435
|
method: 'POST',
|
|
396
436
|
headers: {
|
|
@@ -400,7 +440,7 @@ async function streamWithDeepgram(text, model, onChunk) {
|
|
|
400
440
|
body: JSON.stringify({ text }),
|
|
401
441
|
},
|
|
402
442
|
'Deepgram TTS stream failed',
|
|
403
|
-
'audio/mpeg',
|
|
443
|
+
useWearableSafeAudio ? WEARABLE_SAFE_AUDIO_FORMAT.mimeType : 'audio/mpeg',
|
|
404
444
|
onChunk,
|
|
405
445
|
);
|
|
406
446
|
}
|
|
@@ -586,7 +626,7 @@ async function synthesizeVoiceReply(text, options = {}) {
|
|
|
586
626
|
if (provider === 'openai') {
|
|
587
627
|
request = synthesizeWithOpenAi(content, model, voice, options);
|
|
588
628
|
} else if (provider === 'deepgram') {
|
|
589
|
-
request = synthesizeWithDeepgram(content, model);
|
|
629
|
+
request = synthesizeWithDeepgram(content, model, options);
|
|
590
630
|
} else {
|
|
591
631
|
request = synthesizeWithGemini(content, model, voice, options);
|
|
592
632
|
}
|
|
@@ -668,7 +708,7 @@ async function synthesizeVoiceReplyStream(text, options = {}, onChunk) {
|
|
|
668
708
|
if (provider === 'openai') {
|
|
669
709
|
await streamWithOpenAi(chunk, model, voice, options, onChunk);
|
|
670
710
|
} else if (provider === 'deepgram') {
|
|
671
|
-
await streamWithDeepgram(chunk, model, onChunk);
|
|
711
|
+
await streamWithDeepgram(chunk, model, options, onChunk);
|
|
672
712
|
} else {
|
|
673
713
|
await streamWithGemini(chunk, model, voice, options, onChunk);
|
|
674
714
|
}
|
|
@@ -34,11 +34,17 @@ class VoiceRuntimeManager {
|
|
|
34
34
|
return this.sessions.get(String(sessionId || '').trim()) || null;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
async
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
async openSession({
|
|
38
|
+
userId,
|
|
39
|
+
agentId = null,
|
|
40
|
+
sessionId = null,
|
|
41
|
+
platform = 'voice_live',
|
|
42
|
+
sink,
|
|
43
|
+
outputMode = 'audio_and_text',
|
|
44
|
+
} = {}) {
|
|
45
|
+
if (!sink) {
|
|
46
|
+
throw new Error('A voice session sink is required.');
|
|
40
47
|
}
|
|
41
|
-
|
|
42
48
|
const voiceSettings = getVoiceRuntimeSettings(userId, agentId);
|
|
43
49
|
const liveProviderRuntime = this.#getProviderRuntime(
|
|
44
50
|
userId,
|
|
@@ -53,16 +59,37 @@ class VoiceRuntimeManager {
|
|
|
53
59
|
id: resolvedSessionId,
|
|
54
60
|
userId,
|
|
55
61
|
agentId,
|
|
56
|
-
platform
|
|
62
|
+
platform,
|
|
57
63
|
voiceSettings: {
|
|
58
64
|
...voiceSettings,
|
|
59
65
|
liveApiKey: liveProviderRuntime.apiKey,
|
|
60
66
|
liveBaseUrl: liveProviderRuntime.baseUrl,
|
|
61
67
|
},
|
|
68
|
+
sink,
|
|
69
|
+
outputMode,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
session.adapter = adapter;
|
|
73
|
+
this.sessions.set(resolvedSessionId, session);
|
|
74
|
+
await session.publishReady();
|
|
75
|
+
return session;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async openFlutterSession({ userId, agentId = null, socket, sessionId = null } = {}) {
|
|
79
|
+
if (!socket) {
|
|
80
|
+
throw new Error('Socket is required to open a Flutter voice session.');
|
|
81
|
+
}
|
|
82
|
+
return this.openSession({
|
|
83
|
+
userId,
|
|
84
|
+
agentId,
|
|
85
|
+
sessionId,
|
|
86
|
+
platform: 'voice_live',
|
|
87
|
+
outputMode: 'audio_and_text',
|
|
62
88
|
sink: {
|
|
63
89
|
publishReady: async (_session, extra = {}) => {
|
|
90
|
+
const voiceSettings = getVoiceRuntimeSettings(userId, agentId);
|
|
64
91
|
socket.emit('voice:session_ready', {
|
|
65
|
-
sessionId:
|
|
92
|
+
sessionId: String(sessionId || _session.id).trim(),
|
|
66
93
|
runtimeMode: voiceSettings.runtimeMode,
|
|
67
94
|
provider: voiceSettings.liveProvider,
|
|
68
95
|
model: voiceSettings.liveModel,
|
|
@@ -72,52 +99,58 @@ class VoiceRuntimeManager {
|
|
|
72
99
|
},
|
|
73
100
|
setState: async (_session, state, extra = {}) => {
|
|
74
101
|
socket.emit('voice:assistant_state', {
|
|
75
|
-
sessionId:
|
|
102
|
+
sessionId: _session.id,
|
|
76
103
|
state,
|
|
77
104
|
...extra,
|
|
78
105
|
});
|
|
79
106
|
},
|
|
80
107
|
publishTranscriptPartial: async (_session, content) => {
|
|
81
108
|
socket.emit('voice:transcript_partial', {
|
|
82
|
-
sessionId:
|
|
109
|
+
sessionId: _session.id,
|
|
83
110
|
content,
|
|
84
111
|
});
|
|
85
112
|
},
|
|
86
113
|
publishTranscriptFinal: async (_session, content) => {
|
|
87
114
|
socket.emit('voice:transcript_final', {
|
|
88
|
-
sessionId:
|
|
115
|
+
sessionId: _session.id,
|
|
89
116
|
content,
|
|
90
117
|
});
|
|
91
118
|
},
|
|
92
119
|
publishAssistantOutput: async (_session, content, options = {}) => {
|
|
93
|
-
await this.#deliverFlutterAssistantOutput(socket,
|
|
120
|
+
await this.#deliverFlutterAssistantOutput(socket, _session.id, _session, content, options);
|
|
94
121
|
},
|
|
95
|
-
interruptOutput: async () => {
|
|
122
|
+
interruptOutput: async (_session) => {
|
|
96
123
|
socket.emit('voice:assistant_state', {
|
|
97
|
-
sessionId:
|
|
124
|
+
sessionId: _session.id,
|
|
98
125
|
state: 'interrupted',
|
|
99
126
|
});
|
|
100
127
|
},
|
|
101
128
|
publishError: async (_session, message, extra = {}) => {
|
|
102
129
|
socket.emit('voice:error', {
|
|
103
|
-
sessionId:
|
|
130
|
+
sessionId: _session.id,
|
|
104
131
|
error: message,
|
|
105
132
|
...extra,
|
|
106
133
|
});
|
|
107
134
|
},
|
|
108
|
-
close: async () => {
|
|
135
|
+
close: async (_session) => {
|
|
109
136
|
socket.emit('voice:assistant_state', {
|
|
110
|
-
sessionId:
|
|
137
|
+
sessionId: _session.id,
|
|
111
138
|
state: 'closed',
|
|
112
139
|
});
|
|
113
140
|
},
|
|
114
141
|
},
|
|
115
142
|
});
|
|
143
|
+
}
|
|
116
144
|
|
|
117
|
-
|
|
118
|
-
this.
|
|
119
|
-
|
|
120
|
-
|
|
145
|
+
async openWearableSession({ userId, agentId = null, sessionId = null, sink } = {}) {
|
|
146
|
+
return this.openSession({
|
|
147
|
+
userId,
|
|
148
|
+
agentId,
|
|
149
|
+
sessionId,
|
|
150
|
+
platform: 'wearable_live',
|
|
151
|
+
outputMode: 'audio_and_text',
|
|
152
|
+
sink,
|
|
153
|
+
});
|
|
121
154
|
}
|
|
122
155
|
|
|
123
156
|
async closeSession(sessionId, reason = 'closed') {
|
|
@@ -435,6 +468,8 @@ class VoiceRuntimeManager {
|
|
|
435
468
|
provider: session.voiceSettings?.liveProvider,
|
|
436
469
|
model: session.voiceSettings?.liveTtsModel,
|
|
437
470
|
voice: session.voiceSettings?.liveVoice,
|
|
471
|
+
transport: 'wearable',
|
|
472
|
+
responseFormat: 'wav',
|
|
438
473
|
});
|
|
439
474
|
const spokenContent = sanitizeSpeechText(content);
|
|
440
475
|
|
|
@@ -490,6 +525,88 @@ class VoiceRuntimeManager {
|
|
|
490
525
|
}
|
|
491
526
|
}
|
|
492
527
|
|
|
528
|
+
async deliverWearableAssistantOutput(ws, sessionId, content, options = {}) {
|
|
529
|
+
const session = this.getSession(sessionId);
|
|
530
|
+
if (!session) {
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
const kind = String(options.kind || 'final').trim() || 'final';
|
|
534
|
+
ws.send(JSON.stringify({
|
|
535
|
+
type: 'voice:assistant_text',
|
|
536
|
+
sessionId,
|
|
537
|
+
content,
|
|
538
|
+
kind,
|
|
539
|
+
}));
|
|
540
|
+
|
|
541
|
+
if (kind === 'final') {
|
|
542
|
+
await session.setState('speaking', { kind });
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const voiceOptions = normalizeVoiceSynthesisOptions({
|
|
546
|
+
provider: session.voiceSettings?.liveProvider,
|
|
547
|
+
model: session.voiceSettings?.liveTtsModel,
|
|
548
|
+
voice: session.voiceSettings?.liveVoice,
|
|
549
|
+
});
|
|
550
|
+
const spokenContent = sanitizeSpeechText(content);
|
|
551
|
+
let index = 0;
|
|
552
|
+
let streamError = null;
|
|
553
|
+
const ttsAttempts = this.#buildTtsAttemptOrder(session, voiceOptions);
|
|
554
|
+
|
|
555
|
+
if (spokenContent) {
|
|
556
|
+
try {
|
|
557
|
+
for (const attempt of ttsAttempts) {
|
|
558
|
+
index = 0;
|
|
559
|
+
streamError = null;
|
|
560
|
+
try {
|
|
561
|
+
await synthesizeVoiceReplyStream(
|
|
562
|
+
spokenContent,
|
|
563
|
+
attempt,
|
|
564
|
+
async ({ audioBytes, mimeType }) => {
|
|
565
|
+
if (session.closed || session.interrupted) return;
|
|
566
|
+
ws.send(JSON.stringify({
|
|
567
|
+
type: 'voice:audio_chunk',
|
|
568
|
+
sessionId,
|
|
569
|
+
kind,
|
|
570
|
+
index,
|
|
571
|
+
audioBase64: audioBytes.toString('base64'),
|
|
572
|
+
mimeType,
|
|
573
|
+
}));
|
|
574
|
+
index += 1;
|
|
575
|
+
},
|
|
576
|
+
);
|
|
577
|
+
break;
|
|
578
|
+
} catch (error) {
|
|
579
|
+
streamError = String(error?.message || error || 'Voice playback failed.');
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
} catch (error) {
|
|
583
|
+
streamError = String(error?.message || error || 'Voice playback failed.');
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (!streamError && !session.closed && !session.interrupted) {
|
|
588
|
+
ws.send(JSON.stringify({
|
|
589
|
+
type: 'voice:audio_done',
|
|
590
|
+
sessionId,
|
|
591
|
+
kind,
|
|
592
|
+
totalChunks: index,
|
|
593
|
+
}));
|
|
594
|
+
} else if (kind === 'final' && !session.closed && !session.interrupted) {
|
|
595
|
+
ws.send(JSON.stringify({
|
|
596
|
+
type: 'voice:error',
|
|
597
|
+
sessionId,
|
|
598
|
+
error: streamError,
|
|
599
|
+
recoverable: true,
|
|
600
|
+
phase: 'tts',
|
|
601
|
+
}));
|
|
602
|
+
await session.setState('degraded', { kind, phase: 'tts' });
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
if (kind === 'final' && !streamError) {
|
|
606
|
+
await session.setState('idle');
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
493
610
|
#buildTtsAttemptOrder(session, voiceOptions) {
|
|
494
611
|
const attempts = [];
|
|
495
612
|
const providers = [
|
|
@@ -110,8 +110,35 @@ async function runVoiceTranscriptTurn({
|
|
|
110
110
|
});
|
|
111
111
|
|
|
112
112
|
const replyText = String(runResult?.content || '').trim();
|
|
113
|
+
const assistantMetadata = {
|
|
114
|
+
...persistedMetadata,
|
|
115
|
+
platform,
|
|
116
|
+
tokens: runResult?.totalTokens || 0,
|
|
117
|
+
screenshotIncluded: Boolean(screenshotContext),
|
|
118
|
+
screenshotVisionProvider: screenshotContext?.provider || null,
|
|
119
|
+
screenshotVisionModel: screenshotContext?.model || null,
|
|
120
|
+
};
|
|
113
121
|
if (!replyText) {
|
|
114
|
-
|
|
122
|
+
db.prepare('INSERT INTO conversation_history (user_id, agent_id, agent_run_id, role, content, metadata) VALUES (?, ?, ?, ?, ?, ?)')
|
|
123
|
+
.run(
|
|
124
|
+
userId,
|
|
125
|
+
agentId,
|
|
126
|
+
runResult?.runId || null,
|
|
127
|
+
'assistant',
|
|
128
|
+
'',
|
|
129
|
+
JSON.stringify(assistantMetadata),
|
|
130
|
+
);
|
|
131
|
+
return {
|
|
132
|
+
runId: runResult?.runId || null,
|
|
133
|
+
transcript: transcriptText,
|
|
134
|
+
replyText: '',
|
|
135
|
+
ttsProvider: voiceOptions.provider,
|
|
136
|
+
ttsModel: voiceOptions.model,
|
|
137
|
+
ttsVoice: voiceOptions.voice,
|
|
138
|
+
audioMimeType: 'audio/mpeg',
|
|
139
|
+
audioBase64: '',
|
|
140
|
+
ttsError: null,
|
|
141
|
+
};
|
|
115
142
|
}
|
|
116
143
|
|
|
117
144
|
db.prepare('INSERT INTO conversation_history (user_id, agent_id, agent_run_id, role, content, metadata) VALUES (?, ?, ?, ?, ?, ?)')
|
|
@@ -121,14 +148,7 @@ async function runVoiceTranscriptTurn({
|
|
|
121
148
|
runResult?.runId || null,
|
|
122
149
|
'assistant',
|
|
123
150
|
replyText,
|
|
124
|
-
JSON.stringify(
|
|
125
|
-
...persistedMetadata,
|
|
126
|
-
platform,
|
|
127
|
-
tokens: runResult?.totalTokens || 0,
|
|
128
|
-
screenshotIncluded: Boolean(screenshotContext),
|
|
129
|
-
screenshotVisionProvider: screenshotContext?.provider || null,
|
|
130
|
-
screenshotVisionModel: screenshotContext?.model || null,
|
|
131
|
-
}),
|
|
151
|
+
JSON.stringify(assistantMetadata),
|
|
132
152
|
);
|
|
133
153
|
|
|
134
154
|
let synthesized;
|