@effect-uai/elevenlabs 0.5.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/ElevenLabsMusicGenerator.d.mts +75 -0
  2. package/dist/ElevenLabsMusicGenerator.d.mts.map +1 -0
  3. package/dist/ElevenLabsMusicGenerator.mjs +191 -0
  4. package/dist/ElevenLabsMusicGenerator.mjs.map +1 -0
  5. package/dist/ElevenLabsMusicGenerator.test.mjs +98 -0
  6. package/dist/ElevenLabsMusicGenerator.test.mjs.map +1 -0
  7. package/dist/ElevenLabsSynthesizer.d.mts +25 -14
  8. package/dist/ElevenLabsSynthesizer.d.mts.map +1 -1
  9. package/dist/ElevenLabsSynthesizer.mjs +64 -11
  10. package/dist/ElevenLabsSynthesizer.mjs.map +1 -1
  11. package/dist/ElevenLabsTranscriber.d.mts +7 -5
  12. package/dist/ElevenLabsTranscriber.d.mts.map +1 -1
  13. package/dist/ElevenLabsTranscriber.mjs +11 -3
  14. package/dist/ElevenLabsTranscriber.mjs.map +1 -1
  15. package/dist/codec.d.mts +25 -2
  16. package/dist/codec.d.mts.map +1 -1
  17. package/dist/codec.mjs +18 -1
  18. package/dist/codec.mjs.map +1 -1
  19. package/dist/index.d.mts +4 -2
  20. package/dist/index.mjs +3 -1
  21. package/dist/models.d.mts +13 -1
  22. package/dist/models.d.mts.map +1 -1
  23. package/dist/musicCodec-CZni1gAQ.d.mts +42 -0
  24. package/dist/musicCodec-CZni1gAQ.d.mts.map +1 -0
  25. package/dist/musicCodec.d.mts +2 -0
  26. package/dist/musicCodec.mjs +38 -0
  27. package/dist/musicCodec.mjs.map +1 -0
  28. package/dist/realtimeStt.d.mts +5 -2
  29. package/dist/realtimeStt.d.mts.map +1 -1
  30. package/dist/realtimeStt.mjs +16 -4
  31. package/dist/realtimeStt.mjs.map +1 -1
  32. package/dist/realtimeTts.d.mts +10 -3
  33. package/dist/realtimeTts.d.mts.map +1 -1
  34. package/dist/realtimeTts.mjs +7 -4
  35. package/dist/realtimeTts.mjs.map +1 -1
  36. package/dist/region.d.mts +29 -0
  37. package/dist/region.d.mts.map +1 -0
  38. package/dist/region.mjs +20 -0
  39. package/dist/region.mjs.map +1 -0
  40. package/dist/region.test.d.mts +1 -0
  41. package/dist/region.test.mjs +30 -0
  42. package/dist/region.test.mjs.map +1 -0
  43. package/package.json +9 -3
  44. package/src/ElevenLabsMusicGenerator.test.ts +116 -0
  45. package/src/ElevenLabsMusicGenerator.ts +353 -0
  46. package/src/ElevenLabsSynthesizer.ts +103 -8
  47. package/src/ElevenLabsTranscriber.ts +18 -4
  48. package/src/codec.ts +47 -0
  49. package/src/index.ts +2 -0
  50. package/src/models.ts +17 -0
  51. package/src/musicCodec.ts +86 -0
  52. package/src/realtimeStt.ts +19 -3
  53. package/src/realtimeTts.ts +18 -2
  54. package/src/region.test.ts +30 -0
  55. package/src/region.ts +34 -0
  56. package/dist/ElevenLabsSynthesizer.test.mjs +0 -32
  57. package/dist/ElevenLabsSynthesizer.test.mjs.map +0 -1
  58. package/src/ElevenLabsSynthesizer.test.ts +0 -49
  59. /package/dist/{ElevenLabsSynthesizer.test.d.mts → ElevenLabsMusicGenerator.test.d.mts} +0 -0
