diff --git a/lib/tasks/llm/llms/google_s2s.js b/lib/tasks/llm/llms/google_s2s.js index 87fe5b44..124586a9 100644 --- a/lib/tasks/llm/llms/google_s2s.js +++ b/lib/tasks/llm/llms/google_s2s.js @@ -8,6 +8,7 @@ const google_server_events = [ 'error', 'session.created', 'session.updated', + 'llm_event', ]; const expandWildcards = (events) => { @@ -36,9 +37,10 @@ class TaskLlmGoogle_S2S extends Task { this.model = this.parent.model || 'models/gemini-2.0-flash-live-001'; this.auth = this.parent.auth; this.connectionOptions = this.parent.connectOptions; - const {host, version} = this.connectionOptions || {}; + const {host, version, path} = this.connectionOptions || {}; this.host = host; this.version = version; + this.path = path; const {apiKey} = this.auth || {}; if (!apiKey) throw new Error('auth.apiKey is required for Google S2S'); @@ -49,11 +51,12 @@ class TaskLlmGoogle_S2S extends Task { this.eventHook = this.data.eventHook; this.toolHook = this.data.toolHook; - const {setup, sessionResumption} = this.data.llmOptions; + const {setup, sessionResumption, greeting} = this.data.llmOptions; if (typeof setup !== 'object') { throw new Error('llmOptions with an initial setup is required for Google S2S'); } + this.greeting = typeof greeting === 'string' ? greeting : null; this.setup = { ...setup, model: this.model, @@ -144,7 +147,9 @@ class TaskLlmGoogle_S2S extends Task { const args = [ep.uuid, 'session.create', this.apiKey]; if (this.host) { args.push(this.host); - if (this.version) args.push(this.version); + // 5th arg: full path (preferred) or legacy version string. `path` wins. + if (this.path) args.push(this.path); + else if (this.version) args.push(this.version); } await this._api(ep, args); } catch (err) { @@ -193,6 +198,17 @@ class TaskLlmGoogle_S2S extends Task { })) { this.logger.debug(this.setup, 'TaskLlmGoogle_S2S:_sendInitialMessage - sending session.update'); this.notifyTaskDone(); + return; + } + + if (this.greeting) { + this.logger.debug({text: this.greeting}, 'TaskLlmGoogle_S2S:_sendInitialMessage - sending proactive greeting'); + // Use realtimeInput.text — clientContent is only for seeding history on + // gemini-3.1-flash-live-preview and does not trigger a model response. + // realtimeInput.text works across 2.0 and 3.1 live models. + await this._sendClientEvent(ep, { + realtimeInput: {text: this.greeting} + }); } }