@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.
Files changed (180) hide show
  1. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
  2. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
  3. package/node_modules/@mycadet/ces-client/src/http-credentials.ts +18 -1
  4. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
  5. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
  6. package/node_modules/@mycadet/gateway-client/src/gateway-ipc-contracts.ts +0 -20
  7. package/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
  8. package/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
  9. package/openapi.yaml +2 -708
  10. package/package.json +2 -3
  11. package/scripts/sync-web-search-catalog.ts +1 -2
  12. package/src/__tests__/cloud-profile-guard.test.ts +41 -5
  13. package/src/__tests__/config-schema.test.ts +6 -38
  14. package/src/__tests__/conversation-queue.test.ts +45 -6
  15. package/src/__tests__/db-readiness-http-gate.test.ts +0 -6
  16. package/src/__tests__/persist-onboarding-artifacts.test.ts +0 -1
  17. package/src/__tests__/persona-resolver.test.ts +0 -51
  18. package/src/__tests__/provider-cloud-proxy-integration.test.ts +2 -110
  19. package/src/__tests__/run-due-schedules.test.ts +0 -4
  20. package/src/__tests__/schedule-store.test.ts +0 -12
  21. package/src/__tests__/stt-stream-session.test.ts +4 -4
  22. package/src/__tests__/telephony-tts-guard.test.ts +2 -50
  23. package/src/__tests__/voice-config-update.test.ts +12 -167
  24. package/src/__tests__/web-search-catalog-parity.test.ts +0 -2
  25. package/src/__tests__/workspace-migration-130-speech-mode-to-provider.test.ts +9 -7
  26. package/src/__tests__/workspace-migration-132-web-search-mode-to-provider.test.ts +6 -6
  27. package/src/calls/__tests__/voice-session-bridge.test.ts +1 -4
  28. package/src/calls/resolve-call-tts-provider.ts +6 -25
  29. package/src/calls/telephony-credential-preflight.ts +0 -10
  30. package/src/calls/telephony-tts-capability.ts +1 -17
  31. package/src/cli/commands/tts.ts +1 -2
  32. package/src/cli/index.help.ts +0 -2
  33. package/src/cli/program.ts +0 -2
  34. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +4 -42
  35. package/src/config/schemas/__tests__/stt.test.ts +10 -14
  36. package/src/config/schemas/llm.ts +3 -5
  37. package/src/config/schemas/services.ts +5 -6
  38. package/src/config/schemas/stt.ts +1 -3
  39. package/src/config/schemas/tts.ts +1 -30
  40. package/src/daemon/daemon-readiness.ts +4 -29
  41. package/src/daemon/lifecycle.ts +0 -31
  42. package/src/daemon/providers-setup.ts +0 -8
  43. package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
  44. package/src/heartbeat/heartbeat-service.ts +1 -66
  45. package/src/ipc/__tests__/db-readiness-ipc-gate.test.ts +1 -9
  46. package/src/live-voice/__tests__/live-voice-credential-preflight.test.ts +0 -44
  47. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +4 -6
  48. package/src/live-voice/__tests__/live-voice-stt.test.ts +1 -1
  49. package/src/live-voice/live-voice-credential-preflight.ts +9 -33
  50. package/src/live-voice/live-voice-session.ts +10 -6
  51. package/src/live-voice/live-voice-tts.ts +1 -8
  52. package/src/plugins/defaults/memory/worker.ts +3 -3
  53. package/src/prompts/persona-resolver.ts +0 -35
  54. package/src/providers/__tests__/search-provider-catalog.test.ts +5 -8
  55. package/src/providers/__tests__/voice-error-copy.test.ts +9 -10
  56. package/src/providers/search-provider-catalog.ts +7 -39
  57. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +3 -47
  58. package/src/providers/speech-to-text/__tests__/resolve.test.ts +6 -372
  59. package/src/providers/speech-to-text/provider-catalog.ts +2 -73
  60. package/src/providers/speech-to-text/resolve.ts +3 -120
  61. package/src/runtime/http-router-types.ts +1 -3
  62. package/src/runtime/http-server.ts +1 -1
  63. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +13 -11
  64. package/src/runtime/routes/__tests__/tts-routes.test.ts +2 -6
  65. package/src/runtime/routes/conversation-query-routes.ts +0 -96
  66. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +7 -7
  67. package/src/runtime/routes/index.ts +0 -4
  68. package/src/runtime/routes/live-voice-routes.test.ts +4 -11
  69. package/src/runtime/routes/live-voice-routes.ts +8 -11
  70. package/src/runtime/routes/secret-routes.ts +0 -6
  71. package/src/runtime/routes/tts-routes.ts +0 -38
  72. package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +1 -7
  73. package/src/schedule/__tests__/worker-mcp-readiness.test.ts +0 -4
  74. package/src/schedule/schedule-store.ts +0 -7
  75. package/src/schedule/scheduler.ts +0 -5
  76. package/src/schedule/worker.ts +1 -1
  77. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +2 -10
  78. package/src/stt/daemon-batch-transcriber.ts +4 -38
  79. package/src/stt/types.ts +1 -9
  80. package/src/tools/network/__tests__/web-search.test.ts +3 -250
  81. package/src/tools/network/web-search.ts +4 -165
  82. package/src/tts/__tests__/provider-resolution.test.ts +0 -5
  83. package/src/tts/provider-catalog.ts +0 -4
  84. package/src/tts/types.ts +0 -1
  85. package/src/__tests__/cloud-speech-defaults.test.ts +0 -330
  86. package/src/__tests__/migration-cross-version-compatibility.test.ts +0 -1708
  87. package/src/__tests__/migration-export-http.test.ts +0 -557
  88. package/src/__tests__/migration-export-streaming.test.ts +0 -350
  89. package/src/__tests__/migration-export-to-gcs.test.ts +0 -706
  90. package/src/__tests__/migration-import-commit-http.test.ts +0 -1176
  91. package/src/__tests__/migration-import-from-gcs.test.ts +0 -534
  92. package/src/__tests__/migration-import-from-path.test.ts +0 -324
  93. package/src/__tests__/migration-import-from-url.test.ts +0 -588
  94. package/src/__tests__/migration-import-preflight-http.test.ts +0 -739
  95. package/src/__tests__/migration-jobs-status.test.ts +0 -139
  96. package/src/__tests__/migration-parity-persistence.test.ts +0 -1891
  97. package/src/__tests__/migration-transport.test.ts +0 -1089
  98. package/src/__tests__/migration-validate-http.test.ts +0 -634
  99. package/src/__tests__/migration-wizard.test.ts +0 -1395
  100. package/src/__tests__/rebind-secrets-screen.test.ts +0 -876
  101. package/src/__tests__/sanitize-config-for-transfer.test.ts +0 -259
  102. package/src/__tests__/transfer-progress-screen.test.ts +0 -1217
  103. package/src/__tests__/validation-results-screen.test.ts +0 -1130
  104. package/src/__tests__/vbundle-pax-and-symlink.test.ts +0 -147
  105. package/src/background-wake/background-wake-routes.test.ts +0 -906
  106. package/src/background-wake/cloud-client.test.ts +0 -328
  107. package/src/background-wake/cloud-client.ts +0 -196
  108. package/src/background-wake/next-wake.test.ts +0 -335
  109. package/src/background-wake/next-wake.ts +0 -188
  110. package/src/background-wake/publisher.ts +0 -93
  111. package/src/background-wake/wake-intent-hooks.test.ts +0 -234
  112. package/src/cli/commands/__tests__/backup.test.ts +0 -363
  113. package/src/cli/commands/backup.help.ts +0 -180
  114. package/src/cli/commands/backup.ts +0 -398
  115. package/src/cloud/cloud-speech.test.ts +0 -335
  116. package/src/cloud/cloud-speech.ts +0 -300
  117. package/src/config/cloud-speech-defaults.ts +0 -300
  118. package/src/config/sanitize-for-transfer.ts +0 -111
  119. package/src/live-voice/__tests__/live-voice-cloud-speech-fallback.test.ts +0 -250
  120. package/src/providers/speech-to-text/__tests__/cadet-cloud-realtime.test.ts +0 -457
  121. package/src/providers/speech-to-text/__tests__/cadet-cloud.test.ts +0 -271
  122. package/src/providers/speech-to-text/__tests__/cadet-speech-relay-connection.test.ts +0 -126
  123. package/src/providers/speech-to-text/cadet-cloud-flux-realtime.ts +0 -309
  124. package/src/providers/speech-to-text/cadet-cloud-realtime.ts +0 -315
  125. package/src/providers/speech-to-text/cadet-cloud.ts +0 -108
  126. package/src/providers/speech-to-text/cadet-speech-relay-connection.ts +0 -176
  127. package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +0 -254
  128. package/src/runtime/migrations/__tests__/job-registry.test.ts +0 -346
  129. package/src/runtime/migrations/__tests__/origin-mode.test.ts +0 -62
  130. package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +0 -184
  131. package/src/runtime/migrations/__tests__/staged-import-path.test.ts +0 -110
  132. package/src/runtime/migrations/__tests__/v1-test-helpers.ts +0 -112
  133. package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +0 -285
  134. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +0 -126
  135. package/src/runtime/migrations/__tests__/vbundle-builder-v1-shape.test.ts +0 -253
  136. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +0 -208
  137. package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +0 -485
  138. package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +0 -349
  139. package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +0 -196
  140. package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +0 -551
  141. package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +0 -428
  142. package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +0 -220
  143. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +0 -1823
  144. package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +0 -598
  145. package/src/runtime/migrations/__tests__/vbundle-symlink-importer.test.ts +0 -463
  146. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -538
  147. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming.test.ts +0 -515
  148. package/src/runtime/migrations/__tests__/vbundle-symlink-tar.test.ts +0 -453
  149. package/src/runtime/migrations/__tests__/vbundle-symlink-walker.test.ts +0 -319
  150. package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +0 -240
  151. package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +0 -473
  152. package/src/runtime/migrations/gcs-signed-url.ts +0 -190
  153. package/src/runtime/migrations/job-registry.ts +0 -297
  154. package/src/runtime/migrations/migration-transport.ts +0 -612
  155. package/src/runtime/migrations/migration-wizard.ts +0 -747
  156. package/src/runtime/migrations/origin-mode.ts +0 -42
  157. package/src/runtime/migrations/rebind-secrets-screen.ts +0 -412
  158. package/src/runtime/migrations/staged-import-path.ts +0 -116
  159. package/src/runtime/migrations/transfer-progress-screen.ts +0 -323
  160. package/src/runtime/migrations/validation-results-screen.ts +0 -469
  161. package/src/runtime/migrations/vbundle-builder.ts +0 -1382
  162. package/src/runtime/migrations/vbundle-import-analyzer.ts +0 -405
  163. package/src/runtime/migrations/vbundle-import-policy.ts +0 -241
  164. package/src/runtime/migrations/vbundle-importer.ts +0 -826
  165. package/src/runtime/migrations/vbundle-metadata-merge.ts +0 -113
  166. package/src/runtime/migrations/vbundle-streaming-importer.ts +0 -2844
  167. package/src/runtime/migrations/vbundle-streaming-validator.ts +0 -426
  168. package/src/runtime/migrations/vbundle-tar-stream.ts +0 -244
  169. package/src/runtime/migrations/vbundle-validator.ts +0 -724
  170. package/src/runtime/routes/__tests__/migration-cadet-metadata-reconcile.test.ts +0 -227
  171. package/src/runtime/routes/__tests__/migration-export-secrets-redacted.test.ts +0 -54
  172. package/src/runtime/routes/__tests__/migration-import-allowed-hosts-env.test.ts +0 -70
  173. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +0 -257
  174. package/src/runtime/routes/background-wake-routes.ts +0 -387
  175. package/src/runtime/routes/migration-routes.ts +0 -2584
  176. package/src/tools/network/__tests__/cloud-search-proxy.test.ts +0 -282
  177. package/src/tools/network/cloud-search-proxy.ts +0 -211
  178. package/src/tts/__tests__/cadet-provider.test.ts +0 -253
  179. package/src/tts/providers/cadet-provider.ts +0 -244
  180. package/src/types/tar-stream.d.ts +0 -66
