@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.
- package/dist/ElevenLabsMusicGenerator.d.mts +75 -0
- package/dist/ElevenLabsMusicGenerator.d.mts.map +1 -0
- package/dist/ElevenLabsMusicGenerator.mjs +191 -0
- package/dist/ElevenLabsMusicGenerator.mjs.map +1 -0
- package/dist/ElevenLabsMusicGenerator.test.mjs +98 -0
- package/dist/ElevenLabsMusicGenerator.test.mjs.map +1 -0
- package/dist/ElevenLabsSynthesizer.d.mts +25 -14
- package/dist/ElevenLabsSynthesizer.d.mts.map +1 -1
- package/dist/ElevenLabsSynthesizer.mjs +64 -11
- package/dist/ElevenLabsSynthesizer.mjs.map +1 -1
- package/dist/ElevenLabsTranscriber.d.mts +7 -5
- package/dist/ElevenLabsTranscriber.d.mts.map +1 -1
- package/dist/ElevenLabsTranscriber.mjs +11 -3
- package/dist/ElevenLabsTranscriber.mjs.map +1 -1
- package/dist/codec.d.mts +25 -2
- package/dist/codec.d.mts.map +1 -1
- package/dist/codec.mjs +18 -1
- package/dist/codec.mjs.map +1 -1
- package/dist/index.d.mts +4 -2
- package/dist/index.mjs +3 -1
- package/dist/models.d.mts +13 -1
- package/dist/models.d.mts.map +1 -1
- package/dist/musicCodec-CZni1gAQ.d.mts +42 -0
- package/dist/musicCodec-CZni1gAQ.d.mts.map +1 -0
- package/dist/musicCodec.d.mts +2 -0
- package/dist/musicCodec.mjs +38 -0
- package/dist/musicCodec.mjs.map +1 -0
- package/dist/realtimeStt.d.mts +5 -2
- package/dist/realtimeStt.d.mts.map +1 -1
- package/dist/realtimeStt.mjs +16 -4
- package/dist/realtimeStt.mjs.map +1 -1
- package/dist/realtimeTts.d.mts +10 -3
- package/dist/realtimeTts.d.mts.map +1 -1
- package/dist/realtimeTts.mjs +7 -4
- package/dist/realtimeTts.mjs.map +1 -1
- package/dist/region.d.mts +29 -0
- package/dist/region.d.mts.map +1 -0
- package/dist/region.mjs +20 -0
- package/dist/region.mjs.map +1 -0
- package/dist/region.test.d.mts +1 -0
- package/dist/region.test.mjs +30 -0
- package/dist/region.test.mjs.map +1 -0
- package/package.json +9 -3
- package/src/ElevenLabsMusicGenerator.test.ts +116 -0
- package/src/ElevenLabsMusicGenerator.ts +353 -0
- package/src/ElevenLabsSynthesizer.ts +103 -8
- package/src/ElevenLabsTranscriber.ts +18 -4
- package/src/codec.ts +47 -0
- package/src/index.ts +2 -0
- package/src/models.ts +17 -0
- package/src/musicCodec.ts +86 -0
- package/src/realtimeStt.ts +19 -3
- package/src/realtimeTts.ts +18 -2
- package/src/region.test.ts +30 -0
- package/src/region.ts +34 -0
- package/dist/ElevenLabsSynthesizer.test.mjs +0 -32
- package/dist/ElevenLabsSynthesizer.test.mjs.map +0 -1
- package/src/ElevenLabsSynthesizer.test.ts +0 -49
- /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
|
+
})
|
package/src/realtimeStt.ts
CHANGED
|
@@ -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 = {
|
|
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
|
|
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
|
|
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, {
|
package/src/realtimeTts.ts
CHANGED
|
@@ -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 = {
|
|
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
|
|
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
|
-
})
|
|
File without changes
|