package/src/index.ts CHANGED
@@ -1,4 +1,6 @@
1
+ export * as ElevenLabsMusicGenerator from "./ElevenLabsMusicGenerator.js"
1
2
  export * as ElevenLabsSynthesizer from "./ElevenLabsSynthesizer.js"
2
3
  export * as ElevenLabsTranscriber from "./ElevenLabsTranscriber.js"
3
4
  export * as codec from "./codec.js"
4
5
  export * from "./models.js"
6
+ export * from "./region.js"
package/src/models.ts CHANGED
@@ -44,3 +44,20 @@ export type ElevenLabsSttModel =
44
44
  * full catalog is fetched dynamically via `GET /v1/voices`.
45
45
  */
46
46
  export type ElevenLabsVoiceId = string
47
+
48
+ /**
49
+ * Known ElevenLabs music model identifiers.
50
+ *
51
+ * - `music_v1` — current API default (as of May 2026). General-purpose
52
+ * music composition.
53
+ * - `music_v2` — UI default; API plumbing in progress per the
54
+ * May 26 2026 v2 launch blog. Include in the literal union so
55
+ * callers can opt in when it ships.
56
+ *
57
+ * Reference: https://elevenlabs.io/docs/api-reference/music/compose
58
+ */
59
+ export type ElevenLabsMusicModel =
60
+ | "music_v1"
61
+ | "music_v2"
62
+ // eslint-disable-next-line @typescript-eslint/ban-types
63
+ | (string & {})
@@ -0,0 +1,86 @@
1
+ import { Duration, Effect } from "effect"
2
+ import { warnDroppedWhen } from "@effect-uai/core/Capabilities"
3
+ import type { CommonGenerateMusicRequest } from "@effect-uai/core/Music"
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // Composition plan (camelCase; snake-cased at the wire boundary)
7
+ // ---------------------------------------------------------------------------
8
+
9
+ /**
10
+ * Composition plan accepted by `POST /v1/music` (and returned by
11
+ * `POST /v1/music/plan`). Mirrors the wire `MusicPrompt` schema with
12
+ * camelCase fields. Validation lives server-side (3 s to 10 min total,
13
+ * up to 30 sections, 3 s to 2 min per section, ≤200 chars per lyric
14
+ * line); we pass the body through and surface 422 errors via the
15
+ * HTTP-status mapping.
16
+ *
17
+ * Reference: https://elevenlabs.io/docs/api-reference/music/compose
18
+ */
19
+ export type ElevenLabsCompositionPlan = {
20
+ readonly positiveGlobalStyles: ReadonlyArray<string>
21
+ readonly negativeGlobalStyles: ReadonlyArray<string>
22
+ readonly sections: ReadonlyArray<ElevenLabsSongSection>
23
+ }
24
+
25
+ export type ElevenLabsSongSection = {
26
+ readonly sectionName: string
27
+ readonly positiveLocalStyles: ReadonlyArray<string>
28
+ readonly negativeLocalStyles: ReadonlyArray<string>
29
+ readonly duration: Duration.Duration
30
+ readonly lines: ReadonlyArray<string>
31
+ }
32
+
33
+ export const wireCompositionPlan = (plan: ElevenLabsCompositionPlan) => ({
34
+ positive_global_styles: plan.positiveGlobalStyles,
35
+ negative_global_styles: plan.negativeGlobalStyles,
36
+ sections: plan.sections.map((s) => ({
37
+ section_name: s.sectionName,
38
+ positive_local_styles: s.positiveLocalStyles,
39
+ negative_local_styles: s.negativeLocalStyles,
40
+ duration_ms: Duration.toMillis(s.duration),
41
+ lines: s.lines,
42
+ })),
43
+ })
44
+
45
+ export const decodeCompositionPlan = (raw: unknown): ElevenLabsCompositionPlan => {
46
+ const r = raw as {
47
+ positive_global_styles?: ReadonlyArray<string>
48
+ negative_global_styles?: ReadonlyArray<string>
49
+ sections?: ReadonlyArray<{
50
+ section_name: string
51
+ positive_local_styles?: ReadonlyArray<string>
52
+ negative_local_styles?: ReadonlyArray<string>
53
+ duration_ms: number
54
+ lines?: ReadonlyArray<string>
55
+ }>
56
+ }
57
+ return {
58
+ positiveGlobalStyles: r.positive_global_styles ?? [],
59
+ negativeGlobalStyles: r.negative_global_styles ?? [],
60
+ sections: (r.sections ?? []).map((s) => ({
61
+ sectionName: s.section_name,
62
+ positiveLocalStyles: s.positive_local_styles ?? [],
63
+ negativeLocalStyles: s.negative_local_styles ?? [],
64
+ duration: Duration.millis(s.duration_ms),
65
+ lines: s.lines ?? [],
66
+ })),
67
+ }
68
+ }
69
+
70
+ // ---------------------------------------------------------------------------
71
+ // Bucket-2 warn-and-drop for Common fields ElevenLabs can't honor
72
+ // in prompt mode. No prompt construction; prompts always come from
73
+ // the caller verbatim. Migrate to `dropUnsupported` when capabilities
74
+ // Phase 0 lands.
75
+ // ---------------------------------------------------------------------------
76
+
77
+ export const warnDroppedPromptModeHints = (
78
+ request: CommonGenerateMusicRequest,
79
+ ): Effect.Effect<void> =>
80
+ warnDroppedWhen(request.lyrics, {
81
+ provider: "elevenlabs-music",
82
+ capability: "lyrics",
83
+ field: "lyrics",
84
+ reason:
85
+ "Prompt mode has no lyrics field. Use `compositionPlan` with per-section `lines` to provide lyrics.",
86
+ })
@@ -3,12 +3,18 @@ import { HttpClient, HttpClientRequest } from "effect/unstable/http"
3
3
  import * as Socket from "effect/unstable/socket/Socket"
