@ai-ecoverse/pi-audio 1.24.0 → 1.25.0

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/README.md CHANGED
@@ -23,6 +23,8 @@ pi-audio does not apply that patch. Hosts such as slicc-agent record monkeypatch
23
23
 
24
24
  `@ai-ecoverse/pi-audio/durable` appends a `voice.truncate` entry for a `VoiceCut`. The entry has no model messages. Its `edits` replace the target assistant message with the text heard so far and keep tool calls. The stored entry stays intact, and pi-durable derives the shorter message for the next model request. `@earendil-works/pi-durable` is an optional peer (`^1.1.0`); the root package does not import it. `VoiceCut.entryId` is the decimal string of pi-durable's numeric entry id.
25
25
 
26
+ `@ai-ecoverse/pi-audio/wavespeed` registers WaveSpeed adapters when `registerWaveSpeed()` is called. Both are batch only. `wavespeed-ai/openai-whisper` submits JSON to `/wavespeed-ai/openai-whisper` and polls `/predictions/{id}/result`. Timestamps stay off, so the catalog price is $0.001 per second (`perInputMinute` 0.06). A byte input is uploaded to `/media/upload/binary` first. `minimax/speech-2.6-turbo` is the same submit/poll path for MP3 (`textStreaming: false`, `streaming: false`) at $0.06 per thousand characters. `voice_id` is a caller id (`cloning: true`). These models are not for live voice.
27
+
26
28
  `@ai-ecoverse/pi-audio/cloudflare` registers Workers AI speech adapters when `registerCloudflare()` is called. One Cloudflare token calls `POST /client/v4/accounts/{account_id}/ai/run/{model}`. Put the account in `baseUrl` or the `cf-account-id` header. Streaming models open `wss://gateway.ai.cloudflare.com/v1/{account}/{gateway}/workers-ai?model=…`. The gateway id is the `cf-aig-gateway-id` header, or `default`. `@cf/deepgram/nova-3` streams (`turnDetection: vad`, catalog price $0.0092 per minute). `@cf/deepgram/flux` streams with semantic end of turn ($0.0077 per minute) and has no batch transcribe. `@cf/openai/whisper-large-v3-turbo` is batch only ($0.000513 per minute). `@cf/deepgram/aura-1` ($0.015 per thousand characters), `aura-2-en`, and `aura-2-es` ($0.03) speak MP3 over HTTP and linear16 over the socket (`textStreaming: true`). `@cf/myshell-ai/melotts` is batch MP3 priced at $0.000205 per audio minute. `@cf/pipecat-ai/smart-turn-v2` is not a transcript model, so it is not registered.
27
29
 
28
30
  `@ai-ecoverse/pi-audio/google` registers Gemini speech adapters when `registerGoogle()` is called. `gemini-3.8-flash-tts` and `gemini-3.8-flash-lite-tts` post JSON to `models/{id}:generateContent` with `responseModalities: ["AUDIO"]` and a prebuilt `voiceName`. Unary audio is WAV at 24 kHz unless the format asks for `AUDIO_L16`, `AUDIO_MULAW`, or `AUDIO_ALAW`. `textStreaming` is false. Output audio is priced from 25 tokens per second: $0.0135 per minute for Flash and $0.009 per minute for Flash-Lite through December 31, 2026. Text input is $0.50 per million tokens and is not on `audioCost`. Auth is `x-goog-api-key`.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/models.ts"],
