@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
|
@@ -0,0 +1,828 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Mocks — must be declared before any subject imports
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
|
|
7
|
+
// -- Logger mock ----------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Captured log messages from the resolver. Tests can assert that
|
|
11
|
+
* `resolveStreamingTranscriber` logs a warning when `diarize: "required"`
|
|
12
|
+
* is passed with a non-capable provider.
|
|
13
|
+
*/
|
|
14
|
+
const loggerWarnings: Array<{ data: unknown; message: string }> = [];
|
|
15
|
+
|
|
16
|
+
mock.module("../../../util/logger.js", () => ({
|
|
17
|
+
getLogger: () => ({
|
|
18
|
+
warn: (data: unknown, message: string) => {
|
|
19
|
+
loggerWarnings.push({ data, message });
|
|
20
|
+
},
|
|
21
|
+
info: () => {},
|
|
22
|
+
debug: () => {},
|
|
23
|
+
error: () => {},
|
|
24
|
+
trace: () => {},
|
|
25
|
+
fatal: () => {},
|
|
26
|
+
child: () => ({
|
|
27
|
+
warn: (data: unknown, message: string) => {
|
|
28
|
+
loggerWarnings.push({ data, message });
|
|
29
|
+
},
|
|
30
|
+
info: () => {},
|
|
31
|
+
debug: () => {},
|
|
32
|
+
error: () => {},
|
|
33
|
+
trace: () => {},
|
|
34
|
+
fatal: () => {},
|
|
35
|
+
}),
|
|
36
|
+
}),
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
// -- Config mock ----------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
let mockConfig: Record<string, unknown> = {};
|
|
42
|
+
|
|
43
|
+
mock.module("../../../config/loader.js", () => ({
|
|
44
|
+
getConfig: () => mockConfig,
|
|
45
|
+
loadConfig: () => mockConfig,
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
// -- Credential mock ------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
let mockProviderKeys: Record<string, string | undefined> = {};
|
|
51
|
+
|
|
52
|
+
mock.module("../../../security/secure-keys.js", () => ({
|
|
53
|
+
getProviderKeyAsync: async (provider: string) =>
|
|
54
|
+
mockProviderKeys[provider] ?? undefined,
|
|
55
|
+
getSecureKeyAsync: async () => null,
|
|
56
|
+
getSecureKey: () => null,
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
mock.module("../../../security/credential-key.js", () => ({
|
|
60
|
+
credentialKey: (...args: string[]) => args.join("/"),
|
|
61
|
+
}));
|
|
62
|
+
|
|
63
|
+
// -- Streaming adapter mocks ----------------------------------------------
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Captured constructor calls for each streaming adapter. Tests assert on
|
|
67
|
+
* these arrays to verify the resolver plumbs options (sampleRate, diarize)
|
|
68
|
+
* correctly to each provider.
|
|
69
|
+
*/
|
|
70
|
+
const deepgramCtorCalls: Array<{ apiKey: string; options: unknown }> = [];
|
|
71
|
+
const geminiCtorCalls: Array<{ apiKey: string; options: unknown }> = [];
|
|
72
|
+
const whisperCtorCalls: Array<{ apiKey: string; options: unknown }> = [];
|
|
73
|
+
|
|
74
|
+
mock.module("../deepgram-realtime.js", () => ({
|
|
75
|
+
DeepgramRealtimeTranscriber: class {
|
|
76
|
+
readonly providerId = "deepgram" as const;
|
|
77
|
+
readonly boundaryId = "daemon-streaming" as const;
|
|
78
|
+
constructor(apiKey: string, options: unknown) {
|
|
79
|
+
deepgramCtorCalls.push({ apiKey, options });
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
}));
|
|
83
|
+
|
|
84
|
+
mock.module("../google-gemini-live-stream.js", () => ({
|
|
85
|
+
GoogleGeminiLiveStreamingTranscriber: class {
|
|
86
|
+
readonly providerId = "google-gemini" as const;
|
|
87
|
+
readonly boundaryId = "daemon-streaming" as const;
|
|
88
|
+
constructor(apiKey: string, options: unknown) {
|
|
89
|
+
geminiCtorCalls.push({ apiKey, options });
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
}));
|
|
93
|
+
|
|
94
|
+
mock.module("../openai-whisper-stream.js", () => ({
|
|
95
|
+
OpenAIWhisperStreamingTranscriber: class {
|
|
96
|
+
readonly providerId = "openai-whisper" as const;
|
|
97
|
+
readonly boundaryId = "daemon-streaming" as const;
|
|
98
|
+
constructor(apiKey: string, options: unknown) {
|
|
99
|
+
whisperCtorCalls.push({ apiKey, options });
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
}));
|
|
103
|
+
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
// Subject import (after mocks)
|
|
106
|
+
//
|
|
107
|
+
// Use a dynamic `await import(...)` so the module-top `const log = getLogger(...)`
|
|
108
|
+
// in `resolve.ts` is captured by the mocked logger above. Static ESM imports
|
|
109
|
+
// are hoisted above all module-top statements, which would cause `resolve.ts`
|
|
110
|
+
// to evaluate — and call the real `getLogger` — before `mock.module(...)` runs.
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
const {
|
|
114
|
+
resolveBatchTranscriber,
|
|
115
|
+
resolveConversationStreamingSttCapability,
|
|
116
|
+
resolveStreamingTranscriber,
|
|
117
|
+
resolveTelephonySttCapability,
|
|
118
|
+
} = await import("../resolve.js");
|
|
119
|
+
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
// Helpers
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
function buildConfig(overrides: {
|
|
125
|
+
provider?: string;
|
|
126
|
+
}): Record<string, unknown> {
|
|
127
|
+
return {
|
|
128
|
+
services: {
|
|
129
|
+
stt: {
|
|
130
|
+
mode: "your-own",
|
|
131
|
+
provider: overrides.provider ?? "openai-whisper",
|
|
132
|
+
providers: {
|
|
133
|
+
"openai-whisper": {},
|
|
134
|
+
deepgram: {},
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
// Tests — resolveBatchTranscriber
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
describe("resolveBatchTranscriber", () => {
|
|
146
|
+
beforeEach(() => {
|
|
147
|
+
mockConfig = buildConfig({});
|
|
148
|
+
mockProviderKeys = {};
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("returns a BatchTranscriber when openai-whisper is configured and credentials are available", async () => {
|
|
152
|
+
mockProviderKeys["openai"] = "sk-test-key";
|
|
153
|
+
mockConfig = buildConfig({ provider: "openai-whisper" });
|
|
154
|
+
|
|
155
|
+
const transcriber = await resolveBatchTranscriber();
|
|
156
|
+
|
|
157
|
+
expect(transcriber).not.toBeNull();
|
|
158
|
+
expect(transcriber!.providerId).toBe("openai-whisper");
|
|
159
|
+
expect(transcriber!.boundaryId).toBe("daemon-batch");
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("returns null when credentials are missing for the configured provider", async () => {
|
|
163
|
+
mockProviderKeys = {}; // no keys at all
|
|
164
|
+
mockConfig = buildConfig({ provider: "openai-whisper" });
|
|
165
|
+
|
|
166
|
+
const transcriber = await resolveBatchTranscriber();
|
|
167
|
+
|
|
168
|
+
expect(transcriber).toBeNull();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("returns null when configured provider is unsupported for daemon-batch", async () => {
|
|
172
|
+
// Force an unknown provider past the type system to simulate a future
|
|
173
|
+
// provider that hasn't been wired into the daemon-batch boundary yet.
|
|
174
|
+
mockProviderKeys["some-provider"] = "key";
|
|
175
|
+
mockConfig = buildConfig({ provider: "unknown-provider" as string });
|
|
176
|
+
|
|
177
|
+
const transcriber = await resolveBatchTranscriber();
|
|
178
|
+
|
|
179
|
+
expect(transcriber).toBeNull();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("uses config-driven provider selection, not hardcoded OpenAI", async () => {
|
|
183
|
+
// Verify the resolver reads from config rather than always using "openai".
|
|
184
|
+
// If the config says openai-whisper, we expect credential lookup for "openai".
|
|
185
|
+
mockProviderKeys["openai"] = "sk-config-driven";
|
|
186
|
+
mockConfig = buildConfig({ provider: "openai-whisper" });
|
|
187
|
+
|
|
188
|
+
const transcriber = await resolveBatchTranscriber();
|
|
189
|
+
|
|
190
|
+
expect(transcriber).not.toBeNull();
|
|
191
|
+
expect(transcriber!.providerId).toBe("openai-whisper");
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("resolved transcriber has stable provider identity", async () => {
|
|
195
|
+
mockProviderKeys["openai"] = "sk-identity-test";
|
|
196
|
+
mockConfig = buildConfig({ provider: "openai-whisper" });
|
|
197
|
+
|
|
198
|
+
const transcriber = await resolveBatchTranscriber();
|
|
199
|
+
|
|
200
|
+
// The providerId must remain "openai-whisper" for downstream identity checks.
|
|
201
|
+
expect(transcriber!.providerId).toBe("openai-whisper");
|
|
202
|
+
expect(transcriber!.boundaryId).toBe("daemon-batch");
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// -------------------------------------------------------------------------
|
|
206
|
+
// Deepgram provider resolution
|
|
207
|
+
// -------------------------------------------------------------------------
|
|
208
|
+
|
|
209
|
+
test("returns a BatchTranscriber when deepgram is configured and credentials are available", async () => {
|
|
210
|
+
mockProviderKeys["deepgram"] = "dg-test-key";
|
|
211
|
+
mockConfig = buildConfig({ provider: "deepgram" });
|
|
212
|
+
|
|
213
|
+
const transcriber = await resolveBatchTranscriber();
|
|
214
|
+
|
|
215
|
+
expect(transcriber).not.toBeNull();
|
|
216
|
+
expect(transcriber!.providerId).toBe("deepgram");
|
|
217
|
+
expect(transcriber!.boundaryId).toBe("daemon-batch");
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test("returns null when deepgram is configured but no credentials exist", async () => {
|
|
221
|
+
mockProviderKeys = {}; // no keys
|
|
222
|
+
mockConfig = buildConfig({ provider: "deepgram" });
|
|
223
|
+
|
|
224
|
+
const transcriber = await resolveBatchTranscriber();
|
|
225
|
+
|
|
226
|
+
expect(transcriber).toBeNull();
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test("deepgram uses 'deepgram' credential key, not 'openai'", async () => {
|
|
230
|
+
// Only openai key is set — deepgram should NOT resolve
|
|
231
|
+
mockProviderKeys["openai"] = "sk-test-key";
|
|
232
|
+
mockConfig = buildConfig({ provider: "deepgram" });
|
|
233
|
+
|
|
234
|
+
const transcriber = await resolveBatchTranscriber();
|
|
235
|
+
|
|
236
|
+
expect(transcriber).toBeNull();
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test("resolved deepgram transcriber has stable provider identity", async () => {
|
|
240
|
+
mockProviderKeys["deepgram"] = "dg-identity-test";
|
|
241
|
+
mockConfig = buildConfig({ provider: "deepgram" });
|
|
242
|
+
|
|
243
|
+
const transcriber = await resolveBatchTranscriber();
|
|
244
|
+
|
|
245
|
+
expect(transcriber!.providerId).toBe("deepgram");
|
|
246
|
+
expect(transcriber!.boundaryId).toBe("daemon-batch");
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// -------------------------------------------------------------------------
|
|
250
|
+
// Google Gemini provider resolution
|
|
251
|
+
// -------------------------------------------------------------------------
|
|
252
|
+
|
|
253
|
+
test("returns a BatchTranscriber when google-gemini is configured and credentials are available", async () => {
|
|
254
|
+
mockProviderKeys["gemini"] = "gemini-test-key";
|
|
255
|
+
mockConfig = buildConfig({ provider: "google-gemini" });
|
|
256
|
+
|
|
257
|
+
const transcriber = await resolveBatchTranscriber();
|
|
258
|
+
|
|
259
|
+
expect(transcriber).not.toBeNull();
|
|
260
|
+
expect(transcriber!.providerId).toBe("google-gemini");
|
|
261
|
+
expect(transcriber!.boundaryId).toBe("daemon-batch");
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test("returns null when google-gemini is configured but no credentials exist", async () => {
|
|
265
|
+
mockProviderKeys = {}; // no keys
|
|
266
|
+
mockConfig = buildConfig({ provider: "google-gemini" });
|
|
267
|
+
|
|
268
|
+
const transcriber = await resolveBatchTranscriber();
|
|
269
|
+
|
|
270
|
+
expect(transcriber).toBeNull();
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test("google-gemini uses 'gemini' credential key, not 'openai' or 'deepgram'", async () => {
|
|
274
|
+
// Only openai key is set — google-gemini should NOT resolve
|
|
275
|
+
mockProviderKeys["openai"] = "sk-test-key";
|
|
276
|
+
mockConfig = buildConfig({ provider: "google-gemini" });
|
|
277
|
+
|
|
278
|
+
const transcriber = await resolveBatchTranscriber();
|
|
279
|
+
|
|
280
|
+
expect(transcriber).toBeNull();
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test("resolved google-gemini transcriber has stable provider identity", async () => {
|
|
284
|
+
mockProviderKeys["gemini"] = "gemini-identity-test";
|
|
285
|
+
mockConfig = buildConfig({ provider: "google-gemini" });
|
|
286
|
+
|
|
287
|
+
const transcriber = await resolveBatchTranscriber();
|
|
288
|
+
|
|
289
|
+
expect(transcriber!.providerId).toBe("google-gemini");
|
|
290
|
+
expect(transcriber!.boundaryId).toBe("daemon-batch");
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// ---------------------------------------------------------------------------
|
|
295
|
+
// Tests — resolveTelephonySttCapability
|
|
296
|
+
// ---------------------------------------------------------------------------
|
|
297
|
+
|
|
298
|
+
describe("resolveTelephonySttCapability", () => {
|
|
299
|
+
beforeEach(() => {
|
|
300
|
+
mockConfig = buildConfig({});
|
|
301
|
+
mockProviderKeys = {};
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test("returns 'supported' when provider is telephony-eligible and credentials exist", async () => {
|
|
305
|
+
mockProviderKeys["openai"] = "sk-telephony-test";
|
|
306
|
+
mockConfig = buildConfig({ provider: "openai-whisper" });
|
|
307
|
+
|
|
308
|
+
const result = await resolveTelephonySttCapability();
|
|
309
|
+
|
|
310
|
+
expect(result.status).toBe("supported");
|
|
311
|
+
if (result.status === "supported") {
|
|
312
|
+
expect(result.providerId).toBe("openai-whisper");
|
|
313
|
+
// openai-whisper is batch-only, so telephonyMode should reflect that
|
|
314
|
+
expect(result.telephonyMode).toBe("batch-only");
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
test("returns 'unconfigured' when provider is not in the catalog", async () => {
|
|
319
|
+
mockProviderKeys["unknown-provider"] = "key-doesnt-matter";
|
|
320
|
+
mockConfig = buildConfig({ provider: "unknown-provider" as string });
|
|
321
|
+
|
|
322
|
+
const result = await resolveTelephonySttCapability();
|
|
323
|
+
|
|
324
|
+
expect(result.status).toBe("unconfigured");
|
|
325
|
+
if (result.status === "unconfigured") {
|
|
326
|
+
expect(result.reason).toContain("unknown-provider");
|
|
327
|
+
expect(result.reason).toContain("not in the provider catalog");
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
test("returns 'missing-credentials' when provider is eligible but has no API key", async () => {
|
|
332
|
+
mockProviderKeys = {}; // no keys
|
|
333
|
+
mockConfig = buildConfig({ provider: "openai-whisper" });
|
|
334
|
+
|
|
335
|
+
const result = await resolveTelephonySttCapability();
|
|
336
|
+
|
|
337
|
+
expect(result.status).toBe("missing-credentials");
|
|
338
|
+
if (result.status === "missing-credentials") {
|
|
339
|
+
expect(result.providerId).toBe("openai-whisper");
|
|
340
|
+
expect(result.credentialProvider).toBe("openai");
|
|
341
|
+
expect(result.reason).toContain("openai");
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
test("uses config-driven provider, not a hardcoded default", async () => {
|
|
346
|
+
// Use a provider that IS in the catalog to verify config is read
|
|
347
|
+
mockProviderKeys["openai"] = "sk-config-test";
|
|
348
|
+
mockConfig = buildConfig({ provider: "openai-whisper" });
|
|
349
|
+
|
|
350
|
+
const result = await resolveTelephonySttCapability();
|
|
351
|
+
|
|
352
|
+
expect(result.status).toBe("supported");
|
|
353
|
+
if (result.status === "supported") {
|
|
354
|
+
expect(result.providerId).toBe("openai-whisper");
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
test("returns 'unconfigured' for empty-string provider", async () => {
|
|
359
|
+
mockConfig = buildConfig({ provider: "" as string });
|
|
360
|
+
|
|
361
|
+
const result = await resolveTelephonySttCapability();
|
|
362
|
+
|
|
363
|
+
expect(result.status).toBe("unconfigured");
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// -------------------------------------------------------------------------
|
|
367
|
+
// Google Gemini telephony capability
|
|
368
|
+
// -------------------------------------------------------------------------
|
|
369
|
+
|
|
370
|
+
test("returns 'supported' for google-gemini with batch-only telephonyMode", async () => {
|
|
371
|
+
mockProviderKeys["gemini"] = "gemini-telephony-test";
|
|
372
|
+
mockConfig = buildConfig({ provider: "google-gemini" });
|
|
373
|
+
|
|
374
|
+
const result = await resolveTelephonySttCapability();
|
|
375
|
+
|
|
376
|
+
expect(result.status).toBe("supported");
|
|
377
|
+
if (result.status === "supported") {
|
|
378
|
+
expect(result.providerId).toBe("google-gemini");
|
|
379
|
+
expect(result.telephonyMode).toBe("batch-only");
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
test("returns 'missing-credentials' for google-gemini without a gemini key", async () => {
|
|
384
|
+
mockProviderKeys = {};
|
|
385
|
+
mockConfig = buildConfig({ provider: "google-gemini" });
|
|
386
|
+
|
|
387
|
+
const result = await resolveTelephonySttCapability();
|
|
388
|
+
|
|
389
|
+
expect(result.status).toBe("missing-credentials");
|
|
390
|
+
if (result.status === "missing-credentials") {
|
|
391
|
+
expect(result.providerId).toBe("google-gemini");
|
|
392
|
+
expect(result.credentialProvider).toBe("gemini");
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
// ---------------------------------------------------------------------------
|
|
398
|
+
// Tests — telephony routing alignment with provider catalog
|
|
399
|
+
// ---------------------------------------------------------------------------
|
|
400
|
+
|
|
401
|
+
import { getProviderEntry, listProviderIds } from "../provider-catalog.js";
|
|
402
|
+
|
|
403
|
+
describe("telephony routing catalog alignment", () => {
|
|
404
|
+
/**
|
|
405
|
+
* These tests verify that the assumptions made by the telephony STT
|
|
406
|
+
* routing resolver (telephony-stt-routing.ts) remain consistent with
|
|
407
|
+
* the provider catalog entries. If a catalog entry changes its
|
|
408
|
+
* telephonyMode, routing metadata, or a new provider is added, these
|
|
409
|
+
* tests will catch misalignment early.
|
|
410
|
+
*/
|
|
411
|
+
|
|
412
|
+
test("deepgram catalog entry has realtime-ws telephonyMode (Twilio-native eligible)", () => {
|
|
413
|
+
const entry = getProviderEntry("deepgram");
|
|
414
|
+
expect(entry).toBeDefined();
|
|
415
|
+
expect(entry!.telephonyMode).toBe("realtime-ws");
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
test("google-gemini catalog entry has batch-only telephonyMode (Twilio-native eligible)", () => {
|
|
419
|
+
const entry = getProviderEntry("google-gemini");
|
|
420
|
+
expect(entry).toBeDefined();
|
|
421
|
+
expect(entry!.telephonyMode).toBe("batch-only");
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
test("openai-whisper catalog entry has batch-only telephonyMode (media-stream path)", () => {
|
|
425
|
+
const entry = getProviderEntry("openai-whisper");
|
|
426
|
+
expect(entry).toBeDefined();
|
|
427
|
+
expect(entry!.telephonyMode).toBe("batch-only");
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
test("deepgram uses 'deepgram' credential provider", () => {
|
|
431
|
+
const entry = getProviderEntry("deepgram");
|
|
432
|
+
expect(entry!.credentialProvider).toBe("deepgram");
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
test("google-gemini uses 'gemini' credential provider", () => {
|
|
436
|
+
const entry = getProviderEntry("google-gemini");
|
|
437
|
+
expect(entry!.credentialProvider).toBe("gemini");
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
test("openai-whisper uses 'openai' credential provider", () => {
|
|
441
|
+
const entry = getProviderEntry("openai-whisper");
|
|
442
|
+
expect(entry!.credentialProvider).toBe("openai");
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
test("every catalog provider has a non-none telephonyMode", () => {
|
|
446
|
+
// The telephony routing resolver assumes all known providers
|
|
447
|
+
// participate in some telephony path (native or media-stream).
|
|
448
|
+
// If a provider with telephonyMode: "none" is added, the routing
|
|
449
|
+
// resolver would need to handle it explicitly.
|
|
450
|
+
for (const id of listProviderIds()) {
|
|
451
|
+
const entry = getProviderEntry(id);
|
|
452
|
+
expect(entry).toBeDefined();
|
|
453
|
+
expect(entry!.telephonyMode).not.toBe("none");
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
// -----------------------------------------------------------------------
|
|
458
|
+
// Telephony routing metadata invariants
|
|
459
|
+
// -----------------------------------------------------------------------
|
|
460
|
+
|
|
461
|
+
test("every catalog provider has telephonyRouting metadata", () => {
|
|
462
|
+
for (const id of listProviderIds()) {
|
|
463
|
+
const entry = getProviderEntry(id);
|
|
464
|
+
expect(entry).toBeDefined();
|
|
465
|
+
expect(entry!.telephonyRouting).toBeDefined();
|
|
466
|
+
expect(["conversation-relay-native", "media-stream-custom"]).toContain(
|
|
467
|
+
entry!.telephonyRouting.strategyKind,
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
test("conversation-relay-native providers have twilioNativeMapping with non-empty provider name", () => {
|
|
473
|
+
for (const id of listProviderIds()) {
|
|
474
|
+
const entry = getProviderEntry(id)!;
|
|
475
|
+
if (entry.telephonyRouting.strategyKind === "conversation-relay-native") {
|
|
476
|
+
expect(entry.telephonyRouting.twilioNativeMapping).toBeDefined();
|
|
477
|
+
expect(
|
|
478
|
+
entry.telephonyRouting.twilioNativeMapping!.provider.length,
|
|
479
|
+
).toBeGreaterThan(0);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
test("media-stream-custom providers do not have twilioNativeMapping", () => {
|
|
485
|
+
for (const id of listProviderIds()) {
|
|
486
|
+
const entry = getProviderEntry(id)!;
|
|
487
|
+
if (entry.telephonyRouting.strategyKind === "media-stream-custom") {
|
|
488
|
+
expect(entry.telephonyRouting.twilioNativeMapping).toBeUndefined();
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
test("deepgram routing metadata maps to Twilio-native Deepgram with nova-3 speech model", () => {
|
|
494
|
+
const entry = getProviderEntry("deepgram")!;
|
|
495
|
+
expect(entry.telephonyRouting.strategyKind).toBe(
|
|
496
|
+
"conversation-relay-native",
|
|
497
|
+
);
|
|
498
|
+
expect(entry.telephonyRouting.twilioNativeMapping?.provider).toBe(
|
|
499
|
+
"Deepgram",
|
|
500
|
+
);
|
|
501
|
+
expect(entry.telephonyRouting.twilioNativeMapping?.defaultSpeechModel).toBe(
|
|
502
|
+
"nova-3",
|
|
503
|
+
);
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
test("google-gemini routing metadata maps to Twilio-native Google with no default speech model", () => {
|
|
507
|
+
const entry = getProviderEntry("google-gemini")!;
|
|
508
|
+
expect(entry.telephonyRouting.strategyKind).toBe(
|
|
509
|
+
"conversation-relay-native",
|
|
510
|
+
);
|
|
511
|
+
expect(entry.telephonyRouting.twilioNativeMapping?.provider).toBe("Google");
|
|
512
|
+
expect(
|
|
513
|
+
entry.telephonyRouting.twilioNativeMapping?.defaultSpeechModel,
|
|
514
|
+
).toBeUndefined();
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
test("openai-whisper routing metadata uses media-stream-custom without Twilio mapping", () => {
|
|
518
|
+
const entry = getProviderEntry("openai-whisper")!;
|
|
519
|
+
expect(entry.telephonyRouting.strategyKind).toBe("media-stream-custom");
|
|
520
|
+
expect(entry.telephonyRouting.twilioNativeMapping).toBeUndefined();
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
// -----------------------------------------------------------------------
|
|
524
|
+
// Stable provider identity
|
|
525
|
+
// -----------------------------------------------------------------------
|
|
526
|
+
|
|
527
|
+
test("provider IDs remain stable across catalog lookups", () => {
|
|
528
|
+
// Guard against accidental ID mutation or aliasing bugs.
|
|
529
|
+
for (const id of listProviderIds()) {
|
|
530
|
+
const entry = getProviderEntry(id);
|
|
531
|
+
expect(entry).toBeDefined();
|
|
532
|
+
expect(entry!.id).toBe(id);
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
test("capability resolver returns supported for all catalog providers with credentials", async () => {
|
|
537
|
+
// Verify that every provider in the catalog can resolve to "supported"
|
|
538
|
+
// when the correct credentials are present. This catches regressions
|
|
539
|
+
// where a catalog entry is added but the credential mapping is wrong.
|
|
540
|
+
const credentialMap: Record<string, string> = {
|
|
541
|
+
"openai-whisper": "openai",
|
|
542
|
+
deepgram: "deepgram",
|
|
543
|
+
"google-gemini": "gemini",
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
for (const id of listProviderIds()) {
|
|
547
|
+
const credKey = credentialMap[id];
|
|
548
|
+
expect(credKey).toBeDefined();
|
|
549
|
+
|
|
550
|
+
mockProviderKeys = { [credKey]: `test-key-${id}` };
|
|
551
|
+
mockConfig = buildConfig({ provider: id });
|
|
552
|
+
|
|
553
|
+
const result = await resolveTelephonySttCapability();
|
|
554
|
+
expect(result.status).toBe("supported");
|
|
555
|
+
if (result.status === "supported") {
|
|
556
|
+
expect(result.providerId).toBe(id);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
// ---------------------------------------------------------------------------
|
|
563
|
+
// Tests — resolveConversationStreamingSttCapability
|
|
564
|
+
// ---------------------------------------------------------------------------
|
|
565
|
+
|
|
566
|
+
describe("resolveConversationStreamingSttCapability", () => {
|
|
567
|
+
beforeEach(() => {
|
|
568
|
+
mockConfig = buildConfig({});
|
|
569
|
+
mockProviderKeys = {};
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
// -------------------------------------------------------------------------
|
|
573
|
+
// Deepgram — realtime-ws streaming
|
|
574
|
+
// -------------------------------------------------------------------------
|
|
575
|
+
|
|
576
|
+
test("returns 'supported' with realtime-ws mode for deepgram", async () => {
|
|
577
|
+
mockProviderKeys["deepgram"] = "dg-stream-key";
|
|
578
|
+
mockConfig = buildConfig({ provider: "deepgram" });
|
|
579
|
+
|
|
580
|
+
const result = await resolveConversationStreamingSttCapability();
|
|
581
|
+
|
|
582
|
+
expect(result.status).toBe("supported");
|
|
583
|
+
if (result.status === "supported") {
|
|
584
|
+
expect(result.providerId).toBe("deepgram");
|
|
585
|
+
expect(result.streamingMode).toBe("realtime-ws");
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
test("returns 'missing-credentials' for deepgram without an API key", async () => {
|
|
590
|
+
mockProviderKeys = {};
|
|
591
|
+
mockConfig = buildConfig({ provider: "deepgram" });
|
|
592
|
+
|
|
593
|
+
const result = await resolveConversationStreamingSttCapability();
|
|
594
|
+
|
|
595
|
+
expect(result.status).toBe("missing-credentials");
|
|
596
|
+
if (result.status === "missing-credentials") {
|
|
597
|
+
expect(result.providerId).toBe("deepgram");
|
|
598
|
+
expect(result.credentialProvider).toBe("deepgram");
|
|
599
|
+
expect(result.reason).toContain("deepgram");
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
// -------------------------------------------------------------------------
|
|
604
|
+
// Google Gemini — realtime-ws streaming (Live API)
|
|
605
|
+
// -------------------------------------------------------------------------
|
|
606
|
+
|
|
607
|
+
test("returns 'supported' with realtime-ws mode for google-gemini", async () => {
|
|
608
|
+
mockProviderKeys["gemini"] = "gemini-stream-key";
|
|
609
|
+
mockConfig = buildConfig({ provider: "google-gemini" });
|
|
610
|
+
|
|
611
|
+
const result = await resolveConversationStreamingSttCapability();
|
|
612
|
+
|
|
613
|
+
expect(result.status).toBe("supported");
|
|
614
|
+
if (result.status === "supported") {
|
|
615
|
+
expect(result.providerId).toBe("google-gemini");
|
|
616
|
+
expect(result.streamingMode).toBe("realtime-ws");
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
test("returns 'missing-credentials' for google-gemini without a gemini key", async () => {
|
|
621
|
+
mockProviderKeys = {};
|
|
622
|
+
mockConfig = buildConfig({ provider: "google-gemini" });
|
|
623
|
+
|
|
624
|
+
const result = await resolveConversationStreamingSttCapability();
|
|
625
|
+
|
|
626
|
+
expect(result.status).toBe("missing-credentials");
|
|
627
|
+
if (result.status === "missing-credentials") {
|
|
628
|
+
expect(result.providerId).toBe("google-gemini");
|
|
629
|
+
expect(result.credentialProvider).toBe("gemini");
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
// -------------------------------------------------------------------------
|
|
634
|
+
// OpenAI Whisper — incremental-batch streaming
|
|
635
|
+
// -------------------------------------------------------------------------
|
|
636
|
+
|
|
637
|
+
test("returns 'supported' with incremental-batch mode for openai-whisper", async () => {
|
|
638
|
+
mockProviderKeys["openai"] = "sk-stream-test";
|
|
639
|
+
mockConfig = buildConfig({ provider: "openai-whisper" });
|
|
640
|
+
|
|
641
|
+
const result = await resolveConversationStreamingSttCapability();
|
|
642
|
+
|
|
643
|
+
expect(result.status).toBe("supported");
|
|
644
|
+
if (result.status === "supported") {
|
|
645
|
+
expect(result.providerId).toBe("openai-whisper");
|
|
646
|
+
expect(result.streamingMode).toBe("incremental-batch");
|
|
647
|
+
}
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
test("returns 'missing-credentials' for openai-whisper without an API key", async () => {
|
|
651
|
+
mockProviderKeys = {};
|
|
652
|
+
mockConfig = buildConfig({ provider: "openai-whisper" });
|
|
653
|
+
|
|
654
|
+
const result = await resolveConversationStreamingSttCapability();
|
|
655
|
+
|
|
656
|
+
expect(result.status).toBe("missing-credentials");
|
|
657
|
+
if (result.status === "missing-credentials") {
|
|
658
|
+
expect(result.providerId).toBe("openai-whisper");
|
|
659
|
+
expect(result.credentialProvider).toBe("openai");
|
|
660
|
+
expect(result.reason).toContain("openai");
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
// -------------------------------------------------------------------------
|
|
665
|
+
// Unknown / unconfigured provider
|
|
666
|
+
// -------------------------------------------------------------------------
|
|
667
|
+
|
|
668
|
+
test("returns 'unconfigured' when provider is not in the catalog", async () => {
|
|
669
|
+
mockProviderKeys["unknown-provider"] = "key-doesnt-matter";
|
|
670
|
+
mockConfig = buildConfig({ provider: "unknown-provider" as string });
|
|
671
|
+
|
|
672
|
+
const result = await resolveConversationStreamingSttCapability();
|
|
673
|
+
|
|
674
|
+
expect(result.status).toBe("unconfigured");
|
|
675
|
+
if (result.status === "unconfigured") {
|
|
676
|
+
expect(result.reason).toContain("unknown-provider");
|
|
677
|
+
expect(result.reason).toContain("not in the provider catalog");
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
test("returns 'unconfigured' for empty-string provider", async () => {
|
|
682
|
+
mockConfig = buildConfig({ provider: "" as string });
|
|
683
|
+
|
|
684
|
+
const result = await resolveConversationStreamingSttCapability();
|
|
685
|
+
|
|
686
|
+
expect(result.status).toBe("unconfigured");
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
// -------------------------------------------------------------------------
|
|
690
|
+
// Config-driven behaviour
|
|
691
|
+
// -------------------------------------------------------------------------
|
|
692
|
+
|
|
693
|
+
test("uses config-driven provider, not a hardcoded default", async () => {
|
|
694
|
+
mockProviderKeys["deepgram"] = "dg-config-test";
|
|
695
|
+
mockConfig = buildConfig({ provider: "deepgram" });
|
|
696
|
+
|
|
697
|
+
const result = await resolveConversationStreamingSttCapability();
|
|
698
|
+
|
|
699
|
+
expect(result.status).toBe("supported");
|
|
700
|
+
if (result.status === "supported") {
|
|
701
|
+
expect(result.providerId).toBe("deepgram");
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
// ---------------------------------------------------------------------------
|
|
707
|
+
// Tests — resolveStreamingTranscriber (diarize preference)
|
|
708
|
+
// ---------------------------------------------------------------------------
|
|
709
|
+
|
|
710
|
+
describe("resolveStreamingTranscriber diarize preference", () => {
|
|
711
|
+
beforeEach(() => {
|
|
712
|
+
mockConfig = buildConfig({});
|
|
713
|
+
mockProviderKeys = {};
|
|
714
|
+
deepgramCtorCalls.length = 0;
|
|
715
|
+
geminiCtorCalls.length = 0;
|
|
716
|
+
whisperCtorCalls.length = 0;
|
|
717
|
+
loggerWarnings.length = 0;
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
test("default (no diarize option) constructs Deepgram without the diarize flag", async () => {
|
|
721
|
+
mockProviderKeys["deepgram"] = "dg-key";
|
|
722
|
+
mockConfig = buildConfig({ provider: "deepgram" });
|
|
723
|
+
|
|
724
|
+
const transcriber = await resolveStreamingTranscriber();
|
|
725
|
+
|
|
726
|
+
expect(transcriber).not.toBeNull();
|
|
727
|
+
expect(deepgramCtorCalls).toHaveLength(1);
|
|
728
|
+
const options = deepgramCtorCalls[0]!.options as Record<string, unknown>;
|
|
729
|
+
expect(options).not.toHaveProperty("diarize");
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
test("diarize: 'off' constructs Deepgram without the diarize flag", async () => {
|
|
733
|
+
mockProviderKeys["deepgram"] = "dg-key";
|
|
734
|
+
mockConfig = buildConfig({ provider: "deepgram" });
|
|
735
|
+
|
|
736
|
+
const transcriber = await resolveStreamingTranscriber({ diarize: "off" });
|
|
737
|
+
|
|
738
|
+
expect(transcriber).not.toBeNull();
|
|
739
|
+
expect(deepgramCtorCalls).toHaveLength(1);
|
|
740
|
+
const options = deepgramCtorCalls[0]!.options as Record<string, unknown>;
|
|
741
|
+
expect(options).not.toHaveProperty("diarize");
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
test("diarize: 'preferred' with Deepgram constructs the transcriber with diarize: true", async () => {
|
|
745
|
+
mockProviderKeys["deepgram"] = "dg-key";
|
|
746
|
+
mockConfig = buildConfig({ provider: "deepgram" });
|
|
747
|
+
|
|
748
|
+
const transcriber = await resolveStreamingTranscriber({
|
|
749
|
+
diarize: "preferred",
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
expect(transcriber).not.toBeNull();
|
|
753
|
+
expect(deepgramCtorCalls).toHaveLength(1);
|
|
754
|
+
const options = deepgramCtorCalls[0]!.options as Record<string, unknown>;
|
|
755
|
+
expect(options.diarize).toBe(true);
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
test("diarize: 'preferred' with Gemini silently skips diarization (no error, no diarize arg)", async () => {
|
|
759
|
+
mockProviderKeys["gemini"] = "gemini-key";
|
|
760
|
+
mockConfig = buildConfig({ provider: "google-gemini" });
|
|
761
|
+
|
|
762
|
+
const transcriber = await resolveStreamingTranscriber({
|
|
763
|
+
diarize: "preferred",
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
expect(transcriber).not.toBeNull();
|
|
767
|
+
expect(geminiCtorCalls).toHaveLength(1);
|
|
768
|
+
const options = geminiCtorCalls[0]!.options as Record<string, unknown>;
|
|
769
|
+
// Gemini never receives a diarize option — the resolver silently skips.
|
|
770
|
+
expect(options).not.toHaveProperty("diarize");
|
|
771
|
+
// No warning is logged for the silent-skip path.
|
|
772
|
+
expect(loggerWarnings).toHaveLength(0);
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
test("diarize: 'required' with Gemini returns null and logs a warning identifying the provider", async () => {
|
|
776
|
+
mockProviderKeys["gemini"] = "gemini-key";
|
|
777
|
+
mockConfig = buildConfig({ provider: "google-gemini" });
|
|
778
|
+
|
|
779
|
+
const transcriber = await resolveStreamingTranscriber({
|
|
780
|
+
diarize: "required",
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
expect(transcriber).toBeNull();
|
|
784
|
+
// No provider constructor was invoked.
|
|
785
|
+
expect(geminiCtorCalls).toHaveLength(0);
|
|
786
|
+
expect(deepgramCtorCalls).toHaveLength(0);
|
|
787
|
+
expect(whisperCtorCalls).toHaveLength(0);
|
|
788
|
+
// A warning was logged that identifies the configured provider so
|
|
789
|
+
// operators can debug mis-configured diarization requirements.
|
|
790
|
+
expect(loggerWarnings).toHaveLength(1);
|
|
791
|
+
const warning = loggerWarnings[0]!;
|
|
792
|
+
expect(warning.message).toContain("diarization");
|
|
793
|
+
expect((warning.data as { providerId?: unknown }).providerId).toBe(
|
|
794
|
+
"google-gemini",
|
|
795
|
+
);
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
test("diarize: 'required' with Deepgram constructs the transcriber with diarize: true", async () => {
|
|
799
|
+
mockProviderKeys["deepgram"] = "dg-key";
|
|
800
|
+
mockConfig = buildConfig({ provider: "deepgram" });
|
|
801
|
+
|
|
802
|
+
const transcriber = await resolveStreamingTranscriber({
|
|
803
|
+
diarize: "required",
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
expect(transcriber).not.toBeNull();
|
|
807
|
+
expect(deepgramCtorCalls).toHaveLength(1);
|
|
808
|
+
const options = deepgramCtorCalls[0]!.options as Record<string, unknown>;
|
|
809
|
+
expect(options.diarize).toBe(true);
|
|
810
|
+
// No warning logged on the happy path.
|
|
811
|
+
expect(loggerWarnings).toHaveLength(0);
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
test("sampleRate is still forwarded when diarize is enabled", async () => {
|
|
815
|
+
mockProviderKeys["deepgram"] = "dg-key";
|
|
816
|
+
mockConfig = buildConfig({ provider: "deepgram" });
|
|
817
|
+
|
|
818
|
+
await resolveStreamingTranscriber({
|
|
819
|
+
diarize: "preferred",
|
|
820
|
+
sampleRate: 48000,
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
expect(deepgramCtorCalls).toHaveLength(1);
|
|
824
|
+
const options = deepgramCtorCalls[0]!.options as Record<string, unknown>;
|
|
825
|
+
expect(options.sampleRate).toBe(48000);
|
|
826
|
+
expect(options.diarize).toBe(true);
|
|
827
|
+
});
|
|
828
|
+
});
|