@effect-uai/elevenlabs 0.13.0 → 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.
- package/dist/ElevenLabsTranscriber.d.mts.map +1 -1
- package/dist/ElevenLabsTranscriber.mjs +3 -3
- package/dist/ElevenLabsTranscriber.mjs.map +1 -1
- package/dist/codec.d.mts +5 -2
- package/dist/codec.d.mts.map +1 -1
- package/dist/codec.mjs +5 -1
- package/dist/codec.mjs.map +1 -1
- package/package.json +2 -2
- package/src/ElevenLabsTranscriber.ts +9 -3
- package/src/codec.ts +10 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ElevenLabsTranscriber.d.mts","names":[],"sources":["../src/ElevenLabsTranscriber.ts"],"mappings":";;;;;;;;;;;;;;;;;;
|
|
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
|
|
64
|
-
const httpRequest = HttpClientRequest.post(`${resolveHost(cfg)}/speech-to-text`).pipe(HttpClientRequest.setHeader("xi-api-key", Redacted.value(cfg.apiKey)),
|
|
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":";;;;;;;;;;;;;;;;AAgDA,IAAa,wBAAb,cAA2C,QAAQ,QAGjD,CAAC,CAAC,iEAAiE,CAAC,CAAC,CAAC;AAYxE,MAAM,OAAO,OAAO,OAAO;CACzB,MAAM,OAAO;CACb,OAAO,OAAO,SAAS,OAAO,MAAM;CACpC,KAAK,OAAO,SAAS,OAAO,MAAM;CAClC,MAAM,OAAO,SAAS,OAAO,MAAM;CACnC,YAAY,OAAO,SAAS,OAAO,MAAM;CACzC,SAAS,OAAO,SAAS,OAAO,MAAM;AACxC,CAAC;AAED,MAAM,OAAO,OAAO,OAAO;CACzB,MAAM,OAAO;CACb,eAAe,OAAO,SAAS,OAAO,MAAM;CAC5C,sBAAsB,OAAO,SAAS,OAAO,MAAM;CACnD,OAAO,OAAO,SAAS,OAAO,MAAM,IAAI,CAAC;CACzC,kBAAkB,OAAO,SAAS,OAAO,MAAM;AACjD,CAAC;AACD,MAAM,aAAa,OAAO,oBAAoB,IAAI;AAElD,MAAM,mBAAmB,MACvB,EAAE,UAAU,KAAA,KAAa,EAAE,QAAQ,KAAA,IAC/B,KAAA,IACA;CACE,MAAM,EAAE;CACR,cAAc,EAAE;CAChB,YAAY,EAAE;CACd,GAAI,EAAE,YAAY,KAAA,KAAa,EAAE,YAAY,KAAK,IAAI,EAAE,OAAO,EAAE;CACjE,GAAI,EAAE,eAAe,KAAA,KAAa,EAAE,WAAW,EAAE,WAAW;AAC9D;AAMN,MAAM,aAAa,YACjB,OAAO,IAAI,aAAa;CACtB,MAAM,OAAO,OAAO,YAAY,QAAQ,KAAK;CAC7C,MAAM,WAAW,QAAQ,YAAY,gBAAgB,QAAQ,MAAM,YAAY,EAAE;CACjF,MAAM,OAAO,IAAI,SAAS;CAC1B,KAAK,IAAI,YAAY,QAAQ,KAAK;CAClC,KAAK,IAAI,QAAQ,MAAM,QAAQ;CAC/B,IAAI,QAAQ,aAAa,KAAA,GAAW,KAAK,IAAI,iBAAiB,QAAQ,QAAQ;CAC9E,IAAI,QAAQ,gBAAgB,KAAA,GAC1B,KAAK,IAAI,WAAW,QAAQ,cAAc,SAAS,OAAO;CAC5D,IAAI,QAAQ,mBAAmB,MAAM,KAAK,IAAI,0BAA0B,MAAM;CAC9E,IAAI,QAAQ,gBAAgB,KAAA,GAAW,KAAK,IAAI,gBAAgB,OAAO,QAAQ,WAAW,CAAC;CAC3F,IAAI,QAAQ,gBAAgB,KAAA,GAC1B,KAAK,IAAI,oBAAoB,QAAQ,cAAc,SAAS,OAAO;CAIrE,KAAK,MAAM,QAAQ,QAAQ,gBAAgB,CAAC,GAAG,KAAK,OAAO,YAAY,IAAI;CAC3E,OAAO,aAAa,gBAAgB,QAAQ,QAAQ;EAClD,UAAU;EACV,YAAY;EACZ,OAAO;EACP,QAAQ;CACV,CAAC;CACD,OAAO;AACT,CAAC;AAMH,MAAM,kBAAkB,SAAiB,YACvC,OAAO,IAAI,aAAa;CACtB,MAAM,SAAS,OAAO,WAAW;CACjC,MAAM,WAAW,OAAO,OAAO,QAAQ,UAAU,OAAO,GAAG,aAAa;CACxE,MAAM,cAAc,kBAAkB,KAAK,GAAG,YAAY,GAAG,EAAE,gBAAgB,CAAC,CAAC,KAC/E,kBAAkB,UAAU,cAAc,SAAS,MAAM,IAAI,MAAM,CAAC,GACpE,QACF;CACA,MAAM,WAAW,OAAO,OAAO,QAAQ,WAAW,CAAC,CAAC,KAAK,OAAO,SAAS,gBAAgB,CAAC;CAC1F,IAAI,SAAS,UAAU,KAAK;EAC1B,MAAM,OAAO,OAAO,SAAS,KAAK,KAAK,OAAO,oBAAoB,EAAE,CAAC;EACrE,OAAO,OAAO,OAAO,KAAK,gBAAgB,SAAS,QAAQ,IAAI,CAAC;CAClE;CACA,MAAM,OAAO,OAAO,SAAS,KAAK,KAAK,OAAO,SAAS,gBAAgB,CAAC;CACxE,MAAM,OAAO,OAAO,WAAW,IAAI,CAAC,CAAC,KACnC,OAAO,UACJ,UAAU,IAAI,QAAQ,iBAAiB;EAAE,UAAU;EAAc,KAAK;CAAM,CAAC,CAChF,CACF;CACA,MAAM,QAAQ,KAAK,OACf,IAAI,eAAe,CAAC,CACrB,QAAQ,MAA0B,MAAM,KAAA,CAAS;CACpD,OAAO;EACL,MAAM,KAAK;EACX,GAAI,KAAK,kBAAkB,KAAA,KAAa,EAAE,cAAc,KAAK,cAAc;EAC3E,GAAI,UAAU,KAAA,KAAa,MAAM,SAAS,KAAK,EAAE,MAAM;EACvD,KAAK;CACP;AACF,CAAC;AAMH,MAAa,QAAQ,QACnB,OAAO,IAAI,aAAa;CACtB,MAAM,SAAS,OAAO,WAAW;CACjC,MAAM,OAAO,OAAO,OAAO;CAC3B,OAAO;EACL,aAAa,MACX,eAAe,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,OAAO,eAAe,WAAW,YAAY,MAAM,CAAC;EAClF,0BAA0B,SAAS,YACjC,oBAAoB,GAAG,CAAC,CAAC,SAAS,OAAO,CAAC,CAAC,KACzC,OAAO,eAAe,WAAW,YAAY,MAAM,GACnD,OAAO,eAAe,OAAO,sBAAsB,IAAI,CACzD;CACJ;AACF,CAAC;;;;;;;;AASH,MAAa,SAAS,QACpB,MAAM,SACJ,MAAM,OAAO,uBAAuB,KAAK,GAAG,CAAC,GAC7C,MAAM,OACJ,aACA,OAAO,IAAI,KAAK,GAAG,IAAI,OAA2B;CAChD,aAAa,QACX,EAAE,WAAW,GAAkC;CACjD,yBAAyB,EAAE;AAC7B,EAAE,CACJ,GACA,MAAM,QAAQ,cAAc,KAAA,CAAS,CACvC"}
|
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
|
package/dist/codec.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codec.d.mts","names":[],"sources":["../src/codec.ts"],"mappings":"
|
|
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
|
package/dist/codec.mjs.map
CHANGED
|
@@ -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":"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"}
|
|
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.
|
|
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.
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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))
|