@stacksjs/ai 0.75.37 → 0.75.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -1
- package/dist/speech.d.ts +87 -0
- package/dist/speech.js +1 -0
- package/package.json +4 -3
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export * from './context';
|
|
|
17
17
|
export * from './text';
|
|
18
18
|
// Image generation & vision
|
|
19
19
|
export * from './image';
|
|
20
|
+
// Text-to-speech & speech-to-text (stacksjs/stacks#306)
|
|
21
|
+
export * from './speech';
|
|
20
22
|
// Semantic search, embeddings & RAG
|
|
21
23
|
export * from './search';
|
|
22
24
|
// Personalization, sentiment & classification
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export*from"./types";export*from"./drivers";export*from"./client";export*from"./agents";export*from"./buddy";export*from"./context";export*from"./text";export*from"./image";export*from"./search";export*from"./personalization";export*from"./mcp";export*from"./utils/client-bedrock";export*from"./utils/client-bedrock-runtime";export{buildMessageWithImages,normalizeMessagesForProvider}from"./utils/vision";export{fetchWithRetry}from"./utils/retry";export{clearUsageReporters,listUsageReporters,onUsage,recordUsage}from"./utils/usage";export{estimateMessageTokens,estimateTokens,sanitizePrompt}from"./utils/tokens";export*from"./utils/model-access";
|
|
1
|
+
export*from"./types";export*from"./drivers";export*from"./client";export*from"./agents";export*from"./buddy";export*from"./context";export*from"./text";export*from"./image";export*from"./speech";export*from"./search";export*from"./personalization";export*from"./mcp";export*from"./utils/client-bedrock";export*from"./utils/client-bedrock-runtime";export{buildMessageWithImages,normalizeMessagesForProvider}from"./utils/vision";export{fetchWithRetry}from"./utils/retry";export{clearUsageReporters,listUsageReporters,onUsage,recordUsage}from"./utils/usage";export{estimateMessageTokens,estimateTokens,sanitizePrompt}from"./utils/tokens";export*from"./utils/model-access";
|
package/dist/speech.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { AIProvider } from './types';
|
|
2
|
+
/** The MIME type for a speech format. */
|
|
3
|
+
export declare function speechMimeType(format: SpeechFormat): string;
|
|
4
|
+
/**
|
|
5
|
+
* Synthesize speech from text.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const speech = await textToSpeech('Your order has shipped.', { voice: 'nova' })
|
|
10
|
+
* return new Response(speech.audio, { headers: { 'Content-Type': speech.mimeType } })
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export declare function textToSpeech(text: string, options?: TextToSpeechOptions): Promise<SpeechResult>;
|
|
14
|
+
/**
|
|
15
|
+
* Write synthesized speech to a Stacks storage disk.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* const speech = await textToSpeech('Welcome back!')
|
|
20
|
+
* await storeSpeech(speech, 'audio/welcome.mp3', { disk: 'public' })
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare function storeSpeech(speech: Pick<SpeechResult, 'audio' | 'mimeType'>, path: string, options?: StoreSpeechOptions): Promise<StoredSpeech>;
|
|
24
|
+
/**
|
|
25
|
+
* Transcribe audio to text.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* const { text } = await speechToText(Bun.file('memo.m4a'), { language: 'en' })
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare function speechToText(audio: SpeechInput, options?: SpeechToTextOptions): Promise<TranscriptionResult>;
|
|
33
|
+
/** Drivers that implement text-to-speech / speech-to-text. */
|
|
34
|
+
export declare const SPEECH_DRIVERS: readonly AIProvider[];
|
|
35
|
+
declare interface SpeechRequestOptions {
|
|
36
|
+
driver?: SpeechDriver
|
|
37
|
+
config?: ConfiguredAIOptions
|
|
38
|
+
retry?: RetryConfig
|
|
39
|
+
signal?: AbortSignal
|
|
40
|
+
}
|
|
41
|
+
export declare interface TextToSpeechOptions extends SpeechRequestOptions {
|
|
42
|
+
model?: string
|
|
43
|
+
voice?: OpenAISpeechVoice | (string & {})
|
|
44
|
+
format?: SpeechFormat
|
|
45
|
+
speed?: number
|
|
46
|
+
instructions?: string
|
|
47
|
+
}
|
|
48
|
+
export declare interface SpeechResult {
|
|
49
|
+
audio: Uint8Array
|
|
50
|
+
mimeType: string
|
|
51
|
+
format: SpeechFormat
|
|
52
|
+
driver: AIProvider
|
|
53
|
+
model: string
|
|
54
|
+
voice: string
|
|
55
|
+
}
|
|
56
|
+
export declare interface SpeechToTextOptions extends SpeechRequestOptions {
|
|
57
|
+
model?: string
|
|
58
|
+
language?: string
|
|
59
|
+
prompt?: string
|
|
60
|
+
temperature?: number
|
|
61
|
+
filename?: string
|
|
62
|
+
}
|
|
63
|
+
export declare interface TranscriptionResult {
|
|
64
|
+
text: string
|
|
65
|
+
driver: AIProvider
|
|
66
|
+
model: string
|
|
67
|
+
}
|
|
68
|
+
/** Anything with a Stacks `StorageAdapter`-compatible `write()`. */
|
|
69
|
+
export declare interface SpeechStorageDisk {
|
|
70
|
+
write: (path: string, contents: Uint8Array) => Promise<{ path: string, size: number, contentType?: string }>
|
|
71
|
+
}
|
|
72
|
+
export declare interface StoreSpeechOptions {
|
|
73
|
+
disk?: string | SpeechStorageDisk
|
|
74
|
+
}
|
|
75
|
+
export declare interface StoredSpeech {
|
|
76
|
+
path: string
|
|
77
|
+
size: number
|
|
78
|
+
mimeType: string
|
|
79
|
+
}
|
|
80
|
+
/** A driver name: one of the built-in providers, or anything `config/ai.ts` holds. */
|
|
81
|
+
export type SpeechDriver = AIProvider | (string & {});
|
|
82
|
+
/** Audio container/codec the provider should return. */
|
|
83
|
+
export type SpeechFormat = 'mp3' | 'opus' | 'aac' | 'flac' | 'wav' | 'pcm';
|
|
84
|
+
/** OpenAI's built-in voices. Any other string is passed through (custom voice ids). */
|
|
85
|
+
export type OpenAISpeechVoice = | 'alloy' | 'ash' | 'ballad' | 'cedar' | 'coral' | 'echo' | 'fable'
|
|
86
|
+
| 'marin' | 'nova' | 'onyx' | 'sage' | 'shimmer' | 'verse';
|
|
87
|
+
export type SpeechInput = Blob | Uint8Array | ArrayBuffer;
|
package/dist/speech.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{resolveAIProvider}from"./client";import{fetchWithRetry}from"./utils/retry";export const SPEECH_DRIVERS=["openai"];const OPENAI_BASE_URL="https://api.openai.com/v1",MAX_OPENAI_INPUT=4096;async function loadConfig(config){if(config)return config;try{const{ai}=await import("@stacksjs/config");return ai??{}}catch{return{}}}function unsupported(feature,driver){return Error(`${feature} is not supported by the "${driver}" AI driver. Drivers that support it: ${SPEECH_DRIVERS.join(", ")}. Pass { driver: '${SPEECH_DRIVERS[0]}' } or set \`default\` in config/ai.ts.`)}function resolveSpeechDriver(feature,config,override){const requested=String(override||config.default||"").trim();if(!requested)throw Error(`${feature} needs an AI driver. Pass { driver: '${SPEECH_DRIVERS[0]}' } or set \`default\` in config/ai.ts.`);let provider;try{provider=resolveAIProvider({default:requested})}catch{throw unsupported(feature,requested)}if(!SPEECH_DRIVERS.includes(provider))throw unsupported(feature,provider);return provider}function openAICredentials(config){const driver=config.drivers?.openai??{},apiKey=driver.apiKey?.trim()||process.env.OPENAI_API_KEY?.trim()||"",customBase=driver.baseUrl?.trim();if(!apiKey&&!customBase)throw Error("OpenAI API key not set. Set OPENAI_API_KEY or `drivers.openai.apiKey` in config/ai.ts.");return{apiKey,baseUrl:(customBase||OPENAI_BASE_URL).replace(/\/+$/,"")}}async function failure(provider,operation,response){let body="";try{body=(await response.text()).trim()}catch{}const snippet=body.length>500?`${body.slice(0,500)}...`:body;return Error(`${provider} ${operation} failed with HTTP ${response.status}${snippet?`: ${snippet}`:""}`)}const MIME_TYPES={mp3:"audio/mpeg",opus:"audio/ogg",aac:"audio/aac",flac:"audio/flac",wav:"audio/wav",pcm:"audio/pcm"};export function speechMimeType(format){return MIME_TYPES[format]}export async function textToSpeech(text,options={}){if(typeof text!=="string"||!text.trim())throw Error("textToSpeech() needs non-empty text.");const format=options.format??"mp3";if(!(format in MIME_TYPES))throw Error(`Unsupported speech format "${format}". Use one of: ${Object.keys(MIME_TYPES).join(", ")}.`);if(options.speed!==void 0&&!(options.speed>=0.25&&options.speed<=4))throw Error(`Speech speed must be between 0.25 and 4.0, got ${options.speed}.`);const config=await loadConfig(options.config),driver=resolveSpeechDriver("Text-to-speech",config,options.driver);return openAITextToSpeech(text,format,options,config,driver)}async function openAITextToSpeech(text,format,options,config,driver){if(text.length>MAX_OPENAI_INPUT)throw Error(`OpenAI text-to-speech accepts at most ${MAX_OPENAI_INPUT} characters per request, got ${text.length}. Split the text into chunks.`);const model=options.model??"gpt-4o-mini-tts",voice=options.voice??"alloy";if(options.instructions&&/^tts-1(?:-hd)?$/.test(model))throw Error(`OpenAI model "${model}" does not support \`instructions\`. Use gpt-4o-mini-tts.`);const{apiKey,baseUrl}=openAICredentials(config),body={model,input:text,voice,response_format:format};if(options.speed!==void 0)body.speed=options.speed;if(options.instructions)body.instructions=options.instructions;const headers={"Content-Type":"application/json"};if(apiKey)headers.Authorization=`Bearer ${apiKey}`;const response=await fetchWithRetry(`${baseUrl}/audio/speech`,{method:"POST",headers,body:JSON.stringify(body),signal:options.signal},options.retry);if(!response.ok)throw await failure("OpenAI","text-to-speech",response);const audio=new Uint8Array(await response.arrayBuffer()),contentType=response.headers.get("content-type")?.split(";")[0]?.trim(),mimeType=contentType?.startsWith("audio/")?contentType:MIME_TYPES[format];return{audio,mimeType,format,driver,model,voice}}export async function storeSpeech(speech,path,options={}){if(!path?.trim())throw Error("storeSpeech() needs a destination path.");let disk;if(options.disk&&typeof options.disk==="object")disk=options.disk;else{let storage;try{storage=await import("@stacksjs/storage")}catch(error){throw Error(`storeSpeech() needs @stacksjs/storage to resolve a disk by name (${error instanceof Error?error.message:String(error)}). Install it, or pass a storage adapter as { disk }.`)}disk=storage.Storage.disk(options.disk||void 0)}const result=await disk.write(path,speech.audio);return{path:result.path,size:result.size,mimeType:speech.mimeType}}const EXTENSIONS={"audio/mpeg":"mp3","audio/mp3":"mp3","audio/mp4":"m4a","audio/m4a":"m4a","audio/x-m4a":"m4a","audio/wav":"wav","audio/x-wav":"wav","audio/webm":"webm","audio/ogg":"ogg","audio/flac":"flac"};export async function speechToText(audio,options={}){if(options.temperature!==void 0&&!(options.temperature>=0&&options.temperature<=1))throw Error(`Transcription temperature must be between 0 and 1, got ${options.temperature}.`);const config=await loadConfig(options.config),driver=resolveSpeechDriver("Speech-to-text",config,options.driver),model=options.model??"whisper-1",{apiKey,baseUrl}=openAICredentials(config),blob=audio instanceof Blob?audio:new Blob([audio]);if(blob.size===0)throw Error("speechToText() needs non-empty audio.");const ownName=audio.name,fileName=options.filename||(typeof ownName==="string"&&ownName?ownName.split("/").pop():void 0)||`audio.${EXTENSIONS[blob.type.split(";")[0]??""]??"mp3"}`,form=new FormData;form.append("file",blob,fileName);form.append("model",model);form.append("response_format","json");if(options.language)form.append("language",options.language);if(options.prompt)form.append("prompt",options.prompt);if(options.temperature!==void 0)form.append("temperature",String(options.temperature));const headers={};if(apiKey)headers.Authorization=`Bearer ${apiKey}`;const response=await fetchWithRetry(`${baseUrl}/audio/transcriptions`,{method:"POST",headers,body:form,signal:options.signal},options.retry);if(!response.ok)throw await failure("OpenAI","speech-to-text",response);const data=await response.json();if(typeof data?.text!=="string")throw Error("OpenAI speech-to-text returned no text.");return{text:data.text,driver,model}}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/ai",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.75.
|
|
5
|
+
"version": "0.75.40",
|
|
6
6
|
"description": "Stacks Artificial Intelligence.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -65,8 +65,9 @@
|
|
|
65
65
|
"prepublishOnly": "bun run build"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"@stacksjs/cli": "0.75.
|
|
69
|
-
"@stacksjs/config": "0.75.
|
|
68
|
+
"@stacksjs/cli": "0.75.40",
|
|
69
|
+
"@stacksjs/config": "0.75.40",
|
|
70
|
+
"@stacksjs/storage": "0.75.40",
|
|
70
71
|
"@stacksjs/ts-cloud": "^0.16.15"
|
|
71
72
|
},
|
|
72
73
|
"peerDependencies": {
|