@kuralle-syrinx/gemini 3.1.0 → 4.0.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 (2) hide show
  1. package/package.json +2 -2
  2. package/src/index.ts +30 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kuralle-syrinx/gemini",
3
- "version": "3.1.0",
3
+ "version": "4.0.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -8,7 +8,7 @@
8
8
  "types": "./src/index.ts",
9
9
  "dependencies": {
10
10
  "@google/genai": "^1.0.0",
11
- "@kuralle-syrinx/core": "3.1.0"
11
+ "@kuralle-syrinx/core": "4.0.0"
12
12
  },
13
13
  "devDependencies": {
14
14
  "typescript": "^5.7.0",
package/src/index.ts CHANGED
@@ -61,7 +61,10 @@ export class GeminiTTSPlugin implements VoicePlugin {
61
61
  private voiceName: string = DEFAULT_VOICE;
62
62
  private instruction = "";
63
63
  private timeoutMs = 45_000;
64
- private abortController: AbortController | null = null;
64
+ // One controller per in-flight turn. A single shared field let a second turn's
65
+ // synthesize() overwrite the first's controller, so a barge-in on turn N aborted
66
+ // only turn N+1 and turn N's stale audio could still stream after the interrupt.
67
+ private readonly abortControllers = new Map<string, AbortController>();
65
68
  private textByContextId = new Map<string, string>();
66
69
  private retryConfig: RetryConfig = readRetryConfig({});
67
70
  private disposers: Array<() => void> = [];
@@ -98,10 +101,14 @@ export class GeminiTTSPlugin implements VoicePlugin {
98
101
  await this.synthesize(text, donePkt.contextId);
99
102
  }),
100
103
 
101
- // Listen for TTS interrupts
102
- bus.on("interrupt.tts", () => {
103
- this.abortController?.abort();
104
- this.abortController = null;
104
+ // Listen for TTS interrupts — abort only the interrupted turn's synthesis.
105
+ bus.on("interrupt.tts", (pkt) => {
106
+ const ctxId = (pkt as { contextId: string }).contextId;
107
+ const controller = this.abortControllers.get(ctxId);
108
+ if (controller) {
109
+ controller.abort();
110
+ this.abortControllers.delete(ctxId);
111
+ }
105
112
  }),
106
113
  );
107
114
  }
@@ -109,9 +116,21 @@ export class GeminiTTSPlugin implements VoicePlugin {
109
116
  private async synthesize(text: string, contextId: string): Promise<void> {
110
117
  if (!this.bus) return;
111
118
 
112
- this.abortController = new AbortController();
113
- const signal = this.abortController.signal;
119
+ const controller = new AbortController();
120
+ this.abortControllers.set(contextId, controller);
121
+ const signal = controller.signal;
122
+
123
+ try {
124
+ await this.synthesizeWithRetry(text, contextId, signal);
125
+ } finally {
126
+ // Only clear if still ours — an interrupt may have already removed it.
127
+ if (this.abortControllers.get(contextId) === controller) {
128
+ this.abortControllers.delete(contextId);
129
+ }
130
+ }
131
+ }
114
132
 
133
+ private async synthesizeWithRetry(text: string, contextId: string, signal: AbortSignal): Promise<void> {
115
134
  for (let attempt = 1; attempt <= this.retryConfig.maxAttempts; attempt += 1) {
116
135
  try {
117
136
  const audioChunks = await this.synthesizeOnce(text, contextId, signal);
@@ -216,14 +235,14 @@ export class GeminiTTSPlugin implements VoicePlugin {
216
235
  return audioChunks;
217
236
  }
218
237
 
219
- /** Flush/cancel current synthesis (called on interrupt). */
238
+ /** Flush/cancel all in-flight synthesis (called on interrupt/shutdown). */
220
239
  flush(): void {
221
- this.abortController?.abort();
222
- this.abortController = null;
240
+ for (const controller of this.abortControllers.values()) controller.abort();
241
+ this.abortControllers.clear();
223
242
  }
224
243
 
225
244
  async close(): Promise<void> {
226
- this.abortController?.abort();
245
+ this.flush();
227
246
  for (const dispose of this.disposers.splice(0)) dispose();
228
247
  this.textByContextId.clear();
229
248
  this.bus = null;