@effect-uai/elevenlabs 0.4.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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/dist/ElevenLabsSynthesizer.d.mts +58 -0
  3. package/dist/ElevenLabsSynthesizer.d.mts.map +1 -0
  4. package/dist/ElevenLabsSynthesizer.mjs +82 -0
  5. package/dist/ElevenLabsSynthesizer.mjs.map +1 -0
  6. package/dist/ElevenLabsSynthesizer.test.d.mts +1 -0
  7. package/dist/ElevenLabsSynthesizer.test.mjs +32 -0
  8. package/dist/ElevenLabsSynthesizer.test.mjs.map +1 -0
  9. package/dist/ElevenLabsTranscriber.d.mts +57 -0
  10. package/dist/ElevenLabsTranscriber.d.mts.map +1 -0
  11. package/dist/ElevenLabsTranscriber.mjs +97 -0
  12. package/dist/ElevenLabsTranscriber.mjs.map +1 -0
  13. package/dist/ElevenLabsTranscriber.test.d.mts +1 -0
  14. package/dist/ElevenLabsTranscriber.test.mjs +58 -0
  15. package/dist/ElevenLabsTranscriber.test.mjs.map +1 -0
  16. package/dist/chunk-uyGKjUfl.mjs +14 -0
  17. package/dist/codec.d.mts +43 -0
  18. package/dist/codec.d.mts.map +1 -0
  19. package/dist/codec.mjs +120 -0
  20. package/dist/codec.mjs.map +1 -0
  21. package/dist/codec.test.d.mts +1 -0
  22. package/dist/codec.test.mjs +142 -0
  23. package/dist/codec.test.mjs.map +1 -0
  24. package/dist/dist-DMsg8YLb.mjs +13782 -0
  25. package/dist/dist-DMsg8YLb.mjs.map +1 -0
  26. package/dist/index.d.mts +5 -0
  27. package/dist/index.mjs +5 -0
  28. package/dist/magic-string.es-CaWgF-3p.mjs +1013 -0
  29. package/dist/magic-string.es-CaWgF-3p.mjs.map +1 -0
  30. package/dist/models.d.mts +37 -0
  31. package/dist/models.d.mts.map +1 -0
  32. package/dist/models.mjs +1 -0
  33. package/dist/realtimeStt.d.mts +67 -0
  34. package/dist/realtimeStt.d.mts.map +1 -0
  35. package/dist/realtimeStt.mjs +163 -0
  36. package/dist/realtimeStt.mjs.map +1 -0
  37. package/dist/realtimeTts.d.mts +31 -0
  38. package/dist/realtimeTts.d.mts.map +1 -0
  39. package/dist/realtimeTts.mjs +77 -0
  40. package/dist/realtimeTts.mjs.map +1 -0
  41. package/package.json +64 -0
  42. package/src/ElevenLabsSynthesizer.test.ts +49 -0
  43. package/src/ElevenLabsSynthesizer.ts +165 -0
  44. package/src/ElevenLabsTranscriber.test.ts +63 -0
  45. package/src/ElevenLabsTranscriber.ts +179 -0
  46. package/src/codec.test.ts +128 -0
  47. package/src/codec.ts +185 -0
  48. package/src/index.ts +4 -0
  49. package/src/models.ts +46 -0
  50. package/src/realtimeStt.ts +273 -0
  51. package/src/realtimeTts.ts +147 -0
