UNPKG

@tanstack/ai

Version:

Type-safe TypeScript AI SDK for streaming chat, tool calling, agents, structured outputs, and multimodal generation.

184 lines (183 loc) 5.94 kB
import { resolveDebugOption } from "../../logger/resolve.js"; import { streamGenerationResult } from "../stream-generation-result.js"; import { applyGenerationResultTransforms, createGenerationContext, runGenerationAbort, runGenerationError, runGenerationFinish, runGenerationStart, runGenerationUsage } from "../middleware/run.js"; import { abortReasonMessage, createActivityAbortControls, isActivityAbortError, raceWithAbort } from "../../utilities/activity-abort.js"; import "./adapter.js"; import { aiEventClient } from "@tanstack/ai-event-client"; //#region src/activities/generateVoice/index.ts /** * Voice Activity * * Creates a reusable voice — either designed from a text description or cloned * from reference audio — and returns voice ids that `generateSpeech()` accepts. * This is a self-contained module with implementation, types, and JSDoc. */ /** The adapter kind this activity handles */ var kind = "voice"; function createId(prefix) { return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`; } /** * Voice activity - creates a reusable voice. * * Providers create voices in one of two ways, and some support both: design a * new voice from a text description, or clone one from reference audio. Either * way the result carries voice ids you pass back to `generateSpeech()`. * * @example Design a voice from a description * ```ts * import { generateVoice, generateSpeech } from '@tanstack/ai' * import { elevenlabsVoiceDesign, elevenlabsSpeech } from '@tanstack/ai-elevenlabs' * * const designed = await generateVoice({ * adapter: elevenlabsVoiceDesign('eleven_ttv_v3'), * prompt: 'A warm, gravelly narrator in his sixties with a slight Irish lilt', * }) * * const [preview] = designed.voices * if (!preview) throw new Error('No voice candidates returned') * * const speech = await generateSpeech({ * adapter: elevenlabsSpeech('eleven_v3'), * text: 'Once upon a time...', * voice: preview.voiceId, * }) * ``` * * @example Save the voice to the provider's library * ```ts * const saved = await generateVoice({ * adapter: elevenlabsVoiceDesign('eleven_ttv_v3'), * prompt: 'A bright, upbeat product demo host', * name: 'Demo Host', * description: 'Bright, upbeat, mid-30s', * }) * ``` */ function generateVoice(options) { if (options.stream) return streamGenerationResult((resolved) => runGenerateVoice({ ...options, runId: resolved.runId }), options); return runGenerateVoice(options); } /** * Run the core voice generation logic (non-streaming). */ async function runGenerateVoice(options) { const { adapter, stream: _stream, debug: _debug, middleware, threadId, runId, timeout, abortSignal: callerAbortSignal, ...rest } = options; if (rest.prompt == null && rest.referenceAudio == null) throw new Error("generateVoice() requires `prompt` (design a new voice) or `referenceAudio` (clone an existing one)."); const model = adapter.model; const requestId = createId("voice"); const startTime = Date.now(); const logger = resolveDebugOption(options.debug); const abortControls = createActivityAbortControls({ timeout, abortSignal: callerAbortSignal }); const mwCtx = createGenerationContext({ requestId, activity: "voice", provider: adapter.name, model, modelOptions: rest.modelOptions, artifactInputs: { prompt: rest.prompt, name: rest.name, description: rest.description }, threadId, runId, createId }); await runGenerationStart(middleware, mwCtx); aiEventClient.emit("voice:request:started", { requestId, provider: adapter.name, model, prompt: rest.prompt, name: rest.name, description: rest.description, hasReferenceAudio: rest.referenceAudio != null, modelOptions: rest.modelOptions, timestamp: startTime }); logger.request(`activity=generateVoice provider=${adapter.name}`, { provider: adapter.name, model }); try { const rawResult = await raceWithAbort(adapter.generateVoice({ ...rest, model, logger, ...abortControls.signal ? { abortSignal: abortControls.signal } : {} }), abortControls.signal); abortControls.clear(); const result = await applyGenerationResultTransforms(mwCtx, rawResult); const duration = Date.now() - startTime; aiEventClient.emit("voice:request:completed", { requestId, provider: adapter.name, model, voiceIds: result.voices.map((voice) => voice.voiceId), voiceCount: result.voices.length, previewText: result.previewText, duration, modelOptions: rest.modelOptions, timestamp: Date.now() }); if (result.usage) aiEventClient.emit("voice:usage", { requestId, model, usage: result.usage, modelOptions: rest.modelOptions, timestamp: Date.now() }); logger.output(`activity=generateVoice voices=${result.voices.length}`, { voices: result.voices.length }); if (result.usage) await runGenerationUsage(middleware, mwCtx, result.usage); await runGenerationFinish(middleware, mwCtx, { duration, usage: result.usage }); return result; } catch (error) { abortControls.clear(); const duration = Date.now() - startTime; const err = error; aiEventClient.emit("voice:request:error", { requestId, provider: adapter.name, model, error: { message: err.message, name: err.name }, duration, modelOptions: rest.modelOptions, timestamp: Date.now() }); if (isActivityAbortError(error, abortControls.signal)) await runGenerationAbort(middleware, mwCtx, { reason: abortReasonMessage(error, abortControls.signal), duration }); else await runGenerationError(middleware, mwCtx, { error, duration }); logger.errors("generateVoice activity failed", { error, source: "generateVoice" }); throw error; } } /** * Create typed options for the generateVoice() function without executing. */ function createVoiceOptions(options) { return options; } //#endregion export { createVoiceOptions, generateVoice, kind }; //# sourceMappingURL=index.js.map