@effect-uai/elevenlabs 0.12.1 → 0.14.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.
@@ -1 +1 @@
1
- {"version":3,"file":"ElevenLabsTranscriber.d.mts","names":[],"sources":["../src/ElevenLabsTranscriber.ts"],"mappings":";;;;;;;;;;;;;;;;;;KA4BY,8BAA8B,KAAK;WACpC,OAAO;WACP;WACA;WACA;;KAGC;WACD,aACP,GAAG,gCACA,OAAO,OAAO,kBAAkB,QAAQ;WACpC,yBAAyB;;;cAGvB,8BAA8B;KAK/B;WACD,QAAQ,SAAS;WACjB;WACA,SAAS;;cAyGP,OAAI,KAAS,WAAM,OAAA;;;;;;;;UAa5B,WAAA,aAAA,OAAA;;;;;;;;cASS,QAAK,KAAS,WAAM,MAAA,MAAA,wBAAA,eAAA,oBAAA,WAAA,aAAA,OAAA"}
1
+ {"version":3,"file":"ElevenLabsTranscriber.d.mts","names":[],"sources":["../src/ElevenLabsTranscriber.ts"],"mappings":";;;;;;;;;;;;;;;;;;KAkCY,8BAA8B,KAAK;WACpC,OAAO;WACP;WACA;WACA;;KAGC;WACD,aACP,GAAG,gCACA,OAAO,OAAO,kBAAkB,QAAQ;WACpC,yBAAyB;;;cAGvB,8BAA8B;KAK/B;WACD,QAAQ,SAAS;WACjB;WACA,SAAS;;cAyGP,OAAI,KAAS,WAAM,OAAA;;;;;;;;UAa5B,WAAA,aAAA,OAAA;;;;;;;;cASS,QAAK,KAAS,WAAM,MAAA,MAAA,wBAAA,eAAA,oBAAA,WAAA,aAAA,OAAA"}
@@ -1,5 +1,5 @@
1
1
  import { n as __exportAll } from "./rolldown-runtime-EAsCxrDo.mjs";
2
- import { audioToBlob, defaultFileName, httpStatusError, transportFailure } from "./codec.mjs";
2
+ import { audioToBlob, bodyMultipart, defaultFileName, httpStatusError, transportFailure } from "./codec.mjs";
3
3
  import { resolveHost } from "./region.mjs";
4
4
  import { streamTranscription } from "./realtimeStt.mjs";
5
5
  import { Context, Effect, Layer, Redacted, Schema, Stream } from "effect";
@@ -60,8 +60,8 @@ const buildForm = (request) => Effect.gen(function* () {
60
60
  });
61
61
  const transcribeImpl = (cfg) => (request) => Effect.gen(function* () {
62
62
  const client = yield* HttpClient.HttpClient;
63
- const form = yield* buildForm(request);
64
- const httpRequest = HttpClientRequest.post(`${resolveHost(cfg)}/speech-to-text`).pipe(HttpClientRequest.setHeader("xi-api-key", Redacted.value(cfg.apiKey)), HttpClientRequest.bodyFormData(form));
63
+ const withBody = yield* Effect.flatMap(buildForm(request), bodyMultipart);
64
+ const httpRequest = HttpClientRequest.post(`${resolveHost(cfg)}/speech-to-text`).pipe(HttpClientRequest.setHeader("xi-api-key", Redacted.value(cfg.apiKey)), withBody);
65
65
  const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure));
66
66
  if (response.status >= 400) {
67
67
  const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""));
