@kuralle-syrinx/core 2.1.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 (52) hide show
  1. package/README.md +41 -0
  2. package/package.json +22 -0
  3. package/src/audio/audio.test.ts +285 -0
  4. package/src/audio/index.ts +5 -0
  5. package/src/audio/mulaw.ts +42 -0
  6. package/src/audio/pcm.ts +43 -0
  7. package/src/audio/resample.ts +177 -0
  8. package/src/audio-envelope.test.ts +167 -0
  9. package/src/audio-envelope.ts +143 -0
  10. package/src/conversation-event.ts +62 -0
  11. package/src/error-handler.test.ts +56 -0
  12. package/src/error-handler.ts +149 -0
  13. package/src/idle-timeout.ts +210 -0
  14. package/src/index.ts +215 -0
  15. package/src/init-chain.ts +137 -0
  16. package/src/init-stage-order.ts +79 -0
  17. package/src/latency-filler-fixtures.ts +16 -0
  18. package/src/latency-filler.test.ts +62 -0
  19. package/src/latency-filler.ts +125 -0
  20. package/src/mode-switcher.ts +110 -0
  21. package/src/observability-observer.test.ts +245 -0
  22. package/src/observability-observer.ts +195 -0
  23. package/src/observability.test.ts +85 -0
  24. package/src/observability.ts +93 -0
  25. package/src/packet-factories.test.ts +34 -0
  26. package/src/packet-factories.ts +243 -0
  27. package/src/packets.ts +553 -0
  28. package/src/pipeline-bus.g10.test.ts +145 -0
  29. package/src/pipeline-bus.test.ts +197 -0
  30. package/src/pipeline-bus.ts +369 -0
  31. package/src/plugin-contract.ts +79 -0
  32. package/src/primary-speaker-fixtures.ts +45 -0
  33. package/src/primary-speaker-gate.test.ts +150 -0
  34. package/src/primary-speaker-gate.ts +186 -0
  35. package/src/provider-fallback.test.ts +87 -0
  36. package/src/provider-fallback.ts +88 -0
  37. package/src/reasoner.test.ts +69 -0
  38. package/src/reasoner.ts +54 -0
  39. package/src/retry.test.ts +83 -0
  40. package/src/retry.ts +106 -0
  41. package/src/scheduler.ts +28 -0
  42. package/src/tts-playout-clock.test.ts +125 -0
  43. package/src/tts-playout-clock.ts +116 -0
  44. package/src/turn-arbiter.characterization.test.ts +477 -0
  45. package/src/turn-arbiter.test.ts +567 -0
  46. package/src/turn-arbiter.ts +283 -0
  47. package/src/voice-agent-session-util.ts +240 -0
  48. package/src/voice-agent-session.test.ts +2487 -0
  49. package/src/voice-agent-session.ts +1175 -0
  50. package/src/voice-text.test.ts +81 -0
  51. package/src/voice-text.ts +102 -0
  52. package/tsconfig.json +21 -0