4
- "sourcesContent": ["import type { BaseModel } from '@earendil-works/pi-ai';\nimport type { AudioFormat } from './audio.ts';\nimport type { RealtimeAudioCost, SpeechAudioCost, TranscriptionAudioCost } from './usage.ts';\n\nexport type KnownTranscriptionApi =\n | 'openai-transcriptions'\n | 'openai-realtime-transcription'\n | 'xai-stt'\n | 'elevenlabs-scribe'\n | 'mistral-transcriptions'\n | 'fish-asr'\n | 'cloudflare-ai-run';\n\nexport type TranscriptionApi = KnownTranscriptionApi | (string & {});\n\nexport type KnownSpeechApi =\n | 'openai-speech'\n | 'xai-tts'\n | 'elevenlabs-tts'\n | 'mistral-speech'\n | 'fish-tts'\n | 'google-tts'\n | 'cloudflare-ai-run'\n | 'wavespeed';\n\nexport type SpeechApi = KnownSpeechApi | (string & {});\n\nexport type KnownRealtimeApi =\n | 'openai-realtime'\n | 'xai-realtime'\n | 'gemini-live'\n | 'elevenlabs-agents'\n | 'fish-agents';\n\nexport type RealtimeApi = KnownRealtimeApi | (string & {});\n\nexport type Delivery = 'interrupt' | 'when_idle' | 'silent';\n\nexport type DeliverySupport = 'native' | 'approximate' | 'unsupported';\n\nexport interface VoiceInfo {\n id: string;\n name: string;\n languages?: string[];\n}\n\nexport interface TranscriptionModel<TApi extends TranscriptionApi>\n extends Omit<BaseModel<TApi>, 'input'> {\n type: 'transcription';\n streaming: boolean;\n turnDetection: 'none' | 'vad' | 'semantic';\n inputFormats: AudioFormat[];\n languages?: string[];\n diarization?: boolean;\n audioCost: TranscriptionAudioCost;\n}\n\nexport interface SpeechModel<TApi extends SpeechApi> extends Omit<BaseModel<TApi>, 'input'> {\n type: 'speech';\n textStreaming: boolean;\n outputFormats: AudioFormat[];\n voices?: VoiceInfo[];\n cloning?: boolean;\n audioCost: SpeechAudioCost;\n}\n\nexport interface RealtimeModel<TApi extends RealtimeApi> extends Omit<BaseModel<TApi>, 'input'> {\n type: 'realtime';\n transports: ('websocket' | 'webrtc')[];\n toolCalls: 'blocking' | 'async';\n inputFormat: AudioFormat;\n outputFormat: AudioFormat;\n video?: { maxFps: number };\n maxSessionMs?: number;\n resumable: boolean;\n ephemeralAuth: boolean;\n voices: VoiceInfo[];\n cascade?: { stt: string; llm: string; tts: string };\n provisioning: 'none' | 'agent';\n delivery: Record<Delivery, DeliverySupport>;\n truncation: 'client' | 'server' | 'none';\n liveToolUpdate: boolean;\n toolTimeoutMs?: { min: number; max: number };\n audioCost: RealtimeAudioCost;\n}\n\ndeclare module '@earendil-works/pi-ai' {\n interface ModelTypeMap {\n transcription: TranscriptionModel<TranscriptionApi>;\n speech: SpeechModel<SpeechApi>;\n realtime: RealtimeModel<RealtimeApi>;\n }\n}\n\nexport type VoiceModel =\n | TranscriptionModel<TranscriptionApi>\n | SpeechModel<SpeechApi>\n | RealtimeModel<RealtimeApi>;\n\nexport type VoiceModelType = VoiceModel['type'];\n\nconst catalog = new Map<string, VoiceModel>();\n\nfunction catalogKey(type: string, provider: string, id: string): string {\n return `${type}\\0${provider}\\0${id}`;\n}\n\nexport function registerVoiceModel(model: VoiceModel): () => void {\n const key = catalogKey(model.type, model.provider, model.id);\n catalog.set(key, model);\n return () => {\n if (catalog.get(key) === model) catalog.delete(key);\n };\n}\n\nexport function getVoiceModel<TType extends VoiceModelType>(\n type: TType,\n provider: string,\n id: string\n): Extract<VoiceModel, { type: TType }> | undefined {\n return catalog.get(catalogKey(type, provider, id)) as\n | Extract<VoiceModel, { type: TType }>\n | undefined;\n}\n\nexport function listVoiceModels<TType extends VoiceModelType>(\n type: TType,\n provider?: string\n): Extract<VoiceModel, { type: TType }>[] {\n const found: Extract<VoiceModel, { type: TType }>[] = [];\n for (const model of catalog.values()) {\n if (model.type !== type) continue;\n if (provider !== undefined && model.provider !== provider) continue;\n found.push(model as Extract<VoiceModel, { type: TType }>);\n }\n return found;\n}\n"],
5
- "mappings": "AAqGA,MAAM,UAAU,oBAAI,IAAwB;AAE5C,SAAS,WAAW,MAAc,UAAkB,IAAoB;AACtE,SAAO,GAAG,IAAI,KAAK,QAAQ,KAAK,EAAE;AACpC;AAEO,SAAS,mBAAmB,OAA+B;AAChE,QAAM,MAAM,WAAW,MAAM,MAAM,MAAM,UAAU,MAAM,EAAE;AAC3D,UAAQ,IAAI,KAAK,KAAK;AACtB,SAAO,MAAM;AACX,QAAI,QAAQ,IAAI,GAAG,MAAM,MAAO,SAAQ,OAAO,GAAG;AAAA,EACpD;AACF;AAEO,SAAS,cACd,MACA,UACA,IACkD;AAClD,SAAO,QAAQ,IAAI,WAAW,MAAM,UAAU,EAAE,CAAC;AAGnD;AAEO,SAAS,gBACd,MACA,UACwC;AACxC,QAAM,QAAgD,CAAC;AACvD,aAAW,SAAS,QAAQ,OAAO,GAAG;AACpC,QAAI,MAAM,SAAS,KAAM;AACzB,QAAI,aAAa,UAAa,MAAM,aAAa,SAAU;AAC3D,UAAM,KAAK,KAA6C;AAAA,EAC1D;AACA,SAAO;AACT;",
4
+ "sourcesContent": ["import type { BaseModel } from '@earendil-works/pi-ai';\nimport type { AudioFormat } from './audio.ts';\nimport type { RealtimeAudioCost, SpeechAudioCost, TranscriptionAudioCost } from './usage.ts';\n\nexport type KnownTranscriptionApi =\n | 'openai-transcriptions'\n | 'openai-realtime-transcription'\n | 'xai-stt'\n | 'elevenlabs-scribe'\n | 'mistral-transcriptions'\n | 'fish-asr'\n | 'cloudflare-ai-run'\n | 'wavespeed';\n\nexport type TranscriptionApi = KnownTranscriptionApi | (string & {});\n\nexport type KnownSpeechApi =\n | 'openai-speech'\n | 'xai-tts'\n | 'elevenlabs-tts'\n | 'mistral-speech'\n | 'fish-tts'\n | 'google-tts'\n | 'cloudflare-ai-run'\n | 'wavespeed';\n\nexport type SpeechApi = KnownSpeechApi | (string & {});\n\nexport type KnownRealtimeApi =\n | 'openai-realtime'\n | 'xai-realtime'\n | 'gemini-live'\n | 'elevenlabs-agents'\n | 'fish-agents';\n\nexport type RealtimeApi = KnownRealtimeApi | (string & {});\n\nexport type Delivery = 'interrupt' | 'when_idle' | 'silent';\n\nexport type DeliverySupport = 'native' | 'approximate' | 'unsupported';\n\nexport interface VoiceInfo {\n id: string;\n name: string;\n languages?: string[];\n}\n\nexport interface TranscriptionModel<TApi extends TranscriptionApi>\n extends Omit<BaseModel<TApi>, 'input'> {\n type: 'transcription';\n streaming: boolean;\n turnDetection: 'none' | 'vad' | 'semantic';\n inputFormats: AudioFormat[];\n languages?: string[];\n diarization?: boolean;\n audioCost: TranscriptionAudioCost;\n}\n\nexport interface SpeechModel<TApi extends SpeechApi> extends Omit<BaseModel<TApi>, 'input'> {\n type: 'speech';\n textStreaming: boolean;\n outputFormats: AudioFormat[];\n voices?: VoiceInfo[];\n cloning?: boolean;\n audioCost: SpeechAudioCost;\n}\n\nexport interface RealtimeModel<TApi extends RealtimeApi> extends Omit<BaseModel<TApi>, 'input'> {\n type: 'realtime';\n transports: ('websocket' | 'webrtc')[];\n toolCalls: 'blocking' | 'async';\n inputFormat: AudioFormat;\n outputFormat: AudioFormat;\n video?: { maxFps: number };\n maxSessionMs?: number;\n resumable: boolean;\n ephemeralAuth: boolean;\n voices: VoiceInfo[];\n cascade?: { stt: string; llm: string; tts: string };\n provisioning: 'none' | 'agent';\n delivery: Record<Delivery, DeliverySupport>;\n truncation: 'client' | 'server' | 'none';\n liveToolUpdate: boolean;\n toolTimeoutMs?: { min: number; max: number };\n audioCost: RealtimeAudioCost;\n}\n\ndeclare module '@earendil-works/pi-ai' {\n interface ModelTypeMap {\n transcription: TranscriptionModel<TranscriptionApi>;\n speech: SpeechModel<SpeechApi>;\n realtime: RealtimeModel<RealtimeApi>;\n }\n}\n\nexport type VoiceModel =\n | TranscriptionModel<TranscriptionApi>\n | SpeechModel<SpeechApi>\n | RealtimeModel<RealtimeApi>;\n\nexport type VoiceModelType = VoiceModel['type'];\n\nconst catalog = new Map<string, VoiceModel>();\n\nfunction catalogKey(type: string, provider: string, id: string): string {\n return `${type}\\0${provider}\\0${id}`;\n}\n\nexport function registerVoiceModel(model: VoiceModel): () => void {\n const key = catalogKey(model.type, model.provider, model.id);\n catalog.set(key, model);\n return () => {\n if (catalog.get(key) === model) catalog.delete(key);\n };\n}\n\nexport function getVoiceModel<TType extends VoiceModelType>(\n type: TType,\n provider: string,\n id: string\n): Extract<VoiceModel, { type: TType }> | undefined {\n return catalog.get(catalogKey(type, provider, id)) as\n | Extract<VoiceModel, { type: TType }>\n | undefined;\n}\n\nexport function listVoiceModels<TType extends VoiceModelType>(\n type: TType,\n provider?: string\n): Extract<VoiceModel, { type: TType }>[] {\n const found: Extract<VoiceModel, { type: TType }>[] = [];\n for (const model of catalog.values()) {\n if (model.type !== type) continue;\n if (provider !== undefined && model.provider !== provider) continue;\n found.push(model as Extract<VoiceModel, { type: TType }>);\n }\n return found;\n}\n"],
5
+ "mappings": "AAsGA,MAAM,UAAU,oBAAI,IAAwB;AAE5C,SAAS,WAAW,MAAc,UAAkB,IAAoB;AACtE,SAAO,GAAG,IAAI,KAAK,QAAQ,KAAK,EAAE;AACpC;AAEO,SAAS,mBAAmB,OAA+B;AAChE,QAAM,MAAM,WAAW,MAAM,MAAM,MAAM,UAAU,MAAM,EAAE;AAC3D,UAAQ,IAAI,KAAK,KAAK;AACtB,SAAO,MAAM;AACX,QAAI,QAAQ,IAAI,GAAG,MAAM,MAAO,SAAQ,OAAO,GAAG;AAAA,EACpD;AACF;AAEO,SAAS,cACd,MACA,UACA,IACkD;AAClD,SAAO,QAAQ,IAAI,WAAW,MAAM,UAAU,EAAE,CAAC;AAGnD;AAEO,SAAS,gBACd,MACA,UACwC;AACxC,QAAM,QAAgD,CAAC;AACvD,aAAW,SAAS,QAAQ,OAAO,GAAG;AACpC,QAAI,MAAM,SAAS,KAAM;AACzB,QAAI,aAAa,UAAa,MAAM,aAAa,SAAU;AAC3D,UAAM,KAAK,KAA6C;AAAA,EAC1D;AACA,SAAO;AACT;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  import type { BaseModel } from '@earendil-works/pi-ai';
2
2
  import type { AudioFormat } from './audio.ts';