4
4
  import * as AiError from "@effect-uai/core/AiError"
5
5
  import type { AudioFormat } from "@effect-uai/core/Audio"
6
+ import * as Capabilities from "@effect-uai/core/Capabilities"
6
7
  import * as JSONL from "@effect-uai/core/JSONL"
7
8
  import type { TranscriptEvent, WordTimestamp } from "@effect-uai/core/Transcript"
8
9
  import type { CommonStreamTranscribeRequest } from "@effect-uai/core/Transcriber"
9
10
  import { httpStatusError, transportFailure } from "./codec.js"
11
+ import { type ElevenLabsRegion, resolveHost } from "./region.js"
10
12
 
11
- export type Config = { readonly apiKey: Redacted.Redacted; readonly baseUrl?: string }
13
+ export type Config = {
14
+ readonly apiKey: Redacted.Redacted
15
+ readonly baseUrl?: string
16
+ readonly region?: ElevenLabsRegion
17
+ }
12
18
 
13
19
  // ---------------------------------------------------------------------------
14
20
  // AudioFormat → audio_format slug
@@ -43,7 +49,7 @@ const decodeToken = Schema.decodeUnknownEffect(TokenWire)
43
49
  export const fetchSingleUseToken = (cfg: Config) =>
44
50
  Effect.gen(function* () {
45
51
  const client = yield* HttpClient.HttpClient
46
- const url = `${cfg.baseUrl ?? "https://api.elevenlabs.io/v1"}/single-use-token/realtime_scribe`
52
+ const url = `${resolveHost(cfg)}/single-use-token/realtime_scribe`
47
53
  const httpRequest = HttpClientRequest.post(url).pipe(
48
54
  HttpClientRequest.setHeader("xi-api-key", Redacted.value(cfg.apiKey)),
49
55
  )
@@ -170,6 +176,7 @@ export type RealtimeOptions = {
170
176
  readonly languageCode?: string
171
177
  readonly includeTimestamps?: boolean
172
178
  readonly commitStrategy?: "manual" | "vad"
179
+ readonly keyterms?: ReadonlyArray<string>
173
180
  }
174
181
 
175
182
  export const buildWsUrl = (
@@ -178,7 +185,7 @@ export const buildWsUrl = (
178
185
  audioFormat: string,
179
186
  opts: RealtimeOptions,
180
187
  ) => {
181
- const wsBase = (cfg.baseUrl ?? "https://api.elevenlabs.io/v1").replace(/^http/, "ws")
188
+ const wsBase = resolveHost(cfg).replace(/^http/, "ws")
182
189
  const params = new URLSearchParams({
183
190
  token,
184
191
  audio_format: audioFormat,
@@ -187,6 +194,8 @@ export const buildWsUrl = (
187
194
  ...(opts.languageCode !== undefined && { language_code: opts.languageCode }),
188
195
  ...(opts.includeTimestamps === true && { include_timestamps: "true" }),
189
196
  })
197
+ // Vocab biasing — repeated `keyterms` param (≤50 on realtime).
198
+ for (const term of opts.keyterms ?? []) params.append("keyterms", term)
190
199
  return `${wsBase}/speech-to-text/realtime?${params.toString()}`
191
200
  }
192
201
 
@@ -242,9 +251,16 @@ export const streamTranscription =
242
251
  const slug = yield* inputFormatToSlug(request.inputFormat)
243
252
  const token = yield* fetchSingleUseToken(cfg)
244
253
  const includeTimestamps = request.wordTimestamps === true
254
+ yield* Capabilities.warnDroppedWhen(request.prompt, {
255
+ provider: "elevenlabs",
256
+ capability: "prompt",
257
+ field: "prompt",
258
+ reason: "ElevenLabs Scribe has no free-form prompt field; bias via `biasingTerms`.",
259
+ })
245
260
  const url = buildWsUrl(cfg, token, slug, {
246
261
  ...(request.model !== undefined && { model: request.model }),
247
262
  ...(request.language !== undefined && { languageCode: request.language }),
263
+ ...(request.biasingTerms !== undefined && { keyterms: request.biasingTerms }),
248
264
  includeTimestamps,
249
265
  })
250
266
  const socket = yield* Socket.makeWebSocket(url, {
@@ -3,15 +3,24 @@ import * as Socket from "effect/unstable/socket/Socket"
3
3
  import * as AiError from "@effect-uai/core/AiError"
4
4
  import type { AudioChunk, AudioFormat } from "@effect-uai/core/Audio"
5
5
  import * as JSONL from "@effect-uai/core/JSONL"
6
+ import type { CustomPronunciation } from "@effect-uai/core/SpeechSynthesizer"
6
7
  import {
7
8
  defaultFormat,
8
9
  formatToOutputSlug,
10
+ type PronunciationDictionaryLocator,
11
+ rejectInlinePronunciations,
9
12
  type VoiceSettings,
13
+ wirePronunciationLocators,
10
14
  wireVoiceSettings,
11
15
  } from "./codec.js"
12
16
  import type { ElevenLabsTtsModel, ElevenLabsVoiceId } from "./models.js"
17
+ import { type ElevenLabsRegion, resolveHost } from "./region.js"
13
18
 
14
- export type Config = { readonly apiKey: Redacted.Redacted; readonly baseUrl?: string }
19
+ export type Config = {
20
+ readonly apiKey: Redacted.Redacted
21
+ readonly baseUrl?: string
22
+ readonly region?: ElevenLabsRegion
23
+ }
15
24
 
16
25
  export type { VoiceSettings } from "./codec.js"
17
26
 
@@ -28,6 +37,11 @@ export type StreamSynthesizeRequest = {
28
37
  readonly languageCode?: string
29
38
  readonly voiceSettings?: VoiceSettings
30
39
  readonly autoMode?: boolean
40
+ /** Pre-provisioned pronunciation dictionaries, sent on the BOS frame. */
41
+ readonly pronunciationDictionaryLocators?: ReadonlyArray<PronunciationDictionaryLocator>
42
+ /** Carried from the Common request so the WS path can reject inline
43
+ * pronunciations (ElevenLabs has no stateless inline IPA path). */
44
+ readonly pronunciations?: ReadonlyArray<CustomPronunciation>
31
45
  }
32
46
 
33
47
  // ---------------------------------------------------------------------------
@@ -35,7 +49,7 @@ export type StreamSynthesizeRequest = {
35
49
  // ---------------------------------------------------------------------------
36
50
 
37
51
  export const buildWsUrl = (cfg: Config, request: StreamSynthesizeRequest, outputFormat: string) => {
38
- const wsBase = (cfg.baseUrl ?? "https://api.elevenlabs.io/v1").replace(/^http/, "ws")
52
+ const wsBase = resolveHost(cfg).replace(/^http/, "ws")
39
53
  const params = new URLSearchParams({
40
54
  output_format: outputFormat,
41
55
  auto_mode: String(request.autoMode ?? true),
@@ -51,6 +65,7 @@ const bosFrame = (cfg: Config, request: StreamSynthesizeRequest) => {
51
65
  text: " ",
52
66
  "xi-api-key": Redacted.value(cfg.apiKey),
53
67
  ...(vs !== undefined && { voice_settings: vs }),
68
+ ...wirePronunciationLocators(request.pronunciationDictionaryLocators),
54
69
  })
55
70
  }
56
71
 
@@ -112,6 +127,7 @@ export const streamSynthesis =
112
127
  ): Stream.Stream<AudioChunk, AiError.AiError | E, R | Socket.WebSocketConstructor> =>
113
128
  Stream.unwrap(
114
129
  Effect.gen(function* () {
130
+ yield* rejectInlinePronunciations(request.pronunciations)
115
131
  const slug = yield* formatToOutputSlug(request.outputFormat ?? defaultFormat)
116
132
  // ElevenLabs closes `/stream-input` with code 1000 after delivering the
117
133
  // final audio chunk. Effect's default treats all close codes as errors,
@@ -0,0 +1,30 @@
1
+ import { describe, expect, it } from "vitest"
2
+ import { resolveHost } from "./region.js"
3
+
4
+ describe("elevenlabs/region.resolveHost", () => {
5
+ it("returns default host when no region or baseUrl", () => {
6
+ expect(resolveHost({})).toBe("https://api.elevenlabs.io/v1")
7
+ })
8
+
9
+ it("returns default host for region 'default'", () => {
10
+ expect(resolveHost({ region: "default" })).toBe("https://api.elevenlabs.io/v1")
11
+ })
12
+
13
+ it("computes EU residency host for region 'eu'", () => {
14
+ expect(resolveHost({ region: "eu" })).toBe("https://api.eu.residency.elevenlabs.io/v1")
15
+ })
16
+
17
+ it("computes IN residency host for region 'in'", () => {
18
+ expect(resolveHost({ region: "in" })).toBe("https://api.in.residency.elevenlabs.io/v1")
19
+ })
20
+
21
+ it("passes unknown region through as residency host", () => {
22
+ expect(resolveHost({ region: "ap" })).toBe("https://api.ap.residency.elevenlabs.io/v1")
23
+ })
24
+
25
+ it("baseUrl wins over region", () => {
26
+ expect(resolveHost({ baseUrl: "http://localhost:8080/v1", region: "eu" })).toBe(
27
+ "http://localhost:8080/v1",
28
+ )
29
+ })
30
+ })
package/src/region.ts ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * ElevenLabs regional residency endpoints. The default workspace runs
3
+ * against `api.elevenlabs.io`; EU- and IN-residency workspaces run
4
+ * against `api.{eu,in}.residency.elevenlabs.io` (REST + WSS).
5
+ *
6
+ * API keys are workspace-bound — a key minted in an EU workspace must
7
+ * be used against the EU host (and vice versa). Mixing key/host
8
+ * surfaces as `401 PERMISSION_DENIED`.
9
+ *
10
+ * Reference: https://elevenlabs.io/docs/overview/administration/data-residency
11
+ */
12
+
13
+ // eslint-disable-next-line @typescript-eslint/ban-types
14
+ export type ElevenLabsRegion = "default" | "eu" | "in" | (string & {})
15
+
16
+ const DEFAULT_HOST = "https://api.elevenlabs.io/v1"
17
+
18
+ /**
19
+ * Resolve the HTTP base URL from a Config. `baseUrl` wins when set;
20
+ * otherwise the host is computed from `region`. Unknown region strings
21
+ * pass through as `api.{region}.residency.elevenlabs.io/v1` for forward
22
+ * compat.
23
+ *
24
+ * WS code derives its `wss://` URL by substituting `http→ws` on the
25
+ * resolved host.
26
+ */
27
+ export const resolveHost = (cfg: {
28
+ readonly baseUrl?: string
29
+ readonly region?: ElevenLabsRegion
30
+ }): string => {
31
+ if (cfg.baseUrl !== undefined) return cfg.baseUrl
32
+ if (cfg.region === undefined || cfg.region === "default") return DEFAULT_HOST
33
+ return `https://api.${cfg.region}.residency.elevenlabs.io/v1`
34
+ }
@@ -1,32 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,49 +0,0 @@
1
- import { Effect, Layer, Redacted, Stream } from "effect"
2
- import { FetchHttpClient } from "effect/unstable/http"
3
- import * as Socket from "effect/unstable/socket/Socket"
4
- import { describe, expect, expectTypeOf, it } from "vitest"
5
- import type * as AiError from "@effect-uai/core/AiError"
6
- import type { AudioBlob } from "@effect-uai/core/Audio"
7
- import * as SpeechSynthesizer from "@effect-uai/core/SpeechSynthesizer"
8
- import * as ElevenLabsSynthesizer from "./ElevenLabsSynthesizer.js"
9
-
10
- const cfg: ElevenLabsSynthesizer.Config = { apiKey: Redacted.make("test-key") }
11
- // FetchHttpClient + globalThis.WebSocket are required for `make`, but these
12
- // tests only exercise the codec and compile-time gating — no real network
13
- // call is made.
14
- const live = ElevenLabsSynthesizer.layer(cfg).pipe(
15
- Layer.provide(FetchHttpClient.layer),
16
- Layer.provide(Socket.layerWebSocketConstructorGlobal),
17
- )
18
-
19
- describe("ElevenLabsSynthesizer Layer (compile-time)", () => {
20
- it("registers `TtsIncrementalText` — `streamSynthesisFrom` clears R to never", () => {
21
- const tokens: Stream.Stream<string> = Stream.fromIterable(["hello ", "world"])
22
- const audio = tokens.pipe(
23
- SpeechSynthesizer.streamSynthesisFrom({
24
- model: "eleven_flash_v2_5",
25
- voiceId: "JBFqnCBsd6RMkjVDRZzb",
26
- }),
27
- )
28
- const program = Stream.runDrain(audio).pipe(Effect.provide(live))
29
- expectTypeOf(program).toEqualTypeOf<Effect.Effect<void, AiError.AiError, never>>()
30
- })
31
-
32
- it("sync `synthesize` and chunked `streamSynthesis` require no marker", () => {
33
- const synth = SpeechSynthesizer.synthesize({
34
- text: "hi",
35
- model: "eleven_multilingual_v2",
36
- voiceId: "JBFqnCBsd6RMkjVDRZzb",
37
- }).pipe(Effect.provide(live))
38
- expectTypeOf(synth).toEqualTypeOf<Effect.Effect<AudioBlob, AiError.AiError, never>>()
39
-
40
- const _stream = Stream.runCollect(
41
- SpeechSynthesizer.streamSynthesis({
42
- text: "hi",
43
- model: "eleven_multilingual_v2",
44
- voiceId: "JBFqnCBsd6RMkjVDRZzb",
45
- }),
46
- ).pipe(Effect.provide(live))
47
- expect(_stream).toBeDefined()
48
- })
49
- })