@@ -1 +1 @@
1
- {"version":3,"file":"ElevenLabsTranscriber.mjs","names":[],"sources":["../src/ElevenLabsTranscriber.ts"],"sourcesContent":["import { Context, Effect, Layer, Redacted, Schema, Stream } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as Socket from \"effect/unstable/socket/Socket\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport * as Capabilities from \"@effect-uai/core/Capabilities\"\nimport type { TranscriptResult, WordTimestamp } from \"@effect-uai/core/Transcript\"\nimport {\n type CommonTranscribeRequest,\n SttStreaming,\n Transcriber,\n type TranscriberService,\n} from \"@effect-uai/core/Transcriber\"\nimport { audioToBlob, defaultFileName, httpStatusError, transportFailure } from \"./codec.js\"\nimport type { ElevenLabsSttModel } from \"./models.js\"\nimport { streamTranscription } from \"./realtimeStt.js\"\nimport { type ElevenLabsRegion, resolveHost } from \"./region.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * ElevenLabs-typed transcribe request. `model` narrows to\n * `ElevenLabsSttModel`. `numSpeakers` hints diarization. `audioEvents`\n * surfaces `(laughter)` / `(music)` tags inline in `text` (provider\n * default: true). `fileName` overrides the multipart upload's\n * `filename`; ElevenLabs uses the extension to detect format.\n */\nexport type ElevenLabsTranscribeRequest = Omit<CommonTranscribeRequest, \"model\"> & {\n readonly model: ElevenLabsSttModel\n readonly numSpeakers?: number\n readonly audioEvents?: boolean\n readonly fileName?: string\n}\n\nexport type ElevenLabsTranscriberService = {\n readonly transcribe: (\n r: ElevenLabsTranscribeRequest,\n ) => Effect.Effect<TranscriptResult, AiError.AiError>\n readonly streamTranscriptionFrom: TranscriberService[\"streamTranscriptionFrom\"]\n}\n\nexport class ElevenLabsTranscriber extends Context.Service<\n ElevenLabsTranscriber,\n ElevenLabsTranscriberService\n>()(\"@betalyra/effect-uai/providers/elevenlabs/ElevenLabsTranscriber\") {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n readonly region?: ElevenLabsRegion\n}\n\n// ---------------------------------------------------------------------------\n// Response schema\n// ---------------------------------------------------------------------------\n\nconst Word = Schema.Struct({\n text: Schema.String,\n start: Schema.optional(Schema.Number),\n end: Schema.optional(Schema.Number),\n type: Schema.optional(Schema.String),\n speaker_id: Schema.optional(Schema.String),\n logprob: Schema.optional(Schema.Number),\n})\n\nconst Wire = Schema.Struct({\n text: Schema.String,\n language_code: Schema.optional(Schema.String),\n language_probability: Schema.optional(Schema.Number),\n words: Schema.optional(Schema.Array(Word)),\n transcription_id: Schema.optional(Schema.String),\n})\nconst decodeWire = Schema.decodeUnknownEffect(Wire)\n\nconst toWordTimestamp = (w: typeof Word.Type): WordTimestamp | undefined =>\n w.start === undefined || w.end === undefined\n ? undefined\n : {\n text: w.text,\n startSeconds: w.start,\n endSeconds: w.end,\n ...(w.logprob !== undefined && { confidence: Math.exp(w.logprob) }),\n ...(w.speaker_id !== undefined && { speakerId: w.speaker_id }),\n }\n\n// ---------------------------------------------------------------------------\n// Multipart body\n// ---------------------------------------------------------------------------\n\nconst buildForm = (request: ElevenLabsTranscribeRequest) =>\n Effect.gen(function* () {\n const blob = yield* audioToBlob(request.audio)\n const fileName = request.fileName ?? defaultFileName(request.audio.mimeType ?? \"\")\n const form = new FormData()\n form.set(\"model_id\", request.model)\n form.set(\"file\", blob, fileName)\n if (request.language !== undefined) form.set(\"language_code\", request.language)\n if (request.diarization !== undefined)\n form.set(\"diarize\", request.diarization ? \"true\" : \"false\")\n if (request.wordTimestamps === true) form.set(\"timestamps_granularity\", \"word\")\n if (request.numSpeakers !== undefined) form.set(\"num_speakers\", String(request.numSpeakers))\n if (request.audioEvents !== undefined)\n form.set(\"tag_audio_events\", request.audioEvents ? \"true\" : \"false\")\n // Vocab biasing → Scribe `keyterms` (≤1000 terms, ≤50 chars). Each\n // term is a repeated form field (`keyterms=a&keyterms=b`), matching\n // the ElevenLabs SDK. Scribe has no free-form prompt field.\n for (const term of request.biasingTerms ?? []) form.append(\"keyterms\", term)\n yield* Capabilities.warnDroppedWhen(request.prompt, {\n provider: \"elevenlabs\",\n capability: \"prompt\",\n field: \"prompt\",\n reason: \"ElevenLabs Scribe has no free-form prompt field; bias via `biasingTerms`.\",\n })\n return form\n })\n\n// ---------------------------------------------------------------------------\n// HTTP plumbing\n// ---------------------------------------------------------------------------\n\nconst transcribeImpl = (cfg: Config) => (request: ElevenLabsTranscribeRequest) =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const form = yield* buildForm(request)\n const httpRequest = HttpClientRequest.post(`${resolveHost(cfg)}/speech-to-text`).pipe(\n HttpClientRequest.setHeader(\"xi-api-key\", Redacted.value(cfg.apiKey)),\n HttpClientRequest.bodyFormData(form),\n )\n const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure))\n if (response.status >= 400) {\n const text = yield* response.text.pipe(Effect.orElseSucceed(() => \"\"))\n return yield* Effect.fail(httpStatusError(response.status, text))\n }\n const json = yield* response.json.pipe(Effect.mapError(transportFailure))\n const wire = yield* decodeWire(json).pipe(\n Effect.mapError(\n (cause) => new AiError.GenerationFailed({ provider: \"elevenlabs\", raw: cause }),\n ),\n )\n const words = wire.words\n ?.map(toWordTimestamp)\n .filter((w): w is WordTimestamp => w !== undefined)\n return {\n text: wire.text,\n ...(wire.language_code !== undefined && { languageCode: wire.language_code }),\n ...(words !== undefined && words.length > 0 && { words }),\n raw: json,\n } satisfies TranscriptResult\n })\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\nexport const make = (cfg: Config) =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const ctor = yield* Socket.WebSocketConstructor\n return {\n transcribe: (r) =>\n transcribeImpl(cfg)(r).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n streamTranscriptionFrom: (audioIn, request) =>\n streamTranscription(cfg)(audioIn, request).pipe(\n Stream.provideService(HttpClient.HttpClient, client),\n Stream.provideService(Socket.WebSocketConstructor, ctor),\n ),\n } satisfies ElevenLabsTranscriberService\n })\n\n/**\n * Layer registers `ElevenLabsTranscriber`, the generic `Transcriber`,\n * **and the `SttStreaming` capability marker** — `streamTranscriptionFrom`\n * is wired to the realtime WebSocket. Provide\n * `Socket.layerWebSocketConstructorGlobal` (or a custom constructor) +\n * an `HttpClient` Layer at the call site.\n */\nexport const layer = (cfg: Config) =>\n Layer.mergeAll(\n Layer.effect(ElevenLabsTranscriber, make(cfg)),\n Layer.effect(\n Transcriber,\n Effect.map(make(cfg), (s): TranscriberService => ({\n transcribe: (req: CommonTranscribeRequest) =>\n s.transcribe(req as ElevenLabsTranscribeRequest),\n streamTranscriptionFrom: s.streamTranscriptionFrom,\n })),\n ),\n Layer.succeed(SttStreaming, undefined),\n )\n"],"mappings":"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"}
1
+ {"version":3,"file":"ElevenLabsTranscriber.mjs","names":[],"sources":["../src/ElevenLabsTranscriber.ts"],"sourcesContent":["import { Context, Effect, Layer, Redacted, Schema, Stream } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as Socket from \"effect/unstable/socket/Socket\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport * as Capabilities from \"@effect-uai/core/Capabilities\"\nimport type { TranscriptResult, WordTimestamp } from \"@effect-uai/core/Transcript\"\nimport {\n type CommonTranscribeRequest,\n SttStreaming,\n Transcriber,\n type TranscriberService,\n} from \"@effect-uai/core/Transcriber\"\nimport {\n audioToBlob,\n bodyMultipart,\n defaultFileName,\n httpStatusError,\n transportFailure,\n} from \"./codec.js\"\nimport type { ElevenLabsSttModel } from \"./models.js\"\nimport { streamTranscription } from \"./realtimeStt.js\"\nimport { type ElevenLabsRegion, resolveHost } from \"./region.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * ElevenLabs-typed transcribe request. `model` narrows to\n * `ElevenLabsSttModel`. `numSpeakers` hints diarization. `audioEvents`\n * surfaces `(laughter)` / `(music)` tags inline in `text` (provider\n * default: true). `fileName` overrides the multipart upload's\n * `filename`; ElevenLabs uses the extension to detect format.\n */\nexport type ElevenLabsTranscribeRequest = Omit<CommonTranscribeRequest, \"model\"> & {\n readonly model: ElevenLabsSttModel\n readonly numSpeakers?: number\n readonly audioEvents?: boolean\n readonly fileName?: string\n}\n\nexport type ElevenLabsTranscriberService = {\n readonly transcribe: (\n r: ElevenLabsTranscribeRequest,\n ) => Effect.Effect<TranscriptResult, AiError.AiError>\n readonly streamTranscriptionFrom: TranscriberService[\"streamTranscriptionFrom\"]\n}\n\nexport class ElevenLabsTranscriber extends Context.Service<\n ElevenLabsTranscriber,\n ElevenLabsTranscriberService\n>()(\"@betalyra/effect-uai/providers/elevenlabs/ElevenLabsTranscriber\") {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n readonly region?: ElevenLabsRegion\n}\n\n// ---------------------------------------------------------------------------\n// Response schema\n// ---------------------------------------------------------------------------\n\nconst Word = Schema.Struct({\n text: Schema.String,\n start: Schema.optional(Schema.Number),\n end: Schema.optional(Schema.Number),\n type: Schema.optional(Schema.String),\n speaker_id: Schema.optional(Schema.String),\n logprob: Schema.optional(Schema.Number),\n})\n\nconst Wire = Schema.Struct({\n text: Schema.String,\n language_code: Schema.optional(Schema.String),\n language_probability: Schema.optional(Schema.Number),\n words: Schema.optional(Schema.Array(Word)),\n transcription_id: Schema.optional(Schema.String),\n})\nconst decodeWire = Schema.decodeUnknownEffect(Wire)\n\nconst toWordTimestamp = (w: typeof Word.Type): WordTimestamp | undefined =>\n w.start === undefined || w.end === undefined\n ? undefined\n : {\n text: w.text,\n startSeconds: w.start,\n endSeconds: w.end,\n ...(w.logprob !== undefined && { confidence: Math.exp(w.logprob) }),\n ...(w.speaker_id !== undefined && { speakerId: w.speaker_id }),\n }\n\n// ---------------------------------------------------------------------------\n// Multipart body\n// ---------------------------------------------------------------------------\n\nconst buildForm = (request: ElevenLabsTranscribeRequest) =>\n Effect.gen(function* () {\n const blob = yield* audioToBlob(request.audio)\n const fileName = request.fileName ?? defaultFileName(request.audio.mimeType ?? \"\")\n const form = new FormData()\n form.set(\"model_id\", request.model)\n form.set(\"file\", blob, fileName)\n if (request.language !== undefined) form.set(\"language_code\", request.language)\n if (request.diarization !== undefined)\n form.set(\"diarize\", request.diarization ? \"true\" : \"false\")\n if (request.wordTimestamps === true) form.set(\"timestamps_granularity\", \"word\")\n if (request.numSpeakers !== undefined) form.set(\"num_speakers\", String(request.numSpeakers))\n if (request.audioEvents !== undefined)\n form.set(\"tag_audio_events\", request.audioEvents ? \"true\" : \"false\")\n // Vocab biasing → Scribe `keyterms` (≤1000 terms, ≤50 chars). Each\n // term is a repeated form field (`keyterms=a&keyterms=b`), matching\n // the ElevenLabs SDK. Scribe has no free-form prompt field.\n for (const term of request.biasingTerms ?? []) form.append(\"keyterms\", term)\n yield* Capabilities.warnDroppedWhen(request.prompt, {\n provider: \"elevenlabs\",\n capability: \"prompt\",\n field: \"prompt\",\n reason: \"ElevenLabs Scribe has no free-form prompt field; bias via `biasingTerms`.\",\n })\n return form\n })\n\n// ---------------------------------------------------------------------------\n// HTTP plumbing\n// ---------------------------------------------------------------------------\n\nconst transcribeImpl = (cfg: Config) => (request: ElevenLabsTranscribeRequest) =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const withBody = yield* Effect.flatMap(buildForm(request), bodyMultipart)\n const httpRequest = HttpClientRequest.post(`${resolveHost(cfg)}/speech-to-text`).pipe(\n HttpClientRequest.setHeader(\"xi-api-key\", Redacted.value(cfg.apiKey)),\n withBody,\n )\n const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure))\n if (response.status >= 400) {\n const text = yield* response.text.pipe(Effect.orElseSucceed(() => \"\"))\n return yield* Effect.fail(httpStatusError(response.status, text))\n }\n const json = yield* response.json.pipe(Effect.mapError(transportFailure))\n const wire = yield* decodeWire(json).pipe(\n Effect.mapError(\n (cause) => new AiError.GenerationFailed({ provider: \"elevenlabs\", raw: cause }),\n ),\n )\n const words = wire.words\n ?.map(toWordTimestamp)\n .filter((w): w is WordTimestamp => w !== undefined)\n return {\n text: wire.text,\n ...(wire.language_code !== undefined && { languageCode: wire.language_code }),\n ...(words !== undefined && words.length > 0 && { words }),\n raw: json,\n } satisfies TranscriptResult\n })\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\nexport const make = (cfg: Config) =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const ctor = yield* Socket.WebSocketConstructor\n return {\n transcribe: (r) =>\n transcribeImpl(cfg)(r).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n streamTranscriptionFrom: (audioIn, request) =>\n streamTranscription(cfg)(audioIn, request).pipe(\n Stream.provideService(HttpClient.HttpClient, client),\n Stream.provideService(Socket.WebSocketConstructor, ctor),\n ),\n } satisfies ElevenLabsTranscriberService\n })\n\n/**\n * Layer registers `ElevenLabsTranscriber`, the generic `Transcriber`,\n * **and the `SttStreaming` capability marker** — `streamTranscriptionFrom`\n * is wired to the realtime WebSocket. Provide\n * `Socket.layerWebSocketConstructorGlobal` (or a custom constructor) +\n * an `HttpClient` Layer at the call site.\n */\nexport const layer = (cfg: Config) =>\n Layer.mergeAll(\n Layer.effect(ElevenLabsTranscriber, make(cfg)),\n Layer.effect(\n Transcriber,\n Effect.map(make(cfg), (s): TranscriberService => ({\n transcribe: (req: CommonTranscribeRequest) =>\n s.transcribe(req as ElevenLabsTranscribeRequest),\n streamTranscriptionFrom: s.streamTranscriptionFrom,\n })),\n ),\n Layer.succeed(SttStreaming, undefined),\n )\n"],"mappings":"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"}
package/dist/codec.d.mts CHANGED
@@ -1,9 +1,10 @@
1
1
  import { Effect } from "effect";