3
3
  import type { RealtimeAudioCost, SpeechAudioCost, TranscriptionAudioCost } from './usage.ts';
4
- export type KnownTranscriptionApi = 'openai-transcriptions' | 'openai-realtime-transcription' | 'xai-stt' | 'elevenlabs-scribe' | 'mistral-transcriptions' | 'fish-asr' | 'cloudflare-ai-run';
4
+ export type KnownTranscriptionApi = 'openai-transcriptions' | 'openai-realtime-transcription' | 'xai-stt' | 'elevenlabs-scribe' | 'mistral-transcriptions' | 'fish-asr' | 'cloudflare-ai-run' | 'wavespeed';
5
5
  export type TranscriptionApi = KnownTranscriptionApi | (string & {});
6
6
  export type KnownSpeechApi = 'openai-speech' | 'xai-tts' | 'elevenlabs-tts' | 'mistral-speech' | 'fish-tts' | 'google-tts' | 'cloudflare-ai-run' | 'wavespeed';
7
7
  export type SpeechApi = KnownSpeechApi | (string & {});
@@ -0,0 +1 @@
1
+ export declare function registerWaveSpeed(): () => void;
@@ -0,0 +1 @@
1
+ export { registerWaveSpeed } from './catalog.ts';
@@ -0,0 +1,4 @@
1
+ import type { ProviderRequestOptions } from '@earendil-works/pi-ai';
2
+ import type { SpeechModel } from '../models.ts';
3
+ import { SpeechEventStream, type SpeechInput } from '../speech.ts';
4
+ export declare function speakWaveSpeed(model: SpeechModel<string>, input: SpeechInput, options?: ProviderRequestOptions): SpeechEventStream;
@@ -0,0 +1,4 @@
1
+ import type { ProviderRequestOptions } from '@earendil-works/pi-ai';
2
+ import type { TranscriptionModel } from '../models.ts';
3
+ import type { AudioInput, Transcript } from '../transcription.ts';
4
+ export declare function transcribeWaveSpeed(model: TranscriptionModel<string>, input: AudioInput, options?: ProviderRequestOptions): Promise<Transcript>;
@@ -0,0 +1,25 @@
1
+ import type { ProviderRequestOptions } from '@earendil-works/pi-ai';
2
+ export declare function taskRecord(body: unknown): Record<string, unknown>;
3
+ export declare function errorText(text: string, status: number): string;
4
+ export declare function isHttpUrl(value: string): boolean;
5
+ export declare function outputText(output: unknown): string | undefined;
6
+ export declare function outputUrl(output: unknown): string | undefined;
7
+ export declare function downloadUrl(body: unknown): string;
8
+ export declare function durationSeconds(record: Record<string, unknown>): number;
9
+ declare function waveFetch(model: {
10
+ provider: string;
11
+ headers?: Record<string, string>;
12
+ id: string;
13
+ }, options: ProviderRequestOptions | undefined, url: string, body: BodyInit | undefined, contentType: string | undefined): Promise<unknown>;
14
+ export declare function finishTask(model: {
15
+ provider: string;
16
+ baseUrl: string;
17
+ headers?: Record<string, string>;
18
+ id: string;
19
+ }, options: ProviderRequestOptions | undefined, payload: unknown, rejected: string): Promise<Record<string, unknown>>;
20
+ export declare function readHttp(model: {
21
+ provider: string;
22
+ headers?: Record<string, string>;
23
+ id: string;
24
+ }, options: ProviderRequestOptions | undefined, url: string): Promise<Response>;
25
+ export { waveFetch };
@@ -0,0 +1,60 @@
1
+ import { registerVoiceModel } from "../models.js";
2
+ import { registerSpeechApiProvider, registerTranscriptionApiProvider } from "../registry.js";
3
+ import { speakWaveSpeed } from "./speak.js";
4
+ import { transcribeWaveSpeed } from "./transcribe.js";
5
+ const tokenCost = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
6
+ const wavespeed = "https://api.wavespeed.ai/api/v3";
7
+ const format = (encoding, sampleRate) => ({
8
+ encoding,
9
+ sampleRate,
10
+ channels: 1
11
+ });
12
+ function registerWaveSpeed() {
13
+ const whisper = {
14
+ id: "wavespeed-ai/openai-whisper",
15
+ name: "wavespeed-ai/openai-whisper",
16
+ api: "wavespeed",
17
+ provider: "wavespeed",
18
+ baseUrl: wavespeed,
19
+ type: "transcription",
20
+ streaming: false,
21
+ turnDetection: "none",
22
+ inputFormats: [format("wav", 16e3), format("mp3", 16e3), format("flac", 16e3)],
23
+ cost: tokenCost,
24
+ audioCost: { perInputMinute: 0.06 }
25
+ };
26
+ const speech = {
27
+ id: "minimax/speech-2.6-turbo",
28
+ name: "minimax/speech-2.6-turbo",
29
+ api: "wavespeed",
30
+ provider: "wavespeed",
31
+ baseUrl: wavespeed,
32
+ type: "speech",
33
+ textStreaming: false,
34
+ outputFormats: [format("mp3", 24e3)],
35
+ cloning: true,
36
+ cost: tokenCost,
37
+ audioCost: { perThousandChars: 0.06 }
38
+ };
39
+ const unregisterModels = [registerVoiceModel(whisper), registerVoiceModel(speech)];
40
+ const unregisterTranscription = registerTranscriptionApiProvider({
41
+ api: "wavespeed",
42
+ transcribe: transcribeWaveSpeed
43
+ });
44
+ const unregisterSpeech = registerSpeechApiProvider({
45
+ api: "wavespeed",
46
+ speak: speakWaveSpeed
47
+ });
48
+ let released = false;
49
+ return () => {
50
+ if (released) return;
51
+ released = true;
52
+ for (const unregister of unregisterModels) unregister();
53
+ unregisterTranscription();
54
+ unregisterSpeech();
55
+ };
56
+ }
57
+ export {
58
+ registerWaveSpeed
59
+ };
60
+ //# sourceMappingURL=catalog.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/wavespeed/catalog.ts"],
4
+ "sourcesContent": ["import type { AudioFormat } from '../audio.ts';\nimport { registerVoiceModel, type SpeechModel, type TranscriptionModel } from '../models.ts';\nimport { registerSpeechApiProvider, registerTranscriptionApiProvider } from '../registry.ts';\nimport { speakWaveSpeed } from './speak.ts';\nimport { transcribeWaveSpeed } from './transcribe.ts';\n\nconst tokenCost = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };\nconst wavespeed = 'https://api.wavespeed.ai/api/v3';\n\nconst format = (encoding: 'wav' | 'mp3' | 'flac', sampleRate: number): AudioFormat => ({\n encoding,\n sampleRate,\n channels: 1,\n});\n\nexport function registerWaveSpeed(): () => void {\n const whisper: TranscriptionModel<string> = {\n id: 'wavespeed-ai/openai-whisper',\n name: 'wavespeed-ai/openai-whisper',\n api: 'wavespeed',\n provider: 'wavespeed',\n baseUrl: wavespeed,\n type: 'transcription',\n streaming: false,\n turnDetection: 'none',\n inputFormats: [format('wav', 16000), format('mp3', 16000), format('flac', 16000)],\n cost: tokenCost,\n audioCost: { perInputMinute: 0.06 },\n };\n const speech: SpeechModel<string> = {\n id: 'minimax/speech-2.6-turbo',\n name: 'minimax/speech-2.6-turbo',\n api: 'wavespeed',\n provider: 'wavespeed',\n baseUrl: wavespeed,\n type: 'speech',\n textStreaming: false,\n outputFormats: [format('mp3', 24000)],\n cloning: true,\n cost: tokenCost,\n audioCost: { perThousandChars: 0.06 },\n };\n const unregisterModels = [registerVoiceModel(whisper), registerVoiceModel(speech)];\n const unregisterTranscription = registerTranscriptionApiProvider({\n api: 'wavespeed',\n transcribe: transcribeWaveSpeed,\n });\n const unregisterSpeech = registerSpeechApiProvider({\n api: 'wavespeed',\n speak: speakWaveSpeed,\n });\n let released = false;\n return () => {\n if (released) return;\n released = true;\n for (const unregister of unregisterModels) unregister();\n unregisterTranscription();\n unregisterSpeech();\n };\n}\n"],
5
+ "mappings": "AACA,SAAS,0BAAqE;AAC9E,SAAS,2BAA2B,wCAAwC;AAC5E,SAAS,sBAAsB;AAC/B,SAAS,2BAA2B;AAEpC,MAAM,YAAY,EAAE,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,YAAY,EAAE;AACrE,MAAM,YAAY;AAElB,MAAM,SAAS,CAAC,UAAkC,gBAAqC;AAAA,EACrF;AAAA,EACA;AAAA,EACA,UAAU;AACZ;AAEO,SAAS,oBAAgC;AAC9C,QAAM,UAAsC;AAAA,IAC1C,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,KAAK;AAAA,IACL,UAAU;AAAA,IACV,SAAS;AAAA,IACT,MAAM;AAAA,IACN,WAAW;AAAA,IACX,eAAe;AAAA,IACf,cAAc,CAAC,OAAO,OAAO,IAAK,GAAG,OAAO,OAAO,IAAK,GAAG,OAAO,QAAQ,IAAK,CAAC;AAAA,IAChF,MAAM;AAAA,IACN,WAAW,EAAE,gBAAgB,KAAK;AAAA,EACpC;AACA,QAAM,SAA8B;AAAA,IAClC,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,KAAK;AAAA,IACL,UAAU;AAAA,IACV,SAAS;AAAA,IACT,MAAM;AAAA,IACN,eAAe;AAAA,IACf,eAAe,CAAC,OAAO,OAAO,IAAK,CAAC;AAAA,IACpC,SAAS;AAAA,IACT,MAAM;AAAA,IACN,WAAW,EAAE,kBAAkB,KAAK;AAAA,EACtC;AACA,QAAM,mBAAmB,CAAC,mBAAmB,OAAO,GAAG,mBAAmB,MAAM,CAAC;AACjF,QAAM,0BAA0B,iCAAiC;AAAA,IAC/D,KAAK;AAAA,IACL,YAAY;AAAA,EACd,CAAC;AACD,QAAM,mBAAmB,0BAA0B;AAAA,IACjD,KAAK;AAAA,IACL,OAAO;AAAA,EACT,CAAC;AACD,MAAI,WAAW;AACf,SAAO,MAAM;AACX,QAAI,SAAU;AACd,eAAW;AACX,eAAW,cAAc,iBAAkB,YAAW;AACtD,4BAAwB;AACxB,qBAAiB;AAAA,EACnB;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,5 @@
1
+ import { registerWaveSpeed } from "./catalog.js";
2
+ export {
3
+ registerWaveSpeed
4
+ };
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/wavespeed/index.ts"],
4
+ "sourcesContent": ["export { registerWaveSpeed } from './catalog.ts';\n"],
5
+ "mappings": "AAAA,SAAS,yBAAyB;",
6
+ "names": []
7
+ }
@@ -0,0 +1,66 @@
1
+ import { asVoiceError } from "../openai/wire.js";
2
+ import { SpeechEventStream } from "../speech.js";
3
+ import { priceAudio } from "../usage.js";
4
+ import { finishTask, outputUrl, readHttp } from "./wire.js";
5
+ function resolveFormat(model, requested) {
6
+ const fallback = model.outputFormats[0];
7
+ if (!fallback) throw new Error("No output format");
8
+ if (!requested) return fallback;
9
+ const match = model.outputFormats.find(
10
+ (format) => format.encoding === requested.encoding && format.sampleRate === requested.sampleRate && format.channels === requested.channels
11
+ );
12
+ if (!match) throw new Error(`Unsupported speech format ${requested.encoding}`);
13
+ return match;
14
+ }
15
+ async function readText(text, signal) {
16
+ if (typeof text === "string") return text;
17
+ let combined = "";
18
+ for await (const part of text) {
19
+ if (signal?.aborted) throw new DOMException("aborted", "AbortError");
20
+ combined += part;
21
+ }
22
+ return combined;
23
+ }
24
+ async function audioOf(model, options, output) {
25
+ const url = outputUrl(output);
26
+ if (url === void 0) throw new Error("Speech response had no audio");
27
+ const response = await readHttp(model, options, url);
28
+ const data = new Uint8Array(await response.arrayBuffer());
29
+ if (data.byteLength === 0) throw new Error("Speech response had no audio");
30
+ return data;
31
+ }
32
+ function speakWaveSpeed(model, input, options) {
33
+ const stream = new SpeechEventStream();
34
+ void pumpSpeech(model, input, options, stream);
35
+ return stream;
36
+ }
37
+ async function pumpSpeech(model, input, options, stream) {
38
+ try {
39
+ if (options?.signal?.aborted) throw new DOMException("aborted", "AbortError");
40
+ const format = resolveFormat(model, input.format);
41
+ const text = await readText(input.text, options?.signal);
42
+ const payload = { text, voice_id: input.voice, format: "mp3" };
43
+ if (input.language !== void 0) payload.language_boost = input.language;
44
+ const record = await finishTask(model, options, payload, "Speech payload was not JSON");
45
+ const outputs = record.outputs;
46
+ const first = Array.isArray(outputs) ? outputs[0] : void 0;
47
+ const data = await audioOf(model, options, first);
48
+ stream.push({ type: "start", format });
49
+ stream.push({
50
+ type: "audio",
51
+ chunk: { data, format, timestamp: 0 },
52
+ span: { itemId: model.id, start: 0, end: text.length }
53
+ });
54
+ stream.push({
55
+ type: "done",
56
+ usage: priceAudio(model.audioCost, { characters: text.length })
57
+ });
58
+ } catch (error) {
59
+ const voice = asVoiceError(error);
60
+ stream.push({ type: "error", reason: voice.reason, error: voice.error });
61
+ }
62
+ }
63
+ export {
64
+ speakWaveSpeed
65
+ };
66
+ //# sourceMappingURL=speak.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/wavespeed/speak.ts"],
4
+ "sourcesContent": ["import type { ProviderRequestOptions } from '@earendil-works/pi-ai';\nimport type { AudioFormat } from '../audio.ts';\nimport type { SpeechModel } from '../models.ts';\nimport { asVoiceError } from '../openai/wire.ts';\nimport { SpeechEventStream, type SpeechInput } from '../speech.ts';\nimport { priceAudio } from '../usage.ts';\nimport { finishTask, outputUrl, readHttp } from './wire.ts';\n\nfunction resolveFormat(\n model: SpeechModel<string>,\n requested: AudioFormat | undefined\n): AudioFormat {\n const fallback = model.outputFormats[0];\n if (!fallback) throw new Error('No output format');\n if (!requested) return fallback;\n const match = model.outputFormats.find(\n (format) =>\n format.encoding === requested.encoding &&\n format.sampleRate === requested.sampleRate &&\n format.channels === requested.channels\n );\n if (!match) throw new Error(`Unsupported speech format ${requested.encoding}`);\n return match;\n}\n\nasync function readText(\n text: string | AsyncIterable<string>,\n signal: AbortSignal | undefined\n): Promise<string> {\n if (typeof text === 'string') return text;\n let combined = '';\n for await (const part of text) {\n if (signal?.aborted) throw new DOMException('aborted', 'AbortError');\n combined += part;\n }\n return combined;\n}\n\nasync function audioOf(\n model: SpeechModel<string>,\n options: ProviderRequestOptions | undefined,\n output: unknown\n): Promise<Uint8Array> {\n const url = outputUrl(output);\n if (url === undefined) throw new Error('Speech response had no audio');\n const response = await readHttp(model, options, url);\n const data = new Uint8Array(await response.arrayBuffer());\n if (data.byteLength === 0) throw new Error('Speech response had no audio');\n return data;\n}\n\nexport function speakWaveSpeed(\n model: SpeechModel<string>,\n input: SpeechInput,\n options?: ProviderRequestOptions\n): SpeechEventStream {\n const stream = new SpeechEventStream();\n void pumpSpeech(model, input, options, stream);\n return stream;\n}\n\nasync function pumpSpeech(\n model: SpeechModel<string>,\n input: SpeechInput,\n options: ProviderRequestOptions | undefined,\n stream: SpeechEventStream\n) {\n try {\n if (options?.signal?.aborted) throw new DOMException('aborted', 'AbortError');\n const format = resolveFormat(model, input.format);\n const text = await readText(input.text, options?.signal);\n const payload: Record<string, unknown> = { text, voice_id: input.voice, format: 'mp3' };\n if (input.language !== undefined) payload.language_boost = input.language;\n const record = await finishTask(model, options, payload, 'Speech payload was not JSON');\n const outputs = record.outputs;\n const first = Array.isArray(outputs) ? outputs[0] : undefined;\n const data = await audioOf(model, options, first);\n stream.push({ type: 'start', format });\n stream.push({\n type: 'audio',\n chunk: { data, format, timestamp: 0 },\n span: { itemId: model.id, start: 0, end: text.length },\n });\n stream.push({\n type: 'done',\n usage: priceAudio(model.audioCost, { characters: text.length }),\n });\n } catch (error) {\n const voice = asVoiceError(error);\n stream.push({ type: 'error', reason: voice.reason, error: voice.error });\n }\n}\n"],
5
+ "mappings": "AAGA,SAAS,oBAAoB;AAC7B,SAAS,yBAA2C;AACpD,SAAS,kBAAkB;AAC3B,SAAS,YAAY,WAAW,gBAAgB;AAEhD,SAAS,cACP,OACA,WACa;AACb,QAAM,WAAW,MAAM,cAAc,CAAC;AACtC,MAAI,CAAC,SAAU,OAAM,IAAI,MAAM,kBAAkB;AACjD,MAAI,CAAC,UAAW,QAAO;AACvB,QAAM,QAAQ,MAAM,cAAc;AAAA,IAChC,CAAC,WACC,OAAO,aAAa,UAAU,YAC9B,OAAO,eAAe,UAAU,cAChC,OAAO,aAAa,UAAU;AAAA,EAClC;AACA,MAAI,CAAC,MAAO,OAAM,IAAI,MAAM,6BAA6B,UAAU,QAAQ,EAAE;AAC7E,SAAO;AACT;AAEA,eAAe,SACb,MACA,QACiB;AACjB,MAAI,OAAO,SAAS,SAAU,QAAO;AACrC,MAAI,WAAW;AACf,mBAAiB,QAAQ,MAAM;AAC7B,QAAI,QAAQ,QAAS,OAAM,IAAI,aAAa,WAAW,YAAY;AACnE,gBAAY;AAAA,EACd;AACA,SAAO;AACT;AAEA,eAAe,QACb,OACA,SACA,QACqB;AACrB,QAAM,MAAM,UAAU,MAAM;AAC5B,MAAI,QAAQ,OAAW,OAAM,IAAI,MAAM,8BAA8B;AACrE,QAAM,WAAW,MAAM,SAAS,OAAO,SAAS,GAAG;AACnD,QAAM,OAAO,IAAI,WAAW,MAAM,SAAS,YAAY,CAAC;AACxD,MAAI,KAAK,eAAe,EAAG,OAAM,IAAI,MAAM,8BAA8B;AACzE,SAAO;AACT;AAEO,SAAS,eACd,OACA,OACA,SACmB;AACnB,QAAM,SAAS,IAAI,kBAAkB;AACrC,OAAK,WAAW,OAAO,OAAO,SAAS,MAAM;AAC7C,SAAO;AACT;AAEA,eAAe,WACb,OACA,OACA,SACA,QACA;AACA,MAAI;AACF,QAAI,SAAS,QAAQ,QAAS,OAAM,IAAI,aAAa,WAAW,YAAY;AAC5E,UAAM,SAAS,cAAc,OAAO,MAAM,MAAM;AAChD,UAAM,OAAO,MAAM,SAAS,MAAM,MAAM,SAAS,MAAM;AACvD,UAAM,UAAmC,EAAE,MAAM,UAAU,MAAM,OAAO,QAAQ,MAAM;AACtF,QAAI,MAAM,aAAa,OAAW,SAAQ,iBAAiB,MAAM;AACjE,UAAM,SAAS,MAAM,WAAW,OAAO,SAAS,SAAS,6BAA6B;AACtF,UAAM,UAAU,OAAO;AACvB,UAAM,QAAQ,MAAM,QAAQ,OAAO,IAAI,QAAQ,CAAC,IAAI;AACpD,UAAM,OAAO,MAAM,QAAQ,OAAO,SAAS,KAAK;AAChD,WAAO,KAAK,EAAE,MAAM,SAAS,OAAO,CAAC;AACrC,WAAO,KAAK;AAAA,MACV,MAAM;AAAA,MACN,OAAO,EAAE,MAAM,QAAQ,WAAW,EAAE;AAAA,MACpC,MAAM,EAAE,QAAQ,MAAM,IAAI,OAAO,GAAG,KAAK,KAAK,OAAO;AAAA,IACvD,CAAC;AACD,WAAO,KAAK;AAAA,MACV,MAAM;AAAA,MACN,OAAO,WAAW,MAAM,WAAW,EAAE,YAAY,KAAK,OAAO,CAAC;AAAA,IAChE,CAAC;AAAA,EACH,SAAS,OAAO;AACd,UAAM,QAAQ,aAAa,KAAK;AAChC,WAAO,KAAK,EAAE,MAAM,SAAS,QAAQ,MAAM,QAAQ,OAAO,MAAM,MAAM,CAAC;AAAA,EACzE;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,85 @@
1
+ import { VoiceRequestError } from "../errors.js";
2
+ import {
3
+ asVoiceError,
4
+ audioFile,
5
+ encodeMultipart,
6
+ endpoint,
7
+ multipartType
8
+ } from "../openai/wire.js";
9
+ import { priceAudio } from "../usage.js";
10
+ import {
11
+ downloadUrl,
12
+ durationSeconds,
13
+ finishTask,
14
+ isHttpUrl,
15
+ outputText,
16
+ readHttp,
17
+ waveFetch
18
+ } from "./wire.js";
19
+ async function sourceUrl(model, input, options) {
20
+ if ("url" in input) return input.url;
21
+ const named = audioFile(input.mimeType);
22
+ const encoded = encodeMultipart(
23
+ { data: input.data, mimeType: input.mimeType, filename: named.filename, format: named.format },
24
+ {}
25
+ );
26
+ const copy = new Uint8Array(encoded.byteLength);
27
+ copy.set(encoded);
28
+ const parsed = await waveFetch(
29
+ model,
30
+ options,
31
+ endpoint(model.baseUrl, "media/upload/binary"),
32
+ copy.buffer,
33
+ multipartType()
34
+ );
35
+ return downloadUrl(parsed);
36
+ }
37
+ async function textOf(model, options, output) {
38
+ if (typeof output === "string" && isHttpUrl(output)) {
39
+ const response = await readHttp(model, options, output);
40
+ const text2 = await response.text();
41
+ if (text2 === "") throw new Error("Transcription response had no text");
42
+ return text2;
43
+ }
44
+ const text = outputText(output);
45
+ if (text === void 0 || text === "" || isHttpUrl(text)) {
46
+ throw new Error("Transcription response had no text");
47
+ }
48
+ return text;
49
+ }
50
+ function languageOf(options) {
51
+ const record = options;
52
+ if (record === void 0) return void 0;
53
+ if (typeof record.language !== "string") return void 0;
54
+ return record.language;
55
+ }
56
+ async function transcribeWaveSpeed(model, input, options) {
57
+ try {
58
+ if (options?.signal?.aborted) throw new DOMException("aborted", "AbortError");
59
+ const audio = await sourceUrl(model, input, options);
60
+ const payload = {
61
+ audio,
62
+ task: "transcribe",
63
+ enable_timestamps: false
64
+ };
65
+ const language = languageOf(options);
66
+ if (language !== void 0) payload.language = language;
67
+ const record = await finishTask(model, options, payload, "Transcription payload was not JSON");
68
+ const outputs = record.outputs;
69
+ const first = Array.isArray(outputs) ? outputs[0] : void 0;
70
+ const text = await textOf(model, options, first);
71
+ const meters = {};
72
+ const seconds = durationSeconds(record);
73
+ if (seconds > 0) meters.inputSeconds = seconds;
74
+ const transcript = { text, usage: priceAudio(model.audioCost, meters) };
75
+ if (language !== void 0) transcript.language = language;
76
+ return transcript;
77
+ } catch (error) {
78
+ const voice = asVoiceError(error);
79
+ throw new VoiceRequestError(voice.reason, voice.error);
80
+ }
81
+ }
82
+ export {
83
+ transcribeWaveSpeed
84
+ };
85
+ //# sourceMappingURL=transcribe.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/wavespeed/transcribe.ts"],
4
+ "sourcesContent": ["import type { ProviderRequestOptions } from '@earendil-works/pi-ai';\nimport { VoiceRequestError } from '../errors.ts';\nimport type { TranscriptionModel } from '../models.ts';\nimport {\n asVoiceError,\n audioFile,\n encodeMultipart,\n endpoint,\n multipartType,\n} from '../openai/wire.ts';\nimport type { AudioInput, Transcript } from '../transcription.ts';\nimport { type AudioCounters, priceAudio } from '../usage.ts';\nimport {\n downloadUrl,\n durationSeconds,\n finishTask,\n isHttpUrl,\n outputText,\n readHttp,\n waveFetch,\n} from './wire.ts';\n\nasync function sourceUrl(\n model: TranscriptionModel<string>,\n input: AudioInput,\n options: ProviderRequestOptions | undefined\n): Promise<string> {\n if ('url' in input) return input.url;\n const named = audioFile(input.mimeType);\n const encoded = encodeMultipart(\n { data: input.data, mimeType: input.mimeType, filename: named.filename, format: named.format },\n {}\n );\n const copy = new Uint8Array(encoded.byteLength);\n copy.set(encoded);\n const parsed = await waveFetch(\n model,\n options,\n endpoint(model.baseUrl, 'media/upload/binary'),\n copy.buffer,\n multipartType()\n );\n return downloadUrl(parsed);\n}\n\nasync function textOf(\n model: TranscriptionModel<string>,\n options: ProviderRequestOptions | undefined,\n output: unknown\n): Promise<string> {\n if (typeof output === 'string' && isHttpUrl(output)) {\n const response = await readHttp(model, options, output);\n const text = await response.text();\n if (text === '') throw new Error('Transcription response had no text');\n return text;\n }\n const text = outputText(output);\n if (text === undefined || text === '' || isHttpUrl(text)) {\n throw new Error('Transcription response had no text');\n }\n return text;\n}\n\nfunction languageOf(options: ProviderRequestOptions | undefined): string | undefined {\n const record = options as { language?: unknown } | undefined;\n if (record === undefined) return undefined;\n if (typeof record.language !== 'string') return undefined;\n return record.language;\n}\n\nexport async function transcribeWaveSpeed(\n model: TranscriptionModel<string>,\n input: AudioInput,\n options?: ProviderRequestOptions\n): Promise<Transcript> {\n try {\n if (options?.signal?.aborted) throw new DOMException('aborted', 'AbortError');\n const audio = await sourceUrl(model, input, options);\n const payload: Record<string, unknown> = {\n audio,\n task: 'transcribe',\n enable_timestamps: false,\n };\n const language = languageOf(options);\n if (language !== undefined) payload.language = language;\n const record = await finishTask(model, options, payload, 'Transcription payload was not JSON');\n const outputs = record.outputs;\n const first = Array.isArray(outputs) ? outputs[0] : undefined;\n const text = await textOf(model, options, first);\n const meters: AudioCounters = {};\n const seconds = durationSeconds(record);\n if (seconds > 0) meters.inputSeconds = seconds;\n const transcript: Transcript = { text, usage: priceAudio(model.audioCost, meters) };\n if (language !== undefined) transcript.language = language;\n return transcript;\n } catch (error) {\n const voice = asVoiceError(error);\n throw new VoiceRequestError(voice.reason, voice.error);\n }\n}\n"],
5
+ "mappings": "AACA,SAAS,yBAAyB;AAElC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAA6B,kBAAkB;AAC/C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,eAAe,UACb,OACA,OACA,SACiB;AACjB,MAAI,SAAS,MAAO,QAAO,MAAM;AACjC,QAAM,QAAQ,UAAU,MAAM,QAAQ;AACtC,QAAM,UAAU;AAAA,IACd,EAAE,MAAM,MAAM,MAAM,UAAU,MAAM,UAAU,UAAU,MAAM,UAAU,QAAQ,MAAM,OAAO;AAAA,IAC7F,CAAC;AAAA,EACH;AACA,QAAM,OAAO,IAAI,WAAW,QAAQ,UAAU;AAC9C,OAAK,IAAI,OAAO;AAChB,QAAM,SAAS,MAAM;AAAA,IACnB;AAAA,IACA;AAAA,IACA,SAAS,MAAM,SAAS,qBAAqB;AAAA,IAC7C,KAAK;AAAA,IACL,cAAc;AAAA,EAChB;AACA,SAAO,YAAY,MAAM;AAC3B;AAEA,eAAe,OACb,OACA,SACA,QACiB;AACjB,MAAI,OAAO,WAAW,YAAY,UAAU,MAAM,GAAG;AACnD,UAAM,WAAW,MAAM,SAAS,OAAO,SAAS,MAAM;AACtD,UAAMA,QAAO,MAAM,SAAS,KAAK;AACjC,QAAIA,UAAS,GAAI,OAAM,IAAI,MAAM,oCAAoC;AACrE,WAAOA;AAAA,EACT;AACA,QAAM,OAAO,WAAW,MAAM;AAC9B,MAAI,SAAS,UAAa,SAAS,MAAM,UAAU,IAAI,GAAG;AACxD,UAAM,IAAI,MAAM,oCAAoC;AAAA,EACtD;AACA,SAAO;AACT;AAEA,SAAS,WAAW,SAAiE;AACnF,QAAM,SAAS;AACf,MAAI,WAAW,OAAW,QAAO;AACjC,MAAI,OAAO,OAAO,aAAa,SAAU,QAAO;AAChD,SAAO,OAAO;AAChB;AAEA,eAAsB,oBACpB,OACA,OACA,SACqB;AACrB,MAAI;AACF,QAAI,SAAS,QAAQ,QAAS,OAAM,IAAI,aAAa,WAAW,YAAY;AAC5E,UAAM,QAAQ,MAAM,UAAU,OAAO,OAAO,OAAO;AACnD,UAAM,UAAmC;AAAA,MACvC;AAAA,MACA,MAAM;AAAA,MACN,mBAAmB;AAAA,IACrB;AACA,UAAM,WAAW,WAAW,OAAO;AACnC,QAAI,aAAa,OAAW,SAAQ,WAAW;AAC/C,UAAM,SAAS,MAAM,WAAW,OAAO,SAAS,SAAS,oCAAoC;AAC7F,UAAM,UAAU,OAAO;AACvB,UAAM,QAAQ,MAAM,QAAQ,OAAO,IAAI,QAAQ,CAAC,IAAI;AACpD,UAAM,OAAO,MAAM,OAAO,OAAO,SAAS,KAAK;AAC/C,UAAM,SAAwB,CAAC;AAC/B,UAAM,UAAU,gBAAgB,MAAM;AACtC,QAAI,UAAU,EAAG,QAAO,eAAe;AACvC,UAAM,aAAyB,EAAE,MAAM,OAAO,WAAW,MAAM,WAAW,MAAM,EAAE;AAClF,QAAI,aAAa,OAAW,YAAW,WAAW;AAClD,WAAO;AAAA,EACT,SAAS,OAAO;AACd,UAAM,QAAQ,aAAa,KAAK;AAChC,UAAM,IAAI,kBAAkB,MAAM,QAAQ,MAAM,KAAK;AAAA,EACvD;AACF;",
6
+ "names": ["text"]
7
+ }
@@ -0,0 +1,138 @@
1
+ import {
2
+ applyPayload,
3
+ endpoint,
4
+ isRecord,
5
+ numberField,
6
+ requestHeaders,
7
+ requestSignal,
8
+ stringField
9
+ } from "../openai/wire.js";
10
+ const terminal = /* @__PURE__ */ new Set(["failed", "cancelled", "timeout", "deleted"]);
11
+ function taskRecord(body) {
12
+ if (!isRecord(body) || Array.isArray(body)) throw new Error("WaveSpeed response was not JSON");
13
+ const data = body.data;
14
+ if (isRecord(data) && !Array.isArray(data)) return data;
15
+ return body;
16
+ }
17
+ function errorText(text, status) {
18
+ if (text === "") return `HTTP ${status}`;
19
+ let parsed;
20
+ try {
21
+ parsed = JSON.parse(text);
22
+ } catch {
23
+ return text;
24
+ }
25
+ if (!isRecord(parsed) || Array.isArray(parsed)) return text;
26
+ const message = stringField(parsed, "message");
27
+ if (message !== void 0 && message !== "") return message;
28
+ return text;
29
+ }
30
+ function isHttpUrl(value) {
31
+ if (value.startsWith("https://")) return true;
32
+ if (value.startsWith("http://")) return true;
33
+ return false;
34
+ }
35
+ function outputText(output) {
36
+ if (typeof output === "string") return output;
37
+ if (!isRecord(output) || Array.isArray(output)) return void 0;
38
+ return stringField(output, "text") ?? stringField(output, "transcript");
39
+ }
40
+ function outputUrl(output) {
41
+ if (typeof output === "string" && isHttpUrl(output)) return output;
42
+ if (!isRecord(output) || Array.isArray(output)) return void 0;
43
+ const url = stringField(output, "url") ?? stringField(output, "audio");
44
+ if (url === void 0 || !isHttpUrl(url)) return void 0;
45
+ return url;
46
+ }
47
+ function downloadUrl(body) {
48
+ const record = taskRecord(body);
49
+ const url = stringField(record, "download_url") ?? stringField(record, "url");
50
+ if (url === void 0 || url === "") throw new Error("WaveSpeed upload had no url");
51
+ return url;
52
+ }
53
+ function durationSeconds(record) {
54
+ const direct = numberField(record, "duration");
55
+ if (direct !== void 0) return direct;
56
+ return 0;
57
+ }
58
+ async function waveFetch(model, options, url, body, contentType) {
59
+ const headers = requestHeaders(model, options);
60
+ if (contentType !== void 0) headers["Content-Type"] = contentType;
61
+ const fetchImpl = options?.fetch ?? globalThis.fetch;
62
+ const response = await fetchImpl(url, {
63
+ method: body === void 0 ? "GET" : "POST",
64
+ headers,
65
+ body,
66
+ signal: requestSignal(options)
67
+ });
68
+ if (options?.onResponse !== void 0) {
69
+ await options.onResponse({ status: response.status, headers: {} }, model);
70
+ }
71
+ if (!response.ok) {
72
+ const text2 = await response.text();
73
+ throw new Error(errorText(text2, response.status));
74
+ }
75
+ const text = await response.text();
76
+ if (text === "") throw new Error("WaveSpeed response was not JSON");
77
+ try {
78
+ return JSON.parse(text);
79
+ } catch {
80
+ throw new Error("WaveSpeed response was not JSON");
81
+ }
82
+ }
83
+ function failure(record, status) {
84
+ const error = stringField(record, "error");
85
+ if (error === void 0 || error === "") return new Error(`WaveSpeed ${status}`);
86
+ return new Error(error);
87
+ }
88
+ async function finishTask(model, options, payload, rejected) {
89
+ const body = await applyPayload(payload, model, options);
90
+ if (!isRecord(body) || Array.isArray(body)) throw new Error(rejected);
91
+ let record = taskRecord(
92
+ await waveFetch(
93
+ model,
94
+ options,
95
+ endpoint(model.baseUrl, model.id),
96
+ JSON.stringify(body),
97
+ "application/json"
98
+ )
99
+ );
100
+ for (let attempt = 0; attempt < 9; attempt += 1) {
101
+ const status = stringField(record, "status") ?? "";
102
+ if (status === "completed") return record;
103
+ if (terminal.has(status)) throw failure(record, status);
104
+ if (attempt === 8) break;
105
+ const id = stringField(record, "id");
106
+ if (id === void 0 || id === "") throw new Error("WaveSpeed prediction had no id");
107
+ record = taskRecord(
108
+ await waveFetch(
109
+ model,
110
+ options,
111
+ endpoint(model.baseUrl, `predictions/${id}/result`),
112
+ void 0,
113
+ void 0
114
+ )
115
+ );
116
+ }
117
+ throw new Error("WaveSpeed prediction timed out");
118
+ }
119
+ async function readHttp(model, options, url) {
120
+ const headers = requestHeaders(model, options);
121
+ const fetchImpl = options?.fetch ?? globalThis.fetch;
122
+ const response = await fetchImpl(url, { headers, signal: requestSignal(options) });
123
+ if (!response.ok) throw new Error(errorText("", response.status));
124
+ return response;
125
+ }
126
+ export {
127
+ downloadUrl,
128
+ durationSeconds,
129
+ errorText,
130
+ finishTask,
131
+ isHttpUrl,
132
+ outputText,
133
+ outputUrl,
134
+ readHttp,
135
+ taskRecord,
136
+ waveFetch
137
+ };
138
+ //# sourceMappingURL=wire.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/wavespeed/wire.ts"],
4
+ "sourcesContent": ["import type { ProviderRequestOptions } from '@earendil-works/pi-ai';\nimport {\n applyPayload,\n endpoint,\n isRecord,\n numberField,\n requestHeaders,\n requestSignal,\n stringField,\n} from '../openai/wire.ts';\n\nconst terminal = new Set(['failed', 'cancelled', 'timeout', 'deleted']);\n\nexport function taskRecord(body: unknown): Record<string, unknown> {\n if (!isRecord(body) || Array.isArray(body)) throw new Error('WaveSpeed response was not JSON');\n const data = body.data;\n if (isRecord(data) && !Array.isArray(data)) return data;\n return body;\n}\n\nexport function errorText(text: string, status: number): string {\n if (text === '') return `HTTP ${status}`;\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n return text;\n }\n if (!isRecord(parsed) || Array.isArray(parsed)) return text;\n const message = stringField(parsed, 'message');\n if (message !== undefined && message !== '') return message;\n return text;\n}\n\nexport function isHttpUrl(value: string): boolean {\n if (value.startsWith('https://')) return true;\n if (value.startsWith('http://')) return true;\n return false;\n}\n\nexport function outputText(output: unknown): string | undefined {\n if (typeof output === 'string') return output;\n if (!isRecord(output) || Array.isArray(output)) return undefined;\n return stringField(output, 'text') ?? stringField(output, 'transcript');\n}\n\nexport function outputUrl(output: unknown): string | undefined {\n if (typeof output === 'string' && isHttpUrl(output)) return output;\n if (!isRecord(output) || Array.isArray(output)) return undefined;\n const url = stringField(output, 'url') ?? stringField(output, 'audio');\n if (url === undefined || !isHttpUrl(url)) return undefined;\n return url;\n}\n\nexport function downloadUrl(body: unknown): string {\n const record = taskRecord(body);\n const url = stringField(record, 'download_url') ?? stringField(record, 'url');\n if (url === undefined || url === '') throw new Error('WaveSpeed upload had no url');\n return url;\n}\n\nexport function durationSeconds(record: Record<string, unknown>): number {\n const direct = numberField(record, 'duration');\n if (direct !== undefined) return direct;\n return 0;\n}\n\nasync function waveFetch(\n model: { provider: string; headers?: Record<string, string>; id: string },\n options: ProviderRequestOptions | undefined,\n url: string,\n body: BodyInit | undefined,\n contentType: string | undefined\n): Promise<unknown> {\n const headers = requestHeaders(model, options);\n if (contentType !== undefined) headers['Content-Type'] = contentType;\n const fetchImpl = options?.fetch ?? globalThis.fetch;\n const response = await fetchImpl(url, {\n method: body === undefined ? 'GET' : 'POST',\n headers,\n body,\n signal: requestSignal(options),\n });\n if (options?.onResponse !== undefined) {\n await options.onResponse({ status: response.status, headers: {} }, model as never);\n }\n if (!response.ok) {\n const text = await response.text();\n throw new Error(errorText(text, response.status));\n }\n const text = await response.text();\n if (text === '') throw new Error('WaveSpeed response was not JSON');\n try {\n return JSON.parse(text);\n } catch {\n throw new Error('WaveSpeed response was not JSON');\n }\n}\n\nfunction failure(record: Record<string, unknown>, status: string): Error {\n const error = stringField(record, 'error');\n if (error === undefined || error === '') return new Error(`WaveSpeed ${status}`);\n return new Error(error);\n}\n\nexport async function finishTask(\n model: { provider: string; baseUrl: string; headers?: Record<string, string>; id: string },\n options: ProviderRequestOptions | undefined,\n payload: unknown,\n rejected: string\n): Promise<Record<string, unknown>> {\n const body = await applyPayload(payload, model, options);\n if (!isRecord(body) || Array.isArray(body)) throw new Error(rejected);\n let record = taskRecord(\n await waveFetch(\n model,\n options,\n endpoint(model.baseUrl, model.id),\n JSON.stringify(body),\n 'application/json'\n )\n );\n for (let attempt = 0; attempt < 9; attempt += 1) {\n const status = stringField(record, 'status') ?? '';\n if (status === 'completed') return record;\n if (terminal.has(status)) throw failure(record, status);\n if (attempt === 8) break;\n const id = stringField(record, 'id');\n if (id === undefined || id === '') throw new Error('WaveSpeed prediction had no id');\n record = taskRecord(\n await waveFetch(\n model,\n options,\n endpoint(model.baseUrl, `predictions/${id}/result`),\n undefined,\n undefined\n )\n );\n }\n throw new Error('WaveSpeed prediction timed out');\n}\n\nexport async function readHttp(\n model: { provider: string; headers?: Record<string, string>; id: string },\n options: ProviderRequestOptions | undefined,\n url: string\n): Promise<Response> {\n const headers = requestHeaders(model, options);\n const fetchImpl = options?.fetch ?? globalThis.fetch;\n const response = await fetchImpl(url, { headers, signal: requestSignal(options) });\n if (!response.ok) throw new Error(errorText('', response.status));\n return response;\n}\n\nexport { waveFetch };\n"],
5
+ "mappings": "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",
6
+ "names": ["text"]
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-ecoverse/pi-audio",
3
- "version": "1.24.0",
3
+ "version": "1.25.0",
4
4
  "description": "Voice bindings for pi: speech-to-text, text-to-speech and realtime speech for pi-coding-agent plugins and slicc-agent",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -66,6 +66,10 @@
66
66
  "types": "./dist/types/cloudflare/index.d.ts",
67
67
  "default": "./dist/cloudflare/index.js"
68
68
  },
69
+ "./wavespeed": {
70
+ "types": "./dist/types/wavespeed/index.d.ts",
71
+ "default": "./dist/wavespeed/index.js"
72
+ },
69
73
  "./durable": {
70
74
  "types": "./dist/types/durable/index.d.ts",
71
75
  "default": "./dist/durable/index.js"