@vellumai/assistant 0.6.3 → 0.6.4
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/ARCHITECTURE.md +273 -10
- package/Dockerfile +2 -3
- package/bun.lock +5 -13
- package/docs/backup-troubleshooting.md +52 -0
- package/docs/browser-use-architecture-phase2.md +174 -0
- package/docs/stt-provider-onboarding.md +120 -0
- package/knip.json +12 -2
- package/node_modules/@vellumai/ces-contracts/bun.lock +8 -6
- package/node_modules/@vellumai/ces-contracts/package.json +3 -3
- package/openapi.yaml +982 -72
- package/package.json +4 -6
- package/scripts/generate-openapi.ts +0 -1
- package/scripts/test.sh +73 -18
- package/src/__tests__/agent-image-optimize.test.ts +28 -0
- package/src/__tests__/agent-loop.test.ts +123 -0
- package/src/__tests__/anthropic-provider.test.ts +263 -10
- package/src/__tests__/auto-analysis-end-to-end.test.ts +550 -0
- package/src/__tests__/auto-analysis-prompt.test.ts +50 -0
- package/src/__tests__/browser-fill-credential.test.ts +11 -0
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
- package/src/__tests__/browser-skill-endstate.test.ts +31 -7
- package/src/__tests__/btw-routes.test.ts +7 -0
- package/src/__tests__/call-controller.test.ts +581 -20
- package/src/__tests__/catalog-files.test.ts +138 -0
- package/src/__tests__/channel-invite-transport.test.ts +2 -2
- package/src/__tests__/channel-readiness-routes.test.ts +16 -20
- package/src/__tests__/channel-readiness-service.test.ts +12 -7
- package/src/__tests__/checker.test.ts +157 -10
- package/src/__tests__/clawhub-files.test.ts +347 -0
- package/src/__tests__/commit-message-enrichment-service.test.ts +36 -19
- package/src/__tests__/config-analysis.test.ts +100 -0
- package/src/__tests__/config-schema.test.ts +1013 -66
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +339 -0
- package/src/__tests__/config-watcher.test.ts +43 -8
- package/src/__tests__/contact-store-user-file.test.ts +512 -0
- package/src/__tests__/contacts-write.test.ts +197 -0
- package/src/__tests__/context-window-manager.test.ts +88 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop.test.ts +98 -2
- package/src/__tests__/conversation-confirmation-signals.test.ts +135 -0
- package/src/__tests__/conversation-error.test.ts +70 -0
- package/src/__tests__/conversation-history-web-search.test.ts +11 -4
- package/src/__tests__/conversation-init.benchmark.test.ts +6 -1
- package/src/__tests__/conversation-launcher-skill-regression.test.ts +51 -0
- package/src/__tests__/conversation-list-source.test.ts +145 -0
- package/src/__tests__/conversation-pre-run-repair.test.ts +2 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
- package/src/__tests__/conversation-queue.test.ts +901 -60
- package/src/__tests__/conversation-routes-disk-view.test.ts +270 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +55 -0
- package/src/__tests__/conversation-skill-tools.test.ts +7 -4
- package/src/__tests__/conversation-slash-commands.test.ts +33 -0
- package/src/__tests__/conversation-slash-queue.test.ts +89 -18
- package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
- package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +226 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
- package/src/__tests__/credential-health-service.test.ts +352 -0
- package/src/__tests__/credential-security-invariants.test.ts +5 -3
- package/src/__tests__/credential-vault-unit.test.ts +379 -3
- package/src/__tests__/credentials-cli.test.ts +40 -16
- package/src/__tests__/cross-provider-web-search.test.ts +146 -35
- package/src/__tests__/deterministic-verification-control-plane.test.ts +10 -1
- package/src/__tests__/device-id.test.ts +112 -0
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +167 -4
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -3
- package/src/__tests__/email-html-renderer.test.ts +71 -0
- package/src/__tests__/email-invite-adapter.test.ts +36 -32
- package/src/__tests__/emit-event-signal.test.ts +71 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +75 -8
- package/src/__tests__/fixtures/mock-chrome-extension.ts +11 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +206 -1
- package/src/__tests__/gateway-only-guard.test.ts +0 -1
- package/src/__tests__/gemini-provider.test.ts +64 -0
- package/src/__tests__/get-skill-detail-audit.test.ts +325 -0
- package/src/__tests__/gmail-archive-fallback.test.ts +193 -0
- package/src/__tests__/gmail-archive-gate.test.ts +246 -0
- package/src/__tests__/gmail-preferences.test.ts +117 -0
- package/src/__tests__/headless-browser-interactions.test.ts +43 -0
- package/src/__tests__/headless-browser-mode.test.ts +614 -0
- package/src/__tests__/headless-browser-navigate.test.ts +142 -5
- package/src/__tests__/headless-browser-read-tools.test.ts +11 -0
- package/src/__tests__/headless-browser-snapshot.test.ts +10 -0
- package/src/__tests__/heartbeat-service.test.ts +70 -17
- package/src/__tests__/home-state-routes.test.ts +162 -0
- package/src/__tests__/host-bash-proxy.test.ts +0 -5
- package/src/__tests__/host-browser-e2e-cloud.test.ts +138 -4
- package/src/__tests__/host-browser-e2e-self-hosted.test.ts +4 -4
- package/src/__tests__/host-browser-ws-events-e2e.test.ts +103 -0
- package/src/__tests__/host-cu-proxy.test.ts +0 -5
- package/src/__tests__/identity-intro-cache.test.ts +40 -10
- package/src/__tests__/init-feature-flag-overrides.test.ts +38 -112
- package/src/__tests__/jobs-store-upsert-debounced.test.ts +141 -0
- package/src/__tests__/llm-context-normalization.test.ts +488 -0
- package/src/__tests__/llm-context-route-provider.test.ts +86 -5
- package/src/__tests__/llm-usage-store.test.ts +363 -0
- package/src/__tests__/media-stream-output.test.ts +555 -0
- package/src/__tests__/media-stream-parser.test.ts +374 -0
- package/src/__tests__/media-stream-server-integration.test.ts +1234 -0
- package/src/__tests__/media-stream-stt-session.test.ts +588 -0
- package/src/__tests__/media-turn-detector.test.ts +440 -0
- package/src/__tests__/message-queue.test.ts +125 -0
- package/src/__tests__/migration-export-http.test.ts +6 -6
- package/src/__tests__/migration-import-commit-http.test.ts +8 -6
- package/src/__tests__/migration-import-preflight-http.test.ts +6 -5
- package/src/__tests__/migration-validate-http.test.ts +3 -3
- package/src/__tests__/mock-gateway-ipc.ts +151 -0
- package/src/__tests__/model-intents.test.ts +2 -2
- package/src/__tests__/oauth-apps-routes.test.ts +1 -0
- package/src/__tests__/oauth-cli.test.ts +2 -0
- package/src/__tests__/oauth-connect-orchestrator.test.ts +2 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +1 -0
- package/src/__tests__/oauth-providers-routes.test.ts +2 -0
- package/src/__tests__/oauth-store.test.ts +85 -0
- package/src/__tests__/oauth2-gateway-transport.test.ts +249 -6
- package/src/__tests__/onboarding-template-contract.test.ts +6 -13
- package/src/__tests__/openai-provider.test.ts +176 -0
- package/src/__tests__/openai-responses-cutover-guard.test.ts +184 -0
- package/src/__tests__/openai-responses-provider.test.ts +1105 -0
- package/src/__tests__/openrouter-token-estimation.test.ts +100 -0
- package/src/__tests__/outlook-unsubscribe.test.ts +31 -2
- package/src/__tests__/persona-resolver.test.ts +251 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +4 -0
- package/src/__tests__/platform.test.ts +92 -1
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +47 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +267 -0
- package/src/__tests__/pricing.test.ts +174 -0
- package/src/__tests__/qdrant-manager.test.ts +29 -8
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +194 -0
- package/src/__tests__/relationship-state-contract.test.ts +175 -0
- package/src/__tests__/relay-server.test.ts +423 -5
- package/src/__tests__/search-skills-unified.test.ts +118 -0
- package/src/__tests__/secret-scanner-executor.test.ts +4 -0
- package/src/__tests__/secure-keys.test.ts +107 -0
- package/src/__tests__/send-endpoint-busy.test.ts +5 -1
- package/src/__tests__/sequence-store.test.ts +1 -1
- package/src/__tests__/server-history-render.test.ts +49 -0
- package/src/__tests__/settings-routes.test.ts +201 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
- package/src/__tests__/skills-file-content-endpoint.test.ts +276 -145
- package/src/__tests__/skills-files-catalog-fallback.test.ts +381 -93
- package/src/__tests__/skills.test.ts +5 -2
- package/src/__tests__/skillssh-files.test.ts +446 -0
- package/src/__tests__/slack-block-formatting.test.ts +110 -0
- package/src/__tests__/slack-channel-config.test.ts +564 -1
- package/src/__tests__/stt-catalog-parity.test.ts +282 -0
- package/src/__tests__/stt-stream-session.test.ts +535 -0
- package/src/__tests__/system-prompt.test.ts +112 -26
- package/src/__tests__/telephony-stt-routing.test.ts +329 -0
- package/src/__tests__/terminal-tools.test.ts +18 -7
- package/src/__tests__/test-preload.ts +18 -0
- package/src/__tests__/test-support/browser-skill-harness.ts +4 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +9 -5
- package/src/__tests__/tool-executor-shell-integration.test.ts +4 -0
- package/src/__tests__/tool-executor.test.ts +33 -24
- package/src/__tests__/tool-result-truncation.test.ts +36 -0
- package/src/__tests__/trust-store.test.ts +7 -1
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -1
- package/src/__tests__/tts-catalog-parity.test.ts +345 -0
- package/src/__tests__/twilio-routes-twiml.test.ts +512 -114
- package/src/__tests__/twilio-routes.test.ts +376 -0
- package/src/__tests__/unicode.test.ts +293 -0
- package/src/__tests__/update-bulletin-format.test.ts +59 -0
- package/src/__tests__/update-bulletin.test.ts +206 -5
- package/src/__tests__/usage-routes.test.ts +25 -4
- package/src/__tests__/user-reference.test.ts +46 -61
- package/src/__tests__/verification-control-plane-policy.test.ts +4 -0
- package/src/__tests__/voice-config-update.test.ts +403 -0
- package/src/__tests__/voice-quality.test.ts +434 -19
- package/src/__tests__/workspace-heartbeat-service.test.ts +7 -0
- package/src/__tests__/workspace-migration-033-stt-service-explicit-config.test.ts +547 -0
- package/src/__tests__/workspace-migration-034-remove-calls-voice-transcription-provider.test.ts +596 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +368 -0
- package/src/__tests__/workspace-migration-meets.test.ts +244 -0
- package/src/__tests__/workspace-migration-seed-device-id.test.ts +14 -20
- package/src/__tests__/workspace-policy.test.ts +2 -0
- package/src/agent/image-optimize.ts +24 -12
- package/src/agent/loop.ts +43 -3
- package/src/backup/__tests__/backup-key.test.ts +152 -0
- package/src/backup/__tests__/backup-worker.test.ts +767 -0
- package/src/backup/__tests__/list-snapshots.test.ts +87 -0
- package/src/backup/__tests__/local-writer.test.ts +218 -0
- package/src/backup/__tests__/offsite-writer.test.ts +641 -0
- package/src/backup/__tests__/paths.test.ts +300 -0
- package/src/backup/__tests__/restore.test.ts +498 -0
- package/src/backup/__tests__/snapshot-lock.test.ts +352 -0
- package/src/backup/__tests__/stream-crypt.test.ts +228 -0
- package/src/backup/backup-key.ts +137 -0
- package/src/backup/backup-worker.ts +459 -0
- package/src/backup/list-snapshots.ts +147 -0
- package/src/backup/local-writer.ts +133 -0
- package/src/backup/offsite-writer.ts +222 -0
- package/src/backup/paths.ts +226 -0
- package/src/backup/restore.ts +322 -0
- package/src/backup/snapshot-lock.ts +431 -0
- package/src/backup/stream-crypt.ts +263 -0
- package/src/bundler/package-resolver.ts +4 -0
- package/src/calls/audio-store.ts +11 -5
- package/src/calls/call-controller.ts +226 -71
- package/src/calls/call-domain.ts +9 -0
- package/src/calls/call-speech-output.ts +190 -0
- package/src/calls/call-transport.ts +77 -0
- package/src/calls/media-stream-audio-transcode.ts +173 -0
- package/src/calls/media-stream-output.ts +660 -0
- package/src/calls/media-stream-parser.ts +300 -0
- package/src/calls/media-stream-protocol.ts +166 -0
- package/src/calls/media-stream-server.ts +592 -0
- package/src/calls/media-stream-stt-session.ts +460 -0
- package/src/calls/media-turn-detector.ts +230 -0
- package/src/calls/relay-server.ts +90 -75
- package/src/calls/resolve-call-tts-provider.ts +136 -0
- package/src/calls/telephony-stt-routing.ts +145 -0
- package/src/calls/tts-call-strategy.ts +161 -0
- package/src/calls/tts-text-sanitizer.ts +32 -16
- package/src/calls/twilio-routes.ts +281 -17
- package/src/calls/voice-quality.ts +78 -35
- package/src/calls/voice-session-bridge.ts +8 -1
- package/src/channels/types.ts +16 -0
- package/src/cli/__tests__/run-assistant-command.ts +11 -1
- package/src/cli/commands/__tests__/backup.test.ts +1165 -0
- package/src/cli/commands/__tests__/domain-register.test.ts +234 -0
- package/src/cli/commands/__tests__/domain-status.test.ts +132 -0
- package/src/cli/commands/__tests__/email-attachment.test.ts +422 -0
- package/src/cli/commands/__tests__/email-download.test.ts +16 -1
- package/src/cli/commands/__tests__/email-list.test.ts +22 -4
- package/src/cli/commands/__tests__/email-register.test.ts +4 -4
- package/src/cli/commands/__tests__/email-send.test.ts +37 -4
- package/src/cli/commands/__tests__/email-status.test.ts +5 -1
- package/src/cli/commands/__tests__/email-unregister.test.ts +34 -5
- package/src/cli/commands/backup.ts +993 -0
- package/src/cli/commands/conversations.ts +77 -0
- package/src/cli/commands/credentials.ts +0 -1
- package/src/cli/commands/domain.ts +210 -0
- package/src/cli/commands/email.ts +255 -3
- package/src/cli/commands/oauth/__tests__/connect.test.ts +12 -0
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +1 -0
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -0
- package/src/cli/commands/oauth/mode.ts +12 -3
- package/src/cli/commands/oauth/providers.ts +15 -0
- package/src/cli/commands/oauth/shared.ts +2 -1
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +4 -9
- package/src/cli/commands/platform/__tests__/connect.test.ts +6 -0
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +6 -0
- package/src/cli/program.ts +30 -4
- package/src/config/__tests__/backup-schema.test.ts +134 -0
- package/src/config/assistant-feature-flags.ts +61 -62
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +37 -1
- package/src/config/bundled-skills/browser/SKILL.md +30 -5
- package/src/config/bundled-skills/browser/TOOLS.json +123 -0
- package/src/config/bundled-skills/browser/tools/browser-attach.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-detach.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-status.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +17 -0
- package/src/config/bundled-skills/contacts/SKILL.md +2 -2
- package/src/config/bundled-skills/gmail/SKILL.md +53 -7
- package/src/config/bundled-skills/gmail/TOOLS.json +33 -3
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +116 -9
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +138 -11
- package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +59 -0
- package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +82 -0
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +113 -17
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -2
- package/src/config/bundled-skills/media-processing/SKILL.md +3 -9
- package/src/config/bundled-skills/media-processing/TOOLS.json +1 -6
- package/src/config/bundled-skills/media-processing/__tests__/audio-transcribe.test.ts +125 -0
- package/src/config/bundled-skills/media-processing/__tests__/extract-keyframes.test.ts +181 -0
- package/src/config/bundled-skills/media-processing/__tests__/preprocess-audio.test.ts +141 -0
- package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +32 -87
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +8 -4
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +0 -10
- package/src/config/bundled-skills/messaging/SKILL.md +3 -3
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
- package/src/config/bundled-skills/outlook/SKILL.md +2 -2
- package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +2 -2
- package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +27 -18
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +3 -3
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +26 -22
- package/src/config/bundled-skills/slack/SKILL.md +1 -0
- package/src/config/bundled-skills/transcribe/SKILL.md +9 -14
- package/src/config/bundled-skills/transcribe/TOOLS.json +2 -7
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +256 -0
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +38 -188
- package/src/config/bundled-tool-registry.ts +8 -0
- package/src/config/env-registry.ts +24 -0
- package/src/config/env.ts +34 -10
- package/src/config/feature-flag-registry.json +46 -14
- package/src/config/loader.ts +26 -12
- package/src/config/schema.ts +35 -10
- package/src/config/schemas/__tests__/stt.test.ts +43 -0
- package/src/config/schemas/analysis.ts +51 -0
- package/src/config/schemas/backup.ts +72 -0
- package/src/config/schemas/calls.ts +1 -26
- package/src/config/schemas/elevenlabs.ts +0 -59
- package/src/config/schemas/filing.ts +47 -7
- package/src/config/schemas/heartbeat.ts +27 -5
- package/src/config/schemas/host-browser.ts +47 -1
- package/src/config/schemas/inference.ts +1 -1
- package/src/config/schemas/memory-lifecycle.ts +14 -2
- package/src/config/schemas/services.ts +44 -0
- package/src/config/schemas/stt.ts +59 -0
- package/src/config/schemas/tts.ts +230 -0
- package/src/config/schemas/updates.ts +14 -0
- package/src/config/skills.ts +4 -0
- package/src/config/types.ts +4 -0
- package/src/contacts/contact-store.ts +56 -11
- package/src/contacts/contacts-write.ts +38 -1
- package/src/context/post-turn-tool-result-truncation.ts +3 -2
- package/src/context/tool-result-truncation.ts +2 -1
- package/src/context/window-manager.ts +45 -12
- package/src/credential-execution/executable-discovery.ts +12 -2
- package/src/credential-execution/process-manager.ts +33 -2
- package/src/credential-health/credential-health-service.ts +366 -0
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +324 -0
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +497 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +17 -8
- package/src/daemon/__tests__/lifecycle-startup-ordering.test.ts +127 -0
- package/src/daemon/config-watcher.ts +99 -5
- package/src/daemon/conversation-agent-loop-handlers.ts +6 -0
- package/src/daemon/conversation-agent-loop.ts +101 -24
- package/src/daemon/conversation-error.ts +11 -0
- package/src/daemon/conversation-history.ts +40 -6
- package/src/daemon/conversation-launch.ts +220 -0
- package/src/daemon/conversation-lifecycle.ts +59 -9
- package/src/daemon/conversation-messaging.ts +37 -3
- package/src/daemon/conversation-notifiers.ts +5 -0
- package/src/daemon/conversation-process.ts +581 -19
- package/src/daemon/conversation-queue-manager.ts +24 -0
- package/src/daemon/conversation-runtime-assembly.ts +11 -1
- package/src/daemon/conversation-slash.ts +36 -0
- package/src/daemon/conversation-surfaces.ts +94 -4
- package/src/daemon/conversation-tool-setup.ts +25 -0
- package/src/daemon/conversation-usage.ts +7 -4
- package/src/daemon/conversation.ts +86 -28
- package/src/daemon/handlers/config-slack-channel.ts +269 -94
- package/src/daemon/handlers/conversations.ts +4 -1
- package/src/daemon/handlers/shared.ts +22 -0
- package/src/daemon/handlers/skills.ts +321 -77
- package/src/daemon/host-browser-proxy.ts +2 -1
- package/src/daemon/lifecycle.ts +122 -25
- package/src/daemon/message-protocol.ts +6 -0
- package/src/daemon/message-types/conversations.ts +34 -1
- package/src/daemon/message-types/home.ts +40 -0
- package/src/daemon/message-types/meet.ts +143 -0
- package/src/daemon/message-types/messages.ts +14 -0
- package/src/daemon/message-types/schedules.ts +34 -2
- package/src/daemon/message-types/skills.ts +16 -0
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/daemon/server.ts +347 -2
- package/src/daemon/shutdown-handlers.ts +32 -4
- package/src/daemon/shutdown-registry.ts +40 -0
- package/src/daemon/tool-side-effects.ts +9 -0
- package/src/email/html-renderer.ts +76 -0
- package/src/heartbeat/heartbeat-service.ts +93 -7
- package/src/home/__tests__/assistant-feed-authoring.test.ts +156 -0
- package/src/home/__tests__/emit-feed-event.test.ts +169 -0
- package/src/home/__tests__/feed-scheduler.test.ts +194 -0
- package/src/home/__tests__/feed-types.test.ts +275 -0
- package/src/home/__tests__/feed-writer.test.ts +688 -0
- package/src/home/__tests__/phase5-exit-criteria.test.ts +212 -0
- package/src/home/__tests__/platform-gmail-digest.test.ts +222 -0
- package/src/home/__tests__/progress-formula.test.ts +213 -0
- package/src/home/__tests__/relationship-state-writer.test.ts +740 -0
- package/src/home/__tests__/rollup-producer.test.ts +398 -0
- package/src/home/assistant-feed-authoring.ts +124 -0
- package/src/home/emit-feed-event.ts +158 -0
- package/src/home/feed-scheduler.ts +247 -0
- package/src/home/feed-types.ts +181 -0
- package/src/home/feed-writer.ts +469 -0
- package/src/home/platform-gmail-digest.ts +163 -0
- package/src/home/progress-formula.ts +86 -0
- package/src/home/relationship-state-writer.ts +824 -0
- package/src/home/relationship-state.ts +143 -0
- package/src/home/rollup-producer.ts +384 -0
- package/src/hooks/runner.ts +7 -0
- package/src/inbound/platform-callback-registration.ts +12 -3
- package/src/inbound/public-ingress-urls.ts +12 -0
- package/src/instrument.ts +1 -1
- package/src/ipc/__tests__/cli-ipc.test.ts +200 -0
- package/src/ipc/cli-client.ts +151 -0
- package/src/ipc/cli-server.ts +234 -0
- package/src/ipc/gateway-client.ts +180 -0
- package/src/ipc/routes/index.ts +5 -0
- package/src/ipc/routes/wake-conversation.ts +19 -0
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +356 -0
- package/src/memory/__tests__/auto-analysis-guard.test.ts +57 -0
- package/src/memory/__tests__/conversation-analyze-job.test.ts +232 -0
- package/src/memory/__tests__/find-analysis-conversation.test.ts +196 -0
- package/src/memory/app-store.ts +1 -1
- package/src/memory/attachments-store.ts +70 -0
- package/src/memory/auto-analysis-enqueue.ts +127 -0
- package/src/memory/auto-analysis-guard.ts +27 -0
- package/src/memory/cleanup-schedule-state.ts +37 -0
- package/src/memory/conversation-analyze-job.ts +73 -0
- package/src/memory/conversation-crud.ts +99 -0
- package/src/memory/conversation-disk-view.ts +7 -0
- package/src/memory/conversation-group-migration.ts +34 -2
- package/src/memory/conversation-queries.ts +6 -5
- package/src/memory/db-init.ts +6 -0
- package/src/memory/db-maintenance.ts +108 -0
- package/src/memory/db.ts +1 -0
- package/src/memory/graph/conversation-graph-memory.ts +15 -0
- package/src/memory/graph/extraction.test.ts +23 -0
- package/src/memory/graph/extraction.ts +8 -0
- package/src/memory/graph/retriever.ts +27 -18
- package/src/memory/graph/scoring.test.ts +186 -0
- package/src/memory/graph/scoring.ts +31 -1
- package/src/memory/graph/tools.ts +1 -1
- package/src/memory/group-crud.ts +6 -1
- package/src/memory/indexer.ts +95 -16
- package/src/memory/job-handlers/cleanup.ts +11 -8
- package/src/memory/job-handlers/conversation-starters.ts +16 -10
- package/src/memory/jobs-store.ts +64 -4
- package/src/memory/jobs-worker.ts +22 -9
- package/src/memory/llm-usage-store.ts +92 -56
- package/src/memory/migrations/219-oauth-providers-token-exchange-body-format.ts +15 -0
- package/src/memory/migrations/220-normalize-user-file-by-principal.ts +190 -0
- package/src/memory/migrations/221-conversations-archived-at.ts +16 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/qdrant-manager.ts +43 -16
- package/src/memory/schema/conversations.ts +2 -0
- package/src/memory/schema/oauth.ts +3 -0
- package/src/memory/usage-buckets.ts +396 -0
- package/src/messaging/providers/gmail/client.ts +57 -6
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +282 -0
- package/src/messaging/providers/slack/adapter.ts +143 -38
- package/src/messaging/providers/slack/client.ts +16 -0
- package/src/messaging/providers/slack/types.ts +4 -0
- package/src/notifications/decision-engine.ts +3 -3
- package/src/notifications/signal.ts +5 -0
- package/src/oauth/__tests__/identity-verifier.test.ts +1 -0
- package/src/oauth/byo-connection.test.ts +18 -1
- package/src/oauth/byo-connection.ts +3 -1
- package/src/oauth/connect-orchestrator.ts +2 -0
- package/src/oauth/connection-resolver.ts +6 -2
- package/src/oauth/connection.ts +2 -0
- package/src/oauth/oauth-store.ts +9 -0
- package/src/oauth/platform-connection.test.ts +98 -0
- package/src/oauth/platform-connection.ts +52 -31
- package/src/oauth/seed-providers.ts +7 -0
- package/src/permissions/checker.ts +16 -6
- package/src/permissions/defaults.ts +49 -1
- package/src/permissions/trust-store.ts +3 -3
- package/src/permissions/workspace-policy.ts +3 -0
- package/src/platform/client.test.ts +10 -0
- package/src/platform/sync-identity.ts +129 -0
- package/src/prompts/persona-resolver.ts +126 -2
- package/src/prompts/system-prompt.ts +59 -18
- package/src/prompts/templates/BOOTSTRAP.md +5 -5
- package/src/prompts/templates/SOUL.md +3 -1
- package/src/prompts/templates/UPDATES.md +12 -0
- package/src/prompts/templates/channels/slack.md +20 -0
- package/src/prompts/update-bulletin-format.ts +26 -9
- package/src/prompts/update-bulletin.ts +34 -23
- package/src/prompts/user-reference.ts +20 -17
- package/src/providers/__tests__/provider-secret-catalog.test.ts +42 -0
- package/src/providers/anthropic/client.ts +157 -61
- package/src/providers/fireworks/client.ts +2 -2
- package/src/providers/gemini/client.ts +9 -1
- package/src/providers/model-catalog.ts +6 -0
- package/src/providers/model-intents.ts +4 -4
- package/src/providers/ollama/client.ts +2 -2
- package/src/providers/openai/chat-completions-provider.ts +474 -0
- package/src/providers/openai/client.ts +25 -440
- package/src/providers/openai/responses-provider.ts +502 -0
- package/src/providers/openrouter/client.ts +101 -4
- package/src/providers/provider-secret-catalog.ts +139 -0
- package/src/providers/registry.ts +2 -2
- package/src/providers/retry.ts +14 -3
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +251 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +828 -0
- package/src/providers/speech-to-text/deepgram-realtime.test.ts +980 -0
- package/src/providers/speech-to-text/deepgram-realtime.ts +767 -0
- package/src/providers/speech-to-text/deepgram.test.ts +332 -0
- package/src/providers/speech-to-text/deepgram.ts +115 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.test.ts +743 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.ts +625 -0
- package/src/providers/speech-to-text/google-gemini.test.ts +226 -0
- package/src/providers/speech-to-text/google-gemini.ts +101 -0
- package/src/providers/speech-to-text/openai-whisper-stream.test.ts +564 -0
- package/src/providers/speech-to-text/openai-whisper-stream.ts +381 -0
- package/src/providers/speech-to-text/openai-whisper.test.ts +1 -37
- package/src/providers/speech-to-text/openai-whisper.ts +63 -33
- package/src/providers/speech-to-text/provider-catalog.ts +306 -0
- package/src/providers/speech-to-text/resolve.ts +386 -6
- package/src/providers/types.ts +9 -0
- package/src/runtime/AGENTS.md +43 -1
- package/src/runtime/__tests__/agent-wake.test.ts +831 -0
- package/src/runtime/__tests__/runtime-mode.test.ts +62 -0
- package/src/runtime/__tests__/slack-block-formatting.test.ts +481 -0
- package/src/runtime/agent-wake.ts +512 -0
- package/src/runtime/auth/__tests__/route-policy.test.ts +40 -0
- package/src/runtime/auth/route-policy.ts +30 -5
- package/src/runtime/auth/token-service.ts +56 -1
- package/src/runtime/btw-sidechain.ts +2 -0
- package/src/runtime/capability-tokens.ts +10 -10
- package/src/runtime/channel-invite-transport.ts +1 -1
- package/src/runtime/channel-invite-transports/email.ts +14 -6
- package/src/runtime/channel-readiness-service.ts +12 -22
- package/src/runtime/chrome-extension-registry.ts +38 -2
- package/src/runtime/http-server.ts +395 -10
- package/src/runtime/http-types.ts +6 -2
- package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +36 -0
- package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +360 -0
- package/src/runtime/migrations/migration-transport.ts +1 -0
- package/src/runtime/migrations/migration-wizard.ts +1 -0
- package/src/runtime/migrations/vbundle-import-analyzer.ts +77 -1
- package/src/runtime/migrations/vbundle-importer.ts +34 -0
- package/src/runtime/pending-interactions.ts +0 -11
- package/src/runtime/routes/__tests__/backup-routes.test.ts +967 -0
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +507 -0
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +208 -0
- package/src/runtime/routes/__tests__/stt-routes.test.ts +406 -0
- package/src/runtime/routes/__tests__/tts-routes.test.ts +474 -0
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +148 -17
- package/src/runtime/routes/app-management-routes.ts +12 -18
- package/src/runtime/routes/attachment-routes.test.ts +9 -3
- package/src/runtime/routes/attachment-routes.ts +216 -17
- package/src/runtime/routes/backup-routes.ts +519 -0
- package/src/runtime/routes/browser-extension-pair-routes.ts +82 -23
- package/src/runtime/routes/btw-routes.ts +8 -6
- package/src/runtime/routes/contact-routes.test.ts +298 -0
- package/src/runtime/routes/contact-routes.ts +132 -5
- package/src/runtime/routes/conversation-analysis-routes.ts +22 -142
- package/src/runtime/routes/conversation-management-routes.ts +115 -0
- package/src/runtime/routes/conversation-routes.ts +367 -146
- package/src/runtime/routes/filing-routes.ts +93 -0
- package/src/runtime/routes/home-feed-routes.ts +334 -0
- package/src/runtime/routes/home-state-routes.ts +138 -0
- package/src/runtime/routes/host-browser-routes.ts +3 -14
- package/src/runtime/routes/identity-intro-cache.ts +7 -3
- package/src/runtime/routes/identity-routes.ts +3 -17
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +46 -39
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +15 -15
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +137 -0
- package/src/runtime/routes/integrations/slack/__tests__/share.test.ts +179 -0
- package/src/runtime/routes/integrations/slack/channel.ts +11 -3
- package/src/runtime/routes/integrations/slack/share.ts +45 -7
- package/src/runtime/routes/llm-context-normalization.ts +303 -0
- package/src/runtime/routes/memory-item-routes.test.ts +3 -2
- package/src/runtime/routes/migration-routes.ts +40 -5
- package/src/runtime/routes/settings-routes.ts +22 -5
- package/src/runtime/routes/skills-routes.ts +76 -7
- package/src/runtime/routes/stt-routes.ts +233 -0
- package/src/runtime/routes/surface-action-routes.ts +41 -2
- package/src/runtime/routes/tts-routes.ts +108 -24
- package/src/runtime/routes/usage-routes.ts +30 -2
- package/src/runtime/routes/user-route-dispatcher.ts +50 -5
- package/src/runtime/routes/user-routes.ts +13 -1
- package/src/runtime/routes/work-items-routes.ts +8 -1
- package/src/runtime/runtime-mode.ts +33 -0
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +444 -0
- package/src/runtime/services/__tests__/analyze-deps-singleton.test.ts +67 -0
- package/src/runtime/services/__tests__/auto-analysis-prompt.test.ts +53 -0
- package/src/runtime/services/__tests__/manual-analysis-prompt.test.ts +41 -0
- package/src/runtime/services/analyze-conversation.ts +344 -0
- package/src/runtime/services/analyze-deps-singleton.ts +32 -0
- package/src/runtime/services/auto-analysis-prompt.ts +55 -0
- package/src/runtime/skill-route-registry.ts +49 -0
- package/src/runtime/slack-block-formatting.ts +437 -10
- package/src/schedule/scheduler.ts +50 -0
- package/src/security/oauth2.ts +26 -4
- package/src/security/secure-keys.ts +25 -2
- package/src/security/token-manager.ts +8 -0
- package/src/sequence/engine.ts +23 -0
- package/src/sequence/types.ts +1 -1
- package/src/skills/catalog-files.ts +64 -2
- package/src/skills/category-inference.ts +122 -0
- package/src/skills/clawhub-files.ts +213 -0
- package/src/skills/clawhub.ts +84 -23
- package/src/skills/skill-file-provider.ts +40 -0
- package/src/skills/skillssh-files.ts +395 -0
- package/src/skills/skillssh-registry.ts +4 -4
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +392 -0
- package/src/stt/__tests__/types.test.ts +89 -0
- package/src/stt/daemon-batch-transcriber.ts +195 -0
- package/src/stt/stt-stream-session.ts +499 -0
- package/src/stt/types.ts +330 -0
- package/src/stt/wav-encoder.test.ts +373 -0
- package/src/stt/wav-encoder.ts +175 -0
- package/src/subagent/manager.ts +38 -14
- package/src/tools/browser/__tests__/browser-mode.test.ts +119 -0
- package/src/tools/browser/__tests__/browser-status.test.ts +123 -0
- package/src/tools/browser/browser-execution.ts +1163 -23
- package/src/tools/browser/browser-manager.ts +45 -0
- package/src/tools/browser/browser-mode-constants.ts +12 -0
- package/src/tools/browser/browser-mode.ts +92 -0
- package/src/tools/browser/browser-status-constants.ts +33 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +393 -0
- package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +29 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +1648 -32
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +264 -0
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +183 -17
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +254 -21
- package/src/tools/browser/cdp-client/errors.ts +15 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +39 -16
- package/src/tools/browser/cdp-client/factory.ts +797 -87
- package/src/tools/browser/cdp-client/index.ts +16 -2
- package/src/tools/browser/cdp-client/types.ts +68 -0
- package/src/tools/credentials/vault.ts +35 -6
- package/src/tools/network/web-fetch.ts +5 -2
- package/src/tools/network/web-search.ts +5 -2
- package/src/tools/shared/shell-output.ts +3 -1
- package/src/tools/side-effects.ts +2 -0
- package/src/tools/skills/sandbox-runner.ts +3 -2
- package/src/tools/terminal/safe-env.ts +10 -2
- package/src/tools/terminal/shell.ts +15 -4
- package/src/tools/tool-manifest.ts +21 -0
- package/src/tools/types.ts +17 -0
- package/src/tools/ui-surface/definitions.ts +6 -1
- package/src/tts/__tests__/provider-adapters.test.ts +834 -0
- package/src/tts/__tests__/provider-catalog-consistency.test.ts +196 -0
- package/src/tts/__tests__/provider-catalog.test.ts +183 -0
- package/src/tts/__tests__/provider-registry.test.ts +90 -0
- package/src/tts/provider-catalog.ts +201 -0
- package/src/tts/provider-registry.ts +73 -0
- package/src/tts/providers/deepgram-provider.ts +219 -0
- package/src/tts/providers/elevenlabs-provider.ts +211 -0
- package/src/tts/providers/fish-audio-provider.ts +183 -0
- package/src/tts/providers/index.ts +42 -0
- package/src/tts/providers/register-builtins.ts +130 -0
- package/src/tts/synthesize-text.ts +110 -0
- package/src/tts/tts-config-resolver.ts +78 -0
- package/src/tts/types.ts +153 -0
- package/src/types/onboarding-context.ts +7 -0
- package/src/util/abort-reasons.ts +58 -0
- package/src/util/device-id.ts +32 -16
- package/src/util/errors.ts +9 -1
- package/src/util/platform.ts +54 -10
- package/src/util/pricing.ts +66 -3
- package/src/util/spawn.ts +1 -1
- package/src/util/truncate.ts +4 -2
- package/src/util/unicode.ts +201 -0
- package/src/version.ts +19 -24
- package/src/watcher/engine.ts +23 -0
- package/src/watcher/watcher-store.ts +31 -0
- package/src/workspace/migrations/003-seed-device-id.ts +9 -3
- package/src/workspace/migrations/017-seed-persona-dirs.ts +68 -4
- package/src/workspace/migrations/029-seed-pkb.ts +1 -1
- package/src/workspace/migrations/031-drop-user-md.ts +317 -0
- package/src/workspace/migrations/031-llm-log-retention-zero-to-null.ts +73 -0
- package/src/workspace/migrations/032-tts-provider-unification.ts +227 -0
- package/src/workspace/migrations/033-stt-service-explicit-config.ts +122 -0
- package/src/workspace/migrations/034-remove-calls-voice-transcription-provider.ts +215 -0
- package/src/workspace/migrations/035-seed-slack-channel-persona.ts +50 -0
- package/src/workspace/migrations/036-update-pkb-index-bar.ts +37 -0
- package/src/workspace/migrations/037-create-meets-dir.ts +61 -0
- package/src/workspace/migrations/registry.ts +16 -0
- package/src/workspace/top-level-renderer.ts +13 -1
- package/src/workspace/turn-commit.ts +31 -0
- package/src/__tests__/email-cli.test.ts +0 -297
- package/src/__tests__/email-service-config-fallback.test.ts +0 -102
- package/src/cli/commands/browser-relay.ts +0 -466
- package/src/email/guardrails.ts +0 -221
- package/src/email/provider.ts +0 -117
- package/src/email/providers/agentmail.ts +0 -361
- package/src/email/providers/index.ts +0 -65
- package/src/email/service.ts +0 -384
- package/src/email/types.ts +0 -126
- package/src/prompts/templates/USER.md +0 -13
- package/src/providers/speech-to-text/types.ts +0 -17
- package/src/runtime/routes/browser-cdp-routes.ts +0 -229
package/src/stt/types.ts
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-agnostic speech-to-text domain types for daemon transcription.
|
|
3
|
+
*
|
|
4
|
+
* These types define the boundary between callers that need audio transcription
|
|
5
|
+
* and the concrete STT provider implementations. The goal is to let daemon
|
|
6
|
+
* callsites program against a single typed interface so that provider swaps are
|
|
7
|
+
* localized to the adapter layer.
|
|
8
|
+
*
|
|
9
|
+
* Two execution modes are supported:
|
|
10
|
+
* - **Batch** — a single audio buffer is sent and a final transcript returned.
|
|
11
|
+
* - **Streaming** — audio chunks are sent over a persistent session, and the
|
|
12
|
+
* provider emits partial/final transcript events in real time.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Provider identity
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Canonical provider identifiers for daemon-hosted STT backends.
|
|
21
|
+
* Extend this union as new providers are integrated.
|
|
22
|
+
*/
|
|
23
|
+
export type SttProviderId = "openai-whisper" | "deepgram" | "google-gemini";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Telephony-specific STT capability class.
|
|
27
|
+
*
|
|
28
|
+
* Describes the provider's native audio-ingestion capability when used in
|
|
29
|
+
* a telephony context. This is a **capability classification**, not a direct
|
|
30
|
+
* Twilio strategy selector — the telephony routing resolver
|
|
31
|
+
* (`telephony-stt-routing.ts`) maps capability classes plus catalog routing
|
|
32
|
+
* metadata to concrete Twilio setup strategies (conversation-relay-native
|
|
33
|
+
* vs media-stream-custom).
|
|
34
|
+
*
|
|
35
|
+
* - `"realtime-ws"` — provider offers a WebSocket streaming endpoint suitable
|
|
36
|
+
* for low-latency telephony audio (e.g. Deepgram live transcription).
|
|
37
|
+
* - `"batch-only"` — provider supports only REST batch transcription as its
|
|
38
|
+
* native capability. A `batch-only` provider may still participate in
|
|
39
|
+
* telephony via Twilio-native ConversationRelay (e.g. Google Gemini) or
|
|
40
|
+
* via the media-stream custom path — the routing strategy is determined
|
|
41
|
+
* by the catalog's telephony routing metadata, not this field alone.
|
|
42
|
+
* - `"none"` — provider has no telephony support.
|
|
43
|
+
*/
|
|
44
|
+
export type TelephonySttMode = "realtime-ws" | "batch-only" | "none";
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Conversation streaming STT support mode.
|
|
48
|
+
*
|
|
49
|
+
* Describes how a provider can participate in real-time conversation
|
|
50
|
+
* streaming when used for chat message capture (chat composer and iOS
|
|
51
|
+
* input bar).
|
|
52
|
+
*
|
|
53
|
+
* - `"realtime-ws"` — provider offers a native WebSocket streaming endpoint
|
|
54
|
+
* that accepts audio chunks and emits partial/final transcript events
|
|
55
|
+
* with low latency (e.g. Deepgram live transcription).
|
|
56
|
+
* - `"incremental-batch"` — provider does not offer true streaming but can
|
|
57
|
+
* be polled with incremental audio batches to approximate streaming
|
|
58
|
+
* behaviour (e.g. Google Gemini multimodal).
|
|
59
|
+
* - `"none"` — provider has no conversation streaming support; callers
|
|
60
|
+
* should fall back to batch transcription.
|
|
61
|
+
*/
|
|
62
|
+
export type ConversationStreamingMode =
|
|
63
|
+
| "realtime-ws"
|
|
64
|
+
| "incremental-batch"
|
|
65
|
+
| "none";
|
|
66
|
+
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// Boundary identifier
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Runtime boundary through which STT is executed.
|
|
73
|
+
* - `daemon-batch` — transcription runs in the daemon process via a REST API
|
|
74
|
+
* call to the provider (e.g. OpenAI Whisper).
|
|
75
|
+
* - `daemon-streaming` — transcription runs in the daemon process over a
|
|
76
|
+
* persistent streaming session (e.g. WebSocket or incremental-batch loop).
|
|
77
|
+
*/
|
|
78
|
+
export type SttBoundaryId = "daemon-batch" | "daemon-streaming";
|
|
79
|
+
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// Call-context hints
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Optional metadata hints that a caller can supply when the transcription
|
|
86
|
+
* originates from a phone-call context. These are advisory — providers may
|
|
87
|
+
* ignore hints they do not support.
|
|
88
|
+
*
|
|
89
|
+
* This type is intentionally separate from the batch request so that
|
|
90
|
+
* call-context awareness can be added incrementally without changing the
|
|
91
|
+
* shape for non-call callers.
|
|
92
|
+
*/
|
|
93
|
+
export interface SttCallContextHints {
|
|
94
|
+
/** BCP-47 language code for the expected speech (e.g. "en-US"). */
|
|
95
|
+
language?: string;
|
|
96
|
+
/** Static vocabulary hints (proper nouns, domain terms) the ASR should prioritize. */
|
|
97
|
+
vocabularyHints?: string[];
|
|
98
|
+
/** Short natural-language prompt to bias the transcription model. */
|
|
99
|
+
prompt?: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// Request / result
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
/** Input to a batch transcription call. */
|
|
107
|
+
export interface SttTranscribeRequest {
|
|
108
|
+
/** Raw audio data (WAV, OGG, MP3, etc.). */
|
|
109
|
+
audio: Buffer;
|
|
110
|
+
/** MIME type of the audio data (e.g. "audio/ogg", "audio/wav"). */
|
|
111
|
+
mimeType: string;
|
|
112
|
+
/** Optional abort signal for cancellation / timeout. */
|
|
113
|
+
signal?: AbortSignal;
|
|
114
|
+
/**
|
|
115
|
+
* Optional call-context hints. Present when the transcription request
|
|
116
|
+
* originates from a telephony call. Providers may use these to improve
|
|
117
|
+
* recognition accuracy.
|
|
118
|
+
*/
|
|
119
|
+
callContext?: SttCallContextHints;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Successful transcription output. */
|
|
123
|
+
export interface SttTranscribeResult {
|
|
124
|
+
/** The transcribed text, trimmed. Empty string for silence. */
|
|
125
|
+
text: string;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// Normalized error categories
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Normalized error categories that callers can branch on without coupling to
|
|
134
|
+
* provider-specific error shapes or HTTP status codes.
|
|
135
|
+
*/
|
|
136
|
+
export type SttErrorCategory =
|
|
137
|
+
/** The provider rejected the request due to invalid or missing credentials. */
|
|
138
|
+
| "auth"
|
|
139
|
+
/** The provider rate-limited the request. */
|
|
140
|
+
| "rate-limit"
|
|
141
|
+
/** The request or response timed out. */
|
|
142
|
+
| "timeout"
|
|
143
|
+
/** The audio payload was rejected (unsupported format, too large, etc.). */
|
|
144
|
+
| "invalid-audio"
|
|
145
|
+
/** Any other provider-side or network failure. */
|
|
146
|
+
| "provider-error";
|
|
147
|
+
|
|
148
|
+
/** A transcription error enriched with a normalized category. */
|
|
149
|
+
export class SttError extends Error {
|
|
150
|
+
readonly category: SttErrorCategory;
|
|
151
|
+
|
|
152
|
+
constructor(category: SttErrorCategory, message: string) {
|
|
153
|
+
super(message);
|
|
154
|
+
this.name = "SttError";
|
|
155
|
+
this.category = category;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
// Batch transcriber interface
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Daemon-hosted batch transcriber contract.
|
|
165
|
+
*
|
|
166
|
+
* Implementations accept a buffer of audio data and return a transcription
|
|
167
|
+
* result. Errors propagate as raw provider errors (not wrapped in
|
|
168
|
+
* {@link SttError}) so that callers relying on specific error identities
|
|
169
|
+
* (e.g. `AbortError` for cancellation) continue to work. Callers that need
|
|
170
|
+
* normalized error categories should wrap calls with `normalizeSttError()`
|
|
171
|
+
* from `daemon-batch-transcriber.ts`.
|
|
172
|
+
*/
|
|
173
|
+
export interface BatchTranscriber {
|
|
174
|
+
/** Which provider backs this transcriber. */
|
|
175
|
+
readonly providerId: SttProviderId;
|
|
176
|
+
/** Which runtime boundary this transcriber operates in. */
|
|
177
|
+
readonly boundaryId: SttBoundaryId;
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Transcribe a chunk of audio.
|
|
181
|
+
*
|
|
182
|
+
* Rejects with the raw provider error on failure. Use
|
|
183
|
+
* `normalizeSttError()` to convert to an {@link SttError} with a
|
|
184
|
+
* structured {@link SttErrorCategory}.
|
|
185
|
+
*/
|
|
186
|
+
transcribe(request: SttTranscribeRequest): Promise<SttTranscribeResult>;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ---------------------------------------------------------------------------
|
|
190
|
+
// Streaming client events (client -> daemon)
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Events that a client sends to the daemon streaming session.
|
|
195
|
+
*
|
|
196
|
+
* The discriminated `type` field lets the runtime session handler
|
|
197
|
+
* dispatch to the correct streaming adapter method without coupling
|
|
198
|
+
* to transport-level framing (WebSocket opcodes, HTTP/2 frames, etc.).
|
|
199
|
+
*/
|
|
200
|
+
export type SttStreamClientEvent =
|
|
201
|
+
| SttStreamClientAudioEvent
|
|
202
|
+
| SttStreamClientStopEvent;
|
|
203
|
+
|
|
204
|
+
/** A chunk of audio data to be transcribed. */
|
|
205
|
+
export interface SttStreamClientAudioEvent {
|
|
206
|
+
readonly type: "audio";
|
|
207
|
+
/** Raw audio data for the current chunk. */
|
|
208
|
+
readonly audio: Buffer;
|
|
209
|
+
/** MIME type of the audio data (e.g. "audio/webm", "audio/pcm"). */
|
|
210
|
+
readonly mimeType: string;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Signals that the client has finished sending audio. */
|
|
214
|
+
export interface SttStreamClientStopEvent {
|
|
215
|
+
readonly type: "stop";
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
// Streaming server events (daemon -> client)
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Events that the daemon streaming session emits to the client.
|
|
224
|
+
*
|
|
225
|
+
* The discriminated `type` field allows clients to handle partial
|
|
226
|
+
* and final transcripts, errors, and session lifecycle signals in a
|
|
227
|
+
* type-safe manner.
|
|
228
|
+
*/
|
|
229
|
+
export type SttStreamServerEvent =
|
|
230
|
+
| SttStreamServerPartialEvent
|
|
231
|
+
| SttStreamServerFinalEvent
|
|
232
|
+
| SttStreamServerErrorEvent
|
|
233
|
+
| SttStreamServerClosedEvent;
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* A partial (interim) transcript — may be revised by subsequent
|
|
237
|
+
* partial or final events.
|
|
238
|
+
*/
|
|
239
|
+
export interface SttStreamServerPartialEvent {
|
|
240
|
+
readonly type: "partial";
|
|
241
|
+
/** Interim transcript text. May change with subsequent events. */
|
|
242
|
+
readonly text: string;
|
|
243
|
+
// Provider-emitted speaker label; undefined when diarization is disabled or unsupported. Consumers cross-check with channel-specific signals (e.g., Meet DOM).
|
|
244
|
+
readonly speakerLabel?: string;
|
|
245
|
+
/**
|
|
246
|
+
* Provider-emitted confidence score in [0, 1]. Undefined when the
|
|
247
|
+
* provider does not surface confidence on interim chunks.
|
|
248
|
+
*/
|
|
249
|
+
readonly confidence?: number;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* A final (committed) transcript — this segment will not be revised.
|
|
254
|
+
*/
|
|
255
|
+
export interface SttStreamServerFinalEvent {
|
|
256
|
+
readonly type: "final";
|
|
257
|
+
/** Committed transcript text for a completed speech segment. */
|
|
258
|
+
readonly text: string;
|
|
259
|
+
// Provider-emitted speaker label; undefined when diarization is disabled or unsupported. Consumers cross-check with channel-specific signals (e.g., Meet DOM).
|
|
260
|
+
readonly speakerLabel?: string;
|
|
261
|
+
/**
|
|
262
|
+
* Provider-emitted confidence score in [0, 1]. Undefined when the
|
|
263
|
+
* provider does not surface confidence on this chunk.
|
|
264
|
+
*/
|
|
265
|
+
readonly confidence?: number;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/** An error occurred during streaming transcription. */
|
|
269
|
+
export interface SttStreamServerErrorEvent {
|
|
270
|
+
readonly type: "error";
|
|
271
|
+
/** Normalized error category for caller branching. */
|
|
272
|
+
readonly category: SttErrorCategory;
|
|
273
|
+
/** Human-readable error description. */
|
|
274
|
+
readonly message: string;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** The streaming session has closed (no more events will be emitted). */
|
|
278
|
+
export interface SttStreamServerClosedEvent {
|
|
279
|
+
readonly type: "closed";
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
// Streaming transcriber interface
|
|
284
|
+
// ---------------------------------------------------------------------------
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Daemon-hosted streaming transcriber contract.
|
|
288
|
+
*
|
|
289
|
+
* Implementations manage a persistent session that accepts audio chunks
|
|
290
|
+
* and emits partial/final transcript events. The runtime session
|
|
291
|
+
* orchestrator (PR 5) drives this interface from the gateway WebSocket
|
|
292
|
+
* path.
|
|
293
|
+
*
|
|
294
|
+
* Lifecycle:
|
|
295
|
+
* 1. Call {@link start} to open the provider session.
|
|
296
|
+
* 2. Feed audio chunks via {@link sendAudio}.
|
|
297
|
+
* 3. Call {@link stop} when the client finishes recording.
|
|
298
|
+
* 4. The `onEvent` callback receives server events until `closed`.
|
|
299
|
+
*/
|
|
300
|
+
export interface StreamingTranscriber {
|
|
301
|
+
/** Which provider backs this transcriber. */
|
|
302
|
+
readonly providerId: SttProviderId;
|
|
303
|
+
/** Which runtime boundary this transcriber operates in. */
|
|
304
|
+
readonly boundaryId: "daemon-streaming";
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Open the streaming session with the provider.
|
|
308
|
+
*
|
|
309
|
+
* Must be called once before {@link sendAudio}. Rejects if the
|
|
310
|
+
* provider session cannot be established.
|
|
311
|
+
*/
|
|
312
|
+
start(onEvent: (event: SttStreamServerEvent) => void): Promise<void>;
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Feed a chunk of audio into the streaming session.
|
|
316
|
+
*
|
|
317
|
+
* Callers must not call this before {@link start} resolves or after
|
|
318
|
+
* {@link stop} has been called.
|
|
319
|
+
*/
|
|
320
|
+
sendAudio(audio: Buffer, mimeType: string): void;
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Signal that the client has finished sending audio.
|
|
324
|
+
*
|
|
325
|
+
* The provider may emit additional final events after stop is called.
|
|
326
|
+
* The session is fully closed when the `onEvent` callback receives a
|
|
327
|
+
* `closed` event.
|
|
328
|
+
*/
|
|
329
|
+
stop(): void;
|
|
330
|
+
}
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { encodePcm16LeToWav } from "./wav-encoder.js";
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Helpers
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
/** Default mono 16 kHz PCM16LE options used by most tests. */
|
|
10
|
+
const MONO_16K = { sampleRate: 16000, channels: 1 } as const;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Read a 4-byte ASCII string from a buffer at the given offset.
|
|
14
|
+
*/
|
|
15
|
+
function readTag(buf: Buffer, offset: number): string {
|
|
16
|
+
return buf.toString("ascii", offset, offset + 4);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// RIFF / WAVE structure
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
describe("encodePcm16LeToWav", () => {
|
|
24
|
+
describe("RIFF/WAVE markers", () => {
|
|
25
|
+
test("starts with RIFF chunk ID", () => {
|
|
26
|
+
const wav = encodePcm16LeToWav(Buffer.alloc(0), MONO_16K);
|
|
27
|
+
expect(readTag(wav, 0)).toBe("RIFF");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("contains WAVE format identifier at offset 8", () => {
|
|
31
|
+
const wav = encodePcm16LeToWav(Buffer.alloc(0), MONO_16K);
|
|
32
|
+
expect(readTag(wav, 8)).toBe("WAVE");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("contains fmt sub-chunk ID at offset 12", () => {
|
|
36
|
+
const wav = encodePcm16LeToWav(Buffer.alloc(0), MONO_16K);
|
|
37
|
+
expect(readTag(wav, 12)).toBe("fmt ");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("contains data sub-chunk ID at offset 36", () => {
|
|
41
|
+
const wav = encodePcm16LeToWav(Buffer.alloc(0), MONO_16K);
|
|
42
|
+
expect(readTag(wav, 36)).toBe("data");
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// -------------------------------------------------------------------------
|
|
47
|
+
// RIFF chunk size
|
|
48
|
+
// -------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
describe("RIFF chunk size", () => {
|
|
51
|
+
test("ChunkSize equals fileSize - 8 for non-empty payload", () => {
|
|
52
|
+
const pcm = Buffer.alloc(1024);
|
|
53
|
+
const wav = encodePcm16LeToWav(pcm, MONO_16K);
|
|
54
|
+
const chunkSize = wav.readUInt32LE(4);
|
|
55
|
+
// Total file size = 44 (header) + 1024 (data) = 1068
|
|
56
|
+
// ChunkSize = 1068 - 8 = 1060
|
|
57
|
+
expect(chunkSize).toBe(1068 - 8);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("ChunkSize equals 36 for empty payload (header-only)", () => {
|
|
61
|
+
const wav = encodePcm16LeToWav(Buffer.alloc(0), MONO_16K);
|
|
62
|
+
const chunkSize = wav.readUInt32LE(4);
|
|
63
|
+
// Total file size = 44, ChunkSize = 44 - 8 = 36
|
|
64
|
+
expect(chunkSize).toBe(36);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// -------------------------------------------------------------------------
|
|
69
|
+
// fmt sub-chunk fields
|
|
70
|
+
// -------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
describe("fmt sub-chunk", () => {
|
|
73
|
+
test("fmt sub-chunk size is 16 (PCM)", () => {
|
|
74
|
+
const wav = encodePcm16LeToWav(Buffer.alloc(0), MONO_16K);
|
|
75
|
+
expect(wav.readUInt32LE(16)).toBe(16);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("AudioFormat is 1 (PCM)", () => {
|
|
79
|
+
const wav = encodePcm16LeToWav(Buffer.alloc(0), MONO_16K);
|
|
80
|
+
expect(wav.readUInt16LE(20)).toBe(1);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("NumChannels matches mono input", () => {
|
|
84
|
+
const wav = encodePcm16LeToWav(Buffer.alloc(0), {
|
|
85
|
+
sampleRate: 16000,
|
|
86
|
+
channels: 1,
|
|
87
|
+
});
|
|
88
|
+
expect(wav.readUInt16LE(22)).toBe(1);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("NumChannels matches stereo input", () => {
|
|
92
|
+
const wav = encodePcm16LeToWav(Buffer.alloc(0), {
|
|
93
|
+
sampleRate: 44100,
|
|
94
|
+
channels: 2,
|
|
95
|
+
});
|
|
96
|
+
expect(wav.readUInt16LE(22)).toBe(2);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("SampleRate is written correctly for 16 kHz", () => {
|
|
100
|
+
const wav = encodePcm16LeToWav(Buffer.alloc(0), MONO_16K);
|
|
101
|
+
expect(wav.readUInt32LE(24)).toBe(16000);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("SampleRate is written correctly for 44.1 kHz", () => {
|
|
105
|
+
const wav = encodePcm16LeToWav(Buffer.alloc(0), {
|
|
106
|
+
sampleRate: 44100,
|
|
107
|
+
channels: 1,
|
|
108
|
+
});
|
|
109
|
+
expect(wav.readUInt32LE(24)).toBe(44100);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("SampleRate is written correctly for 48 kHz", () => {
|
|
113
|
+
const wav = encodePcm16LeToWav(Buffer.alloc(0), {
|
|
114
|
+
sampleRate: 48000,
|
|
115
|
+
channels: 2,
|
|
116
|
+
});
|
|
117
|
+
expect(wav.readUInt32LE(24)).toBe(48000);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("ByteRate = sampleRate * channels * bitsPerSample / 8 for mono 16-bit", () => {
|
|
121
|
+
const wav = encodePcm16LeToWav(Buffer.alloc(0), MONO_16K);
|
|
122
|
+
// 16000 * 1 * 16 / 8 = 32000
|
|
123
|
+
expect(wav.readUInt32LE(28)).toBe(32000);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("ByteRate = sampleRate * channels * bitsPerSample / 8 for stereo 16-bit", () => {
|
|
127
|
+
const wav = encodePcm16LeToWav(Buffer.alloc(0), {
|
|
128
|
+
sampleRate: 48000,
|
|
129
|
+
channels: 2,
|
|
130
|
+
});
|
|
131
|
+
// 48000 * 2 * 16 / 8 = 192000
|
|
132
|
+
expect(wav.readUInt32LE(28)).toBe(192000);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("BlockAlign = channels * bitsPerSample / 8 for mono 16-bit", () => {
|
|
136
|
+
const wav = encodePcm16LeToWav(Buffer.alloc(0), MONO_16K);
|
|
137
|
+
// 1 * 16 / 8 = 2
|
|
138
|
+
expect(wav.readUInt16LE(32)).toBe(2);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("BlockAlign = channels * bitsPerSample / 8 for stereo 16-bit", () => {
|
|
142
|
+
const wav = encodePcm16LeToWav(Buffer.alloc(0), {
|
|
143
|
+
sampleRate: 44100,
|
|
144
|
+
channels: 2,
|
|
145
|
+
});
|
|
146
|
+
// 2 * 16 / 8 = 4
|
|
147
|
+
expect(wav.readUInt16LE(32)).toBe(4);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("BitsPerSample defaults to 16", () => {
|
|
151
|
+
const wav = encodePcm16LeToWav(Buffer.alloc(0), MONO_16K);
|
|
152
|
+
expect(wav.readUInt16LE(34)).toBe(16);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("BitsPerSample respects explicit override", () => {
|
|
156
|
+
const wav = encodePcm16LeToWav(Buffer.alloc(0), {
|
|
157
|
+
sampleRate: 16000,
|
|
158
|
+
channels: 1,
|
|
159
|
+
bitsPerSample: 24,
|
|
160
|
+
});
|
|
161
|
+
expect(wav.readUInt16LE(34)).toBe(24);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// -------------------------------------------------------------------------
|
|
166
|
+
// data sub-chunk
|
|
167
|
+
// -------------------------------------------------------------------------
|
|
168
|
+
|
|
169
|
+
describe("data sub-chunk", () => {
|
|
170
|
+
test("data sub-chunk size matches PCM payload length", () => {
|
|
171
|
+
const pcm = Buffer.alloc(512);
|
|
172
|
+
const wav = encodePcm16LeToWav(pcm, MONO_16K);
|
|
173
|
+
expect(wav.readUInt32LE(40)).toBe(512);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("data sub-chunk size is 0 for empty payload", () => {
|
|
177
|
+
const wav = encodePcm16LeToWav(Buffer.alloc(0), MONO_16K);
|
|
178
|
+
expect(wav.readUInt32LE(40)).toBe(0);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// -------------------------------------------------------------------------
|
|
183
|
+
// Payload passthrough
|
|
184
|
+
// -------------------------------------------------------------------------
|
|
185
|
+
|
|
186
|
+
describe("payload passthrough", () => {
|
|
187
|
+
test("PCM data is copied verbatim after the 44-byte header", () => {
|
|
188
|
+
const pcm = Buffer.from([0x01, 0x02, 0x03, 0x04, 0xff, 0xfe]);
|
|
189
|
+
const wav = encodePcm16LeToWav(pcm, MONO_16K);
|
|
190
|
+
|
|
191
|
+
const payload = wav.subarray(44);
|
|
192
|
+
expect(payload).toEqual(pcm);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("large payload is preserved byte-for-byte", () => {
|
|
196
|
+
// 1 second of mono 16 kHz 16-bit audio = 32000 bytes
|
|
197
|
+
const pcm = Buffer.alloc(32000);
|
|
198
|
+
for (let i = 0; i < pcm.length; i++) {
|
|
199
|
+
pcm[i] = i % 256;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const wav = encodePcm16LeToWav(pcm, MONO_16K);
|
|
203
|
+
const payload = wav.subarray(44);
|
|
204
|
+
expect(payload).toEqual(pcm);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("total file length is header + payload", () => {
|
|
208
|
+
const pcm = Buffer.alloc(256);
|
|
209
|
+
const wav = encodePcm16LeToWav(pcm, MONO_16K);
|
|
210
|
+
expect(wav.length).toBe(44 + 256);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// -------------------------------------------------------------------------
|
|
215
|
+
// Empty audio (silence / final flush edge case)
|
|
216
|
+
// -------------------------------------------------------------------------
|
|
217
|
+
|
|
218
|
+
describe("empty audio", () => {
|
|
219
|
+
test("produces a deterministic 44-byte header-only output", () => {
|
|
220
|
+
const wav = encodePcm16LeToWav(Buffer.alloc(0), MONO_16K);
|
|
221
|
+
expect(wav.length).toBe(44);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("header-only output has valid RIFF structure", () => {
|
|
225
|
+
const wav = encodePcm16LeToWav(Buffer.alloc(0), MONO_16K);
|
|
226
|
+
expect(readTag(wav, 0)).toBe("RIFF");
|
|
227
|
+
expect(readTag(wav, 8)).toBe("WAVE");
|
|
228
|
+
expect(readTag(wav, 12)).toBe("fmt ");
|
|
229
|
+
expect(readTag(wav, 36)).toBe("data");
|
|
230
|
+
expect(wav.readUInt32LE(40)).toBe(0);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test("two calls with empty audio produce identical output", () => {
|
|
234
|
+
const a = encodePcm16LeToWav(Buffer.alloc(0), MONO_16K);
|
|
235
|
+
const b = encodePcm16LeToWav(Buffer.alloc(0), MONO_16K);
|
|
236
|
+
expect(a).toEqual(b);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// -------------------------------------------------------------------------
|
|
241
|
+
// Custom bit depths
|
|
242
|
+
// -------------------------------------------------------------------------
|
|
243
|
+
|
|
244
|
+
describe("custom bit depths", () => {
|
|
245
|
+
test("accepts 8-bit depth and computes correct byte rate", () => {
|
|
246
|
+
const wav = encodePcm16LeToWav(Buffer.alloc(0), {
|
|
247
|
+
sampleRate: 8000,
|
|
248
|
+
channels: 1,
|
|
249
|
+
bitsPerSample: 8,
|
|
250
|
+
});
|
|
251
|
+
// ByteRate = 8000 * 1 * 8 / 8 = 8000
|
|
252
|
+
expect(wav.readUInt32LE(28)).toBe(8000);
|
|
253
|
+
// BlockAlign = 1 * 8 / 8 = 1
|
|
254
|
+
expect(wav.readUInt16LE(32)).toBe(1);
|
|
255
|
+
expect(wav.readUInt16LE(34)).toBe(8);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test("accepts 24-bit depth and computes correct byte rate", () => {
|
|
259
|
+
const wav = encodePcm16LeToWav(Buffer.alloc(0), {
|
|
260
|
+
sampleRate: 48000,
|
|
261
|
+
channels: 2,
|
|
262
|
+
bitsPerSample: 24,
|
|
263
|
+
});
|
|
264
|
+
// ByteRate = 48000 * 2 * 24 / 8 = 288000
|
|
265
|
+
expect(wav.readUInt32LE(28)).toBe(288000);
|
|
266
|
+
// BlockAlign = 2 * 24 / 8 = 6
|
|
267
|
+
expect(wav.readUInt16LE(32)).toBe(6);
|
|
268
|
+
expect(wav.readUInt16LE(34)).toBe(24);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test("accepts 32-bit depth and computes correct byte rate", () => {
|
|
272
|
+
const wav = encodePcm16LeToWav(Buffer.alloc(0), {
|
|
273
|
+
sampleRate: 44100,
|
|
274
|
+
channels: 1,
|
|
275
|
+
bitsPerSample: 32,
|
|
276
|
+
});
|
|
277
|
+
// ByteRate = 44100 * 1 * 32 / 8 = 176400
|
|
278
|
+
expect(wav.readUInt32LE(28)).toBe(176400);
|
|
279
|
+
// BlockAlign = 1 * 32 / 8 = 4
|
|
280
|
+
expect(wav.readUInt16LE(32)).toBe(4);
|
|
281
|
+
expect(wav.readUInt16LE(34)).toBe(32);
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// -------------------------------------------------------------------------
|
|
286
|
+
// Input validation
|
|
287
|
+
// -------------------------------------------------------------------------
|
|
288
|
+
|
|
289
|
+
describe("input validation", () => {
|
|
290
|
+
test("throws for zero sample rate", () => {
|
|
291
|
+
expect(() =>
|
|
292
|
+
encodePcm16LeToWav(Buffer.alloc(0), {
|
|
293
|
+
sampleRate: 0,
|
|
294
|
+
channels: 1,
|
|
295
|
+
}),
|
|
296
|
+
).toThrow("Invalid sampleRate");
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test("throws for negative sample rate", () => {
|
|
300
|
+
expect(() =>
|
|
301
|
+
encodePcm16LeToWav(Buffer.alloc(0), {
|
|
302
|
+
sampleRate: -16000,
|
|
303
|
+
channels: 1,
|
|
304
|
+
}),
|
|
305
|
+
).toThrow("Invalid sampleRate");
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test("throws for NaN sample rate", () => {
|
|
309
|
+
expect(() =>
|
|
310
|
+
encodePcm16LeToWav(Buffer.alloc(0), {
|
|
311
|
+
sampleRate: NaN,
|
|
312
|
+
channels: 1,
|
|
313
|
+
}),
|
|
314
|
+
).toThrow("Invalid sampleRate");
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
test("throws for Infinity sample rate", () => {
|
|
318
|
+
expect(() =>
|
|
319
|
+
encodePcm16LeToWav(Buffer.alloc(0), {
|
|
320
|
+
sampleRate: Infinity,
|
|
321
|
+
channels: 1,
|
|
322
|
+
}),
|
|
323
|
+
).toThrow("Invalid sampleRate");
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test("throws for zero channels", () => {
|
|
327
|
+
expect(() =>
|
|
328
|
+
encodePcm16LeToWav(Buffer.alloc(0), {
|
|
329
|
+
sampleRate: 16000,
|
|
330
|
+
channels: 0,
|
|
331
|
+
}),
|
|
332
|
+
).toThrow("Invalid channels");
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test("throws for negative channels", () => {
|
|
336
|
+
expect(() =>
|
|
337
|
+
encodePcm16LeToWav(Buffer.alloc(0), {
|
|
338
|
+
sampleRate: 16000,
|
|
339
|
+
channels: -1,
|
|
340
|
+
}),
|
|
341
|
+
).toThrow("Invalid channels");
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
test("throws for fractional channels", () => {
|
|
345
|
+
expect(() =>
|
|
346
|
+
encodePcm16LeToWav(Buffer.alloc(0), {
|
|
347
|
+
sampleRate: 16000,
|
|
348
|
+
channels: 1.5,
|
|
349
|
+
}),
|
|
350
|
+
).toThrow("Invalid channels");
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test("throws for unsupported bit depth", () => {
|
|
354
|
+
expect(() =>
|
|
355
|
+
encodePcm16LeToWav(Buffer.alloc(0), {
|
|
356
|
+
sampleRate: 16000,
|
|
357
|
+
channels: 1,
|
|
358
|
+
bitsPerSample: 12,
|
|
359
|
+
}),
|
|
360
|
+
).toThrow("Unsupported bitsPerSample");
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
test("throws for zero bit depth", () => {
|
|
364
|
+
expect(() =>
|
|
365
|
+
encodePcm16LeToWav(Buffer.alloc(0), {
|
|
366
|
+
sampleRate: 16000,
|
|
367
|
+
channels: 1,
|
|
368
|
+
bitsPerSample: 0,
|
|
369
|
+
}),
|
|
370
|
+
).toThrow("Unsupported bitsPerSample");
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
});
|