2
+ import { HttpClientRequest } from "effect/unstable/http";
2
3
  import * as AiError from "@effect-uai/core/AiError";
3
4
  import { CustomPronunciation } from "@effect-uai/core/SpeechSynthesizer";
4
5
  import { AudioFormat, AudioSource } from "@effect-uai/core/Audio";
5
6
  declare namespace codec_d_exports {
6
- export { PronunciationDictionaryLocator, VoiceSettings, audioToBlob, defaultFileName, defaultFormat, formatToOutputSlug, httpStatusError, rejectInlinePronunciations, transportFailure, wirePronunciationLocators, wireVoiceSettings };
7
+ export { PronunciationDictionaryLocator, VoiceSettings, audioToBlob, bodyMultipart, defaultFileName, defaultFormat, formatToOutputSlug, httpStatusError, rejectInlinePronunciations, transportFailure, wirePronunciationLocators, wireVoiceSettings };
7
8
  }
8
9
  type VoiceSettings = {
9
10
  readonly stability?: number;
@@ -59,6 +60,8 @@ declare const audioToBlob: (audio: AudioSource) => Effect.Effect<Blob, AiError.A
59
60
  declare const defaultFileName: (mimeType: string) => string;
60
61
  declare const httpStatusError: (status: number, body: string) => AiError.AiError;
61
62
  declare const transportFailure: (cause: unknown) => AiError.AiError;
63
+ /** Core's helper, with our transport error. Never pass `FormData` to the client. */
64
+ declare const bodyMultipart: (form: FormData) => Effect.Effect<(request: HttpClientRequest.HttpClientRequest) => HttpClientRequest.HttpClientRequest, AiError.AiError>;
62
65
  //#endregion
63
- export { PronunciationDictionaryLocator, VoiceSettings, audioToBlob, defaultFileName, defaultFormat, formatToOutputSlug, httpStatusError, rejectInlinePronunciations, codec_d_exports as t, transportFailure, wirePronunciationLocators, wireVoiceSettings };
66
+ export { PronunciationDictionaryLocator, VoiceSettings, audioToBlob, bodyMultipart, defaultFileName, defaultFormat, formatToOutputSlug, httpStatusError, rejectInlinePronunciations, codec_d_exports as t, transportFailure, wirePronunciationLocators, wireVoiceSettings };
64
67
  //# sourceMappingURL=codec.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"codec.d.mts","names":[],"sources":["../src/codec.ts"],"mappings":";;;;;;;KASY;WACD;WACA;WACA;WACA;WACA;;cAGE,oBAAiB,GAAO;;;;;;;;;;;;KAoBzB;WACD;WACA;;cAGE,4BAAyB,UAC1B,cAAc;;;;;;;;;;;;cAiBb,6BAA0B,gBACrB,cAAc,qCAC7B,OAAO,aAAa,QAAQ;;;;;;;;;;;;cA2BlB,qBAAqB,QAAQ,gBAAgB,OAAO,eAAe,QAAQ;;cAgC3E,eAAe;cA6Bf,cAAc,OAAO,gBAAgB,OAAO,OAAO,MAAM,QAAQ;cA2BjE,kBAAe;cAYf,kBAAkB,gBAAgB,iBAAiB,QAAQ;cAyC3D,mBAAgB,mBAAqB,QAAQ"}
1
+ {"version":3,"file":"codec.d.mts","names":[],"sources":["../src/codec.ts"],"mappings":";;;;;;;;KAWY;WACD;WACA;WACA;WACA;WACA;;cAGE,oBAAiB,GAAO;;;;;;;;;;;;KAoBzB;WACD;WACA;;cAGE,4BAAyB,UAC1B,cAAc;;;;;;;;;;;;cAiBb,6BAA0B,gBACrB,cAAc,qCAC7B,OAAO,aAAa,QAAQ;;;;;;;;;;;;cA2BlB,qBAAqB,QAAQ,gBAAgB,OAAO,eAAe,QAAQ;;cAgC3E,eAAe;cA6Bf,cAAc,OAAO,gBAAgB,OAAO,OAAO,MAAM,QAAQ;cA2BjE,kBAAe;cAYf,kBAAkB,gBAAgB,iBAAiB,QAAQ;cAyC3D,mBAAgB,mBAAqB,QAAQ;;cAI7C,gBAAa,MAClB,aACL,OAAO,QACP,SAAS,kBAAkB,sBAAsB,kBAAkB,mBACpE,QAAQ"}
package/dist/codec.mjs CHANGED
@@ -1,9 +1,11 @@
1
1
  import { n as __exportAll } from "./rolldown-runtime-EAsCxrDo.mjs";
2
2
  import { Array, Effect, Encoding, Match, Option, Result } from "effect";
3
3
  import * as AiError from "@effect-uai/core/AiError";
4
+ import * as Multipart from "@effect-uai/core/Multipart";
4
5
  //#region src/codec.ts
5
6
  var codec_exports = /* @__PURE__ */ __exportAll({
6
7
  audioToBlob: () => audioToBlob,
8
+ bodyMultipart: () => bodyMultipart,
7
9
  defaultFileName: () => defaultFileName,
8
10
  defaultFormat: () => defaultFormat,
9
11
  formatToOutputSlug: () => formatToOutputSlug,
@@ -131,7 +133,9 @@ const transportFailure = (cause) => new AiError.Unavailable({
131
133
  provider: "elevenlabs",
132
134
  raw: cause
133
135
  });
136
+ /** Core's helper, with our transport error. Never pass `FormData` to the client. */
137
+ const bodyMultipart = (form) => Multipart.bodyMultipart(form).pipe(Effect.mapError(transportFailure));
134
138
  //#endregion
135
- export { audioToBlob, defaultFileName, defaultFormat, formatToOutputSlug, httpStatusError, rejectInlinePronunciations, codec_exports as t, transportFailure, wirePronunciationLocators, wireVoiceSettings };
139
+ export { audioToBlob, bodyMultipart, defaultFileName, defaultFormat, formatToOutputSlug, httpStatusError, rejectInlinePronunciations, codec_exports as t, transportFailure, wirePronunciationLocators, wireVoiceSettings };
136
140
 
137
141
  //# sourceMappingURL=codec.mjs.map
@@ -1 +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\"\nimport type { CustomPronunciation } from \"@effect-uai/core/SpeechSynthesizer\"\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// Pronunciation: dictionary locators (supported) vs inline IPA (rejected)\n// ---------------------------------------------------------------------------\n\n/**\n * Reference to a pre-provisioned ElevenLabs pronunciation dictionary.\n * The caller provisions and versions the dictionary out-of-band\n * (dashboard or the pronunciation-dictionary API); we only reference it.\n */\nexport type PronunciationDictionaryLocator = {\n readonly dictionaryId: string\n readonly versionId: string\n}\n\nexport const wirePronunciationLocators = (\n locators: ReadonlyArray<PronunciationDictionaryLocator> | undefined,\n) =>\n locators === undefined || locators.length === 0\n ? undefined\n : {\n pronunciation_dictionary_locators: locators.map((l) => ({\n pronunciation_dictionary_id: l.dictionaryId,\n version_id: l.versionId,\n })),\n }\n\n/**\n * ElevenLabs has no stateless inline IPA path (legacy `<phoneme>` SSML\n * is dropped; modern phonetics live in pre-uploaded dictionaries).\n * Inline `pronunciations` are load-bearing (bucket 1), so reject rather\n * than silently mispronounce. Use `pronunciationDictionaryLocators`.\n */\nexport const rejectInlinePronunciations = (\n pronunciations: ReadonlyArray<CustomPronunciation> | undefined,\n): Effect.Effect<void, AiError.AiError> =>\n pronunciations !== undefined && pronunciations.length > 0\n ? Effect.fail(\n new AiError.Unsupported({\n provider: \"elevenlabs\",\n capability: \"pronunciations\",\n reason:\n \"ElevenLabs has no stateless inline IPA path. Provision a pronunciation dictionary and pass `pronunciationDictionaryLocators`, or use a provider with inline phonemes (Inworld, Google).\",\n }),\n )\n : Effect.void\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":";;;;;;;;;;;;;;;AAiBA,MAAa,qBAAqB,MAChC,MAAM,KAAA,IACF,KAAA,IACA;CACE,GAAI,EAAE,cAAc,KAAA,KAAa,EAAE,WAAW,EAAE,UAAU;CAC1D,GAAI,EAAE,oBAAoB,KAAA,KAAa,EAAE,kBAAkB,EAAE,gBAAgB;CAC7E,GAAI,EAAE,UAAU,KAAA,KAAa,EAAE,OAAO,EAAE,MAAM;CAC9C,GAAI,EAAE,oBAAoB,KAAA,KAAa,EAAE,mBAAmB,EAAE,gBAAgB;CAC9E,GAAI,EAAE,UAAU,KAAA,KAAa,EAAE,OAAO,EAAE,MAAM;AAChD;AAgBN,MAAa,6BACX,aAEA,aAAa,KAAA,KAAa,SAAS,WAAW,IAC1C,KAAA,IACA,EACE,mCAAmC,SAAS,KAAK,OAAO;CACtD,6BAA6B,EAAE;CAC/B,YAAY,EAAE;AAChB,EAAE,EACJ;;;;;;;AAQN,MAAa,8BACX,mBAEA,mBAAmB,KAAA,KAAa,eAAe,SAAS,IACpD,OAAO,KACL,IAAI,QAAQ,YAAY;CACtB,UAAU;CACV,YAAY;CACZ,QACE;AACJ,CAAC,CACH,IACA,OAAO;;;;;;;;;;;;AAiBb,MAAa,sBACX,WAEA,MAAM,MAAM,MAAM,CAAC,CAAC,KAClB,MAAM,KAAK,EAAE,WAAW,MAAM,IAAI,MAChC,OAAO,QAAQ,OAAO,EAAE,WAAW,GAAG,EAAE,WAAW,KAAK,CAC1D,GACA,MAAM,KAAK,EAAE,WAAW,OAAO,IAAI,MACjC,OAAO,QAAQ,QAAQ,EAAE,WAAW,GAAG,EAAE,WAAW,KAAK,CAC3D,GACA,MAAM,KAAK,EAAE,WAAW,MAAM,IAAI,MAAM,OAAO,QAAQ,OAAO,EAAE,YAAY,CAAC,GAC7E,MAAM,KAAK;CAAE,WAAW;CAAO,UAAU;AAAY,IAAI,MACvD,OAAO,QAAQ,OAAO,EAAE,YAAY,CACtC,GACA,MAAM,KAAK;CAAE,WAAW;CAAO,UAAU;AAAY,IAAI,MACvD,OAAO,QAAQ,QAAQ,EAAE,YAAY,CACvC,GACA,MAAM,KAAK;CAAE,WAAW;CAAO,UAAU;AAAW,IAAI,MACtD,OAAO,QAAQ,QAAQ,EAAE,YAAY,CACvC,GACA,MAAM,QAAQ,MACZ,OAAO,KACL,IAAI,QAAQ,YAAY;CACtB,UAAU;CACV,YAAY;CACZ,QAAQ,iBAAiB,KAAK,UAAU,CAAC,EAAE;AAC7C,CAAC,CACH,CACF,CACF;;AAGF,MAAa,gBAA6B;CACxC,WAAW;CACX,UAAU;CACV,YAAY;CACZ,SAAS;AACX;AAMA,MAAM,kBAAmC,IAAI,QAAQ,eAAe;CAClE,UAAU;CACV,OAAO;CACP,KAAK;AACP,CAAC;AAED,MAAM,uBAAuB,QAC3B,OAAO,MAAM,SAAS,aAAa,GAAG,GAAG;CACvC,WAAW,OAAO;CAClB,YAAY,UACV,OAAO,KACL,IAAI,QAAQ,eAAe;EAAE,UAAU;EAAc,OAAO;EAAS,KAAK;CAAM,CAAC,CACnF;AACJ,CAAC;AAEH,MAAM,eAAe,OAAmB,aACtC,IAAI,KAAK,CAAC,KAAgC,GAAG,EAAE,MAAM,SAAS,CAAC;AAEjE,MAAa,cACX,MAAM,KAAkB,CAAC,CAAC,KACxB,MAAM,IAAI,UAAU,MAAM,OAAO,QAAQ,YAAY,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,GAC1E,MAAM,IAAI,WAAW,MACnB,oBAAoB,EAAE,MAAM,CAAC,CAAC,KAAK,OAAO,KAAK,UAAU,YAAY,OAAO,EAAE,QAAQ,CAAC,CAAC,CAC1F,GACA,MAAM,IAAI,aAAa,OAAO,KAAK,eAAe,CAAC,GACnD,MAAM,UACR;AAMF,MAAM,oBAA8D;CAClE,CAAC,QAAQ,KAAK;CACd,CAAC,OAAO,KAAK;CACb,CAAC,OAAO,KAAK;CACb,CAAC,OAAO,KAAK;CACb,CAAC,QAAQ,KAAK;CACd,CAAC,QAAQ,MAAM;CACf,CAAC,OAAO,KAAK;CACb,CAAC,OAAO,KAAK;CACb,CAAC,OAAO,KAAK;CACb,CAAC,QAAQ,MAAM;AACjB;AAEA,MAAa,mBAAmB,aAC9BA,MAAI,UAAU,oBAAoB,CAAC,aAAa,SAAS,SAAS,OAAO,CAAC,CAAC,CAAC,KAC1E,OAAO,MAAM;CACX,cAAc;CACd,SAAS,GAAG,SAAS,SAAS;AAChC,CAAC,CACH;AAMF,MAAa,mBAAsE,QAAQ,SACzF,MAAM,MAAM,MAAM,CAAC,CAAC,KAClB,MAAM,KACJ,WACuB,IAAI,QAAQ,YAAY;CAAE,UAAU;CAAc,KAAK;AAAK,CAAC,CACtF,GACA,MAAM,OACJ,KACA,WACuB,IAAI,QAAQ,QAAQ;CAAE,UAAU;CAAc,KAAK;AAAK,CAAC,CAClF,GACA,MAAM,KACJ,WAEE,IAAI,QAAQ,WAAW;CAAE,UAAU;CAAc,SAAS;CAAQ,KAAK;AAAK,CAAC,CACjF,GACA,MAAM,KACJ,WAEE,IAAI,QAAQ,WAAW;CAAE,UAAU;CAAc,SAAS;CAAc,KAAK;AAAK,CAAC,CACvF,GACA,MAAM,KACJ,WAEE,IAAI,QAAQ,WAAW;CAAE,UAAU;CAAc,SAAS;CAAW,KAAK;AAAK,CAAC,CACpF,GACA,MAAM,KACJ,WAEE,IAAI,QAAQ,sBAAsB;CAAE,UAAU;CAAc,KAAK;AAAK,CAAC,CAC3E,GACA,MAAM,MACH,MAAM,KAAK,MACX,MACC,IAAI,QAAQ,YAAY;CAAE,UAAU;CAAc,QAAQ;CAAG,KAAK;AAAK,CAAC,CAC5E,GACA,MAAM,aACmB,IAAI,QAAQ,eAAe;CAAE,UAAU;CAAc,KAAK;AAAK,CAAC,CACzF,CACF;AAEF,MAAa,oBAAoB,UAC/B,IAAI,QAAQ,YAAY;CAAE,UAAU;CAAc,KAAK;AAAM,CAAC"}
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 { HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport type { AudioFormat, AudioSource } from \"@effect-uai/core/Audio\"\nimport * as Multipart from \"@effect-uai/core/Multipart\"\nimport type { CustomPronunciation } from \"@effect-uai/core/SpeechSynthesizer\"\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// Pronunciation: dictionary locators (supported) vs inline IPA (rejected)\n// ---------------------------------------------------------------------------\n\n/**\n * Reference to a pre-provisioned ElevenLabs pronunciation dictionary.\n * The caller provisions and versions the dictionary out-of-band\n * (dashboard or the pronunciation-dictionary API); we only reference it.\n */\nexport type PronunciationDictionaryLocator = {\n readonly dictionaryId: string\n readonly versionId: string\n}\n\nexport const wirePronunciationLocators = (\n locators: ReadonlyArray<PronunciationDictionaryLocator> | undefined,\n) =>\n locators === undefined || locators.length === 0\n ? undefined\n : {\n pronunciation_dictionary_locators: locators.map((l) => ({\n pronunciation_dictionary_id: l.dictionaryId,\n version_id: l.versionId,\n })),\n }\n\n/**\n * ElevenLabs has no stateless inline IPA path (legacy `<phoneme>` SSML\n * is dropped; modern phonetics live in pre-uploaded dictionaries).\n * Inline `pronunciations` are load-bearing (bucket 1), so reject rather\n * than silently mispronounce. Use `pronunciationDictionaryLocators`.\n */\nexport const rejectInlinePronunciations = (\n pronunciations: ReadonlyArray<CustomPronunciation> | undefined,\n): Effect.Effect<void, AiError.AiError> =>\n pronunciations !== undefined && pronunciations.length > 0\n ? Effect.fail(\n new AiError.Unsupported({\n provider: \"elevenlabs\",\n capability: \"pronunciations\",\n reason:\n \"ElevenLabs has no stateless inline IPA path. Provision a pronunciation dictionary and pass `pronunciationDictionaryLocators`, or use a provider with inline phonemes (Inworld, Google).\",\n }),\n )\n : Effect.void\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\n/** Core's helper, with our transport error. Never pass `FormData` to the client. */\nexport const bodyMultipart = (\n form: FormData,\n): Effect.Effect<\n (request: HttpClientRequest.HttpClientRequest) => HttpClientRequest.HttpClientRequest,\n AiError.AiError\n> => Multipart.bodyMultipart(form).pipe(Effect.mapError(transportFailure))\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect-uai/elevenlabs",
3
- "version": "0.12.1",
3
+ "version": "0.14.0",
4
4
  "description": "ElevenLabs speech-to-text, text-to-speech, and music-generation provider for @effect-uai/core.",
5
5
  "keywords": [
6
6
  "ai",
@@ -57,7 +57,7 @@
57
57
  "devDependencies": {
58
58
  "effect": "4.0.0-rc.111",
59
59
  "typescript": "^7.0.2",
60
- "@effect-uai/core": "0.12.1"
60
+ "@effect-uai/core": "0.14.0"
61
61
  },
62
62
  "peerDependencies": {
63
63
  "@effect-uai/core": ">=0.2.0 <1",
@@ -10,7 +10,13 @@ import {
10
10
  Transcriber,
11
11
  type TranscriberService,
12
12
  } from "@effect-uai/core/Transcriber"
13
- import { audioToBlob, defaultFileName, httpStatusError, transportFailure } from "./codec.js"
13
+ import {
14
+ audioToBlob,
15
+ bodyMultipart,
16
+ defaultFileName,
17
+ httpStatusError,
18
+ transportFailure,
19
+ } from "./codec.js"
14
20
  import type { ElevenLabsSttModel } from "./models.js"
15
21
  import { streamTranscription } from "./realtimeStt.js"
16
22
  import { type ElevenLabsRegion, resolveHost } from "./region.js"
@@ -122,10 +128,10 @@ const buildForm = (request: ElevenLabsTranscribeRequest) =>
122
128
  const transcribeImpl = (cfg: Config) => (request: ElevenLabsTranscribeRequest) =>
123
129
  Effect.gen(function* () {
124
130
  const client = yield* HttpClient.HttpClient
125
- const form = yield* buildForm(request)
131
+ const withBody = yield* Effect.flatMap(buildForm(request), bodyMultipart)
126
132
  const httpRequest = HttpClientRequest.post(`${resolveHost(cfg)}/speech-to-text`).pipe(
127
133
  HttpClientRequest.setHeader("xi-api-key", Redacted.value(cfg.apiKey)),
128
- HttpClientRequest.bodyFormData(form),
134
+ withBody,
129
135
  )
130
136
  const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure))
131
137
  if (response.status >= 400) {
package/src/codec.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { Array as Arr, Effect, Encoding, Match, Option, Result } from "effect"
2
+ import { HttpClientRequest } from "effect/unstable/http"
2
3
  import * as AiError from "@effect-uai/core/AiError"
3
4
  import type { AudioFormat, AudioSource } from "@effect-uai/core/Audio"
5
+ import * as Multipart from "@effect-uai/core/Multipart"
4
6
  import type { CustomPronunciation } from "@effect-uai/core/SpeechSynthesizer"
5
7
 
6
8
  // ---------------------------------------------------------------------------
@@ -230,3 +232,11 @@ export const httpStatusError: (status: number, body: string) => AiError.AiError
230
232
 
231
233
  export const transportFailure = (cause: unknown): AiError.AiError =>
232
234
  new AiError.Unavailable({ provider: "elevenlabs", raw: cause })
235
+
236
+ /** Core's helper, with our transport error. Never pass `FormData` to the client. */
237
+ export const bodyMultipart = (
238
+ form: FormData,
239
+ ): Effect.Effect<
240
+ (request: HttpClientRequest.HttpClientRequest) => HttpClientRequest.HttpClientRequest,
241
+ AiError.AiError
242
+ > => Multipart.bodyMultipart(form).pipe(Effect.mapError(transportFailure))