mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-04-26 17:06:23 +02:00
support for managed tts
This commit is contained in:
parent
779ad51f9f
commit
c845a7c40d
4 changed files with 84 additions and 61 deletions
46
apps/x/packages/core/src/auth/tokens.ts
Normal file
46
apps/x/packages/core/src/auth/tokens.ts
Normal 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;
|
||||
}
|
||||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue