/** * LysnrAI backend client — typed HTTP wrappers for the lysnrai-backend (port 4015). * * Auth: Bearer token from the caller's JWT (same JWT_SECRET as platform-service). */ import { config } from './config.js'; export interface LysnraiClientOptions { token?: string; requestId?: string; } // ── Shared fetch helper ──────────────────────────────────────────────────── async function lysnraiFetch( path: string, init: RequestInit, opts: LysnraiClientOptions ): Promise { const headers: Record = { 'Content-Type': 'application/json', ...(opts.token ? { Authorization: `Bearer ${opts.token}` } : {}), ...(opts.requestId ? { 'x-request-id': opts.requestId } : {}), }; const res = await fetch(`${config.LYSNRAI_BACKEND_URL}/api${path}`, { ...init, headers: { ...((init.headers as Record) ?? {}), ...headers }, signal: AbortSignal.timeout(15_000), }); if (!res.ok) { const body = await res.text().catch(() => ''); throw new Error(`lysnrai-backend ${init.method ?? 'GET'} ${path} → ${res.status}: ${body}`); } return res.json() as Promise; } // ── Transcripts ──────────────────────────────────────────────────────────── export interface TranscriptDoc { id: string; userId: string; productId: string; rawText?: string; cleanedText?: string; wordCount?: number; duration?: number; tokensUsed?: number; context?: string; source?: string; language?: string; extractions?: unknown[]; extractionMetadata?: Record; extractedAt?: string; createdAt: string; } export function lysnraiTranscriptsList( params: { limit?: number; offset?: number }, opts: LysnraiClientOptions ): Promise<{ transcripts: TranscriptDoc[]; total: number }> { const qs = new URLSearchParams(); if (params.limit !== undefined) qs.set('limit', String(params.limit)); if (params.offset !== undefined) qs.set('offset', String(params.offset)); const q = qs.toString(); return lysnraiFetch(`/transcripts${q ? `?${q}` : ''}`, { method: 'GET' }, opts); } export function lysnraiTranscriptGet( id: string, opts: LysnraiClientOptions ): Promise { return lysnraiFetch(`/transcripts/${id}`, { method: 'GET' }, opts); } export function lysnraiTranscriptRunExtraction( transcriptId: string, opts: LysnraiClientOptions ): Promise { return lysnraiFetch(`/transcripts/${transcriptId}/extract`, { method: 'POST' }, opts); } // ── STT status ───────────────────────────────────────────────────────────── export interface SttStatusResult { productId: string; primary: 'azure' | 'whisper' | 'none'; azureConfigured: boolean; whisperConfigured: boolean; azureRegion: string | null; generatedAt: string; } export function lysnraiSttGetBackendStatus(opts: LysnraiClientOptions): Promise { return lysnraiFetch('/transcripts/stt-status', { method: 'GET' }, opts); } // ── Sessions ─────────────────────────────────────────────────────────────── export interface SessionDoc { id: string; userId: string; productId: string; title: string; status: 'active' | 'composed' | 'archived'; entries: unknown[]; composedText: string | null; composedAt: string | null; compositionRating: number | null; createdAt: string; updatedAt: string; } export function lysnraiSessionGet( sessionId: string, opts: LysnraiClientOptions ): Promise { return lysnraiFetch(`/sessions/${sessionId}`, { method: 'GET' }, opts); } export function lysnraiSessionsList( params: { limit?: number; offset?: number; status?: string }, opts: LysnraiClientOptions ): Promise<{ sessions: SessionDoc[]; total: number }> { const qs = new URLSearchParams(); if (params.limit !== undefined) qs.set('limit', String(params.limit)); if (params.offset !== undefined) qs.set('offset', String(params.offset)); if (params.status) qs.set('status', params.status); const q = qs.toString(); return lysnraiFetch(`/sessions${q ? `?${q}` : ''}`, { method: 'GET' }, opts); } // ── Organisations ────────────────────────────────────────────────────────── export interface OrgDoc { id: string; name: string; ownerId: string; productId: string; createdAt: string; } export function lysnraiOrgGet(orgId: string, opts: LysnraiClientOptions): Promise { return lysnraiFetch(`/orgs/${orgId}`, { method: 'GET' }, opts); } export function lysnraiOrgsList( params: { limit?: number; offset?: number }, opts: LysnraiClientOptions ): Promise<{ organizations: OrgDoc[] }> { const qs = new URLSearchParams(); if (params.limit !== undefined) qs.set('limit', String(params.limit)); if (params.offset !== undefined) qs.set('offset', String(params.offset)); const q = qs.toString(); return lysnraiFetch(`/orgs${q ? `?${q}` : ''}`, { method: 'GET' }, opts); } // ── API tokens ───────────────────────────────────────────────────────────── export interface ApiTokenDoc { id: string; userId: string; productId: string; name: string; lastUsedAt: string | null; createdAt: string; } export function lysnraiApiTokensList( params: { limit?: number; offset?: number }, opts: LysnraiClientOptions ): Promise<{ tokens: ApiTokenDoc[] }> { const qs = new URLSearchParams(); if (params.limit !== undefined) qs.set('limit', String(params.limit)); if (params.offset !== undefined) qs.set('offset', String(params.offset)); const q = qs.toString(); return lysnraiFetch(`/api-tokens${q ? `?${q}` : ''}`, { method: 'GET' }, opts); } export function lysnraiApiTokenCreate( input: { name: string }, opts: LysnraiClientOptions ): Promise { return lysnraiFetch('/api-tokens', { method: 'POST', body: JSON.stringify(input) }, opts); } export function lysnraiApiTokenRevoke( tokenId: string, opts: LysnraiClientOptions ): Promise<{ success: boolean }> { return lysnraiFetch(`/api-tokens/${tokenId}`, { method: 'DELETE' }, opts); } export function lysnraiSessionsStats(opts: LysnraiClientOptions): Promise> { return lysnraiFetch('/sessions/stats', { method: 'GET' }, opts); } export function lysnraiTranscriptsExportBatch( params: { format?: 'json' | 'txt'; limit?: number }, opts: LysnraiClientOptions ): Promise<{ exportUrl: string; count: number; format: string; generatedAt: string }> { const qs = new URLSearchParams(); if (params.format) qs.set('format', params.format); if (params.limit !== undefined) qs.set('limit', String(params.limit)); const q = qs.toString(); return lysnraiFetch(`/transcripts/export${q ? `?${q}` : ''}`, { method: 'GET' }, opts); }