package/dist/codec.mjs ADDED
@@ -0,0 +1,120 @@
1
+ import { n as __exportAll } from "./chunk-uyGKjUfl.mjs";
2
+ import { Array, Effect, Encoding, Match, Option, Result } from "effect";
3
+ import * as AiError from "@effect-uai/core/AiError";
4
+ //#region src/codec.ts
5
+ var codec_exports = /* @__PURE__ */ __exportAll({
6
+ audioToBlob: () => audioToBlob,
7
+ defaultFileName: () => defaultFileName,
8
+ defaultFormat: () => defaultFormat,
9
+ formatToOutputSlug: () => formatToOutputSlug,
10
+ httpStatusError: () => httpStatusError,
11
+ transportFailure: () => transportFailure,
12
+ wireVoiceSettings: () => wireVoiceSettings
13
+ });
14
+ const wireVoiceSettings = (v) => v === void 0 ? void 0 : {
15
+ ...v.stability !== void 0 && { stability: v.stability },
16
+ ...v.similarityBoost !== void 0 && { similarity_boost: v.similarityBoost },
17
+ ...v.style !== void 0 && { style: v.style },
18
+ ...v.useSpeakerBoost !== void 0 && { use_speaker_boost: v.useSpeakerBoost },
19
+ ...v.speed !== void 0 && { speed: v.speed }
20
+ };
21
+ /**
22
+ * ElevenLabs encodes container + sample rate + bitrate in a single
23
+ * dot-less slug (`mp3_44100_128`, `pcm_16000`, `wav_48000`, `ulaw_8000`,
24
+ * `opus_48000_128`, etc.). This helper inverts the common cases of our
25
+ * `AudioFormat` shape into that slug.
26
+ *
27
+ * Only the formats that map cleanly are produced. Anything we can't
28
+ * encode (mismatched bitrate, unsupported sample rate combination)
29
+ * fails `Unsupported` with the requested format echoed so the user can
30
+ * fix it.
31
+ */
32
+ const formatToOutputSlug = (format) => Match.value(format).pipe(Match.when({ container: "mp3" }, (f) => Effect.succeed(`mp3_${f.sampleRate}_${f.bitRate ?? 128}`)), Match.when({ container: "opus" }, (f) => Effect.succeed(`opus_${f.sampleRate}_${f.bitRate ?? 128}`)), Match.when({ container: "wav" }, (f) => Effect.succeed(`wav_${f.sampleRate}`)), Match.when({
33
+ container: "raw",
34
+ encoding: "pcm_s16le"
35
+ }, (f) => Effect.succeed(`pcm_${f.sampleRate}`)), Match.when({
36
+ container: "raw",
37
+ encoding: "pcm_mulaw"
38
+ }, (f) => Effect.succeed(`ulaw_${f.sampleRate}`)), Match.when({
39
+ container: "raw",
40
+ encoding: "pcm_alaw"
41
+ }, (f) => Effect.succeed(`alaw_${f.sampleRate}`)), Match.orElse((f) => Effect.fail(new AiError.Unsupported({
42
+ provider: "elevenlabs",
43
+ capability: "outputFormat",
44
+ reason: `Cannot encode ${JSON.stringify(f)} as ElevenLabs output_format. Supported: mp3 + (22050|44100), opus 48000, wav 48000, pcm/ulaw/alaw at any sample rate.`
45
+ }))));
46
+ /** Default output: 128 kbps MP3 at 44.1 kHz. */
47
+ const defaultFormat = {
48
+ container: "mp3",
49
+ encoding: "mp3",
50
+ sampleRate: 44100,
51
+ bitRate: 128
52
+ };
53
+ const urlNotSupported = new AiError.InvalidRequest({
54
+ provider: "elevenlabs",
55
+ param: "audio",
56
+ raw: "ElevenLabs accepts URLs via the `cloud_storage_url` field, not the `file` field. Fetch the URL yourself and pass `{ _tag: \"bytes\", bytes, mimeType }` to use the inline path."
57
+ });
58
+ const decodeBase64ToBytes = (b64) => Result.match(Encoding.decodeBase64(b64), {
59
+ onSuccess: Effect.succeed,
60
+ onFailure: (cause) => Effect.fail(new AiError.InvalidRequest({
61
+ provider: "elevenlabs",
62
+ param: "audio",
63
+ raw: cause
64
+ }))
65
+ });
66
+ const bytesToBlob = (bytes, mimeType) => new Blob([bytes], { type: mimeType });
67
+ const audioToBlob = Match.type().pipe(Match.tag("bytes", (a) => Effect.succeed(bytesToBlob(a.bytes, a.mimeType))), Match.tag("base64", (a) => decodeBase64ToBytes(a.base64).pipe(Effect.map((bytes) => bytesToBlob(bytes, a.mimeType)))), Match.tag("url", () => Effect.fail(urlNotSupported)), Match.exhaustive);
68
+ const EXTENSION_BY_MIME = [
69
+ ["mpeg", "mp3"],
70
+ ["mp3", "mp3"],
71
+ ["wav", "wav"],
72
+ ["ogg", "ogg"],
73
+ ["opus", "ogg"],
74
+ ["flac", "flac"],
75
+ ["aac", "aac"],
76
+ ["mp4", "m4a"],
77
+ ["m4a", "m4a"],
78
+ ["webm", "webm"]
79
+ ];
80
+ const defaultFileName = (mimeType) => Array.findFirst(EXTENSION_BY_MIME, ([pattern]) => mimeType.includes(pattern)).pipe(Option.match({
81
+ onNone: () => "audio",
82
+ onSome: ([, ext]) => `audio.${ext}`
83
+ }));
84
+ const httpStatusError = (status, body) => Match.value(status).pipe(Match.when(429, () => new AiError.RateLimited({
85
+ provider: "elevenlabs",
86
+ raw: body
87
+ })), Match.whenOr(408, 504, () => new AiError.Timeout({
88
+ provider: "elevenlabs",
89
+ raw: body
90
+ })), Match.when(401, () => new AiError.AuthFailed({
91
+ provider: "elevenlabs",
92
+ subtype: "auth",
93
+ raw: body
94
+ })), Match.when(403, () => new AiError.AuthFailed({
95
+ provider: "elevenlabs",
96
+ subtype: "permission",
97
+ raw: body
98
+ })), Match.when(402, () => new AiError.AuthFailed({
99
+ provider: "elevenlabs",
100
+ subtype: "billing",
101
+ raw: body
102
+ })), Match.when(413, () => new AiError.ContextLengthExceeded({
103
+ provider: "elevenlabs",
104
+ raw: body
105
+ })), Match.when((n) => n >= 500, (n) => new AiError.Unavailable({
106
+ provider: "elevenlabs",
107
+ status: n,
108
+ raw: body
109
+ })), Match.orElse(() => new AiError.InvalidRequest({
110
+ provider: "elevenlabs",
111
+ raw: body
112
+ })));
113
+ const transportFailure = (cause) => new AiError.Unavailable({
114
+ provider: "elevenlabs",
115
+ raw: cause
116
+ });
117
+ //#endregion
118
+ export { audioToBlob, defaultFileName, defaultFormat, formatToOutputSlug, httpStatusError, codec_exports as t, transportFailure, wireVoiceSettings };
119
+
120
+ //# sourceMappingURL=codec.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codec.mjs","names":["Arr"],"sources":["../src/codec.ts"],"sourcesContent":["import { Array as Arr, Effect, Encoding, Match, Option, Result } from \"effect\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport type { AudioFormat, AudioSource } from \"@effect-uai/core/Audio\"\n\n// ---------------------------------------------------------------------------\n// Shared TTS voice-settings shape (sync + streaming use the same fields)\n// ---------------------------------------------------------------------------\n\nexport type VoiceSettings = {\n readonly stability?: number\n readonly similarityBoost?: number\n readonly style?: number\n readonly useSpeakerBoost?: boolean\n readonly speed?: number\n}\n\nexport const wireVoiceSettings = (v: VoiceSettings | undefined) =>\n v === undefined\n ? undefined\n : {\n ...(v.stability !== undefined && { stability: v.stability }),\n ...(v.similarityBoost !== undefined && { similarity_boost: v.similarityBoost }),\n ...(v.style !== undefined && { style: v.style }),\n ...(v.useSpeakerBoost !== undefined && { use_speaker_boost: v.useSpeakerBoost }),\n ...(v.speed !== undefined && { speed: v.speed }),\n }\n\n// ---------------------------------------------------------------------------\n// AudioFormat → ElevenLabs `output_format` query value\n// ---------------------------------------------------------------------------\n\n/**\n * ElevenLabs encodes container + sample rate + bitrate in a single\n * dot-less slug (`mp3_44100_128`, `pcm_16000`, `wav_48000`, `ulaw_8000`,\n * `opus_48000_128`, etc.). This helper inverts the common cases of our\n * `AudioFormat` shape into that slug.\n *\n * Only the formats that map cleanly are produced. Anything we can't\n * encode (mismatched bitrate, unsupported sample rate combination)\n * fails `Unsupported` with the requested format echoed so the user can\n * fix it.\n */\nexport const formatToOutputSlug: (format: AudioFormat) => Effect.Effect<string, AiError.AiError> = (\n format,\n) =>\n Match.value(format).pipe(\n Match.when({ container: \"mp3\" }, (f) =>\n Effect.succeed(`mp3_${f.sampleRate}_${f.bitRate ?? 128}`),\n ),\n Match.when({ container: \"opus\" }, (f) =>\n Effect.succeed(`opus_${f.sampleRate}_${f.bitRate ?? 128}`),\n ),\n Match.when({ container: \"wav\" }, (f) => Effect.succeed(`wav_${f.sampleRate}`)),\n Match.when({ container: \"raw\", encoding: \"pcm_s16le\" }, (f) =>\n Effect.succeed(`pcm_${f.sampleRate}`),\n ),\n Match.when({ container: \"raw\", encoding: \"pcm_mulaw\" }, (f) =>\n Effect.succeed(`ulaw_${f.sampleRate}`),\n ),\n Match.when({ container: \"raw\", encoding: \"pcm_alaw\" }, (f) =>\n Effect.succeed(`alaw_${f.sampleRate}`),\n ),\n Match.orElse((f) =>\n Effect.fail(\n new AiError.Unsupported({\n provider: \"elevenlabs\",\n capability: \"outputFormat\",\n reason: `Cannot encode ${JSON.stringify(f)} as ElevenLabs output_format. Supported: mp3 + (22050|44100), opus 48000, wav 48000, pcm/ulaw/alaw at any sample rate.`,\n }),\n ),\n ),\n )\n\n/** Default output: 128 kbps MP3 at 44.1 kHz. */\nexport const defaultFormat: AudioFormat = {\n container: \"mp3\",\n encoding: \"mp3\",\n sampleRate: 44100,\n bitRate: 128,\n}\n\n// ---------------------------------------------------------------------------\n// AudioSource → Blob (STT multipart upload)\n// ---------------------------------------------------------------------------\n\nconst urlNotSupported: AiError.AiError = new AiError.InvalidRequest({\n provider: \"elevenlabs\",\n param: \"audio\",\n raw: 'ElevenLabs accepts URLs via the `cloud_storage_url` field, not the `file` field. Fetch the URL yourself and pass `{ _tag: \"bytes\", bytes, mimeType }` to use the inline path.',\n})\n\nconst decodeBase64ToBytes = (b64: string): Effect.Effect<Uint8Array, AiError.AiError> =>\n Result.match(Encoding.decodeBase64(b64), {\n onSuccess: Effect.succeed,\n onFailure: (cause) =>\n Effect.fail(\n new AiError.InvalidRequest({ provider: \"elevenlabs\", param: \"audio\", raw: cause }),\n ),\n })\n\nconst bytesToBlob = (bytes: Uint8Array, mimeType: string): Blob =>\n new Blob([bytes as Uint8Array<ArrayBuffer>], { type: mimeType })\n\nexport const audioToBlob: (audio: AudioSource) => Effect.Effect<Blob, AiError.AiError> =\n Match.type<AudioSource>().pipe(\n Match.tag(\"bytes\", (a) => Effect.succeed(bytesToBlob(a.bytes, a.mimeType))),\n Match.tag(\"base64\", (a) =>\n decodeBase64ToBytes(a.base64).pipe(Effect.map((bytes) => bytesToBlob(bytes, a.mimeType))),\n ),\n Match.tag(\"url\", () => Effect.fail(urlNotSupported)),\n Match.exhaustive,\n )\n\n// ---------------------------------------------------------------------------\n// MIME → file extension (for the multipart `filename` field)\n// ---------------------------------------------------------------------------\n\nconst EXTENSION_BY_MIME: ReadonlyArray<readonly [string, string]> = [\n [\"mpeg\", \"mp3\"],\n [\"mp3\", \"mp3\"],\n [\"wav\", \"wav\"],\n [\"ogg\", \"ogg\"],\n [\"opus\", \"ogg\"],\n [\"flac\", \"flac\"],\n [\"aac\", \"aac\"],\n [\"mp4\", \"m4a\"],\n [\"m4a\", \"m4a\"],\n [\"webm\", \"webm\"],\n]\n\nexport const defaultFileName = (mimeType: string): string =>\n Arr.findFirst(EXTENSION_BY_MIME, ([pattern]) => mimeType.includes(pattern)).pipe(\n Option.match({\n onNone: () => \"audio\",\n onSome: ([, ext]) => `audio.${ext}`,\n }),\n )\n\n// ---------------------------------------------------------------------------\n// HTTP errors\n// ---------------------------------------------------------------------------\n\nexport const httpStatusError: (status: number, body: string) => AiError.AiError = (status, body) =>\n Match.value(status).pipe(\n Match.when(\n 429,\n (): AiError.AiError => new AiError.RateLimited({ provider: \"elevenlabs\", raw: body }),\n ),\n Match.whenOr(\n 408,\n 504,\n (): AiError.AiError => new AiError.Timeout({ provider: \"elevenlabs\", raw: body }),\n ),\n Match.when(\n 401,\n (): AiError.AiError =>\n new AiError.AuthFailed({ provider: \"elevenlabs\", subtype: \"auth\", raw: body }),\n ),\n Match.when(\n 403,\n (): AiError.AiError =>\n new AiError.AuthFailed({ provider: \"elevenlabs\", subtype: \"permission\", raw: body }),\n ),\n Match.when(\n 402,\n (): AiError.AiError =>\n new AiError.AuthFailed({ provider: \"elevenlabs\", subtype: \"billing\", raw: body }),\n ),\n Match.when(\n 413,\n (): AiError.AiError =>\n new AiError.ContextLengthExceeded({ provider: \"elevenlabs\", raw: body }),\n ),\n Match.when(\n (n) => n >= 500,\n (n): AiError.AiError =>\n new AiError.Unavailable({ provider: \"elevenlabs\", status: n, raw: body }),\n ),\n Match.orElse(\n (): AiError.AiError => new AiError.InvalidRequest({ provider: \"elevenlabs\", raw: body }),\n ),\n )\n\nexport const transportFailure = (cause: unknown): AiError.AiError =>\n new AiError.Unavailable({ provider: \"elevenlabs\", raw: cause })\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,142 @@
1
+ import { i as it, n as globalExpect, r as describe } from "./dist-DMsg8YLb.mjs";
2
+ import { audioToBlob, defaultFileName, formatToOutputSlug, httpStatusError } from "./codec.mjs";
3
+ import { Effect, Encoding } from "effect";
4
+ //#region src/codec.test.ts
5
+ describe("formatToOutputSlug", () => {
6
+ it("encodes mp3 with default bitrate when omitted", async () => {
7
+ globalExpect(await Effect.runPromise(formatToOutputSlug({
8
+ container: "mp3",
9
+ encoding: "mp3",
10
+ sampleRate: 44100
11
+ }))).toBe("mp3_44100_128");
12
+ });
13
+ it("honours an explicit mp3 bitrate", async () => {
14
+ globalExpect(await Effect.runPromise(formatToOutputSlug({
15
+ container: "mp3",
16
+ encoding: "mp3",
17
+ sampleRate: 22050,
18
+ bitRate: 32
19
+ }))).toBe("mp3_22050_32");
20
+ });
21
+ it("encodes raw pcm_s16le as `pcm_<sampleRate>`", async () => {
22
+ globalExpect(await Effect.runPromise(formatToOutputSlug({
23
+ container: "raw",
24
+ encoding: "pcm_s16le",
25
+ sampleRate: 16e3
26
+ }))).toBe("pcm_16000");
27
+ });
28
+ it("encodes mu-law as `ulaw_<sampleRate>`", async () => {
29
+ globalExpect(await Effect.runPromise(formatToOutputSlug({
30
+ container: "raw",
31
+ encoding: "pcm_mulaw",
32
+ sampleRate: 8e3
33
+ }))).toBe("ulaw_8000");
34
+ });
35
+ it("encodes opus with default bitrate", async () => {
36
+ globalExpect(await Effect.runPromise(formatToOutputSlug({
37
+ container: "opus",
38
+ encoding: "opus",
39
+ sampleRate: 48e3
40
+ }))).toBe("opus_48000_128");
41
+ });
42
+ it("encodes wav as `wav_<sampleRate>`", async () => {
43
+ globalExpect(await Effect.runPromise(formatToOutputSlug({
44
+ container: "wav",
45
+ encoding: "pcm_s16le",
46
+ sampleRate: 48e3
47
+ }))).toBe("wav_48000");
48
+ });
49
+ it("fails Unsupported for flac / aac / webm / ogg", async () => {
50
+ const exit = await Effect.runPromiseExit(formatToOutputSlug({
51
+ container: "flac",
52
+ encoding: "flac",
53
+ sampleRate: 44100
54
+ }));
55
+ globalExpect(exit._tag).toBe("Failure");
56
+ if (exit._tag === "Failure") {
57
+ globalExpect(JSON.stringify(exit.cause)).toContain("Unsupported");
58
+ globalExpect(JSON.stringify(exit.cause)).toContain("outputFormat");
59
+ }
60
+ });
61
+ });
62
+ describe("audioToBlob", () => {
63
+ it("wraps bytes into a Blob with the right MIME type", async () => {
64
+ const source = {
65
+ _tag: "bytes",
66
+ bytes: new Uint8Array([
67
+ 1,
68
+ 2,
69
+ 3,
70
+ 4
71
+ ]),
72
+ mimeType: "audio/wav"
73
+ };
74
+ const blob = await Effect.runPromise(audioToBlob(source));
75
+ globalExpect(blob.type).toBe("audio/wav");
76
+ globalExpect(blob.size).toBe(4);
77
+ });
78
+ it("decodes a base64 source into bytes", async () => {
79
+ const original = new Uint8Array([
80
+ 222,
81
+ 173,
82
+ 190,
83
+ 239
84
+ ]);
85
+ const source = {
86
+ _tag: "base64",
87
+ base64: Encoding.encodeBase64(original),
88
+ mimeType: "audio/mpeg"
89
+ };
90
+ const blob = await Effect.runPromise(audioToBlob(source));
91
+ globalExpect(blob.type).toBe("audio/mpeg");
92
+ globalExpect(blob.size).toBe(4);
93
+ });
94
+ it("fails InvalidRequest for url sources", async () => {
95
+ const exit = await Effect.runPromiseExit(audioToBlob({
96
+ _tag: "url",
97
+ url: "https://x/y.mp3",
98
+ mimeType: "audio/mpeg"
99
+ }));
100
+ globalExpect(exit._tag).toBe("Failure");
101
+ if (exit._tag === "Failure") {
102
+ globalExpect(JSON.stringify(exit.cause)).toContain("InvalidRequest");
103
+ globalExpect(JSON.stringify(exit.cause)).toContain("cloud_storage_url");
104
+ }
105
+ });
106
+ });
107
+ describe("defaultFileName", () => {
108
+ it("maps audio/mpeg → audio.mp3", () => {
109
+ globalExpect(defaultFileName("audio/mpeg")).toBe("audio.mp3");
110
+ });
111
+ it("maps audio/wav → audio.wav", () => {
112
+ globalExpect(defaultFileName("audio/wav")).toBe("audio.wav");
113
+ });
114
+ it("falls back to audio (no extension) for unknown MIME", () => {
115
+ globalExpect(defaultFileName("application/octet-stream")).toBe("audio");
116
+ });
117
+ });
118
+ describe("httpStatusError", () => {
119
+ const tags = (status) => JSON.parse(JSON.stringify(httpStatusError(status, "body")))._tag;
120
+ it("maps 429 to RateLimited", () => globalExpect(tags(429)).toBe("RateLimited"));
121
+ it("maps 408/504 to Timeout", () => {
122
+ globalExpect(tags(408)).toBe("Timeout");
123
+ globalExpect(tags(504)).toBe("Timeout");
124
+ });
125
+ it("maps 401/402/403 to AuthFailed", () => {
126
+ globalExpect(tags(401)).toBe("AuthFailed");
127
+ globalExpect(tags(402)).toBe("AuthFailed");
128
+ globalExpect(tags(403)).toBe("AuthFailed");
129
+ });
130
+ it("maps 5xx to Unavailable", () => {
131
+ globalExpect(tags(500)).toBe("Unavailable");
132
+ globalExpect(tags(503)).toBe("Unavailable");
133
+ });
134
+ it("maps other 4xx to InvalidRequest", () => {
135
+ globalExpect(tags(400)).toBe("InvalidRequest");
136
+ globalExpect(tags(404)).toBe("InvalidRequest");
137
+ });
138
+ });
139
+ //#endregion
140
+ export {};
141
+
142
+ //# sourceMappingURL=codec.test.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codec.test.mjs","names":["expect"],"sources":["../src/codec.test.ts"],"sourcesContent":["import { Effect, Encoding } from \"effect\"\nimport { describe, expect, it } from \"vitest\"\nimport type { AudioSource } from \"@effect-uai/core/Audio\"\nimport { audioToBlob, defaultFileName, formatToOutputSlug, httpStatusError } from \"./codec.js\"\n\ndescribe(\"formatToOutputSlug\", () => {\n it(\"encodes mp3 with default bitrate when omitted\", async () => {\n const slug = await Effect.runPromise(\n formatToOutputSlug({ container: \"mp3\", encoding: \"mp3\", sampleRate: 44100 }),\n )\n expect(slug).toBe(\"mp3_44100_128\")\n })\n\n it(\"honours an explicit mp3 bitrate\", async () => {\n const slug = await Effect.runPromise(\n formatToOutputSlug({ container: \"mp3\", encoding: \"mp3\", sampleRate: 22050, bitRate: 32 }),\n )\n expect(slug).toBe(\"mp3_22050_32\")\n })\n\n it(\"encodes raw pcm_s16le as `pcm_<sampleRate>`\", async () => {\n const slug = await Effect.runPromise(\n formatToOutputSlug({ container: \"raw\", encoding: \"pcm_s16le\", sampleRate: 16000 }),\n )\n expect(slug).toBe(\"pcm_16000\")\n })\n\n it(\"encodes mu-law as `ulaw_<sampleRate>`\", async () => {\n const slug = await Effect.runPromise(\n formatToOutputSlug({ container: \"raw\", encoding: \"pcm_mulaw\", sampleRate: 8000 }),\n )\n expect(slug).toBe(\"ulaw_8000\")\n })\n\n it(\"encodes opus with default bitrate\", async () => {\n const slug = await Effect.runPromise(\n formatToOutputSlug({ container: \"opus\", encoding: \"opus\", sampleRate: 48000 }),\n )\n expect(slug).toBe(\"opus_48000_128\")\n })\n\n it(\"encodes wav as `wav_<sampleRate>`\", async () => {\n const slug = await Effect.runPromise(\n formatToOutputSlug({ container: \"wav\", encoding: \"pcm_s16le\", sampleRate: 48000 }),\n )\n expect(slug).toBe(\"wav_48000\")\n })\n\n it(\"fails Unsupported for flac / aac / webm / ogg\", async () => {\n const exit = await Effect.runPromiseExit(\n formatToOutputSlug({ container: \"flac\", encoding: \"flac\", sampleRate: 44100 }),\n )\n expect(exit._tag).toBe(\"Failure\")\n if (exit._tag === \"Failure\") {\n expect(JSON.stringify(exit.cause)).toContain(\"Unsupported\")\n expect(JSON.stringify(exit.cause)).toContain(\"outputFormat\")\n }\n })\n})\n\ndescribe(\"audioToBlob\", () => {\n it(\"wraps bytes into a Blob with the right MIME type\", async () => {\n const source: AudioSource = {\n _tag: \"bytes\",\n bytes: new Uint8Array([1, 2, 3, 4]),\n mimeType: \"audio/wav\",\n }\n const blob = await Effect.runPromise(audioToBlob(source))\n expect(blob.type).toBe(\"audio/wav\")\n expect(blob.size).toBe(4)\n })\n\n it(\"decodes a base64 source into bytes\", async () => {\n const original = new Uint8Array([0xde, 0xad, 0xbe, 0xef])\n const source: AudioSource = {\n _tag: \"base64\",\n base64: Encoding.encodeBase64(original),\n mimeType: \"audio/mpeg\",\n }\n const blob = await Effect.runPromise(audioToBlob(source))\n expect(blob.type).toBe(\"audio/mpeg\")\n expect(blob.size).toBe(4)\n })\n\n it(\"fails InvalidRequest for url sources\", async () => {\n const source: AudioSource = { _tag: \"url\", url: \"https://x/y.mp3\", mimeType: \"audio/mpeg\" }\n const exit = await Effect.runPromiseExit(audioToBlob(source))\n expect(exit._tag).toBe(\"Failure\")\n if (exit._tag === \"Failure\") {\n expect(JSON.stringify(exit.cause)).toContain(\"InvalidRequest\")\n expect(JSON.stringify(exit.cause)).toContain(\"cloud_storage_url\")\n }\n })\n})\n\ndescribe(\"defaultFileName\", () => {\n it(\"maps audio/mpeg → audio.mp3\", () => {\n expect(defaultFileName(\"audio/mpeg\")).toBe(\"audio.mp3\")\n })\n it(\"maps audio/wav → audio.wav\", () => {\n expect(defaultFileName(\"audio/wav\")).toBe(\"audio.wav\")\n })\n it(\"falls back to audio (no extension) for unknown MIME\", () => {\n expect(defaultFileName(\"application/octet-stream\")).toBe(\"audio\")\n })\n})\n\ndescribe(\"httpStatusError\", () => {\n const tags = (status: number) => JSON.parse(JSON.stringify(httpStatusError(status, \"body\")))._tag\n it(\"maps 429 to RateLimited\", () => expect(tags(429)).toBe(\"RateLimited\"))\n it(\"maps 408/504 to Timeout\", () => {\n expect(tags(408)).toBe(\"Timeout\")\n expect(tags(504)).toBe(\"Timeout\")\n })\n it(\"maps 401/402/403 to AuthFailed\", () => {\n expect(tags(401)).toBe(\"AuthFailed\")\n expect(tags(402)).toBe(\"AuthFailed\")\n expect(tags(403)).toBe(\"AuthFailed\")\n })\n it(\"maps 5xx to Unavailable\", () => {\n expect(tags(500)).toBe(\"Unavailable\")\n expect(tags(503)).toBe(\"Unavailable\")\n })\n it(\"maps other 4xx to InvalidRequest\", () => {\n expect(tags(400)).toBe(\"InvalidRequest\")\n expect(tags(404)).toBe(\"InvalidRequest\")\n })\n})\n"],"mappings":"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"}