diff --git a/deep-sea-stories/packages/backend/src/agent/gemini/session.ts b/deep-sea-stories/packages/backend/src/agent/gemini/session.ts index de1aaa0..9be0679 100644 --- a/deep-sea-stories/packages/backend/src/agent/gemini/session.ts +++ b/deep-sea-stories/packages/backend/src/agent/gemini/session.ts @@ -10,9 +10,12 @@ import { getInstructionsForStory } from '../../utils.js'; import type { AgentConfig } from '../api.js'; import type { VoiceAgentSession } from '../session.js'; +const AGENT_EXPIRATION_MAX_SECONDS = 15; + export class GeminiSession implements VoiceAgentSession { private onInterrupt: (() => void) | null = null; private onAgentAudio: ((audio: Buffer) => void) | null = null; + private onTurnEnd: (() => void) | null = null; private session: Session | null = null; private transcriptionParts: string[] = []; private genai: GoogleGenAI; @@ -62,6 +65,15 @@ export class GeminiSession implements VoiceAgentSession { ], turnComplete: true, }); + + return new Promise((resolve) => { + const timeout = setTimeout(resolve, AGENT_EXPIRATION_MAX_SECONDS * 1000); + this.onTurnEnd = () => { + this.onTurnEnd = null; + clearTimeout(timeout); + resolve(); + }; + }); } async waitUntilDone() { @@ -73,6 +85,7 @@ export class GeminiSession implements VoiceAgentSession { async open() { if (this.opening) return; this.opening = true; + this.ending = false; const params: LiveConnectParameters = { model: GEMINI_MODEL, @@ -199,6 +212,8 @@ export class GeminiSession implements VoiceAgentSession { this.transcriptionParts = []; } + if (turnFinished) this.onTurnEnd?.(); + const base64 = message.data; if (base64) { this.handleAgentAudio(Buffer.from(base64, 'base64')); diff --git a/deep-sea-stories/packages/backend/src/game/room.ts b/deep-sea-stories/packages/backend/src/game/room.ts index 30e260d..6f11be5 100644 --- a/deep-sea-stories/packages/backend/src/game/room.ts +++ b/deep-sea-stories/packages/backend/src/game/room.ts @@ -187,6 +187,7 @@ export class GameRoom { console.log(`⏰ Game time limit reached for room ${this.roomId}`); try { await this.gameSession?.announceTimeExpired(); + await this.stopGame(true); } catch (e) { console.error('Error announcing time expired:', e); } @@ -194,6 +195,11 @@ export class GameRoom { } async stopGame(wait: boolean = false) { + if (!this.gameSession) return; + + const gameSession = this.gameSession; + this.gameSession = null; + console.log('Stopping game room %s', this.roomId); if (this.gameTimeoutId) { clearTimeout(this.gameTimeoutId); @@ -201,25 +207,20 @@ export class GameRoom { } try { - if (this.gameSession) { - await this.gameSession.stopGame(wait); - await this.fishjamClient.deletePeer( - this.roomId, - this.gameSession.agentId, - ); - } - } catch (e) { - if (!(e instanceof PeerNotFoundException)) throw e; - } finally { - this.gameSession = null; - this.story = undefined; + await gameSession.stopGame(wait); this.notifierService.emitNotification(this.roomId, { type: 'gameEnded' as const, timestamp: Date.now(), }); + await this.fishjamClient.deletePeer(this.roomId, gameSession.agentId); + } catch (e) { + if (!(e instanceof PeerNotFoundException)) throw e; + } finally { + this.story = undefined; this.gameStarted = false; + console.log(`Stopped game for room ${this.roomId}`); } }