@effect-uai/elevenlabs 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/ElevenLabsSynthesizer.d.mts +58 -0
- package/dist/ElevenLabsSynthesizer.d.mts.map +1 -0
- package/dist/ElevenLabsSynthesizer.mjs +82 -0
- package/dist/ElevenLabsSynthesizer.mjs.map +1 -0
- package/dist/ElevenLabsSynthesizer.test.d.mts +1 -0
- package/dist/ElevenLabsSynthesizer.test.mjs +32 -0
- package/dist/ElevenLabsSynthesizer.test.mjs.map +1 -0
- package/dist/ElevenLabsTranscriber.d.mts +57 -0
- package/dist/ElevenLabsTranscriber.d.mts.map +1 -0
- package/dist/ElevenLabsTranscriber.mjs +97 -0
- package/dist/ElevenLabsTranscriber.mjs.map +1 -0
- package/dist/ElevenLabsTranscriber.test.d.mts +1 -0
- package/dist/ElevenLabsTranscriber.test.mjs +58 -0
- package/dist/ElevenLabsTranscriber.test.mjs.map +1 -0
- package/dist/chunk-uyGKjUfl.mjs +14 -0
- package/dist/codec.d.mts +43 -0
- package/dist/codec.d.mts.map +1 -0
- package/dist/codec.mjs +120 -0
- package/dist/codec.mjs.map +1 -0
- package/dist/codec.test.d.mts +1 -0
- package/dist/codec.test.mjs +142 -0
- package/dist/codec.test.mjs.map +1 -0
- package/dist/dist-DMsg8YLb.mjs +13782 -0
- package/dist/dist-DMsg8YLb.mjs.map +1 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.mjs +5 -0
- package/dist/magic-string.es-CaWgF-3p.mjs +1013 -0
- package/dist/magic-string.es-CaWgF-3p.mjs.map +1 -0
- package/dist/models.d.mts +37 -0
- package/dist/models.d.mts.map +1 -0
- package/dist/models.mjs +1 -0
- package/dist/realtimeStt.d.mts +67 -0
- package/dist/realtimeStt.d.mts.map +1 -0
- package/dist/realtimeStt.mjs +163 -0
- package/dist/realtimeStt.mjs.map +1 -0
- package/dist/realtimeTts.d.mts +31 -0
- package/dist/realtimeTts.d.mts.map +1 -0
- package/dist/realtimeTts.mjs +77 -0
- package/dist/realtimeTts.mjs.map +1 -0
- package/package.json +64 -0
- package/src/ElevenLabsSynthesizer.test.ts +49 -0
- package/src/ElevenLabsSynthesizer.ts +165 -0
- package/src/ElevenLabsTranscriber.test.ts +63 -0
- package/src/ElevenLabsTranscriber.ts +179 -0
- package/src/codec.test.ts +128 -0
- package/src/codec.ts +185 -0
- package/src/index.ts +4 -0
- package/src/models.ts +46 -0
- package/src/realtimeStt.ts +273 -0
- package/src/realtimeTts.ts +147 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Betalyra Sociedade Unipessoal Lda.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { VoiceSettings } from "./codec.mjs";
|
|
2
|
+
import { ElevenLabsTtsModel, ElevenLabsVoiceId } from "./models.mjs";
|
|
3
|
+
import { Context, Effect, Layer, Redacted, Stream } from "effect";
|
|
4
|
+
import { HttpClient } from "effect/unstable/http";
|
|
5
|
+
import * as Socket from "effect/unstable/socket/Socket";
|
|
6
|
+
import { CommonStreamSynthesizeRequest, CommonSynthesizeRequest, SpeechSynthesizer, SpeechSynthesizerService, TtsIncrementalText } from "@effect-uai/core/SpeechSynthesizer";
|
|
7
|
+
import * as AiError from "@effect-uai/core/AiError";
|
|
8
|
+
import * as _$_effect_uai_core_Audio0 from "@effect-uai/core/Audio";
|
|
9
|
+
import { AudioBlob, AudioChunk } from "@effect-uai/core/Audio";
|
|
10
|
+
|
|
11
|
+
//#region src/ElevenLabsSynthesizer.d.ts
|
|
12
|
+
declare namespace ElevenLabsSynthesizer_d_exports {
|
|
13
|
+
export { Config, ElevenLabsSynthesizeRequest, ElevenLabsSynthesizer, ElevenLabsSynthesizerService, layer, make };
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* ElevenLabs-typed synthesize request. `voiceSettings` exposes the
|
|
17
|
+
* provider's prosody/timbre controls; `seed` makes generation
|
|
18
|
+
* deterministic; `previousText` / `nextText` thread context across
|
|
19
|
+
* sequential calls for natural prosody.
|
|
20
|
+
*/
|
|
21
|
+
type ElevenLabsSynthesizeRequest = Omit<CommonSynthesizeRequest, "model" | "voiceId"> & {
|
|
22
|
+
readonly model: ElevenLabsTtsModel;
|
|
23
|
+
readonly voiceId: ElevenLabsVoiceId;
|
|
24
|
+
readonly voiceSettings?: VoiceSettings;
|
|
25
|
+
readonly seed?: number;
|
|
26
|
+
readonly previousText?: string;
|
|
27
|
+
readonly nextText?: string;
|
|
28
|
+
};
|
|
29
|
+
type ElevenLabsSynthesizerService = {
|
|
30
|
+
readonly synthesize: (r: ElevenLabsSynthesizeRequest) => Effect.Effect<AudioBlob, AiError.AiError>;
|
|
31
|
+
readonly streamSynthesis: (r: ElevenLabsSynthesizeRequest) => Stream.Stream<AudioChunk, AiError.AiError>;
|
|
32
|
+
readonly streamSynthesisFrom: SpeechSynthesizerService["streamSynthesisFrom"];
|
|
33
|
+
};
|
|
34
|
+
declare const ElevenLabsSynthesizer_base: Context.ServiceClass<ElevenLabsSynthesizer, "@betalyra/effect-uai/providers/elevenlabs/ElevenLabsSynthesizer", ElevenLabsSynthesizerService>;
|
|
35
|
+
declare class ElevenLabsSynthesizer extends ElevenLabsSynthesizer_base {}
|
|
36
|
+
type Config = {
|
|
37
|
+
readonly apiKey: Redacted.Redacted;
|
|
38
|
+
readonly baseUrl?: string;
|
|
39
|
+
};
|
|
40
|
+
declare const make: (cfg: Config) => Effect.Effect<{
|
|
41
|
+
synthesize: (r: ElevenLabsSynthesizeRequest) => Effect.Effect<{
|
|
42
|
+
format: _$_effect_uai_core_Audio0.AudioFormat;
|
|
43
|
+
bytes: Uint8Array<ArrayBuffer>;
|
|
44
|
+
}, AiError.AiError, never>;
|
|
45
|
+
streamSynthesis: (r: ElevenLabsSynthesizeRequest) => Stream.Stream<AudioChunk, AiError.AiError, never>;
|
|
46
|
+
streamSynthesisFrom: <E, R>(textIn: Stream.Stream<string, E, R>, request: CommonStreamSynthesizeRequest) => Stream.Stream<AudioChunk, AiError.AiError | E, Exclude<R, Socket.WebSocketConstructor>>;
|
|
47
|
+
}, never, HttpClient.HttpClient | Socket.WebSocketConstructor>;
|
|
48
|
+
/**
|
|
49
|
+
* Layer registers `ElevenLabsSynthesizer`, the generic
|
|
50
|
+
* `SpeechSynthesizer`, **and `TtsIncrementalText`** — incremental
|
|
51
|
+
* text-in is wired to the realtime `/stream-input` WebSocket. Provide
|
|
52
|
+
* `Socket.layerWebSocketConstructorGlobal` and an `HttpClient` Layer at
|
|
53
|
+
* the call site.
|
|
54
|
+
*/
|
|
55
|
+
declare const layer: (cfg: Config) => Layer.Layer<ElevenLabsSynthesizer | SpeechSynthesizer | TtsIncrementalText, never, HttpClient.HttpClient | Socket.WebSocketConstructor>;
|
|
56
|
+
//#endregion
|
|
57
|
+
export { Config, ElevenLabsSynthesizeRequest, ElevenLabsSynthesizer, ElevenLabsSynthesizerService, layer, make, ElevenLabsSynthesizer_d_exports as t };
|
|
58
|
+
//# sourceMappingURL=ElevenLabsSynthesizer.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ElevenLabsSynthesizer.d.mts","names":[],"sources":["../src/ElevenLabsSynthesizer.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;KAiCY,2BAAA,GAA8B,IAAA,CAAK,uBAAA;EAAA,SACpC,KAAA,EAAO,kBAAA;EAAA,SACP,OAAA,EAAS,iBAAA;EAAA,SACT,aAAA,GAAgB,aAAA;EAAA,SAChB,IAAA;EAAA,SACA,YAAA;EAAA,SACA,QAAA;AAAA;AAAA,KAGC,4BAAA;EAAA,SACD,UAAA,GAAa,CAAA,EAAG,2BAAA,KAAgC,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,OAAA,CAAQ,OAAA;EAAA,SACjF,eAAA,GACP,CAAA,EAAG,2BAAA,KACA,MAAA,CAAO,MAAA,CAAO,UAAA,EAAY,OAAA,CAAQ,OAAA;EAAA,SAC9B,mBAAA,EAAqB,wBAAA;AAAA;AAAA,cAC/B,0BAAA;cAEY,qBAAA,SAA8B,0BAAA;AAAA,KAK/B,MAAA;EAAA,SAAoB,MAAA,EAAQ,QAAA,CAAS,QAAA;EAAA,SAAmB,OAAA;AAAA;AAAA,cAoEvD,IAAA,GAAQ,GAAA,EAAK,MAAA,KAAM,MAAA,CAAA,MAAA;;;;;;;;;AAjFhC;;;;;;cAwGa,KAAA,GAAS,GAAA,EAAK,MAAA,KAAM,KAAA,CAAA,KAAA,CAAA,qBAAA,GAAA,iBAAA,GAAA,kBAAA,SAAA,UAAA,CAAA,UAAA,GAAA,MAAA,CAAA,oBAAA"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { n as __exportAll } from "./chunk-uyGKjUfl.mjs";
|
|
2
|
+
import { defaultFormat, formatToOutputSlug, httpStatusError, transportFailure, wireVoiceSettings } from "./codec.mjs";
|
|
3
|
+
import { streamSynthesis } from "./realtimeTts.mjs";
|
|
4
|
+
import { Context, Effect, Layer, Redacted, Stream } from "effect";
|
|
5
|
+
import { HttpClient, HttpClientRequest } from "effect/unstable/http";
|
|
6
|
+
import * as Socket from "effect/unstable/socket/Socket";
|
|
7
|
+
import { SpeechSynthesizer, TtsIncrementalText } from "@effect-uai/core/SpeechSynthesizer";
|
|
8
|
+
//#region src/ElevenLabsSynthesizer.ts
|
|
9
|
+
var ElevenLabsSynthesizer_exports = /* @__PURE__ */ __exportAll({
|
|
10
|
+
ElevenLabsSynthesizer: () => ElevenLabsSynthesizer,
|
|
11
|
+
layer: () => layer,
|
|
12
|
+
make: () => make
|
|
13
|
+
});
|
|
14
|
+
var ElevenLabsSynthesizer = class extends Context.Service()("@betalyra/effect-uai/providers/elevenlabs/ElevenLabsSynthesizer") {};
|
|
15
|
+
const buildBody = (r) => ({
|
|
16
|
+
text: r.text,
|
|
17
|
+
model_id: r.model,
|
|
18
|
+
...r.languageCode !== void 0 && { language_code: r.languageCode },
|
|
19
|
+
...r.seed !== void 0 && { seed: r.seed },
|
|
20
|
+
...r.previousText !== void 0 && { previous_text: r.previousText },
|
|
21
|
+
...r.nextText !== void 0 && { next_text: r.nextText },
|
|
22
|
+
...r.voiceSettings !== void 0 && { voice_settings: wireVoiceSettings(r.voiceSettings) }
|
|
23
|
+
});
|
|
24
|
+
const baseUrl = (cfg) => cfg.baseUrl ?? "https://api.elevenlabs.io/v1";
|
|
25
|
+
const buildHttpRequest = (cfg, r, path) => Effect.gen(function* () {
|
|
26
|
+
const format = r.outputFormat ?? defaultFormat;
|
|
27
|
+
const slug = yield* formatToOutputSlug(format);
|
|
28
|
+
const url = `${baseUrl(cfg)}/text-to-speech/${r.voiceId}${path}?output_format=${slug}`;
|
|
29
|
+
return {
|
|
30
|
+
httpRequest: HttpClientRequest.post(url).pipe(HttpClientRequest.setHeader("xi-api-key", Redacted.value(cfg.apiKey)), HttpClientRequest.bodyJsonUnsafe(buildBody(r))),
|
|
31
|
+
format
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
const synthesizeImpl = (cfg) => (request) => Effect.gen(function* () {
|
|
35
|
+
const client = yield* HttpClient.HttpClient;
|
|
36
|
+
const { httpRequest, format } = yield* buildHttpRequest(cfg, request, "");
|
|
37
|
+
const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure));
|
|
38
|
+
if (response.status >= 400) {
|
|
39
|
+
const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""));
|
|
40
|
+
return yield* Effect.fail(httpStatusError(response.status, text));
|
|
41
|
+
}
|
|
42
|
+
const bytes = yield* response.arrayBuffer.pipe(Effect.mapError(transportFailure));
|
|
43
|
+
return {
|
|
44
|
+
format,
|
|
45
|
+
bytes: new Uint8Array(bytes)
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
const streamSynthesisImpl = (cfg) => (request) => Stream.unwrap(Effect.gen(function* () {
|
|
49
|
+
const client = yield* HttpClient.HttpClient;
|
|
50
|
+
const { httpRequest } = yield* buildHttpRequest(cfg, request, "/stream");
|
|
51
|
+
const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure));
|
|
52
|
+
if (response.status >= 400) {
|
|
53
|
+
const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""));
|
|
54
|
+
return Stream.fail(httpStatusError(response.status, text));
|
|
55
|
+
}
|
|
56
|
+
return response.stream.pipe(Stream.mapError(transportFailure), Stream.map((bytes) => ({ bytes })));
|
|
57
|
+
}));
|
|
58
|
+
const make = (cfg) => Effect.gen(function* () {
|
|
59
|
+
const client = yield* HttpClient.HttpClient;
|
|
60
|
+
const ctor = yield* Socket.WebSocketConstructor;
|
|
61
|
+
return {
|
|
62
|
+
synthesize: (r) => synthesizeImpl(cfg)(r).pipe(Effect.provideService(HttpClient.HttpClient, client)),
|
|
63
|
+
streamSynthesis: (r) => streamSynthesisImpl(cfg)(r).pipe(Stream.provideService(HttpClient.HttpClient, client)),
|
|
64
|
+
streamSynthesisFrom: (textIn, request) => streamSynthesis(cfg)(textIn, request).pipe(Stream.provideService(Socket.WebSocketConstructor, ctor))
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
/**
|
|
68
|
+
* Layer registers `ElevenLabsSynthesizer`, the generic
|
|
69
|
+
* `SpeechSynthesizer`, **and `TtsIncrementalText`** — incremental
|
|
70
|
+
* text-in is wired to the realtime `/stream-input` WebSocket. Provide
|
|
71
|
+
* `Socket.layerWebSocketConstructorGlobal` and an `HttpClient` Layer at
|
|
72
|
+
* the call site.
|
|
73
|
+
*/
|
|
74
|
+
const layer = (cfg) => Layer.mergeAll(Layer.effect(ElevenLabsSynthesizer, make(cfg)), Layer.effect(SpeechSynthesizer, Effect.map(make(cfg), (s) => ({
|
|
75
|
+
synthesize: (req) => s.synthesize(req),
|
|
76
|
+
streamSynthesis: (req) => s.streamSynthesis(req),
|
|
77
|
+
streamSynthesisFrom: (textIn, req) => s.streamSynthesisFrom(textIn, req)
|
|
78
|
+
}))), Layer.succeed(TtsIncrementalText, void 0));
|
|
79
|
+
//#endregion
|
|
80
|
+
export { ElevenLabsSynthesizer, layer, make, ElevenLabsSynthesizer_exports as t };
|
|
81
|
+
|
|
82
|
+
//# sourceMappingURL=ElevenLabsSynthesizer.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ElevenLabsSynthesizer.mjs","names":["realtimeStream"],"sources":["../src/ElevenLabsSynthesizer.ts"],"sourcesContent":["import { Context, Effect, Layer, Redacted, Stream } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as Socket from \"effect/unstable/socket/Socket\"\nimport type * as AiError from \"@effect-uai/core/AiError\"\nimport type { AudioBlob, AudioChunk } from \"@effect-uai/core/Audio\"\nimport {\n type CommonStreamSynthesizeRequest,\n type CommonSynthesizeRequest,\n SpeechSynthesizer,\n type SpeechSynthesizerService,\n TtsIncrementalText,\n} from \"@effect-uai/core/SpeechSynthesizer\"\nimport {\n defaultFormat,\n formatToOutputSlug,\n httpStatusError,\n transportFailure,\n type VoiceSettings,\n wireVoiceSettings,\n} from \"./codec.js\"\nimport type { ElevenLabsTtsModel, ElevenLabsVoiceId } from \"./models.js\"\nimport { streamSynthesis as realtimeStream } from \"./realtimeTts.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * ElevenLabs-typed synthesize request. `voiceSettings` exposes the\n * provider's prosody/timbre controls; `seed` makes generation\n * deterministic; `previousText` / `nextText` thread context across\n * sequential calls for natural prosody.\n */\nexport type ElevenLabsSynthesizeRequest = Omit<CommonSynthesizeRequest, \"model\" | \"voiceId\"> & {\n readonly model: ElevenLabsTtsModel\n readonly voiceId: ElevenLabsVoiceId\n readonly voiceSettings?: VoiceSettings\n readonly seed?: number\n readonly previousText?: string\n readonly nextText?: string\n}\n\nexport type ElevenLabsSynthesizerService = {\n readonly synthesize: (r: ElevenLabsSynthesizeRequest) => Effect.Effect<AudioBlob, AiError.AiError>\n readonly streamSynthesis: (\n r: ElevenLabsSynthesizeRequest,\n ) => Stream.Stream<AudioChunk, AiError.AiError>\n readonly streamSynthesisFrom: SpeechSynthesizerService[\"streamSynthesisFrom\"]\n}\n\nexport class ElevenLabsSynthesizer extends Context.Service<\n ElevenLabsSynthesizer,\n ElevenLabsSynthesizerService\n>()(\"@betalyra/effect-uai/providers/elevenlabs/ElevenLabsSynthesizer\") {}\n\nexport type Config = { readonly apiKey: Redacted.Redacted; readonly baseUrl?: string }\n\n// ---------------------------------------------------------------------------\n// Codec — request → JSON body\n// ---------------------------------------------------------------------------\n\nconst buildBody = (r: ElevenLabsSynthesizeRequest) => ({\n text: r.text,\n model_id: r.model,\n ...(r.languageCode !== undefined && { language_code: r.languageCode }),\n ...(r.seed !== undefined && { seed: r.seed }),\n ...(r.previousText !== undefined && { previous_text: r.previousText }),\n ...(r.nextText !== undefined && { next_text: r.nextText }),\n ...(r.voiceSettings !== undefined && { voice_settings: wireVoiceSettings(r.voiceSettings) }),\n})\n\n// ---------------------------------------------------------------------------\n// HTTP plumbing (sync + chunked-HTTP streaming)\n// ---------------------------------------------------------------------------\n\nconst baseUrl = (cfg: Config) => cfg.baseUrl ?? \"https://api.elevenlabs.io/v1\"\n\nconst buildHttpRequest = (cfg: Config, r: ElevenLabsSynthesizeRequest, path: \"\" | \"/stream\") =>\n Effect.gen(function* () {\n const format = r.outputFormat ?? defaultFormat\n const slug = yield* formatToOutputSlug(format)\n const url = `${baseUrl(cfg)}/text-to-speech/${r.voiceId}${path}?output_format=${slug}`\n const httpRequest = HttpClientRequest.post(url).pipe(\n HttpClientRequest.setHeader(\"xi-api-key\", Redacted.value(cfg.apiKey)),\n HttpClientRequest.bodyJsonUnsafe(buildBody(r)),\n )\n return { httpRequest, format }\n })\n\nconst synthesizeImpl = (cfg: Config) => (request: ElevenLabsSynthesizeRequest) =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const { httpRequest, format } = yield* buildHttpRequest(cfg, request, \"\")\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 bytes = yield* response.arrayBuffer.pipe(Effect.mapError(transportFailure))\n return { format, bytes: new Uint8Array(bytes) } satisfies AudioBlob\n })\n\nconst streamSynthesisImpl = (cfg: Config) => (request: ElevenLabsSynthesizeRequest) =>\n Stream.unwrap(\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const { httpRequest } = yield* buildHttpRequest(cfg, request, \"/stream\")\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 Stream.fail(httpStatusError(response.status, text))\n }\n return response.stream.pipe(\n Stream.mapError(transportFailure),\n Stream.map((bytes): AudioChunk => ({ bytes })),\n )\n }),\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 synthesize: (r) =>\n synthesizeImpl(cfg)(r).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n streamSynthesis: (r) =>\n streamSynthesisImpl(cfg)(r).pipe(Stream.provideService(HttpClient.HttpClient, client)),\n streamSynthesisFrom: (textIn, request) =>\n realtimeStream(cfg)(textIn, request as ElevenLabsSynthesizeRequest).pipe(\n Stream.provideService(Socket.WebSocketConstructor, ctor),\n ),\n } satisfies ElevenLabsSynthesizerService\n })\n\n/**\n * Layer registers `ElevenLabsSynthesizer`, the generic\n * `SpeechSynthesizer`, **and `TtsIncrementalText`** — incremental\n * text-in is wired to the realtime `/stream-input` WebSocket. Provide\n * `Socket.layerWebSocketConstructorGlobal` and an `HttpClient` Layer at\n * the call site.\n */\nexport const layer = (cfg: Config) =>\n Layer.mergeAll(\n Layer.effect(ElevenLabsSynthesizer, make(cfg)),\n Layer.effect(\n SpeechSynthesizer,\n Effect.map(\n make(cfg),\n (s): SpeechSynthesizerService => ({\n synthesize: (req: CommonSynthesizeRequest) =>\n s.synthesize(req as ElevenLabsSynthesizeRequest),\n streamSynthesis: (req: CommonSynthesizeRequest) =>\n s.streamSynthesis(req as ElevenLabsSynthesizeRequest),\n streamSynthesisFrom: (textIn, req: CommonStreamSynthesizeRequest) =>\n s.streamSynthesisFrom(textIn, req),\n }),\n ),\n ),\n Layer.succeed(TtsIncrementalText, undefined),\n )\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { i as it, n as globalExpect, r as describe, t as import_dist } from "./dist-DMsg8YLb.mjs";
|
|
2
|
+
import { layer } from "./ElevenLabsSynthesizer.mjs";
|
|
3
|
+
import { Effect, Layer, Redacted, Stream } from "effect";
|
|
4
|
+
import { FetchHttpClient } from "effect/unstable/http";
|
|
5
|
+
import * as Socket from "effect/unstable/socket/Socket";
|
|
6
|
+
import * as SpeechSynthesizer$1 from "@effect-uai/core/SpeechSynthesizer";
|
|
7
|
+
const live = layer({ apiKey: Redacted.make("test-key") }).pipe(Layer.provide(FetchHttpClient.layer), Layer.provide(Socket.layerWebSocketConstructorGlobal));
|
|
8
|
+
describe("ElevenLabsSynthesizer Layer (compile-time)", () => {
|
|
9
|
+
it("registers `TtsIncrementalText` — `streamSynthesisFrom` clears R to never", () => {
|
|
10
|
+
const audio = Stream.fromIterable(["hello ", "world"]).pipe(SpeechSynthesizer$1.streamSynthesisFrom({
|
|
11
|
+
model: "eleven_flash_v2_5",
|
|
12
|
+
voiceId: "JBFqnCBsd6RMkjVDRZzb"
|
|
13
|
+
}));
|
|
14
|
+
(0, import_dist.expectTypeOf)(Stream.runDrain(audio).pipe(Effect.provide(live))).toEqualTypeOf();
|
|
15
|
+
});
|
|
16
|
+
it("sync `synthesize` and chunked `streamSynthesis` require no marker", () => {
|
|
17
|
+
(0, import_dist.expectTypeOf)(SpeechSynthesizer$1.synthesize({
|
|
18
|
+
text: "hi",
|
|
19
|
+
model: "eleven_multilingual_v2",
|
|
20
|
+
voiceId: "JBFqnCBsd6RMkjVDRZzb"
|
|
21
|
+
}).pipe(Effect.provide(live))).toEqualTypeOf();
|
|
22
|
+
globalExpect(Stream.runCollect(SpeechSynthesizer$1.streamSynthesis({
|
|
23
|
+
text: "hi",
|
|
24
|
+
model: "eleven_multilingual_v2",
|
|
25
|
+
voiceId: "JBFqnCBsd6RMkjVDRZzb"
|
|
26
|
+
})).pipe(Effect.provide(live))).toBeDefined();
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
//#endregion
|
|
30
|
+
export {};
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=ElevenLabsSynthesizer.test.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ElevenLabsSynthesizer.test.mjs","names":["ElevenLabsSynthesizer.layer","SpeechSynthesizer"],"sources":["../src/ElevenLabsSynthesizer.test.ts"],"sourcesContent":["import { Effect, Layer, Redacted, Stream } from \"effect\"\nimport { FetchHttpClient } from \"effect/unstable/http\"\nimport * as Socket from \"effect/unstable/socket/Socket\"\nimport { describe, expect, expectTypeOf, it } from \"vitest\"\nimport type * as AiError from \"@effect-uai/core/AiError\"\nimport type { AudioBlob } from \"@effect-uai/core/Audio\"\nimport * as SpeechSynthesizer from \"@effect-uai/core/SpeechSynthesizer\"\nimport * as ElevenLabsSynthesizer from \"./ElevenLabsSynthesizer.js\"\n\nconst cfg: ElevenLabsSynthesizer.Config = { apiKey: Redacted.make(\"test-key\") }\n// FetchHttpClient + globalThis.WebSocket are required for `make`, but these\n// tests only exercise the codec and compile-time gating — no real network\n// call is made.\nconst live = ElevenLabsSynthesizer.layer(cfg).pipe(\n Layer.provide(FetchHttpClient.layer),\n Layer.provide(Socket.layerWebSocketConstructorGlobal),\n)\n\ndescribe(\"ElevenLabsSynthesizer Layer (compile-time)\", () => {\n it(\"registers `TtsIncrementalText` — `streamSynthesisFrom` clears R to never\", () => {\n const tokens: Stream.Stream<string> = Stream.fromIterable([\"hello \", \"world\"])\n const audio = tokens.pipe(\n SpeechSynthesizer.streamSynthesisFrom({\n model: \"eleven_flash_v2_5\",\n voiceId: \"JBFqnCBsd6RMkjVDRZzb\",\n }),\n )\n const program = Stream.runDrain(audio).pipe(Effect.provide(live))\n expectTypeOf(program).toEqualTypeOf<Effect.Effect<void, AiError.AiError, never>>()\n })\n\n it(\"sync `synthesize` and chunked `streamSynthesis` require no marker\", () => {\n const synth = SpeechSynthesizer.synthesize({\n text: \"hi\",\n model: \"eleven_multilingual_v2\",\n voiceId: \"JBFqnCBsd6RMkjVDRZzb\",\n }).pipe(Effect.provide(live))\n expectTypeOf(synth).toEqualTypeOf<Effect.Effect<AudioBlob, AiError.AiError, never>>()\n\n const _stream = Stream.runCollect(\n SpeechSynthesizer.streamSynthesis({\n text: \"hi\",\n model: \"eleven_multilingual_v2\",\n voiceId: \"JBFqnCBsd6RMkjVDRZzb\",\n }),\n ).pipe(Effect.provide(live))\n expect(_stream).toBeDefined()\n })\n})\n"],"mappings":";;;;;;AAaA,MAAM,OAAOA,MAA4B,EAJG,QAAQ,SAAS,KAAK,WAAW,EAIpC,CAAI,CAAC,KAC5C,MAAM,QAAQ,gBAAgB,MAAM,EACpC,MAAM,QAAQ,OAAO,gCAAgC,CACtD;AAED,SAAS,oDAAoD;AAC3D,IAAG,kFAAkF;EAEnF,MAAM,QADgC,OAAO,aAAa,CAAC,UAAU,QAAQ,CACzD,CAAC,KACnBC,oBAAkB,oBAAoB;GACpC,OAAO;GACP,SAAS;GACV,CAAC,CACH;AAED,GAAA,GAAA,YAAA,cADgB,OAAO,SAAS,MAAM,CAAC,KAAK,OAAO,QAAQ,KAAK,CAC5C,CAAC,CAAC,eAA4D;GAClF;AAEF,IAAG,2EAA2E;AAM5E,GAAA,GAAA,YAAA,cALcA,oBAAkB,WAAW;GACzC,MAAM;GACN,OAAO;GACP,SAAS;GACV,CAAC,CAAC,KAAK,OAAO,QAAQ,KAAK,CACV,CAAC,CAAC,eAAiE;AASrF,eAPgB,OAAO,WACrBA,oBAAkB,gBAAgB;GAChC,MAAM;GACN,OAAO;GACP,SAAS;GACV,CAAC,CACH,CAAC,KAAK,OAAO,QAAQ,KAAK,CACb,CAAC,CAAC,aAAa;GAC7B;EACF"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { ElevenLabsSttModel } from "./models.mjs";
|
|
2
|
+
import { Context, Effect, Layer, Redacted, Schema, Stream } from "effect";
|
|
3
|
+
import { HttpClient } from "effect/unstable/http";
|
|
4
|
+
import * as Socket from "effect/unstable/socket/Socket";
|
|
5
|
+
import * as AiError from "@effect-uai/core/AiError";
|
|
6
|
+
import * as _$_effect_uai_core_Transcriber0 from "@effect-uai/core/Transcriber";
|
|
7
|
+
import { CommonTranscribeRequest, SttStreaming, Transcriber, TranscriberService } from "@effect-uai/core/Transcriber";
|
|
8
|
+
import * as _$_effect_uai_core_Transcript0 from "@effect-uai/core/Transcript";
|
|
9
|
+
import { TranscriptResult, WordTimestamp } from "@effect-uai/core/Transcript";
|
|
10
|
+
|
|
11
|
+
//#region src/ElevenLabsTranscriber.d.ts
|
|
12
|
+
declare namespace ElevenLabsTranscriber_d_exports {
|
|
13
|
+
export { Config, ElevenLabsTranscribeRequest, ElevenLabsTranscriber, ElevenLabsTranscriberService, layer, make };
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* ElevenLabs-typed transcribe request. `model` narrows to
|
|
17
|
+
* `ElevenLabsSttModel`. `numSpeakers` hints diarization. `audioEvents`
|
|
18
|
+
* surfaces `(laughter)` / `(music)` tags inline in `text` (provider
|
|
19
|
+
* default: true). `fileName` overrides the multipart upload's
|
|
20
|
+
* `filename`; ElevenLabs uses the extension to detect format.
|
|
21
|
+
*/
|
|
22
|
+
type ElevenLabsTranscribeRequest = Omit<CommonTranscribeRequest, "model"> & {
|
|
23
|
+
readonly model: ElevenLabsSttModel;
|
|
24
|
+
readonly numSpeakers?: number;
|
|
25
|
+
readonly audioEvents?: boolean;
|
|
26
|
+
readonly fileName?: string;
|
|
27
|
+
};
|
|
28
|
+
type ElevenLabsTranscriberService = {
|
|
29
|
+
readonly transcribe: (r: ElevenLabsTranscribeRequest) => Effect.Effect<TranscriptResult, AiError.AiError>;
|
|
30
|
+
readonly streamTranscriptionFrom: TranscriberService["streamTranscriptionFrom"];
|
|
31
|
+
};
|
|
32
|
+
declare const ElevenLabsTranscriber_base: Context.ServiceClass<ElevenLabsTranscriber, "@betalyra/effect-uai/providers/elevenlabs/ElevenLabsTranscriber", ElevenLabsTranscriberService>;
|
|
33
|
+
declare class ElevenLabsTranscriber extends ElevenLabsTranscriber_base {}
|
|
34
|
+
type Config = {
|
|
35
|
+
readonly apiKey: Redacted.Redacted;
|
|
36
|
+
readonly baseUrl?: string;
|
|
37
|
+
};
|
|
38
|
+
declare const make: (cfg: Config) => Effect.Effect<{
|
|
39
|
+
transcribe: (r: ElevenLabsTranscribeRequest) => Effect.Effect<{
|
|
40
|
+
raw: Schema.Json;
|
|
41
|
+
words?: WordTimestamp[];
|
|
42
|
+
languageCode?: string;
|
|
43
|
+
text: string;
|
|
44
|
+
}, AiError.AiError, never>;
|
|
45
|
+
streamTranscriptionFrom: <E, R>(audioIn: Stream.Stream<Uint8Array<ArrayBufferLike>, E, R>, request: _$_effect_uai_core_Transcriber0.CommonStreamTranscribeRequest) => Stream.Stream<_$_effect_uai_core_Transcript0.TranscriptEvent, AiError.AiError | E, Exclude<Exclude<R, HttpClient.HttpClient>, Socket.WebSocketConstructor>>;
|
|
46
|
+
}, never, HttpClient.HttpClient | Socket.WebSocketConstructor>;
|
|
47
|
+
/**
|
|
48
|
+
* Layer registers `ElevenLabsTranscriber`, the generic `Transcriber`,
|
|
49
|
+
* **and the `SttStreaming` capability marker** — `streamTranscriptionFrom`
|
|
50
|
+
* is wired to the realtime WebSocket. Provide
|
|
51
|
+
* `Socket.layerWebSocketConstructorGlobal` (or a custom constructor) +
|
|
52
|
+
* an `HttpClient` Layer at the call site.
|
|
53
|
+
*/
|
|
54
|
+
declare const layer: (cfg: Config) => Layer.Layer<ElevenLabsTranscriber | Transcriber | SttStreaming, never, HttpClient.HttpClient | Socket.WebSocketConstructor>;
|
|
55
|
+
//#endregion
|
|
56
|
+
export { Config, ElevenLabsTranscribeRequest, ElevenLabsTranscriber, ElevenLabsTranscriberService, layer, make, ElevenLabsTranscriber_d_exports as t };
|
|
57
|
+
//# sourceMappingURL=ElevenLabsTranscriber.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ElevenLabsTranscriber.d.mts","names":[],"sources":["../src/ElevenLabsTranscriber.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;KA0BY,2BAAA,GAA8B,IAAA,CAAK,uBAAA;EAAA,SACpC,KAAA,EAAO,kBAAA;EAAA,SACP,WAAA;EAAA,SACA,WAAA;EAAA,SACA,QAAA;AAAA;AAAA,KAGC,4BAAA;EAAA,SACD,UAAA,GACP,CAAA,EAAG,2BAAA,KACA,MAAA,CAAO,MAAA,CAAO,gBAAA,EAAkB,OAAA,CAAQ,OAAA;EAAA,SACpC,uBAAA,EAAyB,kBAAA;AAAA;AAAA,cACnC,0BAAA;cAEY,qBAAA,SAA8B,0BAAA;AAAA,KAK/B,MAAA;EAAA,SAAoB,MAAA,EAAQ,QAAA,CAAS,QAAA;EAAA,SAAmB,OAAA;AAAA;AAAA,cAgGvD,IAAA,GAAQ,GAAA,EAAK,MAAA,KAAM,MAAA,CAAA,MAAA;;;;;;;;;;;;;;;;cAsBnB,KAAA,GAAS,GAAA,EAAK,MAAA,KAAM,KAAA,CAAA,KAAA,CAAA,qBAAA,GAAA,WAAA,GAAA,YAAA,SAAA,UAAA,CAAA,UAAA,GAAA,MAAA,CAAA,oBAAA"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { n as __exportAll } from "./chunk-uyGKjUfl.mjs";
|
|
2
|
+
import { audioToBlob, defaultFileName, httpStatusError, transportFailure } from "./codec.mjs";
|
|
3
|
+
import { streamTranscription } from "./realtimeStt.mjs";
|
|
4
|
+
import { Context, Effect, Layer, Redacted, Schema, Stream } from "effect";
|
|
5
|
+
import { HttpClient, HttpClientRequest } from "effect/unstable/http";
|
|
6
|
+
import * as Socket from "effect/unstable/socket/Socket";
|
|
7
|
+
import * as AiError from "@effect-uai/core/AiError";
|
|
8
|
+
import { SttStreaming, Transcriber } from "@effect-uai/core/Transcriber";
|
|
9
|
+
//#region src/ElevenLabsTranscriber.ts
|
|
10
|
+
var ElevenLabsTranscriber_exports = /* @__PURE__ */ __exportAll({
|
|
11
|
+
ElevenLabsTranscriber: () => ElevenLabsTranscriber,
|
|
12
|
+
layer: () => layer,
|
|
13
|
+
make: () => make
|
|
14
|
+
});
|
|
15
|
+
var ElevenLabsTranscriber = class extends Context.Service()("@betalyra/effect-uai/providers/elevenlabs/ElevenLabsTranscriber") {};
|
|
16
|
+
const Word = Schema.Struct({
|
|
17
|
+
text: Schema.String,
|
|
18
|
+
start: Schema.optional(Schema.Number),
|
|
19
|
+
end: Schema.optional(Schema.Number),
|
|
20
|
+
type: Schema.optional(Schema.String),
|
|
21
|
+
speaker_id: Schema.optional(Schema.String),
|
|
22
|
+
logprob: Schema.optional(Schema.Number)
|
|
23
|
+
});
|
|
24
|
+
const Wire = Schema.Struct({
|
|
25
|
+
text: Schema.String,
|
|
26
|
+
language_code: Schema.optional(Schema.String),
|
|
27
|
+
language_probability: Schema.optional(Schema.Number),
|
|
28
|
+
words: Schema.optional(Schema.Array(Word)),
|
|
29
|
+
transcription_id: Schema.optional(Schema.String)
|
|
30
|
+
});
|
|
31
|
+
const decodeWire = Schema.decodeUnknownEffect(Wire);
|
|
32
|
+
const toWordTimestamp = (w) => w.start === void 0 || w.end === void 0 ? void 0 : {
|
|
33
|
+
text: w.text,
|
|
34
|
+
startSeconds: w.start,
|
|
35
|
+
endSeconds: w.end,
|
|
36
|
+
...w.logprob !== void 0 && { confidence: Math.exp(w.logprob) },
|
|
37
|
+
...w.speaker_id !== void 0 && { speakerId: w.speaker_id }
|
|
38
|
+
};
|
|
39
|
+
const buildForm = (request) => Effect.gen(function* () {
|
|
40
|
+
const blob = yield* audioToBlob(request.audio);
|
|
41
|
+
const fileName = request.fileName ?? defaultFileName(request.audio.mimeType ?? "");
|
|
42
|
+
const form = new FormData();
|
|
43
|
+
form.set("model_id", request.model);
|
|
44
|
+
form.set("file", blob, fileName);
|
|
45
|
+
if (request.language !== void 0) form.set("language_code", request.language);
|
|
46
|
+
if (request.diarization !== void 0) form.set("diarize", request.diarization ? "true" : "false");
|
|
47
|
+
if (request.wordTimestamps === true) form.set("timestamps_granularity", "word");
|
|
48
|
+
if (request.numSpeakers !== void 0) form.set("num_speakers", String(request.numSpeakers));
|
|
49
|
+
if (request.audioEvents !== void 0) form.set("tag_audio_events", request.audioEvents ? "true" : "false");
|
|
50
|
+
return form;
|
|
51
|
+
});
|
|
52
|
+
const baseUrl = (cfg) => cfg.baseUrl ?? "https://api.elevenlabs.io/v1";
|
|
53
|
+
const transcribeImpl = (cfg) => (request) => Effect.gen(function* () {
|
|
54
|
+
const client = yield* HttpClient.HttpClient;
|
|
55
|
+
const form = yield* buildForm(request);
|
|
56
|
+
const httpRequest = HttpClientRequest.post(`${baseUrl(cfg)}/speech-to-text`).pipe(HttpClientRequest.setHeader("xi-api-key", Redacted.value(cfg.apiKey)), HttpClientRequest.bodyFormData(form));
|
|
57
|
+
const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure));
|
|
58
|
+
if (response.status >= 400) {
|
|
59
|
+
const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""));
|
|
60
|
+
return yield* Effect.fail(httpStatusError(response.status, text));
|
|
61
|
+
}
|
|
62
|
+
const json = yield* response.json.pipe(Effect.mapError(transportFailure));
|
|
63
|
+
const wire = yield* decodeWire(json).pipe(Effect.mapError((cause) => new AiError.GenerationFailed({
|
|
64
|
+
provider: "elevenlabs",
|
|
65
|
+
raw: cause
|
|
66
|
+
})));
|
|
67
|
+
const words = wire.words?.map(toWordTimestamp).filter((w) => w !== void 0);
|
|
68
|
+
return {
|
|
69
|
+
text: wire.text,
|
|
70
|
+
...wire.language_code !== void 0 && { languageCode: wire.language_code },
|
|
71
|
+
...words !== void 0 && words.length > 0 && { words },
|
|
72
|
+
raw: json
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
const make = (cfg) => Effect.gen(function* () {
|
|
76
|
+
const client = yield* HttpClient.HttpClient;
|
|
77
|
+
const ctor = yield* Socket.WebSocketConstructor;
|
|
78
|
+
return {
|
|
79
|
+
transcribe: (r) => transcribeImpl(cfg)(r).pipe(Effect.provideService(HttpClient.HttpClient, client)),
|
|
80
|
+
streamTranscriptionFrom: (audioIn, request) => streamTranscription(cfg)(audioIn, request).pipe(Stream.provideService(HttpClient.HttpClient, client), Stream.provideService(Socket.WebSocketConstructor, ctor))
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
/**
|
|
84
|
+
* Layer registers `ElevenLabsTranscriber`, the generic `Transcriber`,
|
|
85
|
+
* **and the `SttStreaming` capability marker** — `streamTranscriptionFrom`
|
|
86
|
+
* is wired to the realtime WebSocket. Provide
|
|
87
|
+
* `Socket.layerWebSocketConstructorGlobal` (or a custom constructor) +
|
|
88
|
+
* an `HttpClient` Layer at the call site.
|
|
89
|
+
*/
|
|
90
|
+
const layer = (cfg) => Layer.mergeAll(Layer.effect(ElevenLabsTranscriber, make(cfg)), Layer.effect(Transcriber, Effect.map(make(cfg), (s) => ({
|
|
91
|
+
transcribe: (req) => s.transcribe(req),
|
|
92
|
+
streamTranscriptionFrom: s.streamTranscriptionFrom
|
|
93
|
+
}))), Layer.succeed(SttStreaming, void 0));
|
|
94
|
+
//#endregion
|
|
95
|
+
export { ElevenLabsTranscriber, layer, make, ElevenLabsTranscriber_exports as t };
|
|
96
|
+
|
|
97
|
+
//# sourceMappingURL=ElevenLabsTranscriber.mjs.map
|
|
@@ -0,0 +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 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\"\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 = { readonly apiKey: Redacted.Redacted; readonly baseUrl?: string }\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 return form\n })\n\n// ---------------------------------------------------------------------------\n// HTTP plumbing\n// ---------------------------------------------------------------------------\n\nconst baseUrl = (cfg: Config) => cfg.baseUrl ?? \"https://api.elevenlabs.io/v1\"\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(`${baseUrl(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(\n make(cfg),\n (s): TranscriberService => ({\n transcribe: (req: CommonTranscribeRequest) =>\n s.transcribe(req as ElevenLabsTranscribeRequest),\n streamTranscriptionFrom: s.streamTranscriptionFrom,\n }),\n ),\n ),\n Layer.succeed(SttStreaming, undefined),\n )\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { i as it, n as globalExpect, r as describe, t as import_dist } from "./dist-DMsg8YLb.mjs";
|
|
2
|
+
import { ElevenLabsTranscriber, layer } from "./ElevenLabsTranscriber.mjs";
|
|
3
|
+
import { Effect, Layer, Redacted, Stream } from "effect";
|
|
4
|
+
import { FetchHttpClient } from "effect/unstable/http";
|
|
5
|
+
import * as Socket from "effect/unstable/socket/Socket";
|
|
6
|
+
import * as Transcriber$1 from "@effect-uai/core/Transcriber";
|
|
7
|
+
const live = layer({ apiKey: Redacted.make("test-key") }).pipe(Layer.provide(FetchHttpClient.layer), Layer.provide(Socket.layerWebSocketConstructorGlobal));
|
|
8
|
+
const dummyAudio = {
|
|
9
|
+
_tag: "bytes",
|
|
10
|
+
bytes: new Uint8Array([
|
|
11
|
+
0,
|
|
12
|
+
1,
|
|
13
|
+
2,
|
|
14
|
+
3
|
|
15
|
+
]),
|
|
16
|
+
mimeType: "audio/wav"
|
|
17
|
+
};
|
|
18
|
+
describe("ElevenLabsTranscriber input format guard (runtime)", () => {
|
|
19
|
+
it("fails Unsupported for unsupported raw encodings", async () => {
|
|
20
|
+
const program = ElevenLabsTranscriber.use((s) => Stream.runDrain(s.streamTranscriptionFrom(Stream.fromIterable([new Uint8Array([0])]), {
|
|
21
|
+
model: "scribe_v2_realtime",
|
|
22
|
+
inputFormat: {
|
|
23
|
+
container: "mp3",
|
|
24
|
+
encoding: "mp3",
|
|
25
|
+
sampleRate: 44100
|
|
26
|
+
}
|
|
27
|
+
})));
|
|
28
|
+
const exit = await Effect.runPromiseExit(program.pipe(Effect.provide(live)));
|
|
29
|
+
globalExpect(exit._tag).toBe("Failure");
|
|
30
|
+
if (exit._tag === "Failure") {
|
|
31
|
+
globalExpect(JSON.stringify(exit.cause)).toContain("Unsupported");
|
|
32
|
+
globalExpect(JSON.stringify(exit.cause)).toContain("inputFormat");
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
describe("ElevenLabsTranscriber Layer (compile-time)", () => {
|
|
37
|
+
it("registers `SttStreaming` — `streamTranscriptionFrom` clears R to never", () => {
|
|
38
|
+
const events = Stream.fromIterable([new Uint8Array([0])]).pipe(Transcriber$1.streamTranscriptionFrom({
|
|
39
|
+
model: "scribe_v2_realtime",
|
|
40
|
+
inputFormat: {
|
|
41
|
+
container: "raw",
|
|
42
|
+
encoding: "pcm_s16le",
|
|
43
|
+
sampleRate: 16e3
|
|
44
|
+
}
|
|
45
|
+
}));
|
|
46
|
+
(0, import_dist.expectTypeOf)(Stream.runDrain(events).pipe(Effect.provide(live))).toEqualTypeOf();
|
|
47
|
+
});
|
|
48
|
+
it("sync `transcribe` requires no marker", () => {
|
|
49
|
+
(0, import_dist.expectTypeOf)(Transcriber$1.transcribe({
|
|
50
|
+
audio: dummyAudio,
|
|
51
|
+
model: "scribe_v2"
|
|
52
|
+
}).pipe(Effect.provide(live))).toEqualTypeOf();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
//#endregion
|
|
56
|
+
export {};
|
|
57
|
+
|
|
58
|
+
//# sourceMappingURL=ElevenLabsTranscriber.test.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ElevenLabsTranscriber.test.mjs","names":["ElevenLabsTranscriber.layer","Transcriber"],"sources":["../src/ElevenLabsTranscriber.test.ts"],"sourcesContent":["import { Effect, Layer, Redacted, Stream } from \"effect\"\nimport { FetchHttpClient } from \"effect/unstable/http\"\nimport * as Socket from \"effect/unstable/socket/Socket\"\nimport { describe, expect, expectTypeOf, it } from \"vitest\"\nimport type * as AiError from \"@effect-uai/core/AiError\"\nimport type { TranscriptResult } from \"@effect-uai/core/Transcript\"\nimport * as Transcriber from \"@effect-uai/core/Transcriber\"\nimport * as ElevenLabsTranscriber from \"./ElevenLabsTranscriber.js\"\n\nconst cfg: ElevenLabsTranscriber.Config = { apiKey: Redacted.make(\"test-key\") }\n// FetchHttpClient + globalThis.WebSocket are required for `make`, but these\n// tests only exercise the codec + compile-time / runtime branches — no real\n// HTTP or WS connection is opened.\nconst live = ElevenLabsTranscriber.layer(cfg).pipe(\n Layer.provide(FetchHttpClient.layer),\n Layer.provide(Socket.layerWebSocketConstructorGlobal),\n)\n\nconst dummyAudio = {\n _tag: \"bytes\" as const,\n bytes: new Uint8Array([0, 1, 2, 3]),\n mimeType: \"audio/wav\" as const,\n}\n\ndescribe(\"ElevenLabsTranscriber input format guard (runtime)\", () => {\n it(\"fails Unsupported for unsupported raw encodings\", async () => {\n const program = ElevenLabsTranscriber.ElevenLabsTranscriber.use((s) =>\n Stream.runDrain(\n s.streamTranscriptionFrom(Stream.fromIterable([new Uint8Array([0])]), {\n model: \"scribe_v2_realtime\",\n inputFormat: { container: \"mp3\", encoding: \"mp3\", sampleRate: 44100 },\n }),\n ),\n )\n const exit = await Effect.runPromiseExit(program.pipe(Effect.provide(live)))\n expect(exit._tag).toBe(\"Failure\")\n if (exit._tag === \"Failure\") {\n expect(JSON.stringify(exit.cause)).toContain(\"Unsupported\")\n expect(JSON.stringify(exit.cause)).toContain(\"inputFormat\")\n }\n })\n})\n\ndescribe(\"ElevenLabsTranscriber Layer (compile-time)\", () => {\n it(\"registers `SttStreaming` — `streamTranscriptionFrom` clears R to never\", () => {\n const audio: Stream.Stream<Uint8Array> = Stream.fromIterable([new Uint8Array([0])])\n const events = audio.pipe(\n Transcriber.streamTranscriptionFrom({\n model: \"scribe_v2_realtime\",\n inputFormat: { container: \"raw\", encoding: \"pcm_s16le\", sampleRate: 16000 },\n }),\n )\n const provided = Stream.runDrain(events).pipe(Effect.provide(live))\n expectTypeOf(provided).toEqualTypeOf<Effect.Effect<void, AiError.AiError, never>>()\n })\n\n it(\"sync `transcribe` requires no marker\", () => {\n const t = Transcriber.transcribe({ audio: dummyAudio, model: \"scribe_v2\" }).pipe(\n Effect.provide(live),\n )\n expectTypeOf(t).toEqualTypeOf<Effect.Effect<TranscriptResult, AiError.AiError, never>>()\n })\n})\n"],"mappings":";;;;;;AAaA,MAAM,OAAOA,MAA4B,EAJG,QAAQ,SAAS,KAAK,WAAW,EAIpC,CAAI,CAAC,KAC5C,MAAM,QAAQ,gBAAgB,MAAM,EACpC,MAAM,QAAQ,OAAO,gCAAgC,CACtD;AAED,MAAM,aAAa;CACjB,MAAM;CACN,OAAO,IAAI,WAAW;EAAC;EAAG;EAAG;EAAG;EAAE,CAAC;CACnC,UAAU;CACX;AAED,SAAS,4DAA4D;AACnE,IAAG,mDAAmD,YAAY;EAChE,MAAM,UAAA,sBAAsD,KAAK,MAC/D,OAAO,SACL,EAAE,wBAAwB,OAAO,aAAa,CAAC,IAAI,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE;GACpE,OAAO;GACP,aAAa;IAAE,WAAW;IAAO,UAAU;IAAO,YAAY;IAAO;GACtE,CAAC,CACH,CACF;EACD,MAAM,OAAO,MAAM,OAAO,eAAe,QAAQ,KAAK,OAAO,QAAQ,KAAK,CAAC,CAAC;AAC5E,eAAO,KAAK,KAAK,CAAC,KAAK,UAAU;AACjC,MAAI,KAAK,SAAS,WAAW;AAC3B,gBAAO,KAAK,UAAU,KAAK,MAAM,CAAC,CAAC,UAAU,cAAc;AAC3D,gBAAO,KAAK,UAAU,KAAK,MAAM,CAAC,CAAC,UAAU,cAAc;;GAE7D;EACF;AAEF,SAAS,oDAAoD;AAC3D,IAAG,gFAAgF;EAEjF,MAAM,SADmC,OAAO,aAAa,CAAC,IAAI,WAAW,CAAC,EAAE,CAAC,CAAC,CAC9D,CAAC,KACnBC,cAAY,wBAAwB;GAClC,OAAO;GACP,aAAa;IAAE,WAAW;IAAO,UAAU;IAAa,YAAY;IAAO;GAC5E,CAAC,CACH;AAED,GAAA,GAAA,YAAA,cADiB,OAAO,SAAS,OAAO,CAAC,KAAK,OAAO,QAAQ,KAAK,CAC7C,CAAC,CAAC,eAA4D;GACnF;AAEF,IAAG,8CAA8C;AAI/C,GAAA,GAAA,YAAA,cAHUA,cAAY,WAAW;GAAE,OAAO;GAAY,OAAO;GAAa,CAAC,CAAC,KAC1E,OAAO,QAAQ,KAAK,CAER,CAAC,CAAC,eAAwE;GACxF;EACF"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
|
|
4
|
+
var __exportAll = (all, no_symbols) => {
|
|
5
|
+
let target = {};
|
|
6
|
+
for (var name in all) __defProp(target, name, {
|
|
7
|
+
get: all[name],
|
|
8
|
+
enumerable: true
|
|
9
|
+
});
|
|
10
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
11
|
+
return target;
|
|
12
|
+
};
|
|
13
|
+
//#endregion
|
|
14
|
+
export { __exportAll as n, __commonJSMin as t };
|
package/dist/codec.d.mts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import * as AiError from "@effect-uai/core/AiError";
|
|
3
|
+
import { AudioFormat, AudioSource } from "@effect-uai/core/Audio";
|
|
4
|
+
|
|
5
|
+
//#region src/codec.d.ts
|
|
6
|
+
declare namespace codec_d_exports {
|
|
7
|
+
export { VoiceSettings, audioToBlob, defaultFileName, defaultFormat, formatToOutputSlug, httpStatusError, transportFailure, wireVoiceSettings };
|
|
8
|
+
}
|
|
9
|
+
type VoiceSettings = {
|
|
10
|
+
readonly stability?: number;
|
|
11
|
+
readonly similarityBoost?: number;
|
|
12
|
+
readonly style?: number;
|
|
13
|
+
readonly useSpeakerBoost?: boolean;
|
|
14
|
+
readonly speed?: number;
|
|
15
|
+
};
|
|
16
|
+
declare const wireVoiceSettings: (v: VoiceSettings | undefined) => {
|
|
17
|
+
speed?: number;
|
|
18
|
+
use_speaker_boost?: boolean;
|
|
19
|
+
style?: number;
|
|
20
|
+
similarity_boost?: number;
|
|
21
|
+
stability?: number;
|
|
22
|
+
} | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* ElevenLabs encodes container + sample rate + bitrate in a single
|
|
25
|
+
* dot-less slug (`mp3_44100_128`, `pcm_16000`, `wav_48000`, `ulaw_8000`,
|
|
26
|
+
* `opus_48000_128`, etc.). This helper inverts the common cases of our
|
|
27
|
+
* `AudioFormat` shape into that slug.
|
|
28
|
+
*
|
|
29
|
+
* Only the formats that map cleanly are produced. Anything we can't
|
|
30
|
+
* encode (mismatched bitrate, unsupported sample rate combination)
|
|
31
|
+
* fails `Unsupported` with the requested format echoed so the user can
|
|
32
|
+
* fix it.
|
|
33
|
+
*/
|
|
34
|
+
declare const formatToOutputSlug: (format: AudioFormat) => Effect.Effect<string, AiError.AiError>;
|
|
35
|
+
/** Default output: 128 kbps MP3 at 44.1 kHz. */
|
|
36
|
+
declare const defaultFormat: AudioFormat;
|
|
37
|
+
declare const audioToBlob: (audio: AudioSource) => Effect.Effect<Blob, AiError.AiError>;
|
|
38
|
+
declare const defaultFileName: (mimeType: string) => string;
|
|
39
|
+
declare const httpStatusError: (status: number, body: string) => AiError.AiError;
|
|
40
|
+
declare const transportFailure: (cause: unknown) => AiError.AiError;
|
|
41
|
+
//#endregion
|
|
42
|
+
export { VoiceSettings, audioToBlob, defaultFileName, defaultFormat, formatToOutputSlug, httpStatusError, codec_d_exports as t, transportFailure, wireVoiceSettings };
|
|
43
|
+
//# sourceMappingURL=codec.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codec.d.mts","names":[],"sources":["../src/codec.ts"],"mappings":";;;;;;;;KAQY,aAAA;EAAA,SACD,SAAA;EAAA,SACA,eAAA;EAAA,SACA,KAAA;EAAA,SACA,eAAA;EAAA,SACA,KAAA;AAAA;AAAA,cAGE,iBAAA,GAAqB,CAAA,EAAG,aAAA;;;;;;;;;;;;;;;;;AAArC;cA0Ba,kBAAA,GAAqB,MAAA,EAAQ,WAAA,KAAgB,MAAA,CAAO,MAAA,SAAe,OAAA,CAAQ,OAAA;;cAgC3E,aAAA,EAAe,WAAA;AAAA,cA6Bf,WAAA,GAAc,KAAA,EAAO,WAAA,KAAgB,MAAA,CAAO,MAAA,CAAO,IAAA,EAAM,OAAA,CAAQ,OAAA;AAAA,cA2BjE,eAAA,GAAmB,QAAA;AAAA,cAYnB,eAAA,GAAkB,MAAA,UAAgB,IAAA,aAAiB,OAAA,CAAQ,OAAA;AAAA,cAyC3D,gBAAA,GAAoB,KAAA,cAAiB,OAAA,CAAQ,OAAA"}
|