@mycadet/assistant 0.12.10 → 0.13.0
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/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
- package/node_modules/@mycadet/ces-client/src/http-credentials.ts +18 -1
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
- package/node_modules/@mycadet/gateway-client/src/gateway-ipc-contracts.ts +0 -20
- package/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
- package/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
- package/openapi.yaml +2 -708
- package/package.json +2 -3
- package/scripts/sync-web-search-catalog.ts +1 -2
- package/src/__tests__/cloud-profile-guard.test.ts +41 -5
- package/src/__tests__/config-schema.test.ts +6 -38
- package/src/__tests__/conversation-queue.test.ts +45 -6
- package/src/__tests__/db-readiness-http-gate.test.ts +0 -6
- package/src/__tests__/persist-onboarding-artifacts.test.ts +0 -1
- package/src/__tests__/persona-resolver.test.ts +0 -51
- package/src/__tests__/provider-cloud-proxy-integration.test.ts +2 -110
- package/src/__tests__/run-due-schedules.test.ts +0 -4
- package/src/__tests__/schedule-store.test.ts +0 -12
- package/src/__tests__/stt-stream-session.test.ts +4 -4
- package/src/__tests__/telephony-tts-guard.test.ts +2 -50
- package/src/__tests__/voice-config-update.test.ts +12 -167
- package/src/__tests__/web-search-catalog-parity.test.ts +0 -2
- package/src/__tests__/workspace-migration-130-speech-mode-to-provider.test.ts +9 -7
- package/src/__tests__/workspace-migration-132-web-search-mode-to-provider.test.ts +6 -6
- package/src/calls/__tests__/voice-session-bridge.test.ts +1 -4
- package/src/calls/resolve-call-tts-provider.ts +6 -25
- package/src/calls/telephony-credential-preflight.ts +0 -10
- package/src/calls/telephony-tts-capability.ts +1 -17
- package/src/cli/commands/tts.ts +1 -2
- package/src/cli/index.help.ts +0 -2
- package/src/cli/program.ts +0 -2
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +4 -42
- package/src/config/schemas/__tests__/stt.test.ts +10 -14
- package/src/config/schemas/llm.ts +3 -5
- package/src/config/schemas/services.ts +5 -6
- package/src/config/schemas/stt.ts +1 -3
- package/src/config/schemas/tts.ts +1 -30
- package/src/daemon/daemon-readiness.ts +4 -29
- package/src/daemon/lifecycle.ts +0 -31
- package/src/daemon/providers-setup.ts +0 -8
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
- package/src/heartbeat/heartbeat-service.ts +1 -66
- package/src/ipc/__tests__/db-readiness-ipc-gate.test.ts +1 -9
- package/src/live-voice/__tests__/live-voice-credential-preflight.test.ts +0 -44
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +4 -6
- package/src/live-voice/__tests__/live-voice-stt.test.ts +1 -1
- package/src/live-voice/live-voice-credential-preflight.ts +9 -33
- package/src/live-voice/live-voice-session.ts +10 -6
- package/src/live-voice/live-voice-tts.ts +1 -8
- package/src/plugins/defaults/memory/worker.ts +3 -3
- package/src/prompts/persona-resolver.ts +0 -35
- package/src/providers/__tests__/search-provider-catalog.test.ts +5 -8
- package/src/providers/__tests__/voice-error-copy.test.ts +9 -10
- package/src/providers/search-provider-catalog.ts +7 -39
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +3 -47
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +6 -372
- package/src/providers/speech-to-text/provider-catalog.ts +2 -73
- package/src/providers/speech-to-text/resolve.ts +3 -120
- package/src/runtime/http-router-types.ts +1 -3
- package/src/runtime/http-server.ts +1 -1
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +13 -11
- package/src/runtime/routes/__tests__/tts-routes.test.ts +2 -6
- package/src/runtime/routes/conversation-query-routes.ts +0 -96
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +7 -7
- package/src/runtime/routes/index.ts +0 -4
- package/src/runtime/routes/live-voice-routes.test.ts +4 -11
- package/src/runtime/routes/live-voice-routes.ts +8 -11
- package/src/runtime/routes/secret-routes.ts +0 -6
- package/src/runtime/routes/tts-routes.ts +0 -38
- package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +1 -7
- package/src/schedule/__tests__/worker-mcp-readiness.test.ts +0 -4
- package/src/schedule/schedule-store.ts +0 -7
- package/src/schedule/scheduler.ts +0 -5
- package/src/schedule/worker.ts +1 -1
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +2 -10
- package/src/stt/daemon-batch-transcriber.ts +4 -38
- package/src/stt/types.ts +1 -9
- package/src/tools/network/__tests__/web-search.test.ts +3 -250
- package/src/tools/network/web-search.ts +4 -165
- package/src/tts/__tests__/provider-resolution.test.ts +0 -5
- package/src/tts/provider-catalog.ts +0 -4
- package/src/tts/types.ts +0 -1
- package/src/__tests__/cloud-speech-defaults.test.ts +0 -330
- package/src/__tests__/migration-cross-version-compatibility.test.ts +0 -1708
- package/src/__tests__/migration-export-http.test.ts +0 -557
- package/src/__tests__/migration-export-streaming.test.ts +0 -350
- package/src/__tests__/migration-export-to-gcs.test.ts +0 -706
- package/src/__tests__/migration-import-commit-http.test.ts +0 -1176
- package/src/__tests__/migration-import-from-gcs.test.ts +0 -534
- package/src/__tests__/migration-import-from-path.test.ts +0 -324
- package/src/__tests__/migration-import-from-url.test.ts +0 -588
- package/src/__tests__/migration-import-preflight-http.test.ts +0 -739
- package/src/__tests__/migration-jobs-status.test.ts +0 -139
- package/src/__tests__/migration-parity-persistence.test.ts +0 -1891
- package/src/__tests__/migration-transport.test.ts +0 -1089
- package/src/__tests__/migration-validate-http.test.ts +0 -634
- package/src/__tests__/migration-wizard.test.ts +0 -1395
- package/src/__tests__/rebind-secrets-screen.test.ts +0 -876
- package/src/__tests__/sanitize-config-for-transfer.test.ts +0 -259
- package/src/__tests__/transfer-progress-screen.test.ts +0 -1217
- package/src/__tests__/validation-results-screen.test.ts +0 -1130
- package/src/__tests__/vbundle-pax-and-symlink.test.ts +0 -147
- package/src/background-wake/background-wake-routes.test.ts +0 -906
- package/src/background-wake/cloud-client.test.ts +0 -328
- package/src/background-wake/cloud-client.ts +0 -196
- package/src/background-wake/next-wake.test.ts +0 -335
- package/src/background-wake/next-wake.ts +0 -188
- package/src/background-wake/publisher.ts +0 -93
- package/src/background-wake/wake-intent-hooks.test.ts +0 -234
- package/src/cli/commands/__tests__/backup.test.ts +0 -363
- package/src/cli/commands/backup.help.ts +0 -180
- package/src/cli/commands/backup.ts +0 -398
- package/src/cloud/cloud-speech.test.ts +0 -335
- package/src/cloud/cloud-speech.ts +0 -300
- package/src/config/cloud-speech-defaults.ts +0 -300
- package/src/config/sanitize-for-transfer.ts +0 -111
- package/src/live-voice/__tests__/live-voice-cloud-speech-fallback.test.ts +0 -250
- package/src/providers/speech-to-text/__tests__/cadet-cloud-realtime.test.ts +0 -457
- package/src/providers/speech-to-text/__tests__/cadet-cloud.test.ts +0 -271
- package/src/providers/speech-to-text/__tests__/cadet-speech-relay-connection.test.ts +0 -126
- package/src/providers/speech-to-text/cadet-cloud-flux-realtime.ts +0 -309
- package/src/providers/speech-to-text/cadet-cloud-realtime.ts +0 -315
- package/src/providers/speech-to-text/cadet-cloud.ts +0 -108
- package/src/providers/speech-to-text/cadet-speech-relay-connection.ts +0 -176
- package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +0 -254
- package/src/runtime/migrations/__tests__/job-registry.test.ts +0 -346
- package/src/runtime/migrations/__tests__/origin-mode.test.ts +0 -62
- package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +0 -184
- package/src/runtime/migrations/__tests__/staged-import-path.test.ts +0 -110
- package/src/runtime/migrations/__tests__/v1-test-helpers.ts +0 -112
- package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +0 -285
- package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +0 -126
- package/src/runtime/migrations/__tests__/vbundle-builder-v1-shape.test.ts +0 -253
- package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +0 -208
- package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +0 -485
- package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +0 -349
- package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +0 -196
- package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +0 -551
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +0 -428
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +0 -220
- package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +0 -1823
- package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +0 -598
- package/src/runtime/migrations/__tests__/vbundle-symlink-importer.test.ts +0 -463
- package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -538
- package/src/runtime/migrations/__tests__/vbundle-symlink-streaming.test.ts +0 -515
- package/src/runtime/migrations/__tests__/vbundle-symlink-tar.test.ts +0 -453
- package/src/runtime/migrations/__tests__/vbundle-symlink-walker.test.ts +0 -319
- package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +0 -240
- package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +0 -473
- package/src/runtime/migrations/gcs-signed-url.ts +0 -190
- package/src/runtime/migrations/job-registry.ts +0 -297
- package/src/runtime/migrations/migration-transport.ts +0 -612
- package/src/runtime/migrations/migration-wizard.ts +0 -747
- package/src/runtime/migrations/origin-mode.ts +0 -42
- package/src/runtime/migrations/rebind-secrets-screen.ts +0 -412
- package/src/runtime/migrations/staged-import-path.ts +0 -116
- package/src/runtime/migrations/transfer-progress-screen.ts +0 -323
- package/src/runtime/migrations/validation-results-screen.ts +0 -469
- package/src/runtime/migrations/vbundle-builder.ts +0 -1382
- package/src/runtime/migrations/vbundle-import-analyzer.ts +0 -405
- package/src/runtime/migrations/vbundle-import-policy.ts +0 -241
- package/src/runtime/migrations/vbundle-importer.ts +0 -826
- package/src/runtime/migrations/vbundle-metadata-merge.ts +0 -113
- package/src/runtime/migrations/vbundle-streaming-importer.ts +0 -2844
- package/src/runtime/migrations/vbundle-streaming-validator.ts +0 -426
- package/src/runtime/migrations/vbundle-tar-stream.ts +0 -244
- package/src/runtime/migrations/vbundle-validator.ts +0 -724
- package/src/runtime/routes/__tests__/migration-cadet-metadata-reconcile.test.ts +0 -227
- package/src/runtime/routes/__tests__/migration-export-secrets-redacted.test.ts +0 -54
- package/src/runtime/routes/__tests__/migration-import-allowed-hosts-env.test.ts +0 -70
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +0 -257
- package/src/runtime/routes/background-wake-routes.ts +0 -387
- package/src/runtime/routes/migration-routes.ts +0 -2584
- package/src/tools/network/__tests__/cloud-search-proxy.test.ts +0 -282
- package/src/tools/network/cloud-search-proxy.ts +0 -211
- package/src/tts/__tests__/cadet-provider.test.ts +0 -253
- package/src/tts/providers/cadet-provider.ts +0 -244
- package/src/types/tar-stream.d.ts +0 -66
|
@@ -1,300 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Managed-speech defaulting.
|
|
3
|
-
*
|
|
4
|
-
* When the cloud connection is usable and a speech service has no working
|
|
5
|
-
* BYOK credential, that service's effective provider is `"cadet"` so a fresh
|
|
6
|
-
* Cadet connection gets voice features with zero configuration. A service
|
|
7
|
-
* whose BYOK credential is configured is never redirected — connecting Cadet
|
|
8
|
-
* must not silently reroute an existing voice setup — and a service already on
|
|
9
|
-
* Cadet is left alone.
|
|
10
|
-
*
|
|
11
|
-
* {@link resolveEffectiveSpeechProviders} is the single definition of that
|
|
12
|
-
* rule. It writes nothing, so runtime paths that only need to know which
|
|
13
|
-
* provider they will actually use (live-voice readiness, transcription,
|
|
14
|
-
* synthesis) resolve the same ids the scope-gated writer
|
|
15
|
-
* {@link maybeDefaultSpeechToCloud} would persist, without needing write
|
|
16
|
-
* access to the config.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import { ttsSecretResolves } from "../calls/telephony-tts-capability.js";
|
|
20
|
-
import { managedSpeechAvailable } from "../cloud/cloud-speech.js";
|
|
21
|
-
import { fluxModelForLanguage } from "../providers/speech-to-text/deepgram-flux-frames.js";
|
|
22
|
-
import {
|
|
23
|
-
baseModelFamilyFor,
|
|
24
|
-
getProviderEntry,
|
|
25
|
-
isCloudSttProvider,
|
|
26
|
-
resolveSttCatalogKey,
|
|
27
|
-
sttConfigForCatalogKey,
|
|
28
|
-
supportsProviderTurnDetection,
|
|
29
|
-
} from "../providers/speech-to-text/provider-catalog.js";
|
|
30
|
-
import { sttProviderKeyResolves } from "../providers/speech-to-text/resolve.js";
|
|
31
|
-
import {
|
|
32
|
-
STT_ROLES,
|
|
33
|
-
sttCatalogKeyForRole,
|
|
34
|
-
type SttRole,
|
|
35
|
-
sttRoleCapabilityGap,
|
|
36
|
-
sttSelectionForRole,
|
|
37
|
-
} from "../stt/roles.js";
|
|
38
|
-
import type { SttProviderId } from "../stt/types.js";
|
|
39
|
-
import { getCatalogProvider } from "../tts/provider-catalog.js";
|
|
40
|
-
import type { TtsProviderId } from "../tts/types.js";
|
|
41
|
-
import { getLogger } from "../util/logger.js";
|
|
42
|
-
import {
|
|
43
|
-
getConfig,
|
|
44
|
-
invalidateConfigCache,
|
|
45
|
-
loadRawConfig,
|
|
46
|
-
saveRawConfig,
|
|
47
|
-
setNestedValue,
|
|
48
|
-
} from "./loader.js";
|
|
49
|
-
import type { AssistantConfig } from "./types.js";
|
|
50
|
-
|
|
51
|
-
const log = getLogger("cloud-speech-defaults");
|
|
52
|
-
|
|
53
|
-
/** Whether the configured BYOK STT provider has a usable credential. */
|
|
54
|
-
async function sttByokCredentialResolves(provider: string): Promise<boolean> {
|
|
55
|
-
const entry = getProviderEntry(provider as SttProviderId);
|
|
56
|
-
if (!entry) {
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
return sttProviderKeyResolves(entry.credentialProvider);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** Whether the configured BYOK TTS provider has all its secrets. */
|
|
63
|
-
async function ttsByokCredentialsResolve(provider: string): Promise<boolean> {
|
|
64
|
-
let entry;
|
|
65
|
-
try {
|
|
66
|
-
entry = getCatalogProvider(provider);
|
|
67
|
-
} catch {
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
for (const secret of entry.secretRequirements) {
|
|
71
|
-
if (!(await ttsSecretResolves(secret.credentialStoreKey))) {
|
|
72
|
-
return false;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return true;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* The cloud provider that stands in for a configured one whose credential
|
|
80
|
-
* did not resolve.
|
|
81
|
-
*
|
|
82
|
-
* Cloud speech has two STT entries, and they are not interchangeable:
|
|
83
|
-
* `cadet-flux` decides end-of-turn itself, `cadet` leaves it to the
|
|
84
|
-
* session's silence timer. Substituting the plain one for a provider that had
|
|
85
|
-
* provider turn detection would keep live voice working while quietly
|
|
86
|
-
* changing how it takes turns, which reads as "Flux is no better" rather than
|
|
87
|
-
* as a missing credential. Match the capability instead.
|
|
88
|
-
*/
|
|
89
|
-
function managedStandInFor(
|
|
90
|
-
configured: SttProviderId,
|
|
91
|
-
language: string | undefined,
|
|
92
|
-
): SttProviderId {
|
|
93
|
-
if (!supportsProviderTurnDetection(configured)) {
|
|
94
|
-
return "cadet";
|
|
95
|
-
}
|
|
96
|
-
// Flux has a model for ten languages. Outside them the relay rejects the
|
|
97
|
-
// dial outright, so standing in with it would hand the speaker a mic that
|
|
98
|
-
// does not work at all rather than one that detects turns less well. The
|
|
99
|
-
// language is the one the resolver will actually send.
|
|
100
|
-
return fluxModelForLanguage(language) === null ? "cadet" : "cadet-flux";
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/** The speech providers a runtime path uses, after cloud-speech defaulting. */
|
|
104
|
-
export interface EffectiveSpeechProviders {
|
|
105
|
-
stt: SttProviderId;
|
|
106
|
-
tts: TtsProviderId;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Resolve the speech providers the runtime actually uses.
|
|
111
|
-
*
|
|
112
|
-
* A configured service whose BYOK credential does not resolve is reported as
|
|
113
|
-
* its managed stand-in while cloud speech is available (see
|
|
114
|
-
* {@link managedStandInFor}, which preserves provider turn detection); every
|
|
115
|
-
* other service keeps its configured provider. Read-only: callers that hold no `settings.write`
|
|
116
|
-
* scope (the live-voice WebSocket transport) resolve the same verdict the
|
|
117
|
-
* preflight route does without persisting anything.
|
|
118
|
-
*
|
|
119
|
-
* `config` selects the configuration to read the configured providers from,
|
|
120
|
-
* for callers already holding one (defaults to the loaded config). `role`
|
|
121
|
-
* selects which consumer's STT override applies; omitting it reads the global
|
|
122
|
-
* provider.
|
|
123
|
-
*/
|
|
124
|
-
export async function resolveEffectiveSpeechProviders(
|
|
125
|
-
config?: AssistantConfig,
|
|
126
|
-
options: { role?: SttRole } = {},
|
|
127
|
-
): Promise<EffectiveSpeechProviders> {
|
|
128
|
-
const services = (config ?? getConfig()).services;
|
|
129
|
-
const configuredStt = sttCatalogKeyForRole(services.stt, options.role);
|
|
130
|
-
const configuredTts = services.tts.provider as TtsProviderId;
|
|
131
|
-
|
|
132
|
-
if (!(await managedSpeechAvailable())) {
|
|
133
|
-
return { stt: configuredStt, tts: configuredTts };
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const stt =
|
|
137
|
-
!isCloudSttProvider(configuredStt) &&
|
|
138
|
-
!(await sttByokCredentialResolves(configuredStt))
|
|
139
|
-
? managedStandInFor(configuredStt, services.stt.language)
|
|
140
|
-
: configuredStt;
|
|
141
|
-
|
|
142
|
-
const tts =
|
|
143
|
-
configuredTts !== "cadet" &&
|
|
144
|
-
!(await ttsByokCredentialsResolve(configuredTts))
|
|
145
|
-
? "cadet"
|
|
146
|
-
: configuredTts;
|
|
147
|
-
|
|
148
|
-
return {
|
|
149
|
-
stt: managedLiveVoiceModelFamily(stt, options.role, services.stt),
|
|
150
|
-
tts,
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Managed live voice runs Flux.
|
|
156
|
-
*
|
|
157
|
-
* Turn detection is the reason to reach for Flux at all, and managed users
|
|
158
|
-
* cannot ask for it: the provider picker offers no model family, so `cadet`
|
|
159
|
-
* is as specific as they can be. Deciding it here rather than persisting a
|
|
160
|
-
* `services.stt.roles.liveVoice` entry keeps managed opinionated without
|
|
161
|
-
* writing config on anyone's behalf, and reaches installs already on `cadet`
|
|
162
|
-
* that a substitution rule never sees.
|
|
163
|
-
*
|
|
164
|
-
* Two things still win over it. A family the user named is honoured, because
|
|
165
|
-
* `services.stt.providers.cadet.model` accepts `nova-3` and quietly ignoring
|
|
166
|
-
* a valid setting is the silent substitution roles exist to prevent. And a
|
|
167
|
-
* language outside Flux's roster stays on nova-3, since the relay refuses the
|
|
168
|
-
* dial rather than degrading (see `managedStandInFor`).
|
|
169
|
-
*
|
|
170
|
-
* Live voice only: Flux streams and nothing else, so every other consumer
|
|
171
|
-
* would lose its transcriber.
|
|
172
|
-
*/
|
|
173
|
-
function managedLiveVoiceModelFamily(
|
|
174
|
-
resolved: SttProviderId,
|
|
175
|
-
role: SttRole | undefined,
|
|
176
|
-
stt: AssistantConfig["services"]["stt"],
|
|
177
|
-
): SttProviderId {
|
|
178
|
-
if (role !== "liveVoice" || resolved !== "cadet") {
|
|
179
|
-
return resolved;
|
|
180
|
-
}
|
|
181
|
-
if (sttSelectionForRole(stt, role).model !== undefined) {
|
|
182
|
-
return resolved;
|
|
183
|
-
}
|
|
184
|
-
return fluxModelForLanguage(stt.language) === null ? "cadet" : "cadet-flux";
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Whether a provider can serve every consumer, or only the one that chose it.
|
|
189
|
-
*
|
|
190
|
-
* `cadet` covers every boundary, so substituting it is safe everywhere. A
|
|
191
|
-
* narrowing row like `cadet-flux` streams and nothing else, and the roles it
|
|
192
|
-
* fails are exactly the consumers that must not be moved onto it.
|
|
193
|
-
*/
|
|
194
|
-
function servesEveryRole(provider: SttProviderId): boolean {
|
|
195
|
-
const selection = sttConfigForCatalogKey(provider);
|
|
196
|
-
return STT_ROLES.every(
|
|
197
|
-
(role) => sttRoleCapabilityGap(role, selection) === null,
|
|
198
|
-
);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* The writes that put `selection` in force as the global provider.
|
|
203
|
-
*
|
|
204
|
-
* The family is always written, including the base one. Substituting away
|
|
205
|
-
* from a variant leaves its `model` behind otherwise, and the next load
|
|
206
|
-
* resolves straight back to the variant this substitution just rejected: the
|
|
207
|
-
* provider would read as plain cadet while running Flux, with batch and
|
|
208
|
-
* telephony quietly unavailable. A provider with no families has no name to
|
|
209
|
-
* write, and no variant to have left a stale value behind either.
|
|
210
|
-
*/
|
|
211
|
-
function globalSttUpdates(selection: {
|
|
212
|
-
provider: string;
|
|
213
|
-
model?: string | undefined;
|
|
214
|
-
}): { path: string; provider: string }[] {
|
|
215
|
-
const updates = [
|
|
216
|
-
{ path: "services.stt.provider", provider: selection.provider },
|
|
217
|
-
];
|
|
218
|
-
const family =
|
|
219
|
-
selection.model ?? baseModelFamilyFor(selection.provider as SttProviderId);
|
|
220
|
-
if (family !== undefined) {
|
|
221
|
-
updates.push({
|
|
222
|
-
path: `services.stt.providers.${selection.provider}.model`,
|
|
223
|
-
provider: family,
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
return updates;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Persist the effective speech providers resolved by
|
|
231
|
-
* {@link resolveEffectiveSpeechProviders} whenever they differ from the
|
|
232
|
-
* configured ones.
|
|
233
|
-
*
|
|
234
|
-
* Safe to call repeatedly (idempotent) and safe to fire-and-forget. Callers
|
|
235
|
-
* must hold the `settings.write` scope — this is the only path that writes
|
|
236
|
-
* `services.stt/tts.provider` on behalf of cloud-speech defaulting.
|
|
237
|
-
*/
|
|
238
|
-
export async function maybeDefaultSpeechToCloud(): Promise<void> {
|
|
239
|
-
try {
|
|
240
|
-
const services = getConfig().services;
|
|
241
|
-
const effective = await resolveEffectiveSpeechProviders();
|
|
242
|
-
|
|
243
|
-
const updates: { path: string; provider: string }[] = [];
|
|
244
|
-
if (effective.stt !== resolveSttCatalogKey(services.stt)) {
|
|
245
|
-
// A variant row is not a valid services.stt.provider value, so persist
|
|
246
|
-
// the pair that selects it. Writing the key itself would put an id the
|
|
247
|
-
// schema rejects on disk, and an unparseable services block is how the
|
|
248
|
-
// loader's salvage ladder ends up resetting the whole section.
|
|
249
|
-
const standIn = sttConfigForCatalogKey(effective.stt);
|
|
250
|
-
if (servesEveryRole(effective.stt)) {
|
|
251
|
-
updates.push(...globalSttUpdates(standIn));
|
|
252
|
-
} else {
|
|
253
|
-
// A stand-in that cannot serve every consumer belongs to the one that
|
|
254
|
-
// chose it. Live voice is that consumer: provider turn detection is
|
|
255
|
-
// the only reason managedStandInFor reaches for a narrow row, and
|
|
256
|
-
// writing that row globally would take batch transcription and
|
|
257
|
-
// telephony from every other consumer in a single write.
|
|
258
|
-
updates.push({
|
|
259
|
-
path: "services.stt.roles.liveVoice.provider",
|
|
260
|
-
provider: standIn.provider,
|
|
261
|
-
});
|
|
262
|
-
if (standIn.model !== undefined) {
|
|
263
|
-
updates.push({
|
|
264
|
-
path: "services.stt.roles.liveVoice.model",
|
|
265
|
-
provider: standIn.model,
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
// The other consumers still need a provider that answers, and the
|
|
269
|
-
// narrow stand-in's own base is the cloud row that serves them.
|
|
270
|
-
if (servesEveryRole(standIn.provider as SttProviderId)) {
|
|
271
|
-
updates.push(...globalSttUpdates({ provider: standIn.provider }));
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
if (effective.tts !== services.tts.provider) {
|
|
276
|
-
updates.push({ path: "services.tts.provider", provider: effective.tts });
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
if (updates.length === 0) {
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const raw = loadRawConfig();
|
|
284
|
-
for (const { path, provider } of updates) {
|
|
285
|
-
setNestedValue(raw, path, provider);
|
|
286
|
-
}
|
|
287
|
-
saveRawConfig(raw);
|
|
288
|
-
invalidateConfigCache();
|
|
289
|
-
log.info(
|
|
290
|
-
{ defaulted: updates.map(({ path }) => path) },
|
|
291
|
-
"Defaulted unconfigured speech services to the Cadet provider after connection",
|
|
292
|
-
);
|
|
293
|
-
} catch (err) {
|
|
294
|
-
// Convenience defaulting must never break credential storage.
|
|
295
|
-
log.warn(
|
|
296
|
-
{ error: err instanceof Error ? err.message : String(err) },
|
|
297
|
-
"Cloud speech defaulting failed (non-fatal)",
|
|
298
|
-
);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
INGRESS_ASSISTANT_ID_KEY,
|
|
3
|
-
INGRESS_LAST_TUNNEL_KEY,
|
|
4
|
-
INGRESS_PAIRING_TUNNEL_KEY,
|
|
5
|
-
} from "@mycadet/service-contracts/ingress";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Strips environment-specific fields from config JSON before transferring
|
|
9
|
-
* between local and platform environments (teleport/restore).
|
|
10
|
-
*
|
|
11
|
-
* Fields removed or reset:
|
|
12
|
-
* - `ingress.publicBaseUrl` → set to `""`
|
|
13
|
-
* - `ingress.enabled` → deleted
|
|
14
|
-
* - `ingress.publicBaseUrlManagedBy` → deleted
|
|
15
|
-
* - `ingress.lastTunnel` → deleted
|
|
16
|
-
* - `ingress.pairingTunnel` → deleted
|
|
17
|
-
* - `ingress.assistantId` → deleted
|
|
18
|
-
* - `telegram.registeredWebhookUrl` → deleted
|
|
19
|
-
* - `daemon` → deleted entirely
|
|
20
|
-
* - `skills.load.extraDirs` → set to `[]`
|
|
21
|
-
* - `hostBrowser.cdpInspect.desktopAuto` → deleted **only when the source
|
|
22
|
-
* either relies on the schema default or explicitly sets
|
|
23
|
-
* `enabled: true`**. An explicit `enabled: false` is preserved so a
|
|
24
|
-
* platform→local teleport doesn't silently re-enable auto-attach
|
|
25
|
-
* against the user's opt-out.
|
|
26
|
-
*
|
|
27
|
-
* `logFile.dir` is intentionally *not* stripped: the logger's container
|
|
28
|
-
* fallback (`util/logger.ts#resolveLogDir`) already redirects to the
|
|
29
|
-
* default log dir with a warning when the configured path can't be
|
|
30
|
-
* created, and stripping `dir` would disable rotating file logging
|
|
31
|
-
* entirely because `lifecycle.ts` gates `initLogger` on a truthy
|
|
32
|
-
* `config.logFile.dir`.
|
|
33
|
-
*/
|
|
34
|
-
export function sanitizeConfigForTransfer(configJson: string): string {
|
|
35
|
-
let config: Record<string, unknown>;
|
|
36
|
-
try {
|
|
37
|
-
const parsed = JSON.parse(configJson);
|
|
38
|
-
if (
|
|
39
|
-
typeof parsed !== "object" ||
|
|
40
|
-
parsed === null ||
|
|
41
|
-
Array.isArray(parsed)
|
|
42
|
-
) {
|
|
43
|
-
return configJson;
|
|
44
|
-
}
|
|
45
|
-
config = parsed;
|
|
46
|
-
} catch {
|
|
47
|
-
return configJson;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Strip ingress environment-specific fields
|
|
51
|
-
if (config.ingress && typeof config.ingress === "object") {
|
|
52
|
-
const ingress = config.ingress as Record<string, unknown>;
|
|
53
|
-
ingress.publicBaseUrl = "";
|
|
54
|
-
delete ingress.enabled;
|
|
55
|
-
delete ingress.publicBaseUrlManagedBy;
|
|
56
|
-
// The remembered tunnels and the assistant they fronted belong to the
|
|
57
|
-
// source host; carrying them over makes tunnel-status UIs name the old
|
|
58
|
-
// deployment, and the pairing record would have the destination advertise
|
|
59
|
-
// an address on the source's tailnet.
|
|
60
|
-
delete ingress[INGRESS_LAST_TUNNEL_KEY];
|
|
61
|
-
delete ingress[INGRESS_PAIRING_TUNNEL_KEY];
|
|
62
|
-
delete ingress[INGRESS_ASSISTANT_ID_KEY];
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Strip the recorded Telegram webhook URL. It records where *this*
|
|
66
|
-
// deployment pointed Telegram, so carrying it across a teleport would have
|
|
67
|
-
// the destination compare its own registration against the source's address
|
|
68
|
-
// and report a mismatch on a channel that is fine. Absent, the health sweep
|
|
69
|
-
// reports unverified until the destination reconciles and records its own.
|
|
70
|
-
if (config.telegram && typeof config.telegram === "object") {
|
|
71
|
-
const telegram = config.telegram as Record<string, unknown>;
|
|
72
|
-
delete telegram.registeredWebhookUrl;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Strip daemon entirely
|
|
76
|
-
delete config.daemon;
|
|
77
|
-
|
|
78
|
-
// Strip skills.load.extraDirs
|
|
79
|
-
if (config.skills && typeof config.skills === "object") {
|
|
80
|
-
const skills = config.skills as Record<string, unknown>;
|
|
81
|
-
if (skills.load && typeof skills.load === "object") {
|
|
82
|
-
const load = skills.load as Record<string, unknown>;
|
|
83
|
-
load.extraDirs = [];
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Strip hostBrowser.cdpInspect.desktopAuto — the auto-attach-to-Chrome
|
|
88
|
-
// behavior is gated on a macOS-originated turn; preserving a
|
|
89
|
-
// source-host-derived `enabled: true` inside a Linux managed pod's
|
|
90
|
-
// config is misleading and brittle. Preserve an explicit
|
|
91
|
-
// `enabled: false` opt-out, though — the schema default is `true`,
|
|
92
|
-
// so unconditionally stripping this subobject would re-enable
|
|
93
|
-
// auto-attach after a cloud→local teleport.
|
|
94
|
-
if (config.hostBrowser && typeof config.hostBrowser === "object") {
|
|
95
|
-
const hostBrowser = config.hostBrowser as Record<string, unknown>;
|
|
96
|
-
if (hostBrowser.cdpInspect && typeof hostBrowser.cdpInspect === "object") {
|
|
97
|
-
const cdpInspect = hostBrowser.cdpInspect as Record<string, unknown>;
|
|
98
|
-
const desktopAuto = cdpInspect.desktopAuto;
|
|
99
|
-
const isExplicitOptOut =
|
|
100
|
-
desktopAuto !== null &&
|
|
101
|
-
typeof desktopAuto === "object" &&
|
|
102
|
-
!Array.isArray(desktopAuto) &&
|
|
103
|
-
(desktopAuto as Record<string, unknown>).enabled === false;
|
|
104
|
-
if (!isExplicitOptOut) {
|
|
105
|
-
delete cdpInspect.desktopAuto;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return JSON.stringify(config, null, 2) + "\n";
|
|
111
|
-
}
|
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Live voice on cloud speech without a config write.
|
|
3
|
-
*
|
|
4
|
-
* `/v1/live-voice` validates guardian identity only — it does not enforce the
|
|
5
|
-
* caller's `settings.write` scope — so the WebSocket transport must never
|
|
6
|
-
* persist `services.stt/tts.provider`. Instead every live-voice leg resolves
|
|
7
|
-
* the EFFECTIVE providers: while cloud speech is available, a configured
|
|
8
|
-
* BYOK provider whose credential does not resolve is served by `"cadet"`.
|
|
9
|
-
* These tests pin both halves of that contract — the readiness verdict and
|
|
10
|
-
* the runtime legs agree, and `config.json` is byte-identical afterwards.
|
|
11
|
-
*
|
|
12
|
-
* `mock.module` is process-global in Bun and leaks into sibling files that run
|
|
13
|
-
* later in the same `bun test` invocation, so each stub delegates to the real
|
|
14
|
-
* implementation unless this file's tests are active (`fallbackMocksActive`,
|
|
15
|
-
* toggled in beforeAll/afterAll). Run this file on its own.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
19
|
-
import { join } from "node:path";
|
|
20
|
-
import {
|
|
21
|
-
afterAll,
|
|
22
|
-
afterEach,
|
|
23
|
-
beforeAll,
|
|
24
|
-
beforeEach,
|
|
25
|
-
describe,
|
|
26
|
-
expect,
|
|
27
|
-
mock,
|
|
28
|
-
test,
|
|
29
|
-
} from "bun:test";
|
|
30
|
-
|
|
31
|
-
import type { ResolveStreamingTranscriberOptions } from "../../providers/speech-to-text/resolve.js";
|
|
32
|
-
import type { StreamingTranscriber } from "../../stt/types.js";
|
|
33
|
-
|
|
34
|
-
let fallbackMocksActive = false;
|
|
35
|
-
|
|
36
|
-
const realSttResolveModule = {
|
|
37
|
-
...(await import("../../providers/speech-to-text/resolve.js")),
|
|
38
|
-
};
|
|
39
|
-
const realSecureKeysModule = {
|
|
40
|
-
...(await import("../../security/secure-keys.js")),
|
|
41
|
-
};
|
|
42
|
-
const realCloudSpeechModule = {
|
|
43
|
-
...(await import("../../cloud/cloud-speech.js")),
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
// -- Mutable stub state -------------------------------------------------------
|
|
47
|
-
|
|
48
|
-
/** Whether the cloud connection can serve cloud speech. */
|
|
49
|
-
let managedAvailable = false;
|
|
50
|
-
/** Credential-store keys that resolve to a value. */
|
|
51
|
-
let providerKeys: Record<string, string>;
|
|
52
|
-
/** Provider ids `resolveStreamingTranscriber` was asked for, in call order. */
|
|
53
|
-
let requestedSttProviders: (string | undefined)[];
|
|
54
|
-
|
|
55
|
-
/** A provider yields a streaming transcriber when its credential resolves. */
|
|
56
|
-
function transcriberFor(providerId: string | undefined): boolean {
|
|
57
|
-
// Both managed rows ride the same platform connection, so neither has a
|
|
58
|
-
// credential of its own. Live voice asks for the flux row specifically,
|
|
59
|
-
// since managed live voice resolves to it when no family is named.
|
|
60
|
-
return providerId === "cadet" || providerId === "cadet-flux"
|
|
61
|
-
? managedAvailable
|
|
62
|
-
: providerId !== undefined && providerKeys[providerId] !== undefined;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
mock.module("../../providers/speech-to-text/resolve.js", () => ({
|
|
66
|
-
...realSttResolveModule,
|
|
67
|
-
resolveStreamingTranscriber: async (
|
|
68
|
-
options: ResolveStreamingTranscriberOptions = {},
|
|
69
|
-
) => {
|
|
70
|
-
if (!fallbackMocksActive) {
|
|
71
|
-
return realSttResolveModule.resolveStreamingTranscriber(options);
|
|
72
|
-
}
|
|
73
|
-
requestedSttProviders.push(options.providerId);
|
|
74
|
-
return transcriberFor(options.providerId)
|
|
75
|
-
? ({} as StreamingTranscriber)
|
|
76
|
-
: null;
|
|
77
|
-
},
|
|
78
|
-
}));
|
|
79
|
-
|
|
80
|
-
mock.module("../../security/secure-keys.js", () => ({
|
|
81
|
-
...realSecureKeysModule,
|
|
82
|
-
getProviderKeyAsync: async (provider: string) =>
|
|
83
|
-
fallbackMocksActive
|
|
84
|
-
? providerKeys[provider]
|
|
85
|
-
: realSecureKeysModule.getProviderKeyAsync(provider),
|
|
86
|
-
getSecureKeyAsync: async (account: string) =>
|
|
87
|
-
fallbackMocksActive
|
|
88
|
-
? providerKeys[account]
|
|
89
|
-
: realSecureKeysModule.getSecureKeyAsync(account),
|
|
90
|
-
}));
|
|
91
|
-
|
|
92
|
-
mock.module("../../cloud/cloud-speech.js", () => ({
|
|
93
|
-
...realCloudSpeechModule,
|
|
94
|
-
managedSpeechAvailable: async () =>
|
|
95
|
-
fallbackMocksActive
|
|
96
|
-
? managedAvailable
|
|
97
|
-
: realCloudSpeechModule.managedSpeechAvailable(),
|
|
98
|
-
}));
|
|
99
|
-
|
|
100
|
-
import { resolveEffectiveSpeechProviders } from "../../config/cloud-speech-defaults.js";
|
|
101
|
-
import { getConfig, invalidateConfigCache } from "../../config/loader.js";
|
|
102
|
-
import {
|
|
103
|
-
_resetTtsProviderOverridesForTests,
|
|
104
|
-
_setTtsProviderForTests,
|
|
105
|
-
} from "../../tts/provider-catalog.js";
|
|
106
|
-
import type {
|
|
107
|
-
TtsProvider,
|
|
108
|
-
TtsSynthesisRequest,
|
|
109
|
-
TtsSynthesisResult,
|
|
110
|
-
} from "../../tts/types.js";
|
|
111
|
-
import { resolveLiveVoiceCredentialReadiness } from "../live-voice-credential-preflight.js";
|
|
112
|
-
import { streamLiveVoiceTtsAudio } from "../live-voice-tts.js";
|
|
113
|
-
|
|
114
|
-
const WORKSPACE_DIR = process.env.CADET_WORKSPACE_DIR!;
|
|
115
|
-
const CONFIG_PATH = join(WORKSPACE_DIR, "config.json");
|
|
116
|
-
|
|
117
|
-
/** Config with both speech services pointed at BYOK providers. */
|
|
118
|
-
const BYOK_CONFIG = {
|
|
119
|
-
services: {
|
|
120
|
-
stt: { provider: "deepgram", providers: {} },
|
|
121
|
-
tts: { provider: "elevenlabs", providers: {} },
|
|
122
|
-
},
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
function writeConfig(obj: unknown): void {
|
|
126
|
-
if (!existsSync(WORKSPACE_DIR)) {
|
|
127
|
-
mkdirSync(WORKSPACE_DIR, { recursive: true });
|
|
128
|
-
}
|
|
129
|
-
writeFileSync(CONFIG_PATH, JSON.stringify(obj));
|
|
130
|
-
invalidateConfigCache();
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function readConfigFile(): string {
|
|
134
|
-
return readFileSync(CONFIG_PATH, "utf8");
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/** A streaming TTS adapter standing in for the cloud `cadet` provider. */
|
|
138
|
-
function registerManagedTtsProvider(): TtsProvider {
|
|
139
|
-
const provider: TtsProvider = {
|
|
140
|
-
id: "cadet",
|
|
141
|
-
capabilities: { supportsStreaming: true, supportedFormats: ["pcm", "mp3"] },
|
|
142
|
-
async synthesize(): Promise<TtsSynthesisResult> {
|
|
143
|
-
throw new Error("buffered synthesis should not be used");
|
|
144
|
-
},
|
|
145
|
-
async synthesizeStream(
|
|
146
|
-
_request: TtsSynthesisRequest,
|
|
147
|
-
onChunk: (chunk: Uint8Array) => void,
|
|
148
|
-
): Promise<TtsSynthesisResult> {
|
|
149
|
-
onChunk(Buffer.from("managed!"));
|
|
150
|
-
return { audio: Buffer.from("managed!"), contentType: "audio/pcm" };
|
|
151
|
-
},
|
|
152
|
-
};
|
|
153
|
-
_setTtsProviderForTests(provider);
|
|
154
|
-
return provider;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
beforeAll(() => {
|
|
158
|
-
fallbackMocksActive = true;
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
afterAll(() => {
|
|
162
|
-
fallbackMocksActive = false;
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
afterEach(() => {
|
|
166
|
-
_resetTtsProviderOverridesForTests();
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
beforeEach(() => {
|
|
170
|
-
writeConfig(BYOK_CONFIG);
|
|
171
|
-
managedAvailable = false;
|
|
172
|
-
providerKeys = {};
|
|
173
|
-
requestedSttProviders = [];
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
describe("live voice on cloud speech", () => {
|
|
177
|
-
test("unkeyed BYOK TTS resolves the cloud provider and reports ready", async () => {
|
|
178
|
-
managedAvailable = true;
|
|
179
|
-
providerKeys = { deepgram: "test-key" };
|
|
180
|
-
const configBefore = readConfigFile();
|
|
181
|
-
|
|
182
|
-
const effective = await resolveEffectiveSpeechProviders();
|
|
183
|
-
expect(effective).toEqual({ stt: "deepgram", tts: "cadet" });
|
|
184
|
-
|
|
185
|
-
const readiness = await resolveLiveVoiceCredentialReadiness();
|
|
186
|
-
expect(readiness).toEqual({ status: "ready" });
|
|
187
|
-
|
|
188
|
-
expect(readConfigFile()).toBe(configBefore);
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
test("synthesis runs on the cloud provider while the config still names the BYOK one", async () => {
|
|
192
|
-
managedAvailable = true;
|
|
193
|
-
providerKeys = { deepgram: "test-key" };
|
|
194
|
-
registerManagedTtsProvider();
|
|
195
|
-
const configBefore = readConfigFile();
|
|
196
|
-
|
|
197
|
-
const chunks: string[] = [];
|
|
198
|
-
const result = await streamLiveVoiceTtsAudio({
|
|
199
|
-
text: "hello",
|
|
200
|
-
config: getConfig(),
|
|
201
|
-
onAudioChunk: (chunk) => {
|
|
202
|
-
chunks.push(chunk.dataBase64);
|
|
203
|
-
},
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
expect(result.provider).toBe("cadet");
|
|
207
|
-
expect(chunks).toHaveLength(1);
|
|
208
|
-
expect(getConfig().services.tts.provider).toBe("elevenlabs");
|
|
209
|
-
expect(readConfigFile()).toBe(configBefore);
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
test("unkeyed BYOK STT resolves the cloud provider and reports ready", async () => {
|
|
213
|
-
managedAvailable = true;
|
|
214
|
-
providerKeys = { elevenlabs: "test-key" };
|
|
215
|
-
const configBefore = readConfigFile();
|
|
216
|
-
|
|
217
|
-
const effective = await resolveEffectiveSpeechProviders();
|
|
218
|
-
expect(effective).toEqual({ stt: "cadet", tts: "elevenlabs" });
|
|
219
|
-
|
|
220
|
-
const readiness = await resolveLiveVoiceCredentialReadiness();
|
|
221
|
-
expect(readiness).toEqual({ status: "ready" });
|
|
222
|
-
|
|
223
|
-
// The transcriber the session arms is asked for the same provider the
|
|
224
|
-
// readiness verdict was earned on. Live voice resolves the flux row while
|
|
225
|
-
// the roleless verdict above stays on the base one, which is the whole
|
|
226
|
-
// point of resolving per role.
|
|
227
|
-
expect(requestedSttProviders).toEqual(["cadet-flux"]);
|
|
228
|
-
expect(readConfigFile()).toBe(configBefore);
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
test("cloud speech unavailable keeps the configured providers and reports not-ready", async () => {
|
|
232
|
-
managedAvailable = false;
|
|
233
|
-
const configBefore = readConfigFile();
|
|
234
|
-
|
|
235
|
-
const effective = await resolveEffectiveSpeechProviders();
|
|
236
|
-
expect(effective).toEqual({ stt: "deepgram", tts: "elevenlabs" });
|
|
237
|
-
|
|
238
|
-
const readiness = await resolveLiveVoiceCredentialReadiness();
|
|
239
|
-
expect(readiness.status).toBe("not-ready");
|
|
240
|
-
if (readiness.status !== "not-ready") {
|
|
241
|
-
throw new Error("expected not-ready");
|
|
242
|
-
}
|
|
243
|
-
expect(readiness.missing.map((gap) => gap.providerId)).toEqual([
|
|
244
|
-
"deepgram",
|
|
245
|
-
"elevenlabs",
|
|
246
|
-
]);
|
|
247
|
-
expect(requestedSttProviders).toEqual(["deepgram"]);
|
|
248
|
-
expect(readConfigFile()).toBe(configBefore);
|
|
249
|
-
});
|
|
250
|
-
});
|