@@ -0,0 +1,210 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // Syrinx Kernel v2 — Idle Timeout Manager
4
+ //
5
+ // Tracks user silence and escalates: inject message → inject warning → disconnect.
6
+ // Configurable duration, escalation messages, and consecutive backoff.
7
+ //
8
+ // Design decision (per RFC Q4 resolution): idle messages are injected as
9
+ // synthetic LlmDeltaPacket + LlmDonePacket through the normal TTS path.
10
+ // This keeps voice style, logging, interruption, and abort semantics consistent.
11
+
12
+ import type { PipelineBus } from "./pipeline-bus.js";
13
+ import { Route } from "./pipeline-bus.js";
14
+ import type {
15
+ StartIdleTimeoutPacket,
16
+ StopIdleTimeoutPacket,
17
+ InjectMessagePacket,
18
+ DisconnectRequestedPacket,
19
+ } from "./packets.js";
20
+ import { TimerScheduler, type Scheduler } from "./scheduler.js";
21
+
22
+ // =============================================================================
23
+ // Configuration
24
+ // =============================================================================
25
+
26
+ export interface IdleTimeoutConfig {
27
+ /** Base duration before triggering idle behavior (ms). Default: 15000 */
28
+ durationMs: number;
29
+ /**
30
+ * Number of consecutive timeouts before disconnecting.
31
+ * 0 = never disconnect (just inject messages repeatedly).
32
+ * Default: 3
33
+ */
34
+ maxConsecutive: number;
35
+ /**
36
+ * Messages to inject at each consecutive idle.
37
+ * Index 0 = first timeout, index 1 = second timeout, etc.
38
+ * If more timeouts occur than messages, the last message repeats.
39
+ * Default: ["Are you still there?", "I'll end this call soon."]
40
+ */
41
+ escalationMessages: string[];
42
+ /**
43
+ * Whether to disconnect the session after maxConsecutive is reached.
44
+ * If false, the maxConsecutive counter stops incrementing and the
45
+ * last escalation message repeats indefinitely.
46
+ * Default: true
47
+ */
48
+ disconnectAfterMax: boolean;
49
+ }
50
+
51
+ export const DEFAULT_IDLE_TIMEOUT_CONFIG: IdleTimeoutConfig = {
52
+ durationMs: 15_000,
53
+ maxConsecutive: 3,
54
+ escalationMessages: ["Are you still there?", "I'll end this call soon."],
55
+ disconnectAfterMax: true,
56
+ };
57
+
58
+ // =============================================================================
59
+ // Manager
60
+ // =============================================================================
61
+
62
+ export class IdleTimeoutManager {
63
+ private timerScheduled = false;
64
+ private count = 0;
65
+ private readonly config: IdleTimeoutConfig;
66
+ private readonly bus: PipelineBus;
67
+ private currentContextId: string;
68
+ private readonly scheduler: Scheduler;
69
+
70
+ constructor(
71
+ bus: PipelineBus,
72
+ config?: Partial<IdleTimeoutConfig>,
73
+ scheduler?: Scheduler,
74
+ ) {
75
+ this.bus = bus;
76
+ this.config = { ...DEFAULT_IDLE_TIMEOUT_CONFIG, ...config };
77
+ this.currentContextId = "";
78
+ this.scheduler = scheduler ?? new TimerScheduler();
79
+ }
80
+
81
+ // -------------------------------------------------------------------------
82
+ // Public API
83
+ // -------------------------------------------------------------------------
84
+
85
+ /** Set the current turn context ID (used for injected message context). */
86
+ setContextId(id: string): void {
87
+ this.currentContextId = id;
88
+ }
89
+
90
+ /** Start (or restart) the idle timeout timer. */
91
+ start(): void {
92
+ this.clearTimer();
93
+ if (this.config.durationMs <= 0) return;
94
+
95
+ this.timerScheduled = true;
96
+ this.scheduler.schedule("voice.idle_timeout", this.config.durationMs, () => {
97
+ this.timerScheduled = false;
98
+ this.onTimeout();
99
+ });
100
+ }
101
+
102
+ /**
103
+ * Stop the idle timeout timer.
104
+ * @param resetCount — If true, resets the consecutive idle counter.
105
+ * Use true when the user actively engages (speaks or types).
106
+ * Use false for system-driven stops (e.g., TTS still playing).
107
+ */
108
+ stop(resetCount: boolean): void {
109
+ this.clearTimer();
110
+ if (resetCount) {
111
+ this.count = 0;
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Extend the current timer by a duration (e.g., TTS audio playback time).
117
+ * Stops the current timer and restarts with the remaining time + extension.
118
+ */
119
+ extend(ms: number): void {
120
+ this.clearTimer();
121
+ this.timerScheduled = true;
122
+ this.scheduler.schedule("voice.idle_timeout", this.config.durationMs + ms, () => {
123
+ this.timerScheduled = false;
124
+ this.onTimeout();
125
+ });
126
+ }
127
+
128
+ /** Clean up — stop timer without side effects. */
129
+ dispose(): void {
130
+ this.clearTimer();
131
+ }
132
+
133
+ // -------------------------------------------------------------------------
134
+ // Private
135
+ // -------------------------------------------------------------------------
136
+
137
+ private clearTimer(): void {
138
+ if (!this.timerScheduled) return;
139
+ this.scheduler.cancel("voice.idle_timeout");
140
+ this.timerScheduled = false;
141
+ }
142
+
143
+ /**
144
+ * Resolve a non-empty context id for injected idle turns. If no turn context has been seen yet
145
+ * (e.g. the idle timer fired before the first user turn completed), synthesize a stable one —
146
+ * an empty contextId propagates downstream and is rejected by providers that validate it
147
+ * (e.g. Cartesia TTS: "context_id must be alphanumeric/_/-").
148
+ */
149
+ private ensureContextId(): string {
150
+ if (this.currentContextId === "") {
151
+ this.currentContextId = `idle-${String(Date.now())}`;
152
+ }
153
+ return this.currentContextId;
154
+ }
155
+
156
+ private onTimeout(): void {
157
+ this.count++;
158
+ const contextId = this.ensureContextId();
159
+
160
+ // Check if we should disconnect
161
+ if (
162
+ this.config.maxConsecutive > 0 &&
163
+ this.count >= this.config.maxConsecutive &&
164
+ this.config.disconnectAfterMax
165
+ ) {
166
+ const disconnect: DisconnectRequestedPacket = {
167
+ kind: "session.disconnect",
168
+ contextId,
169
+ timestampMs: Date.now(),
170
+ reason: `idle_timeout: ${this.count} consecutive`,
171
+ };
172
+ this.bus.push(Route.Critical, disconnect);
173
+ return;
174
+ }
175
+
176
+ // Inject escalation message
177
+ const msgIdx = Math.min(
178
+ this.count - 1,
179
+ this.config.escalationMessages.length - 1,
180
+ );
181
+ const message = this.config.escalationMessages[msgIdx];
182
+ if (message) {
183
+ const inject: InjectMessagePacket = {
184
+ kind: "inject.message",
185
+ contextId,
186
+ timestampMs: Date.now(),
187
+ text: message,
188
+ };
189
+ this.bus.push(Route.Main, inject);
190
+ }
191
+
192
+ // Restart timer for next escalation
193
+ this.start();
194
+ }
195
+
196
+ // -------------------------------------------------------------------------
197
+ // Bus packet handlers (for integration with VoiceAgentSession)
198
+ // -------------------------------------------------------------------------
199
+
200
+ /** Handle a StartIdleTimeoutPacket from the bus. */
201
+ handleStart(pkt: StartIdleTimeoutPacket): void {
202
+ if (pkt.contextId) this.setContextId(pkt.contextId);
203
+ this.start();
204
+ }
205
+
206
+ /** Handle a StopIdleTimeoutPacket from the bus. */
207
+ handleStop(pkt: StopIdleTimeoutPacket): void {
208
+ this.stop(pkt.resetCount);
209
+ }
210
+ }
package/src/index.ts ADDED
@@ -0,0 +1,215 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // Syrinx Kernel v2 — Public API
4
+ //
5
+ // Everything a consumer needs to build voice agents with the new kernel.
6
+
7
+ // Core types
8
+ export {
9
+ type AudioFormat,
10
+ type VoicePacket,
11
+ type AsyncPacket,
12
+ type VoiceErrorPacket,
13
+ ErrorCategory,
14
+ SessionState,
15
+ InitStage,
16
+ type InitStepCompletedPacket,
17
+ type InitializationFailedPacket,
18
+ type InitializationCompletedPacket,
19
+ } from "./packets.js";
20
+
21
+ // Pipeline packets — input
22
+ export {
23
+ type UserAudioReceivedPacket,
24
+ type UserTextReceivedPacket,
25
+ type DenoiseAudioPacket,
26
+ type DenoisedAudioPacket,
27
+ type VadAudioPacket,
28
+ type VadSpeechStartedPacket,
29
+ type VadSpeechEndedPacket,
30
+ type VadSpeechActivityPacket,
31
+ type SpeechToTextAudioPacket,
32
+ type SttInterimPacket,
33
+ type SttResultPacket,
34
+ type FinalizeSttPacket,
35
+ type SttErrorPacket,
36
+ type EndOfSpeechAudioPacket,
37
+ type EndOfSpeechPacket,
38
+ type InterimEndOfSpeechPacket,
39
+ type UserInputPacket,
40
+ } from "./packets.js";
41
+
42
+ // Pipeline packets — interruption
43
+ export {
44
+ type InterruptionDetectedPacket,
45
+ type InterruptTtsPacket,
46
+ type InterruptLlmPacket,
47
+ type InterruptSttPacket,
48
+ type TurnChangePacket,
49
+ } from "./packets.js";
50
+
51
+ // Pipeline packets — LLM
52
+ export {
53
+ type LlmDeltaPacket,
54
+ type LlmResponseDonePacket,
55
+ type LlmErrorPacket,
56
+ type LlmToolCallPacket,
57
+ type LlmToolResultPacket,
58
+ } from "./packets.js";
59
+
60
+ // Pipeline packets — reasoning (suspend/resume)
61
+ export {
62
+ type ReasoningSuspendedPacket,
63
+ type ReasoningResumePacket,
64
+ } from "./packets.js";
65
+
66
+ // Pipeline packets — TTS
67
+ export {
68
+ type TextToSpeechTextPacket,
69
+ type TextToSpeechDonePacket,
70
+ type TextToSpeechAudioPacket,
71
+ type TextToSpeechEndPacket,
72
+ type TextToSpeechPlayoutStartedPacket,
73
+ type TextToSpeechPlayoutProgressPacket,
74
+ type TextToSpeechWordTimestampsPacket,
75
+ type TtsWordTimestamp,
76
+ type TtsErrorPacket,
77
+ } from "./packets.js";
78
+
79
+ // Pipeline packets — behavior
80
+ export {
81
+ type RecordAssistantAudioDataPacket,
82
+ type RecordAssistantAudioPacket,
83
+ type RecordAssistantAudioTruncatePacket,
84
+ type RecordUserAudioPacket,
85
+ type StartIdleTimeoutPacket,
86
+ type StopIdleTimeoutPacket,
87
+ type InjectMessagePacket,
88
+ type DisconnectRequestedPacket,
89
+ } from "./packets.js";
90
+
91
+ // Pipeline packets — mode
92
+ export {
93
+ type ModeSwitchRequestedPacket,
94
+ type ModeSwitchCompletedPacket,
95
+ } from "./packets.js";
96
+
97
+ // Pipeline packets — persistence
98
+ export {
99
+ type MessageCreatePacket,
100
+ type ConversationMetricPacket,
101
+ type TurnBoundaryKind,
102
+ type TurnBoundaryEventPacket,
103
+ type ObservabilityPacket,
104
+ type PipelineErrorPacket,
105
+ } from "./packets.js";
106
+
107
+ // Observability backbone (VE-07)
108
+ export {
109
+ monotonicNowMs,
110
+ type MetricTags,
111
+ type SpanHandle,
112
+ type MetricsExporter,
113
+ noopMetricsExporter,
114
+ InMemoryMetricsExporter,
115
+ reconstructTurnTimeline,
116
+ type TurnTimelineStep,
117
+ } from "./observability.js";
118
+
119
+ export {
120
+ ObservabilityObserver,
121
+ type ObservabilityObserverDeps,
122
+ type ObservabilityDims,
123
+ } from "./observability-observer.js";
124
+
125
+ // PipelineBus
126
+ export { PipelineBusImpl, Route, type PipelineBus, type PipelineBusConfig, type PacketHandler } from "./pipeline-bus.js";
127
+
128
+ // Init chain
129
+ export { runInitChain, runFinalizeChain, type InitStep, InitializationError } from "./init-chain.js";
130
+
131
+ // Plugin contract
132
+ export {
133
+ type VoicePlugin,
134
+ type PluginConfig,
135
+ type EndpointingOwner,
136
+ type EndpointingCapability,
137
+ requireStringConfig,
138
+ optionalStringConfig,
139
+ } from "./plugin-contract.js";
140
+
141
+ // Error handler
142
+ export { categorizeSttError, categorizeTtsError, categorizeLlmError, isRecoverable, isFatalError } from "./error-handler.js";
143
+
144
+ // Retry helpers
145
+ export { DEFAULT_RETRY_CONFIG, VOICE_PROVIDER_RETRY_CONFIG, VOICE_PROVIDER_OUTAGE_RETRY_CONFIG, readRetryConfig, readProviderRetryConfig, retryDelayMs, waitForRetryDelay, type RetryConfig } from "./retry.js";
146
+
147
+ // Provider fallback/degradation
148
+ export { ProviderFallback, type FallbackProvider, type ProviderFallbackOptions } from "./provider-fallback.js";
149
+
150
+ // Runtime scheduler seam
151
+ export { TimerScheduler, type Scheduler, type ScheduledCallback } from "./scheduler.js";
152
+
153
+ // Idle timeout
154
+ export { IdleTimeoutManager, type IdleTimeoutConfig, DEFAULT_IDLE_TIMEOUT_CONFIG } from "./idle-timeout.js";
155
+
156
+ // Mode switcher
157
+ export { ModeSwitcher, type ModeSwitchHandlers } from "./mode-switcher.js";
158
+
159
+ // Conversation events
160
+ export { type ConversationEvent, createConversationEventStream } from "./conversation-event.js";
161
+
162
+ // Websocket audio envelope
163
+ export {
164
+ SYRINX_AUDIO_ENVELOPE_NAME,
165
+ SYRINX_AUDIO_ENVELOPE_MAGIC,
166
+ assertAudioFormat,
167
+ assertAudioPayload,
168
+ encodeSyrinxAudioEnvelope,
169
+ decodeSyrinxAudioEnvelope,
170
+ hasSyrinxAudioEnvelope,
171
+ type SyrinxAudioEnvelope,
172
+ type SyrinxAudioEnvelopeHeader,
173
+ } from "./audio-envelope.js";
174
+
175
+ // VoiceAgentSession
176
+ export { VoiceAgentSession, type VoiceAgentSessionConfig, type VoiceAgentSessionEvents } from "./voice-agent-session.js";
177
+
178
+ // Primary-speaker barge-in gate (VE-02)
179
+ export {
180
+ PrimarySpeakerGate,
181
+ extractSpeakerFingerprint,
182
+ fingerprintSimilarity,
183
+ type SpeakerFingerprint,
184
+ type PrimarySpeakerGateConfig,
185
+ } from "./primary-speaker-gate.js";
186
+ export {
187
+ synthesizeTonePcm16,
188
+ mixPcm16,
189
+ PRIMARY_SPEAKER_TONE_HZ,
190
+ BYSTANDER_SPEAKER_TONE_HZ,
191
+ ASSISTANT_ECHO_TONE_HZ,
192
+ } from "./primary-speaker-fixtures.js";
193
+
194
+ // Latency-hiding filler track (VE-03)
195
+ export {
196
+ LatencyFillerController,
197
+ selectLatencyFillerConnective,
198
+ stripRedundantFillerPrefix,
199
+ LATENCY_FILLER_CONNECTIVES,
200
+ type LatencyFillerConfig,
201
+ type LatencyFillerState,
202
+ type LatencyFillerConnective,
203
+ } from "./latency-filler.js";
204
+ export {
205
+ LATENCY_FILLER_FIXTURES,
206
+ type LatencyFillerFixture,
207
+ } from "./latency-filler-fixtures.js";
208
+
209
+ // Reasoner seam (RFC §4.2)
210
+ export {
211
+ type Reasoner,
212
+ type ReasonerTurn,
213
+ type ReasonerMessage,
214
+ type ReasoningPart,
215
+ } from "./reasoner.js";
@@ -0,0 +1,137 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // Syrinx Kernel v2 — Init Chain
4
+ //
5
+ // Serial initialization of pipeline components with ordered teardown on failure.
6
+ // Each step runs synchronously in order. On failure, already-initialized steps
7
+ // are torn down in reverse order.
8
+
9
+ import type { PipelineBus } from "./pipeline-bus.js";
10
+ import { Route } from "./pipeline-bus.js";
11
+ import type {
12
+ InitStage,
13
+ InitStepCompletedPacket,
14
+ InitializationFailedPacket,
15
+ InitializationCompletedPacket,
16
+ } from "./packets.js";
17
+ import { ErrorCategory } from "./packets.js";
18
+
19
+ // =============================================================================
20
+ // Types
21
+ // =============================================================================
22
+
23
+ export interface InitStep {
24
+ /** Human-readable name for logging and error messages. */
25
+ readonly name: string;
26
+ /** Which initialization stage this step represents. */
27
+ readonly stage: InitStage;
28
+ /** Run the initialization. Throws on failure. */
29
+ run(): Promise<void>;
30
+ /** Clean up resources. Called during teardown (on failure) or finalize chain. */
31
+ cleanup?(): Promise<void>;
32
+ }
33
+
34
+ // =============================================================================
35
+ // Error
36
+ // =============================================================================
37
+
38
+ export class InitializationError extends Error {
39
+ constructor(
40
+ public readonly stage: InitStage,
41
+ public readonly component: string,
42
+ cause: Error,
43
+ ) {
44
+ super(`Initialization failed at ${stage}/${component}: ${cause.message}`);
45
+ this.name = "InitializationError";
46
+ }
47
+ }
48
+
49
+ // =============================================================================
50
+ // Core
51
+ // =============================================================================
52
+
53
+ /**
54
+ * Run a serial init chain. Each step runs in order. On failure:
55
+ * 1. Emits InitializationFailedPacket through the bus.
56
+ * 2. Tears down already-initialized steps in reverse order.
57
+ * 3. Throws InitializationError.
58
+ *
59
+ * On success:
60
+ * 1. Emits InitStepCompletedPacket for each step (with initMs).
61
+ * 2. Emits InitializationCompletedPacket.
62
+ */
63
+ export async function runInitChain(
64
+ bus: PipelineBus,
65
+ steps: readonly InitStep[],
66
+ ): Promise<void> {
67
+ const initialized: InitStep[] = [];
68
+
69
+ for (const step of steps) {
70
+ const startMs = performance.now();
71
+ try {
72
+ await step.run();
73
+ const initMs = performance.now() - startMs;
74
+
75
+ const completed: InitStepCompletedPacket = {
76
+ kind: "init.step_completed",
77
+ contextId: "",
78
+ timestampMs: Date.now(),
79
+ stage: step.stage,
80
+ component: step.name,
81
+ initMs,
82
+ };
83
+ bus.push(Route.Main, completed);
84
+ initialized.push(step);
85
+ } catch (err) {
86
+ const error = err instanceof Error ? err : new Error(String(err));
87
+
88
+ const failed: InitializationFailedPacket = {
89
+ kind: "init.failed",
90
+ contextId: "",
91
+ timestampMs: Date.now(),
92
+ stage: step.stage,
93
+ component: step.name,
94
+ category: ErrorCategory.InternalFault,
95
+ cause: error,
96
+ isRecoverable: false,
97
+ };
98
+ bus.push(Route.Main, failed);
99
+
100
+ // Reverse teardown of already-initialized steps
101
+ for (const done of [...initialized].reverse()) {
102
+ try {
103
+ await done.cleanup?.();
104
+ } catch (cleanupErr) {
105
+ // Log but don't throw — cleanup failures shouldn't mask the init error
106
+ // In production, this goes to the bus as a warning
107
+ }
108
+ }
109
+
110
+ throw new InitializationError(step.stage, step.name, error);
111
+ }
112
+ }
113
+
114
+ const completed: InitializationCompletedPacket = {
115
+ kind: "init.completed",
116
+ contextId: "",
117
+ timestampMs: Date.now(),
118
+ };
119
+ bus.push(Route.Main, completed);
120
+ }
121
+
122
+ /**
123
+ * Run a reverse finalize chain. Steps are torn down in reverse order.
124
+ * Errors during teardown are logged but do not stop the chain.
125
+ * All steps get their cleanup called regardless of earlier failures.
126
+ */
127
+ export async function runFinalizeChain(
128
+ steps: readonly InitStep[],
129
+ ): Promise<void> {
130
+ for (const step of [...steps].reverse()) {
131
+ try {
132
+ await step.cleanup?.();
133
+ } catch {
134
+ // Log but continue — finalize must complete
135
+ }
136
+ }
137
+ }
@@ -0,0 +1,79 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // Syrinx Kernel v2 — Init Stage Ordering
4
+ //
5
+ // Pure mapping from plugin name → init stage, and the deterministic order in
6
+ // which stages initialize (and, reversed, finalize). Extracted from
7
+ // VoiceAgentSession so the orchestrator owns chain assembly, not the ordering
8
+ // policy.
9
+
10
+ import { InitStage } from "./packets.js";
11
+
12
+ export function pluginStage(name: string): InitStage {
13
+ switch (name) {
14
+ case "stt":
15
+ case "deepgram":
16
+ return InitStage.STT;
17
+ case "tts":
18
+ case "cartesia":
19
+ case "elevenlabs":
20
+ return InitStage.TTS;
21
+ case "vad":
22
+ case "silero":
23
+ return InitStage.VAD;
24
+ case "eos":
25
+ case "pipecat":
26
+ return InitStage.EOS;
27
+ case "denoiser":
28
+ case "rnnoise":
29
+ return InitStage.Denoiser;
30
+ case "bridge":
31
+ case "aisdk":
32
+ return InitStage.Assistant;
33
+ case "recorder":
34
+ return InitStage.Recorder;
35
+ case "auth":
36
+ return InitStage.Auth;
37
+ default:
38
+ return InitStage.Assistant;
39
+ }
40
+ }
41
+
42
+ export function stageOrder(stage: InitStage): number {
43
+ switch (stage) {
44
+ case InitStage.Assistant:
45
+ return 10;
46
+ case InitStage.Conversation:
47
+ return 20;
48
+ case InitStage.Recorder:
49
+ return 30;
50
+ case InitStage.Normalizer:
51
+ return 40;
52
+ case InitStage.Auth:
53
+ return 50;
54
+ case InitStage.STT:
55
+ return 60;
56
+ case InitStage.TTS:
57
+ return 70;
58
+ case InitStage.VAD:
59
+ return 80;
60
+ case InitStage.EOS:
61
+ return 90;
62
+ case InitStage.Denoiser:
63
+ return 100;
64
+ case InitStage.Behavior:
65
+ return 110;
66
+ case InitStage.Telemetry:
67
+ return 120;
68
+ }
69
+ }
70
+
71
+ export function isAudioStage(stage: InitStage): boolean {
72
+ return (
73
+ stage === InitStage.STT ||
74
+ stage === InitStage.TTS ||
75
+ stage === InitStage.VAD ||
76
+ stage === InitStage.EOS ||
77
+ stage === InitStage.Denoiser
78
+ );
79
+ }
@@ -0,0 +1,16 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // Labeled utterances for latency-filler connective selection tests.
4
+
5
+ export interface LatencyFillerFixture {
6
+ readonly id: string;
7
+ readonly userText: string;
8
+ readonly expectedConnective: string;
9
+ }
10
+
11
+ export const LATENCY_FILLER_FIXTURES: readonly LatencyFillerFixture[] = [
12
+ { id: "question", userText: "Can I still add Biology 101?", expectedConnective: "Well," },
13
+ { id: "thanks", userText: "Thanks for checking that.", expectedConnective: "Right," },
14
+ { id: "statement-0", userText: "I need the late add form.", expectedConnective: "So," },
15
+ { id: "statement-1", userText: "My hold is blocking registration.", expectedConnective: "Well," },
16
+ ] as const;