@mycadet/assistant 0.12.11 → 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 +2 -2
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/ingress.ts +2 -2
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +2 -2
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/ingress.ts +2 -2
- package/node_modules/@mycadet/gateway-client/src/gateway-ipc-contracts.ts +0 -20
- package/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +2 -2
- package/node_modules/@mycadet/service-contracts/src/ingress.ts +2 -2
- 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 +2 -2
- 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/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__/tts-routes.test.ts +2 -6
- 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,271 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
-
|
|
3
|
-
import { SttError } from "../../../stt/types.js";
|
|
4
|
-
import { describeSttFailure } from "../../voice-error-copy.js";
|
|
5
|
-
|
|
6
|
-
let mockTranscribeResult: unknown;
|
|
7
|
-
let mockClient: { cloudAssistantId: string } | null = null;
|
|
8
|
-
|
|
9
|
-
mock.module("../../../cloud/cloud-speech.js", () => ({
|
|
10
|
-
managedSpeechTranscribe: async () => mockTranscribeResult,
|
|
11
|
-
}));
|
|
12
|
-
|
|
13
|
-
mock.module("../../../cloud/client.js", () => ({
|
|
14
|
-
CadetCloudClient: {
|
|
15
|
-
create: async () => mockClient,
|
|
16
|
-
},
|
|
17
|
-
}));
|
|
18
|
-
|
|
19
|
-
import {
|
|
20
|
-
cadetCloudSpeechAvailable,
|
|
21
|
-
cadetCloudTranscribe,
|
|
22
|
-
sttErrorFromCloudSpeech,
|
|
23
|
-
} from "../cadet-cloud.js";
|
|
24
|
-
|
|
25
|
-
describe("cadetCloudSpeechAvailable", () => {
|
|
26
|
-
test("true with a connected platform client", async () => {
|
|
27
|
-
mockClient = { cloudAssistantId: "asst-1" };
|
|
28
|
-
expect(await cadetCloudSpeechAvailable()).toBe(true);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test("false without a client or without an assistant ID", async () => {
|
|
32
|
-
mockClient = null;
|
|
33
|
-
expect(await cadetCloudSpeechAvailable()).toBe(false);
|
|
34
|
-
mockClient = { cloudAssistantId: "" };
|
|
35
|
-
expect(await cadetCloudSpeechAvailable()).toBe(false);
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
describe("cadetCloudTranscribe", () => {
|
|
40
|
-
beforeEach(() => {
|
|
41
|
-
mockClient = { cloudAssistantId: "asst-1" };
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test("returns the transcription text", async () => {
|
|
45
|
-
mockTranscribeResult = {
|
|
46
|
-
ok: true,
|
|
47
|
-
value: { text: "hello", durationSeconds: 2 },
|
|
48
|
-
};
|
|
49
|
-
expect(await cadetCloudTranscribe(Buffer.from([1]), "audio/wav")).toEqual({
|
|
50
|
-
text: "hello",
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test("throws a normalized SttError on failure", async () => {
|
|
55
|
-
mockTranscribeResult = {
|
|
56
|
-
ok: false,
|
|
57
|
-
kind: "platform-error",
|
|
58
|
-
status: 429,
|
|
59
|
-
message: "slow down",
|
|
60
|
-
};
|
|
61
|
-
await expect(
|
|
62
|
-
cadetCloudTranscribe(Buffer.from([1]), "audio/wav"),
|
|
63
|
-
).rejects.toMatchObject({ name: "SttError", category: "rate-limit" });
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
describe("sttErrorFromCloudSpeech", () => {
|
|
68
|
-
const cases: Array<{
|
|
69
|
-
name: string;
|
|
70
|
-
failure: Record<string, unknown>;
|
|
71
|
-
category: string;
|
|
72
|
-
userFacing: boolean;
|
|
73
|
-
messageIncludes?: string;
|
|
74
|
-
messageExcludes?: string[];
|
|
75
|
-
}> = [
|
|
76
|
-
{
|
|
77
|
-
name: "unavailable → auth, user-facing reconnect copy",
|
|
78
|
-
failure: { ok: false, kind: "unavailable", message: "no connection" },
|
|
79
|
-
category: "auth",
|
|
80
|
-
userFacing: true,
|
|
81
|
-
messageIncludes: "no connection",
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
name: "insufficient_balance → provider-error, user-facing credit copy",
|
|
85
|
-
failure: {
|
|
86
|
-
ok: false,
|
|
87
|
-
kind: "platform-error",
|
|
88
|
-
status: 402,
|
|
89
|
-
code: "insufficient_balance",
|
|
90
|
-
message: "balance",
|
|
91
|
-
},
|
|
92
|
-
category: "provider-error",
|
|
93
|
-
userFacing: true,
|
|
94
|
-
messageIncludes: "Cadet credits",
|
|
95
|
-
},
|
|
96
|
-
// Platform-supplied `detail` present: real remediation, surfaced verbatim.
|
|
97
|
-
{
|
|
98
|
-
name: "401 with platform detail → auth, user-facing verbatim",
|
|
99
|
-
failure: {
|
|
100
|
-
ok: false,
|
|
101
|
-
kind: "platform-error",
|
|
102
|
-
status: 401,
|
|
103
|
-
detail: "Your session expired.",
|
|
104
|
-
message: "Your session expired.",
|
|
105
|
-
},
|
|
106
|
-
category: "auth",
|
|
107
|
-
userFacing: true,
|
|
108
|
-
messageIncludes: "session expired",
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
name: "429 with platform detail → rate-limit, user-facing verbatim",
|
|
112
|
-
failure: {
|
|
113
|
-
ok: false,
|
|
114
|
-
kind: "platform-error",
|
|
115
|
-
status: 429,
|
|
116
|
-
detail: "Too many requests, retry in 30s.",
|
|
117
|
-
message: "Too many requests, retry in 30s.",
|
|
118
|
-
},
|
|
119
|
-
category: "rate-limit",
|
|
120
|
-
userFacing: true,
|
|
121
|
-
messageIncludes: "retry in 30s",
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
name: "413 with platform detail → invalid-audio, user-facing verbatim",
|
|
125
|
-
failure: {
|
|
126
|
-
ok: false,
|
|
127
|
-
kind: "platform-error",
|
|
128
|
-
status: 413,
|
|
129
|
-
detail: "Audio exceeds the 25 MB limit.",
|
|
130
|
-
message: "Audio exceeds the 25 MB limit.",
|
|
131
|
-
},
|
|
132
|
-
category: "invalid-audio",
|
|
133
|
-
userFacing: true,
|
|
134
|
-
messageIncludes: "25 MB",
|
|
135
|
-
},
|
|
136
|
-
// No platform `detail`: the generic "(platform returned N)" fallback stays
|
|
137
|
-
// non-user-facing so describeSttFailure emits friendly category copy
|
|
138
|
-
// instead of leaking a raw HTTP status — except a bare auth status, whose
|
|
139
|
-
// friendly copy would be the wrong BYOK "check your API key" guidance, so it
|
|
140
|
-
// gets a sanitized cloud-connection message surfaced verbatim.
|
|
141
|
-
{
|
|
142
|
-
name: "401 without detail → auth, user-facing sanitized reconnect copy",
|
|
143
|
-
failure: {
|
|
144
|
-
ok: false,
|
|
145
|
-
kind: "platform-error",
|
|
146
|
-
status: 401,
|
|
147
|
-
message: "Cloud speech transcription failed (platform returned 401).",
|
|
148
|
-
},
|
|
149
|
-
category: "auth",
|
|
150
|
-
userFacing: true,
|
|
151
|
-
messageIncludes: "reconnect your Cadet account",
|
|
152
|
-
messageExcludes: ["API key", "platform returned"],
|
|
153
|
-
},
|
|
154
|
-
{
|
|
155
|
-
name: "403 without detail → auth, user-facing sanitized reconnect copy",
|
|
156
|
-
failure: {
|
|
157
|
-
ok: false,
|
|
158
|
-
kind: "platform-error",
|
|
159
|
-
status: 403,
|
|
160
|
-
message: "Cloud speech transcription failed (platform returned 403).",
|
|
161
|
-
},
|
|
162
|
-
category: "auth",
|
|
163
|
-
userFacing: true,
|
|
164
|
-
messageIncludes: "reconnect your Cadet account",
|
|
165
|
-
messageExcludes: ["API key", "platform returned"],
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
name: "429 without detail → rate-limit, not user-facing",
|
|
169
|
-
failure: {
|
|
170
|
-
ok: false,
|
|
171
|
-
kind: "platform-error",
|
|
172
|
-
status: 429,
|
|
173
|
-
message: "Cloud speech transcription failed (platform returned 429).",
|
|
174
|
-
},
|
|
175
|
-
category: "rate-limit",
|
|
176
|
-
userFacing: false,
|
|
177
|
-
},
|
|
178
|
-
{
|
|
179
|
-
name: "413 without detail → invalid-audio, not user-facing",
|
|
180
|
-
failure: {
|
|
181
|
-
ok: false,
|
|
182
|
-
kind: "platform-error",
|
|
183
|
-
status: 413,
|
|
184
|
-
message: "Cloud speech transcription failed (platform returned 413).",
|
|
185
|
-
},
|
|
186
|
-
category: "invalid-audio",
|
|
187
|
-
userFacing: false,
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
name: "502 without detail → provider-error, not user-facing",
|
|
191
|
-
failure: {
|
|
192
|
-
ok: false,
|
|
193
|
-
kind: "platform-error",
|
|
194
|
-
status: 502,
|
|
195
|
-
message: "Cloud speech transcription failed (platform returned 502).",
|
|
196
|
-
},
|
|
197
|
-
category: "provider-error",
|
|
198
|
-
userFacing: false,
|
|
199
|
-
},
|
|
200
|
-
];
|
|
201
|
-
|
|
202
|
-
for (const {
|
|
203
|
-
name,
|
|
204
|
-
failure,
|
|
205
|
-
category,
|
|
206
|
-
userFacing,
|
|
207
|
-
messageIncludes,
|
|
208
|
-
messageExcludes,
|
|
209
|
-
} of cases) {
|
|
210
|
-
test(name, () => {
|
|
211
|
-
const err = sttErrorFromCloudSpeech(
|
|
212
|
-
failure as Parameters<typeof sttErrorFromCloudSpeech>[0],
|
|
213
|
-
);
|
|
214
|
-
expect(err).toBeInstanceOf(SttError);
|
|
215
|
-
expect(err.category).toBe(category as SttError["category"]);
|
|
216
|
-
// User-facing when the failure carries real remediation (or a sanitized
|
|
217
|
-
// managed-auth message); a bare non-auth status stays non-user-facing for
|
|
218
|
-
// friendly category rewrite.
|
|
219
|
-
expect(err.userFacing).toBe(userFacing);
|
|
220
|
-
if (messageIncludes) {
|
|
221
|
-
expect(err.message).toContain(messageIncludes);
|
|
222
|
-
}
|
|
223
|
-
for (const excluded of messageExcludes ?? []) {
|
|
224
|
-
expect(err.message).not.toContain(excluded);
|
|
225
|
-
}
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
describe("describeSttFailure over managed STT failures", () => {
|
|
231
|
-
test("surfaces a cloud-supplied detail verbatim", () => {
|
|
232
|
-
const err = sttErrorFromCloudSpeech({
|
|
233
|
-
ok: false,
|
|
234
|
-
kind: "platform-error",
|
|
235
|
-
status: 429,
|
|
236
|
-
detail: "Too many requests, retry in 30s.",
|
|
237
|
-
message: "Too many requests, retry in 30s.",
|
|
238
|
-
});
|
|
239
|
-
expect(describeSttFailure(err, "cadet")).toBe(
|
|
240
|
-
"Too many requests, retry in 30s.",
|
|
241
|
-
);
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
test("replaces a bare HTTP-status fallback with friendly category copy", () => {
|
|
245
|
-
const err = sttErrorFromCloudSpeech({
|
|
246
|
-
ok: false,
|
|
247
|
-
kind: "platform-error",
|
|
248
|
-
status: 429,
|
|
249
|
-
message: "Cloud speech transcription failed (platform returned 429).",
|
|
250
|
-
});
|
|
251
|
-
const copy = describeSttFailure(err, "cadet");
|
|
252
|
-
expect(copy).not.toContain("platform returned 429");
|
|
253
|
-
expect(copy).toContain("rate-limiting");
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
test("bare auth failure gets sanitized reconnect copy, not the BYOK rewrite", () => {
|
|
257
|
-
const err = sttErrorFromCloudSpeech({
|
|
258
|
-
ok: false,
|
|
259
|
-
kind: "platform-error",
|
|
260
|
-
status: 401,
|
|
261
|
-
message: "Cloud speech transcription failed (platform returned 401).",
|
|
262
|
-
});
|
|
263
|
-
const copy = describeSttFailure(err, "cadet");
|
|
264
|
-
// Managed STT holds no local key: the "check your API key in Settings →
|
|
265
|
-
// Voice" guidance is wrong, and the raw HTTP status must not leak.
|
|
266
|
-
expect(copy).toContain("reconnect your Cadet account");
|
|
267
|
-
expect(copy).not.toContain("API key");
|
|
268
|
-
expect(copy).not.toContain("Settings → Voice");
|
|
269
|
-
expect(copy).not.toContain("platform returned 401");
|
|
270
|
-
});
|
|
271
|
-
});
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
-
|
|
3
|
-
let mockMintResult: string | null = "minted-token";
|
|
4
|
-
|
|
5
|
-
mock.module("../../../runtime/auth/token-service.js", () => ({
|
|
6
|
-
mintToken: (params: { aud: string; sub: string }) => {
|
|
7
|
-
if (mockMintResult === null) {
|
|
8
|
-
throw new Error("signing key unavailable");
|
|
9
|
-
}
|
|
10
|
-
return `${mockMintResult}:${params.aud}:${params.sub}`;
|
|
11
|
-
},
|
|
12
|
-
}));
|
|
13
|
-
|
|
14
|
-
import {
|
|
15
|
-
mapTunnelError,
|
|
16
|
-
probeTunnelRejection,
|
|
17
|
-
resolveSpeechRelayConnection,
|
|
18
|
-
} from "../cadet-speech-relay-connection.js";
|
|
19
|
-
|
|
20
|
-
describe("resolveSpeechRelayConnection", () => {
|
|
21
|
-
const originalEnv = process.env.GATEWAY_INTERNAL_URL;
|
|
22
|
-
|
|
23
|
-
beforeEach(() => {
|
|
24
|
-
mockMintResult = "minted-token";
|
|
25
|
-
delete process.env.GATEWAY_INTERNAL_URL;
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
afterEach(() => {
|
|
29
|
-
if (originalEnv === undefined) {
|
|
30
|
-
delete process.env.GATEWAY_INTERNAL_URL;
|
|
31
|
-
} else {
|
|
32
|
-
process.env.GATEWAY_INTERNAL_URL = originalEnv;
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test("resolves the gateway origin and a per-dial token minter", async () => {
|
|
37
|
-
process.env.GATEWAY_INTERNAL_URL = "http://gateway:7822/";
|
|
38
|
-
|
|
39
|
-
const connection = await resolveSpeechRelayConnection();
|
|
40
|
-
expect(connection).not.toBeNull();
|
|
41
|
-
expect(connection!.httpBaseUrl).toBe("http://gateway:7822");
|
|
42
|
-
expect(connection!.wsBaseUrl).toBe("ws://gateway:7822");
|
|
43
|
-
// The token targets the gateway audience with the daemon service sub.
|
|
44
|
-
expect(connection!.mintServiceToken()).toBe(
|
|
45
|
-
"minted-token:cadet-gateway:svc:daemon:self",
|
|
46
|
-
);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test("returns null when the signing key is unavailable", async () => {
|
|
50
|
-
mockMintResult = null;
|
|
51
|
-
expect(await resolveSpeechRelayConnection()).toBeNull();
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
describe("mapTunnelError", () => {
|
|
56
|
-
test("maps the relay contract's codes onto categories", () => {
|
|
57
|
-
expect(mapTunnelError({ code: "invalid_key" })).toMatchObject({
|
|
58
|
-
category: "auth",
|
|
59
|
-
message: expect.stringContaining("TUNNEL_BASE_URL"),
|
|
60
|
-
});
|
|
61
|
-
expect(
|
|
62
|
-
mapTunnelError({ code: "missing_platform_connection" }),
|
|
63
|
-
).toMatchObject({
|
|
64
|
-
category: "auth",
|
|
65
|
-
message: expect.stringContaining("cloud connect"),
|
|
66
|
-
});
|
|
67
|
-
expect(mapTunnelError({ code: "invalid_token" })).toMatchObject({
|
|
68
|
-
category: "auth",
|
|
69
|
-
message: expect.stringContaining("restart the assistant"),
|
|
70
|
-
});
|
|
71
|
-
expect(mapTunnelError({ code: "insufficient_balance" })).toMatchObject({
|
|
72
|
-
category: "provider-error",
|
|
73
|
-
message: expect.stringContaining("credits"),
|
|
74
|
-
});
|
|
75
|
-
expect(mapTunnelError({ code: "provider_unreachable" }).category).toBe(
|
|
76
|
-
"provider-error",
|
|
77
|
-
);
|
|
78
|
-
expect(mapTunnelError({ code: "upstream_error" }).category).toBe(
|
|
79
|
-
"provider-error",
|
|
80
|
-
);
|
|
81
|
-
expect(
|
|
82
|
-
mapTunnelError({ code: "novel_code", detail: "what" }),
|
|
83
|
-
).toMatchObject({
|
|
84
|
-
category: "provider-error",
|
|
85
|
-
message: expect.stringContaining("novel_code"),
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
describe("probeTunnelRejection", () => {
|
|
91
|
-
const originalFetch = globalThis.fetch;
|
|
92
|
-
|
|
93
|
-
afterEach(() => {
|
|
94
|
-
globalThis.fetch = originalFetch;
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
test("returns the JSON rejection body", async () => {
|
|
98
|
-
globalThis.fetch = (async () =>
|
|
99
|
-
new Response(JSON.stringify({ code: "invalid_key", detail: "nope" }), {
|
|
100
|
-
status: 401,
|
|
101
|
-
})) as unknown as typeof fetch;
|
|
102
|
-
|
|
103
|
-
expect(await probeTunnelRejection("http://gateway.test/x")).toEqual({
|
|
104
|
-
code: "invalid_key",
|
|
105
|
-
detail: "nope",
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test("returns null on the gateway's gate-passed 426", async () => {
|
|
110
|
-
globalThis.fetch = (async () =>
|
|
111
|
-
new Response(
|
|
112
|
-
JSON.stringify({ code: "upgrade_required", detail: "connect via WS" }),
|
|
113
|
-
{ status: 426 },
|
|
114
|
-
)) as unknown as typeof fetch;
|
|
115
|
-
|
|
116
|
-
expect(await probeTunnelRejection("http://gateway.test/x")).toBeNull();
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
test("returns null when the probe itself fails", async () => {
|
|
120
|
-
globalThis.fetch = (async () => {
|
|
121
|
-
throw new Error("refused");
|
|
122
|
-
}) as unknown as typeof fetch;
|
|
123
|
-
|
|
124
|
-
expect(await probeTunnelRejection("http://gateway.test/x")).toBeNull();
|
|
125
|
-
});
|
|
126
|
-
});
|
|
@@ -1,309 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cloud Flux STT through the gateway's speech relay
|
|
3
|
-
* (gateway -> tunnel -> Deepgram Flux; tunnel contact is gateway-only).
|
|
4
|
-
*
|
|
5
|
-
* Composes {@link DeepgramFluxRealtimeTranscriber} against the relay's
|
|
6
|
-
* managed STT v2 endpoint with `contract=flux`, so Flux's own turn events
|
|
7
|
-
* arrive verbatim. Without that opt-in tunnel translates them into the
|
|
8
|
-
* released v1 Deepgram dialect, which drops `EagerEndOfTurn` and
|
|
9
|
-
* `TurnResumed` entirely and leaves live voice with no provider turn end to
|
|
10
|
-
* act on.
|
|
11
|
-
*
|
|
12
|
-
* This wrapper owns what is relay-specific, mirroring
|
|
13
|
-
* `CadetManagedRealtimeTranscriber` on the v1 path:
|
|
14
|
-
*
|
|
15
|
-
* - Dialing the gateway (`/v2/speech/stt/stream`, `?key=` carries a
|
|
16
|
-
* daemon-minted service token, no `model` param since tunnel derives and
|
|
17
|
-
* prices the Flux model from `language`).
|
|
18
|
-
* - Surfacing `tunnel_error` control frames as categorized SttErrors instead
|
|
19
|
-
* of generic socket failures.
|
|
20
|
-
* - Transparent re-dial on tunnel's 30-minute session cap, so one live-voice
|
|
21
|
-
* session can outlive it.
|
|
22
|
-
*
|
|
23
|
-
* There is deliberately no `finalizeUtterance`: Flux commits turns itself,
|
|
24
|
-
* and the interface makes the method optional so live voice feature-detects
|
|
25
|
-
* its absence and never asks for a flush.
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
import {
|
|
29
|
-
type StreamingTranscriber,
|
|
30
|
-
SttError,
|
|
31
|
-
type SttStreamServerEvent,
|
|
32
|
-
} from "../../stt/types.js";
|
|
33
|
-
import { getLogger } from "../../util/logger.js";
|
|
34
|
-
import {
|
|
35
|
-
mapTunnelError,
|
|
36
|
-
probeTunnelRejection,
|
|
37
|
-
type SpeechRelayConnection,
|
|
38
|
-
type TunnelErrorInfo,
|
|
39
|
-
} from "./cadet-speech-relay-connection.js";
|
|
40
|
-
import { DeepgramFluxRealtimeTranscriber } from "./deepgram-flux-realtime.js";
|
|
41
|
-
|
|
42
|
-
const log = getLogger("cadet-cloud-flux-realtime");
|
|
43
|
-
|
|
44
|
-
/** Managed STT v2: tunnel resolves this to Deepgram's Flux `/v2/listen`. */
|
|
45
|
-
const STT_STREAM_PATH = "/v2/speech/stt/stream";
|
|
46
|
-
|
|
47
|
-
export interface CadetManagedFluxRealtimeOptions {
|
|
48
|
-
/** Audio sample rate in Hz (default: 16000). */
|
|
49
|
-
sampleRate?: number;
|
|
50
|
-
/**
|
|
51
|
-
* BCP-47 language code (or `"multi"`), forwarded to the relay. This is
|
|
52
|
-
* how tunnel picks between `flux-general-en` and `flux-general-multi`, so
|
|
53
|
-
* it is the only lever over the Flux model from this side.
|
|
54
|
-
*/
|
|
55
|
-
language?: string;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export class CadetManagedFluxRealtimeTranscriber implements StreamingTranscriber {
|
|
59
|
-
readonly providerId = "cadet-flux" as const;
|
|
60
|
-
readonly boundaryId = "daemon-streaming" as const;
|
|
61
|
-
|
|
62
|
-
private readonly connection: SpeechRelayConnection;
|
|
63
|
-
private readonly options: CadetManagedFluxRealtimeOptions;
|
|
64
|
-
|
|
65
|
-
private onEvent: ((event: SttStreamServerEvent) => void) | null = null;
|
|
66
|
-
/** The current relay session; null while re-dialing after a session cap. */
|
|
67
|
-
private inner: DeepgramFluxRealtimeTranscriber | null = null;
|
|
68
|
-
/**
|
|
69
|
-
* The most recent `tunnel_error` frame from the current session. Set just
|
|
70
|
-
* before tunnel closes the socket; consumed when the inner adapter turns
|
|
71
|
-
* that close into an error event.
|
|
72
|
-
*/
|
|
73
|
-
private pendingRelayError: TunnelErrorInfo | null = null;
|
|
74
|
-
/**
|
|
75
|
-
* Set when the current session hit tunnel's duration cap: the swap to a
|
|
76
|
-
* fresh session is deferred to the capped session's `closed` event so its
|
|
77
|
-
* close cleanup drains through first.
|
|
78
|
-
*/
|
|
79
|
-
private redialOnClose = false;
|
|
80
|
-
private stopping = false;
|
|
81
|
-
private closedEmitted = false;
|
|
82
|
-
// Consumers see one stream across relay reconnects, with one turn sequence.
|
|
83
|
-
private turnIndexOffset = 0;
|
|
84
|
-
private latestTurnIndex = -1;
|
|
85
|
-
|
|
86
|
-
constructor(
|
|
87
|
-
connection: SpeechRelayConnection,
|
|
88
|
-
options: CadetManagedFluxRealtimeOptions = {},
|
|
89
|
-
) {
|
|
90
|
-
this.connection = connection;
|
|
91
|
-
this.options = options;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async start(onEvent: (event: SttStreamServerEvent) => void): Promise<void> {
|
|
95
|
-
if (this.onEvent) {
|
|
96
|
-
throw new Error(
|
|
97
|
-
"CadetManagedFluxRealtimeTranscriber: start() called twice",
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
this.onEvent = onEvent;
|
|
101
|
-
try {
|
|
102
|
-
const inner = await this.dial();
|
|
103
|
-
if (this.stopping) {
|
|
104
|
-
// stop() raced the initial dial: it saw no inner to tear down and
|
|
105
|
-
// already emitted closed, so the freshly opened relay session must
|
|
106
|
-
// not be kept (a leaked session keeps tunnel metering).
|
|
107
|
-
inner.stop();
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
this.inner = inner;
|
|
111
|
-
} catch (err) {
|
|
112
|
-
// A failed WebSocket upgrade exposes no HTTP details; replay the
|
|
113
|
-
// request as a plain GET to recover the relay's {code, detail}
|
|
114
|
-
// rejection.
|
|
115
|
-
const rejection = await probeTunnelRejection(this.probeUrl());
|
|
116
|
-
if (rejection) {
|
|
117
|
-
const mapped = mapTunnelError(rejection);
|
|
118
|
-
throw new SttError(mapped.category, mapped.message, {
|
|
119
|
-
userFacing: true,
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
throw err;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
sendAudio(audio: Buffer, mimeType: string): void {
|
|
127
|
-
// Audio arriving during a session-cap re-dial (sub-second) is dropped,
|
|
128
|
-
// mirroring the inner adapter's backpressure-drop semantics.
|
|
129
|
-
this.inner?.sendAudio(audio, mimeType);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
stop(): void {
|
|
133
|
-
if (this.stopping) {
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
this.stopping = true;
|
|
137
|
-
if (this.inner) {
|
|
138
|
-
this.inner.stop();
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
// Mid-redial stop: there is no session to drain.
|
|
142
|
-
this.emitClosedOnce();
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// -- Relay session management ------------------------------------------
|
|
146
|
-
|
|
147
|
-
private async dial(): Promise<DeepgramFluxRealtimeTranscriber> {
|
|
148
|
-
this.pendingRelayError = null;
|
|
149
|
-
// Fresh per dial: a session-cap re-dial happens ~30 minutes in, long
|
|
150
|
-
// after any previously minted token expired.
|
|
151
|
-
const inner = new DeepgramFluxRealtimeTranscriber(
|
|
152
|
-
this.connection.mintServiceToken(),
|
|
153
|
-
{
|
|
154
|
-
baseUrl: this.connection.wsBaseUrl,
|
|
155
|
-
path: STT_STREAM_PATH,
|
|
156
|
-
queryAuth: true,
|
|
157
|
-
omitModelParam: true,
|
|
158
|
-
nativeContract: true,
|
|
159
|
-
...(this.options.sampleRate !== undefined
|
|
160
|
-
? { sampleRate: this.options.sampleRate }
|
|
161
|
-
: {}),
|
|
162
|
-
...(this.options.language ? { language: this.options.language } : {}),
|
|
163
|
-
onUnhandledFrame: (frame) => this.handleRelayFrame(frame),
|
|
164
|
-
},
|
|
165
|
-
);
|
|
166
|
-
await inner.start((event) => this.handleInnerEvent(inner, event));
|
|
167
|
-
return inner;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
private probeUrl(): string {
|
|
171
|
-
const key = encodeURIComponent(this.connection.mintServiceToken());
|
|
172
|
-
return `${this.connection.httpBaseUrl}${STT_STREAM_PATH}?key=${key}`;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
private handleRelayFrame(frame: Record<string, unknown>): void {
|
|
176
|
-
if (frame.type !== "tunnel_error" || typeof frame.code !== "string") {
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
this.pendingRelayError = {
|
|
180
|
-
code: frame.code,
|
|
181
|
-
...(typeof frame.detail === "string" ? { detail: frame.detail } : {}),
|
|
182
|
-
};
|
|
183
|
-
log.info(
|
|
184
|
-
{ code: frame.code, detail: frame.detail },
|
|
185
|
-
"Received tunnel_error frame on managed Flux relay",
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
private handleInnerEvent(
|
|
190
|
-
source: DeepgramFluxRealtimeTranscriber,
|
|
191
|
-
event: SttStreamServerEvent,
|
|
192
|
-
): void {
|
|
193
|
-
// Events from a superseded session (the one replaced by a re-dial) must
|
|
194
|
-
// not leak through: its close/error was already accounted for.
|
|
195
|
-
if (source !== this.inner) {
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if (event.type === "error") {
|
|
200
|
-
const relayError = this.pendingRelayError;
|
|
201
|
-
if (relayError?.code === "session_duration_exceeded") {
|
|
202
|
-
// Tunnel's 30-minute cap, not a failure. Swallow the error but keep
|
|
203
|
-
// the capped session current so its close cleanup drains first; the
|
|
204
|
-
// re-dial happens on its `closed` event.
|
|
205
|
-
this.redialOnClose = !this.stopping;
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
if (relayError) {
|
|
209
|
-
const mapped = mapTunnelError(relayError);
|
|
210
|
-
this.emit({
|
|
211
|
-
type: "error",
|
|
212
|
-
category: mapped.category,
|
|
213
|
-
message: mapped.message,
|
|
214
|
-
});
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
this.emit(event);
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
if (event.type === "closed") {
|
|
222
|
-
if (this.redialOnClose && !this.stopping) {
|
|
223
|
-
this.redialOnClose = false;
|
|
224
|
-
this.beginRedial();
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
this.emitClosedOnce();
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (
|
|
232
|
-
(event.type === "turn-start" || event.type === "turn-end") &&
|
|
233
|
-
event.turnIndex !== undefined
|
|
234
|
-
) {
|
|
235
|
-
const turnIndex = this.turnIndexOffset + event.turnIndex;
|
|
236
|
-
this.latestTurnIndex = Math.max(this.latestTurnIndex, turnIndex);
|
|
237
|
-
this.emit({ ...event, turnIndex });
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
this.emit(event);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* Replace the capped session with a fresh one. Detached: audio sent during
|
|
245
|
-
* the swap is dropped (see {@link sendAudio}); a dial failure is a real
|
|
246
|
-
* error and ends the stream.
|
|
247
|
-
*/
|
|
248
|
-
private beginRedial(): void {
|
|
249
|
-
log.info("Cloud Flux relay session hit tunnel's duration cap, re-dialing");
|
|
250
|
-
this.inner = null;
|
|
251
|
-
this.turnIndexOffset = this.latestTurnIndex + 1;
|
|
252
|
-
void (async () => {
|
|
253
|
-
try {
|
|
254
|
-
const inner = await this.dial();
|
|
255
|
-
if (this.stopping) {
|
|
256
|
-
inner.stop();
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
this.inner = inner;
|
|
260
|
-
log.info("Cloud Flux relay re-dial complete; session continues");
|
|
261
|
-
} catch (err) {
|
|
262
|
-
if (this.stopping) {
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
const rejection = await probeTunnelRejection(this.probeUrl());
|
|
266
|
-
if (rejection) {
|
|
267
|
-
const mapped = mapTunnelError(rejection);
|
|
268
|
-
this.emit({
|
|
269
|
-
type: "error",
|
|
270
|
-
category: mapped.category,
|
|
271
|
-
message: mapped.message,
|
|
272
|
-
});
|
|
273
|
-
} else {
|
|
274
|
-
this.emit({
|
|
275
|
-
type: "error",
|
|
276
|
-
category: "provider-error",
|
|
277
|
-
message: `Cloud Flux relay re-dial failed after the session cap: ${
|
|
278
|
-
err instanceof Error ? err.message : String(err)
|
|
279
|
-
}`,
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
this.emitClosedOnce();
|
|
283
|
-
}
|
|
284
|
-
})();
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// -- Event emission -----------------------------------------------------
|
|
288
|
-
|
|
289
|
-
private emit(event: SttStreamServerEvent): void {
|
|
290
|
-
if (!this.onEvent || this.closedEmitted) {
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
if (event.type === "closed") {
|
|
294
|
-
this.closedEmitted = true;
|
|
295
|
-
}
|
|
296
|
-
try {
|
|
297
|
-
this.onEvent(event);
|
|
298
|
-
} catch (err) {
|
|
299
|
-
log.warn(
|
|
300
|
-
{ error: err },
|
|
301
|
-
"Listener error in Cadet Cloud Flux realtime adapter",
|
|
302
|
-
);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
private emitClosedOnce(): void {
|
|
307
|
-
this.emit({ type: "closed" });
|
|
308
|
-
}
|
|
309
|
-
}
|