fix(desktop): route streaming TTS through the active profile backend

The /api/audio/speak-stream WebSocket URL was always minted for the primary
backend with no profile param, so streaming read-aloud used the default
profile's TTS provider even when another profile was active. Mint the
ticket for the active profile's connection and carry ?profile= (same seam
as /api/pty), via a read-only getApiRequestProfile() accessor.
This commit is contained in:
Teknium 2026-07-28 09:34:08 -07:00
parent 5f1c400e72
commit e27997d63f
2 changed files with 19 additions and 4 deletions

View file

@ -249,6 +249,13 @@ function profileScoped(profile?: null | string): { profile?: string } {
return selected ? { profile: selected } : {}
}
/** Profile that profile-scoped REST/WS calls should target (null primary).
* Read-only twin of setApiRequestProfile for modules (e.g. voice playback)
* that build their own connection URLs and must stay on the same backend. */
export function getApiRequestProfile(): null | string {
return _apiProfile
}
/** Options for a plugin REST call mirrors the app's own `hermesDesktop.api`
* shape, minus the path (which is namespace-derived). */
export interface PluginRestOptions {

View file

@ -1,6 +1,6 @@
import { resolveGatewayWsUrl } from '@hermes/shared'
import { speakText } from '@/hermes'
import { getApiRequestProfile, speakText } from '@/hermes'
import {
$voicePlayback,
setVoicePlaybackState,
@ -74,9 +74,11 @@ async function resolveSpeakStreamUrl(): Promise<null | string> {
}
try {
// Mint a fresh credential (single-use ticket in OAuth mode), then swap the
// gateway endpoint for the PCM one — auth is shared across WS routes.
const wsUrl = await resolveGatewayWsUrl(desktop, await desktop.getConnection())
// Mint a fresh credential (single-use ticket in OAuth mode) for the
// ACTIVE profile's backend, then swap the gateway endpoint for the PCM
// one — auth is shared across WS routes.
const profile = getApiRequestProfile()
const wsUrl = await resolveGatewayWsUrl(desktop, await desktop.getConnection(profile))
const url = new URL(wsUrl)
if (!url.pathname.endsWith('/api/ws')) {
@ -85,6 +87,12 @@ async function resolveSpeakStreamUrl(): Promise<null | string> {
url.pathname = url.pathname.replace(/\/api\/ws$/, '/api/audio/speak-stream')
// The backend resolves the TTS provider chain from this profile's
// config/.env (same seam as /api/pty?profile=).
if (profile) {
url.searchParams.set('profile', profile)
}
return url.toString()
} catch {
return null