@@ -1,253 +0,0 @@
1
- import { beforeEach, describe, expect, mock, test } from "bun:test";
2
-
3
- let mockSynthesizeResult: unknown;
4
- let synthesizeCalls: Array<{
5
- text: string;
6
- format: string;
7
- signal?: AbortSignal;
8
- }> = [];
9
-
10
- mock.module("../../cloud/cloud-speech.js", () => ({
11
- managedSpeechSynthesize: async (input: {
12
- text: string;
13
- format: string;
14
- signal?: AbortSignal;
15
- }) => {
16
- synthesizeCalls.push(input);
17
- return mockSynthesizeResult;
18
- },
19
- }));
20
-
21
- let mockRelayConnection: {
22
- wsBaseUrl: string;
23
- httpBaseUrl: string;
24
- mintServiceToken: () => string;
25
- } | null = null;
26
-
27
- mock.module(
28
- "../../providers/speech-to-text/cadet-speech-relay-connection.js",
29
- () => ({
30
- resolveSpeechRelayConnection: async () => mockRelayConnection,
31
- mapTunnelError: (e: { code: string }) => ({
32
- category: "provider-error",
33
- message: `mapped:${e.code}`,
34
- }),
35
- probeTunnelRejection: async () => mockProbeRejection,
36
- }),
37
- );
38
- let mockProbeRejection: { code: string; detail?: string } | null = null;
39
-
40
- let socketCalls: Array<{ url: string; text: string }> = [];
41
- let mockSocketResult: () => Promise<Buffer> = async () => Buffer.from("pcm");
42
-
43
- mock.module("../providers/cadet-tts-socket.js", () => ({
44
- synthesizeOverCadetTtsSocket: async (opts: {
45
- url: string;
46
- text: string;
47
- onChunk?: (c: Uint8Array) => void;
48
- }) => {
49
- socketCalls.push({ url: opts.url, text: opts.text });
50
- const audio = await mockSocketResult();
51
- opts.onChunk?.(audio);
52
- return audio;
53
- },
54
- }));
55
-
56
- import {
57
- cadetTtsProviderDefinition,
58
- createCadetProvider,
59
- } from "../providers/cadet-provider.js";
60
- import type { TtsSynthesisRequest } from "../types.js";
61
-
62
- const MP3_BYTES = Buffer.from([0x49, 0x44, 0x33]);
63
-
64
- function request(
65
- overrides: Partial<TtsSynthesisRequest> = {},
66
- ): TtsSynthesisRequest {
67
- return {
68
- text: "hello",
69
- useCase: "message-playback",
70
- ...overrides,
71
- } as TtsSynthesisRequest;
72
- }
73
-
74
- describe("cadet TTS adapter", () => {
75
- beforeEach(() => {
76
- synthesizeCalls = [];
77
- socketCalls = [];
78
- mockProbeRejection = null;
79
- mockSocketResult = async () => Buffer.from("pcm");
80
- mockRelayConnection = {
81
- wsBaseUrl: "ws://gateway.test",
82
- httpBaseUrl: "http://gateway.test",
83
- mintServiceToken: () => "tok-1",
84
- };
85
- mockSynthesizeResult = {
86
- ok: true,
87
- value: { audio: MP3_BYTES, contentType: "audio/mpeg" },
88
- };
89
- });
90
-
91
- test("message playback synthesizes mp3 and round-trips audio", async () => {
92
- const provider = createCadetProvider();
93
- const result = await provider.synthesize(request());
94
-
95
- expect(synthesizeCalls).toEqual([{ text: "hello", format: "mp3" }]);
96
- expect(Buffer.compare(result.audio, MP3_BYTES)).toBe(0);
97
- expect(result.contentType).toBe("audio/mpeg");
98
- });
99
-
100
- test("PCM hint maps to the cloud's fixed 16 kHz PCM format", async () => {
101
- const provider = createCadetProvider();
102
- await provider.synthesize(request({ outputFormat: "pcm" }));
103
-
104
- expect(synthesizeCalls[0].format).toBe("pcm_16000");
105
- expect(
106
- provider.resolveOutputSampleRateHz?.(request({ outputFormat: "pcm" })),
107
- ).toBe(16000);
108
- // Contract: undefined for non-PCM output.
109
- expect(provider.resolveOutputSampleRateHz?.(request())).toBeUndefined();
110
- });
111
-
112
- test("forwards the abort signal to the cloud call", async () => {
113
- const controller = new AbortController();
114
- const provider = createCadetProvider();
115
- await provider.synthesize(request({ signal: controller.signal }));
116
-
117
- expect(synthesizeCalls[0].signal).toBe(controller.signal);
118
- });
119
-
120
- test("insufficient balance throws a top-up message", async () => {
121
- mockSynthesizeResult = {
122
- ok: false,
123
- kind: "platform-error",
124
- status: 402,
125
- code: "insufficient_balance",
126
- message: "balance",
127
- };
128
- const provider = createCadetProvider();
129
- await expect(provider.synthesize(request())).rejects.toThrow(
130
- /Cadet credits/,
131
- );
132
- });
133
-
134
- test("unavailable platform throws a connect-your-account message", async () => {
135
- mockSynthesizeResult = {
136
- ok: false,
137
- kind: "unavailable",
138
- message: "No Cadet Cloud connection.",
139
- };
140
- const provider = createCadetProvider();
141
- await expect(provider.synthesize(request())).rejects.toThrow(
142
- /cloud connect/,
143
- );
144
- });
145
- });
146
-
147
- describe("cadet TTS streaming", () => {
148
- beforeEach(() => {
149
- synthesizeCalls = [];
150
- socketCalls = [];
151
- mockProbeRejection = null;
152
- mockSocketResult = async () => Buffer.from("pcm");
153
- mockRelayConnection = {
154
- wsBaseUrl: "ws://gateway.test",
155
- httpBaseUrl: "http://gateway.test",
156
- mintServiceToken: () => "tok-1",
157
- };
158
- mockSynthesizeResult = {
159
- ok: true,
160
- value: { audio: MP3_BYTES, contentType: "audio/mpeg" },
161
- };
162
- });
163
-
164
- test("PCM streaming dials the gateway relay with token and audio params", async () => {
165
- const provider = createCadetProvider();
166
- const chunks: Uint8Array[] = [];
167
-
168
- const result = await provider.synthesizeStream!(
169
- request({ outputFormat: "pcm" }),
170
- (c) => chunks.push(c),
171
- );
172
-
173
- expect(socketCalls).toHaveLength(1);
174
- const url = new URL(socketCalls[0]!.url);
175
- expect(url.origin).toBe("ws://gateway.test");
176
- expect(url.pathname).toBe("/v1/speech/tts/stream");
177
- expect(url.searchParams.get("key")).toBe("tok-1");
178
- expect(url.searchParams.get("encoding")).toBe("linear16");
179
- expect(url.searchParams.get("sample_rate")).toBe("16000");
180
- // Raw PCM, not Deepgram's default WAV container — RIFF headers would
181
- // play as static on the headerless media-stream path.
182
- expect(url.searchParams.get("container")).toBe("none");
183
- expect(result.contentType).toBe("audio/pcm");
184
- expect(chunks).toHaveLength(1);
185
- // Batch path untouched.
186
- expect(synthesizeCalls).toHaveLength(0);
187
- });
188
-
189
- test("non-PCM streaming delegates to the batch path with one chunk", async () => {
190
- const provider = createCadetProvider();
191
- const chunks: Uint8Array[] = [];
192
-
193
- const result = await provider.synthesizeStream!(request(), (c) =>
194
- chunks.push(c),
195
- );
196
-
197
- expect(socketCalls).toHaveLength(0);
198
- expect(synthesizeCalls).toEqual([{ text: "hello", format: "mp3" }]);
199
- expect(result.contentType).toBe("audio/mpeg");
200
- expect(chunks).toHaveLength(1);
201
- });
202
-
203
- test("a rejected dial surfaces the relay's mapped rejection via the probe", async () => {
204
- mockSocketResult = async () => {
205
- throw new Error("socket closed before Flushed");
206
- };
207
- mockProbeRejection = { code: "insufficient_balance" };
208
- const provider = createCadetProvider();
209
-
210
- await expect(
211
- provider.synthesizeStream!(request({ outputFormat: "pcm" }), () => {}),
212
- ).rejects.toThrow("mapped:insufficient_balance");
213
- });
214
-
215
- test("no relay connection throws a connect-your-account message", async () => {
216
- mockRelayConnection = null;
217
- const provider = createCadetProvider();
218
-
219
- await expect(
220
- provider.synthesizeStream!(request({ outputFormat: "pcm" }), () => {}),
221
- ).rejects.toThrow(/cloud connect/);
222
- });
223
-
224
- test("streaming capability is advertised", () => {
225
- expect(createCadetProvider().capabilities.supportsStreaming).toBe(true);
226
- });
227
- });
228
-
229
- describe("cadet TTS definition", () => {
230
- test("declares streaming synthesized-play with PCM media-stream playback", () => {
231
- expect(cadetTtsProviderDefinition.capabilities.supportsStreaming).toBe(
232
- true,
233
- );
234
- expect(cadetTtsProviderDefinition.callMode).toBe("synthesized-play");
235
- expect(cadetTtsProviderDefinition.allowNativeFallback).toBe(false);
236
- expect(cadetTtsProviderDefinition.mediaStreamPlayback.outputFormat).toBe(
237
- "pcm",
238
- );
239
- // True since cloud voice selection: clients gate their voice dropdown
240
- // on this flag (old daemons report false and show no selector).
241
- expect(cadetTtsProviderDefinition.supportsVoiceSelection).toBe(true);
242
- });
243
-
244
- test("requires only the cloud connection credential", () => {
245
- expect(cadetTtsProviderDefinition.secretRequirements).toEqual([
246
- {
247
- credentialStoreKey: "credential/cadet/assistant_api_key",
248
- displayName: "Cadet account connection",
249
- setCommand: "assistant cloud connect",
250
- },
251
- ]);
252
- });
253
- });
@@ -1,244 +0,0 @@
1
- /**
2
- * Cadet Cloud TTS: synthesis through the cloud's cloud speech
3
- * surfaces (Cadet-held Deepgram key, billed to Cadet credits). No provider
4
- * API key exists on this machine — the cloud connection is the credential.
5
- *
6
- * Two paths:
7
- * - Batch `synthesize` (read-aloud, mp3 surfaces) via the Django endpoint.
8
- * - Streaming `synthesizeStream` (live voice, telephony PCM) via the
9
- * gateway's speech relay (gateway → tunnel → Deepgram; tunnel contact is
10
- * gateway-only — the daemon holds no tunnel address and no speech
11
- * credential).
12
- *
13
- * `allowNativeFallback: false` means synthesis failures propagate to the
14
- * caller's error handler, so failures here throw with user-actionable
15
- * messages rather than returning silence.
16
- */
17
-
18
- import {
19
- type CloudSpeechResult,
20
- type CloudSpeechTtsFormat,
21
- managedSpeechSynthesize,
22
- } from "../../cloud/cloud-speech.js";
23
- import { getConfig } from "../../config/loader.js";
24
- import {
25
- mapTunnelError,
26
- probeTunnelRejection,
27
- resolveSpeechRelayConnection,
28
- } from "../../providers/speech-to-text/cadet-speech-relay-connection.js";
29
- import { getLogger } from "../../util/logger.js";
30
- import type { TtsProviderDefinition } from "../provider-definition.js";
31
- import type {
32
- TtsProvider,
33
- TtsProviderCapabilities,
34
- TtsSynthesisRequest,
35
- TtsSynthesisResult,
36
- } from "../types.js";
37
- import { synthesizeOverCadetTtsSocket } from "./cadet-tts-socket.js";
38
-
39
- const log = getLogger("cadet-tts");
40
-
41
- const TTS_STREAM_PATH = "/v1/speech/tts/stream";
42
-
43
- /**
44
- * The cloud's PCM format is fixed at 16 kHz, 16-bit LE mono — the
45
- * media-stream transcoder needs to know the rate regardless of hints.
46
- */
47
- const CADET_PCM_SAMPLE_RATE_HZ = 16_000;
48
-
49
- type CloudSpeechFailure = Extract<CloudSpeechResult<never>, { ok: false }>;
50
-
51
- /**
52
- * Map the synthesis request onto the cloud's format vocabulary.
53
- *
54
- * The PCM hint (media-stream playback) maps to raw 16 kHz PCM for mu-law
55
- * transcoding; every other use case gets mp3.
56
- */
57
- function resolveManagedFormat(
58
- request: TtsSynthesisRequest,
59
- ): CloudSpeechTtsFormat {
60
- return request.outputFormat === "pcm" ? "pcm_16000" : "mp3";
61
- }
62
-
63
- /**
64
- * The configured cloud voice, if any. A request-level voiceId wins over
65
- * config; undefined means the cloud's default voice.
66
- */
67
- function resolveCloudVoice(request: TtsSynthesisRequest): string | undefined {
68
- return request.voiceId ?? getConfig().services.tts.providers.cadet.model;
69
- }
70
-
71
- function synthesisError(failure: CloudSpeechFailure): Error {
72
- if (failure.code === "insufficient_balance") {
73
- return new Error(
74
- "Cadet credits are exhausted — add funds to your Cadet account to continue using cloud speech.",
75
- );
76
- }
77
- if (failure.kind === "unavailable") {
78
- return new Error(
79
- `Cloud speech is unavailable: ${failure.message} Run 'assistant cloud connect' to connect your Cadet account.`,
80
- );
81
- }
82
- return new Error(failure.message);
83
- }
84
-
85
- async function performSynthesis(
86
- request: TtsSynthesisRequest,
87
- ): Promise<TtsSynthesisResult> {
88
- const result = await managedSpeechSynthesize({
89
- text: request.text,
90
- format: resolveManagedFormat(request),
91
- model: resolveCloudVoice(request),
92
- signal: request.signal,
93
- });
94
- if (!result.ok) {
95
- throw synthesisError(result);
96
- }
97
- return {
98
- audio: result.value.audio,
99
- contentType: result.value.contentType,
100
- };
101
- }
102
-
103
- /**
104
- * Stream one PCM utterance through the gateway's speech relay. Non-PCM
105
- * requests delegate to the batch path — streaming exists for realtime PCM
106
- * consumers (live voice, telephony), and the relay's format is pinned to
107
- * linear16.
108
- */
109
- async function performStreamingSynthesis(
110
- request: TtsSynthesisRequest,
111
- onChunk: (chunk: Uint8Array) => void,
112
- ): Promise<TtsSynthesisResult> {
113
- if (request.outputFormat !== "pcm") {
114
- const result = await performSynthesis(request);
115
- onChunk(result.audio);
116
- return result;
117
- }
118
-
119
- const connection = await resolveSpeechRelayConnection();
120
- if (!connection) {
121
- throw new Error(
122
- "Cloud speech is unavailable: the assistant cannot reach its speech relay. Run 'assistant cloud connect' to connect your Cadet account.",
123
- );
124
- }
125
-
126
- const params = new URLSearchParams({
127
- key: connection.mintServiceToken(),
128
- encoding: "linear16",
129
- sample_rate: String(CADET_PCM_SAMPLE_RATE_HZ),
130
- // Deepgram defaults linear16 to a WAV container; the media-stream path
131
- // consumes headerless PCM, so RIFF header bytes would play as static.
132
- container: "none",
133
- });
134
- const voice = resolveCloudVoice(request);
135
- if (voice) {
136
- params.set("model", voice);
137
- }
138
- const url = `${connection.wsBaseUrl}${TTS_STREAM_PATH}?${params.toString()}`;
139
-
140
- log.info(
141
- { textLength: request.text.length },
142
- "Starting managed streaming TTS synthesis",
143
- );
144
-
145
- try {
146
- const audio = await synthesizeOverCadetTtsSocket({
147
- url,
148
- text: request.text,
149
- onChunk,
150
- signal: request.signal,
151
- makeTimeoutError: (timeoutMs) =>
152
- new Error(`Managed streaming TTS timed out after ${timeoutMs}ms`),
153
- makeStreamError: (detail) =>
154
- new Error(`Managed streaming TTS failed: ${detail}`),
155
- makeEmptyError: () =>
156
- new Error("Managed streaming TTS returned no audio"),
157
- makeRelayError: (code, detail) =>
158
- new Error(mapTunnelError({ code, detail }).message),
159
- });
160
- return {
161
- audio,
162
- contentType: "audio/pcm",
163
- };
164
- } catch (err) {
165
- if (request.signal?.aborted) {
166
- throw err;
167
- }
168
- // A rejected upgrade hides the relay's {code, detail}; replay the gate
169
- // as a plain GET (the gateway probes tunnel's own gate too) so auth and
170
- // balance failures surface with their mapped messages. The probe must
171
- // carry the same voice model — a model-specific rejection (e.g.
172
- // missing_price) only reproduces when the probe asks for that model.
173
- const probeParams = new URLSearchParams({
174
- key: connection.mintServiceToken(),
175
- });
176
- if (voice) {
177
- probeParams.set("model", voice);
178
- }
179
- const rejection = await probeTunnelRejection(
180
- `${connection.httpBaseUrl}${TTS_STREAM_PATH}?${probeParams.toString()}`,
181
- );
182
- if (rejection) {
183
- throw new Error(mapTunnelError(rejection).message);
184
- }
185
- throw err;
186
- }
187
- }
188
-
189
- export function createCadetProvider(): TtsProvider {
190
- const capabilities: TtsProviderCapabilities = {
191
- supportsStreaming: true,
192
- supportedFormats: ["mp3", "pcm"],
193
- };
194
-
195
- return {
196
- id: "cadet",
197
- capabilities,
198
- // Per the TtsProvider contract this returns undefined for non-PCM
199
- // output; the cloud's PCM format is pinned to 16 kHz.
200
- resolveOutputSampleRateHz: (request) =>
201
- request.outputFormat === "pcm" ? CADET_PCM_SAMPLE_RATE_HZ : undefined,
202
- synthesize: performSynthesis,
203
- synthesizeStream: performStreamingSynthesis,
204
- };
205
- }
206
-
207
- /**
208
- * The complete Cadet Cloud provider definition — catalog metadata plus
209
- * the runtime adapter — assembled into the canonical catalog by
210
- * `provider-catalog.ts`.
211
- */
212
- export const cadetTtsProviderDefinition: TtsProviderDefinition = {
213
- id: "cadet",
214
- displayName: "Cadet",
215
- subtitle:
216
- "Text-to-speech through your Cadet account — billed to Cadet credits, no separate API key needed.",
217
- // Advertises cloud voice selection (`services.tts.providers.cadet.model`)
218
- // to clients; web gates its voice dropdown on this flag, so daemons that
219
- // predate the field never show a selector whose writes they would ignore.
220
- supportsVoiceSelection: true,
221
- apiKeyPlaceholder: "Connected via your Cadet account",
222
- credentialsGuide: {
223
- description:
224
- "Connect this assistant to your Cadet account; cloud speech uses that connection instead of a provider API key.",
225
- url: "https://cloud.mycadet.ai/",
226
- linkLabel: "Open Cadet Cloud",
227
- },
228
- callMode: "synthesized-play",
229
- allowNativeFallback: false,
230
- capabilities: {
231
- supportsStreaming: true,
232
- supportedFormats: ["mp3", "pcm"],
233
- },
234
- // The adapter honours the PCM hint via the cloud's pcm_16000 format.
235
- mediaStreamPlayback: { outputFormat: "pcm" },
236
- secretRequirements: [
237
- {
238
- credentialStoreKey: "credential/cadet/assistant_api_key",
239
- displayName: "Cadet account connection",
240
- setCommand: "assistant cloud connect",
241
- },
242
- ],
243
- adapter: createCadetProvider(),
244
- };
@@ -1,66 +0,0 @@
1
- /**
2
- * Minimal ambient types for `tar-stream` v3, which ships no type definitions.
3
- *
4
- * We only declare the surface used by `runtime/migrations/vbundle-tar-stream.ts`
5
- * (the `extract()` factory and its `"entry"` event shape). If additional usage
6
- * is needed later, extend this file rather than adding `@types/tar-stream` —
7
- * the DT types target v2 and do not match v3's streamx-based extractor.
8
- */
9
- declare module "tar-stream" {
10
- import type { Readable, Writable } from "node:stream";
11
-
12
- export interface TarHeader {
13
- name: string;
14
- size: number;
15
- type:
16
- | "file"
17
- | "link"
18
- | "symlink"
19
- | "character-device"
20
- | "block-device"
21
- | "directory"
22
- | "fifo"
23
- | "contiguous-file"
24
- | "pax-header"
25
- | "pax-global-header"
26
- | "gnu-long-path"
27
- | "gnu-long-link-path"
28
- | "other"
29
- | string;
30
- linkname?: string | null;
31
- mode?: number;
32
- uid?: number;
33
- gid?: number;
34
- mtime?: Date;
35
- pax?: Record<string, string> | null;
36
- }
37
-
38
- export interface ExtractOptions {
39
- filenameEncoding?: string;
40
- allowUnknownFormat?: boolean;
41
- }
42
-
43
- export type EntryCallback = (err?: Error | null) => void;
44
-
45
- export interface Extract extends Writable {
46
- on(
47
- event: "entry",
48
- listener: (
49
- header: TarHeader,
50
- stream: Readable,
51
- next: EntryCallback,
52
- ) => void,
53
- ): this;
54
- on(event: "finish", listener: () => void): this;
55
- on(event: "error", listener: (err: Error) => void): this;
56
- on(event: "close", listener: () => void): this;
57
- on(event: string, listener: (...args: unknown[]) => void): this;
58
- destroy(err?: Error): void;
59
- }
60
-
61
- export function extract(opts?: ExtractOptions): Extract;
62
-
63
- // `pack` exists but is unused by the runtime migrations code; declare it
64
- // permissively so importers that reach for it still type-check.
65
- export function pack(opts?: unknown): Writable;
66
- }