support for managed tts

This commit is contained in:
Ramnique Singh 2026-03-13 12:42:14 +05:30
parent 779ad51f9f
commit c845a7c40d
4 changed files with 84 additions and 61 deletions

View file

@ -0,0 +1,46 @@
import container from '../di/container.js';
import { IOAuthRepo } from './repo.js';
import { IClientRegistrationRepo } from './client-repo.js';
import { getProviderConfig } from './providers.js';
import * as oauthClient from './oauth-client.js';
export async function getAccessToken(): Promise<string> {
const oauthRepo = container.resolve<IOAuthRepo>('oauthRepo');
const { tokens } = await oauthRepo.read('rowboat');
if (!tokens) {
throw new Error('Not signed into Rowboat');
}
if (!oauthClient.isTokenExpired(tokens)) {
return tokens.access_token;
}
if (!tokens.refresh_token) {
throw new Error('Rowboat token expired and no refresh token available. Please sign in again.');
}
const providerConfig = getProviderConfig('rowboat');
if (providerConfig.discovery.mode !== 'issuer') {
throw new Error('Rowboat provider requires issuer discovery mode');
}
const clientRepo = container.resolve<IClientRegistrationRepo>('clientRegistrationRepo');
const registration = await clientRepo.getClientRegistration('rowboat');
if (!registration) {
throw new Error('Rowboat client not registered. Please sign in again.');
}
const config = await oauthClient.discoverConfiguration(
providerConfig.discovery.issuer,
registration.client_id,
);
const refreshed = await oauthClient.refreshTokens(
config,
tokens.refresh_token,
tokens.scopes,
);
await oauthRepo.upsert('rowboat', { tokens: refreshed });
return refreshed.access_token;
}

View file

@ -1,53 +1,8 @@
import { ProviderV2 } from '@ai-sdk/provider';
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
import container from '../di/container.js';
import { IOAuthRepo } from '../auth/repo.js';
import { IClientRegistrationRepo } from '../auth/client-repo.js';
import { getProviderConfig } from '../auth/providers.js';
import * as oauthClient from '../auth/oauth-client.js';
import { getAccessToken } from '../auth/tokens.js';
import { API_URL } from '../config/env.js';
async function getAccessToken(): Promise<string> {
const oauthRepo = container.resolve<IOAuthRepo>('oauthRepo');
const { tokens } = await oauthRepo.read('rowboat');
if (!tokens) {
throw new Error('Not signed into Rowboat');
}
if (!oauthClient.isTokenExpired(tokens)) {
return tokens.access_token;
}
if (!tokens.refresh_token) {
throw new Error('Rowboat token expired and no refresh token available. Please sign in again.');
}
const providerConfig = getProviderConfig('rowboat');
if (providerConfig.discovery.mode !== 'issuer') {
throw new Error('Rowboat provider requires issuer discovery mode');
}
const clientRepo = container.resolve<IClientRegistrationRepo>('clientRegistrationRepo');
const registration = await clientRepo.getClientRegistration('rowboat');
if (!registration) {
throw new Error('Rowboat client not registered. Please sign in again.');
}
const config = await oauthClient.discoverConfiguration(
providerConfig.discovery.issuer,
registration.client_id,
);
const refreshed = await oauthClient.refreshTokens(
config,
tokens.refresh_token,
tokens.scopes,
);
await oauthRepo.upsert('rowboat', { tokens: refreshed });
return refreshed.access_token;
}
export async function getGatewayProvider(): Promise<ProviderV2> {
const accessToken = await getAccessToken();
return createOpenRouter({

View file

@ -1,5 +1,8 @@
import * as fs from 'fs/promises';
import * as path from 'path';
import { isSignedIn } from '../account/account.js';
import { getAccessToken } from '../auth/tokens.js';
import { API_URL } from '../config/env.js';
const homedir = process.env.HOME || process.env.USERPROFILE || '';
@ -32,21 +35,36 @@ export async function getVoiceConfig(): Promise<VoiceConfig> {
export async function synthesizeSpeech(text: string): Promise<{ audioBase64: string; mimeType: string }> {
const config = await getVoiceConfig();
if (!config.elevenlabs) {
throw new Error('ElevenLabs not configured. Create ~/.rowboat/config/elevenlabs.json with { "apiKey": "<your-key>" }');
const signedIn = await isSignedIn();
let url: string;
let headers: Record<string, string>;
if (signedIn) {
const voiceId = config.elevenlabs?.voiceId || 'UgBBYS2sOqTuMpoF3BR0';
const accessToken = await getAccessToken();
url = `${API_URL}/v1/voice/text-to-speech/${voiceId}`;
headers = {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
};
console.log('[voice] synthesizing speech via Rowboat proxy, text length:', text.length, 'voiceId:', voiceId);
} else {
if (!config.elevenlabs) {
throw new Error('ElevenLabs not configured. Create ~/.rowboat/config/elevenlabs.json with { "apiKey": "<your-key>" }');
}
const voiceId = config.elevenlabs.voiceId || 'UgBBYS2sOqTuMpoF3BR0';
url = `https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`;
headers = {
'xi-api-key': config.elevenlabs.apiKey,
'Content-Type': 'application/json',
};
console.log('[voice] synthesizing speech via ElevenLabs, text length:', text.length, 'voiceId:', voiceId);
}
const voiceId = config.elevenlabs.voiceId || 'UgBBYS2sOqTuMpoF3BR0';
const url = `https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`;
console.log('[voice] synthesizing speech, text length:', text.length, 'voiceId:', voiceId);
const response = await fetch(url, {
method: 'POST',
headers: {
'xi-api-key': config.elevenlabs.apiKey,
'Content-Type': 'application/json',
},
headers,
body: JSON.stringify({
text,
model_id: 'eleven_flash_v2_5',
@ -59,8 +77,8 @@ export async function synthesizeSpeech(text: string): Promise<{ audioBase64: str
if (!response.ok) {
const errText = await response.text().catch(() => 'Unknown error');
console.error('[voice] ElevenLabs API error:', response.status, errText);
throw new Error(`ElevenLabs API error ${response.status}: ${errText}`);
console.error('[voice] TTS API error:', response.status, errText);
throw new Error(`TTS API error ${response.status}: ${errText}`);
}
const arrayBuffer = await response.arrayBuffer();