@effect-uai/elevenlabs 0.16.0 → 0.18.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 +1 -1
- package/dist/ElevenLabsMusicGenerator.mjs +1 -1
- package/dist/ElevenLabsMusicGenerator.mjs.map +1 -1
- package/dist/ElevenLabsMusicGenerator.test.mjs +3 -3
- package/dist/ElevenLabsMusicGenerator.test.mjs.map +1 -1
- package/dist/ElevenLabsSynthesizer.d.mts +2 -2
- package/dist/ElevenLabsSynthesizer.mjs +2 -2
- package/dist/ElevenLabsSynthesizer.mjs.map +1 -1
- package/dist/ElevenLabsTranscriber.d.mts +2 -2
- package/dist/ElevenLabsTranscriber.mjs +2 -2
- package/dist/ElevenLabsTranscriber.mjs.map +1 -1
- package/dist/ElevenLabsTranscriber.test.mjs +4 -4
- package/dist/ElevenLabsTranscriber.test.mjs.map +1 -1
- package/dist/codec.d.mts +1 -1
- package/dist/codec.d.mts.map +1 -1
- package/dist/codec.mjs +3 -2
- package/dist/codec.mjs.map +1 -1
- package/dist/codec.test.mjs +4 -3
- package/dist/codec.test.mjs.map +1 -1
- package/dist/dist-CXrdHvlJ.mjs +171 -0
- package/dist/dist-CXrdHvlJ.mjs.map +1 -0
- package/dist/dist-DHer1cql.mjs +1533 -0
- package/dist/dist-DHer1cql.mjs.map +1 -0
- package/dist/dist-DkEvSNwD.mjs +7 -0
- package/dist/dist-DkEvSNwD.mjs.map +1 -0
- package/dist/{test.DNmyFkvJ-N3QqMRwZ.mjs → index.C4tKZ0yW-DjxCdpW_.mjs} +9524 -5893
- package/dist/index.C4tKZ0yW-DjxCdpW_.mjs.map +1 -0
- package/dist/realtimeStt.d.mts +2 -2
- package/dist/realtimeStt.d.mts.map +1 -1
- package/dist/realtimeStt.mjs +19 -15
- package/dist/realtimeStt.mjs.map +1 -1
- package/dist/realtimeStt.test.d.mts +1 -0
- package/dist/realtimeStt.test.mjs +80 -0
- package/dist/realtimeStt.test.mjs.map +1 -0
- package/dist/realtimeTts.d.mts +1 -1
- package/dist/realtimeTts.d.mts.map +1 -1
- package/dist/realtimeTts.mjs +20 -18
- package/dist/realtimeTts.mjs.map +1 -1
- package/dist/realtimeTts.test.d.mts +1 -0
- package/dist/realtimeTts.test.mjs +59 -0
- package/dist/realtimeTts.test.mjs.map +1 -0
- package/dist/region.test.mjs +1 -1
- package/package.json +4 -4
- package/src/ElevenLabsMusicGenerator.test.ts +1 -1
- package/src/ElevenLabsMusicGenerator.ts +1 -1
- package/src/ElevenLabsSynthesizer.ts +2 -2
- package/src/ElevenLabsTranscriber.test.ts +2 -2
- package/src/ElevenLabsTranscriber.ts +2 -2
- package/src/codec.test.ts +3 -2
- package/src/codec.ts +4 -3
- package/src/realtimeStt.test.ts +92 -0
- package/src/realtimeStt.ts +28 -38
- package/src/realtimeTts.test.ts +72 -0
- package/src/realtimeTts.ts +38 -39
- package/dist/dist-C0AvqZTC.mjs +0 -145
- package/dist/dist-C0AvqZTC.mjs.map +0 -1
- package/dist/magic-string.es-CUAnjKYC.mjs +0 -1014
- package/dist/magic-string.es-CUAnjKYC.mjs.map +0 -1
- package/dist/test.DNmyFkvJ-N3QqMRwZ.mjs.map +0 -1
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { describe, it } from "@effect/vitest"
|
|
2
|
+
import { Deferred, Effect, Exit, Fiber, Layer, Redacted, Stream } from "effect"
|
|
3
|
+
import { HttpClient, HttpClientResponse } from "effect/http"
|
|
4
|
+
import { expect } from "vitest"
|
|
5
|
+
import * as FakeWebSocket from "@effect-uai/core/testing/FakeWebSocket"
|
|
6
|
+
import { streamTranscription } from "./realtimeStt.js"
|
|
7
|
+
|
|
8
|
+
const pcm = { container: "raw", encoding: "pcm_s16le", sampleRate: 16000, channels: 1 } as const
|
|
9
|
+
|
|
10
|
+
const request = { model: "scribe_v2_realtime", inputFormat: pcm }
|
|
11
|
+
|
|
12
|
+
/** Hands out a token and signals `issued`, so the test closes only after the connect starts. */
|
|
13
|
+
const tokenClient = (issued: Deferred.Deferred<void>) =>
|
|
14
|
+
Layer.succeed(
|
|
15
|
+
HttpClient.HttpClient,
|
|
16
|
+
HttpClient.make((req) =>
|
|
17
|
+
Deferred.succeed(issued, undefined).pipe(
|
|
18
|
+
Effect.as(HttpClientResponse.fromWeb(req, new Response(JSON.stringify({ token: "t" })))),
|
|
19
|
+
),
|
|
20
|
+
),
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
const transcribing = FakeWebSocket.make({
|
|
24
|
+
greeting: [
|
|
25
|
+
{ message_type: "session_started", session_id: "s1" },
|
|
26
|
+
{ message_type: "partial_transcript", text: "hel" },
|
|
27
|
+
{ message_type: "committed_transcript", text: "hello" },
|
|
28
|
+
],
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
/** Run a transcription against `server`, closing it, if asked, once the frames are in. */
|
|
32
|
+
const transcribe = <E>(
|
|
33
|
+
server: FakeWebSocket.FakeWebSocketServer,
|
|
34
|
+
audioIn: Stream.Stream<Uint8Array, E>,
|
|
35
|
+
close?: { readonly code: number; readonly afterSent?: number },
|
|
36
|
+
) =>
|
|
37
|
+
Effect.gen(function* () {
|
|
38
|
+
const issued = yield* Deferred.make<void>()
|
|
39
|
+
const run = streamTranscription({ apiKey: Redacted.make("test-key") })(audioIn, request).pipe(
|
|
40
|
+
Stream.provide(Layer.merge(server.layer, tokenClient(issued))),
|
|
41
|
+
)
|
|
42
|
+
const collector = yield* Effect.forkChild(Effect.exit(Stream.runCollect(run)))
|
|
43
|
+
yield* Deferred.await(issued)
|
|
44
|
+
if (close !== undefined) {
|
|
45
|
+
yield* server.greeted
|
|
46
|
+
yield* server.awaitSent(close.afterSent ?? 0)
|
|
47
|
+
yield* server.close(close.code)
|
|
48
|
+
}
|
|
49
|
+
return yield* Fiber.join(collector)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
describe("ElevenLabs realtime STT socket", () => {
|
|
53
|
+
it.live("streams audio chunks, decodes transcripts, and ends on a clean close", () =>
|
|
54
|
+
Effect.gen(function* () {
|
|
55
|
+
const server = yield* transcribing
|
|
56
|
+
const exit = yield* transcribe(server, Stream.make(new Uint8Array([1, 2])), {
|
|
57
|
+
code: 1000,
|
|
58
|
+
afterSent: 1,
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
expect(exit).toEqual(
|
|
62
|
+
Exit.succeed([
|
|
63
|
+
{ _tag: "partial", text: "hel" },
|
|
64
|
+
{ _tag: "final", text: "hello" },
|
|
65
|
+
]),
|
|
66
|
+
)
|
|
67
|
+
const sent = (yield* server.sent) as ReadonlyArray<{
|
|
68
|
+
message_type: string
|
|
69
|
+
sample_rate: number
|
|
70
|
+
}>
|
|
71
|
+
expect(sent).toMatchObject([{ message_type: "input_audio_chunk", sample_rate: 16000 }])
|
|
72
|
+
}).pipe(Effect.scoped),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
it.live("fails rather than ending when the connection drops", () =>
|
|
76
|
+
Effect.gen(function* () {
|
|
77
|
+
const server = yield* transcribing
|
|
78
|
+
const exit = yield* transcribe(server, Stream.never, { code: 1011 })
|
|
79
|
+
|
|
80
|
+
expect(Exit.isFailure(exit) && JSON.stringify(exit.cause)).toContain("Unavailable")
|
|
81
|
+
}).pipe(Effect.scoped),
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
it.live("fails with the audio input's own error", () =>
|
|
85
|
+
Effect.gen(function* () {
|
|
86
|
+
const server = yield* transcribing
|
|
87
|
+
const exit = yield* transcribe(server, Stream.fail("microphone unplugged"))
|
|
88
|
+
|
|
89
|
+
expect(Exit.isFailure(exit) && JSON.stringify(exit.cause)).toContain("microphone unplugged")
|
|
90
|
+
}).pipe(Effect.scoped),
|
|
91
|
+
)
|
|
92
|
+
})
|
package/src/realtimeStt.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
1
|
+
import { Effect, Match, Option, Predicate, Redacted, Schema, Stream } from "effect"
|
|
2
|
+
import { Base64 } from "effect/encoding"
|
|
3
|
+
import { HttpClient, HttpClientRequest } from "effect/http"
|
|
4
|
+
import * as Socket from "effect/socket/Socket"
|
|
4
5
|
import * as AiError from "@effect-uai/core/AiError"
|
|
5
6
|
import type { AudioFormat } from "@effect-uai/core/Audio"
|
|
6
7
|
import * as Capabilities from "@effect-uai/core/Capabilities"
|
|
7
8
|
import * as JSONL from "@effect-uai/core/JSONL"
|
|
8
9
|
import type { TranscriptEvent, WordTimestamp } from "@effect-uai/core/Transcript"
|
|
9
10
|
import type { CommonStreamTranscribeRequest } from "@effect-uai/core/Transcriber"
|
|
11
|
+
import * as WebSocketSession from "@effect-uai/core/WebSocketSession"
|
|
10
12
|
import { httpStatusError, transportFailure } from "./codec.js"
|
|
11
13
|
import { type ElevenLabsRegion, resolveHost } from "./region.js"
|
|
12
14
|
|
|
@@ -200,29 +202,19 @@ export const buildWsUrl = (
|
|
|
200
202
|
export const encodeAudioFrame = (bytes: Uint8Array, sampleRate: number) =>
|
|
201
203
|
JSON.stringify({
|
|
202
204
|
message_type: "input_audio_chunk",
|
|
203
|
-
audio_base_64:
|
|
205
|
+
audio_base_64: Base64.encode(bytes),
|
|
204
206
|
sample_rate: sampleRate,
|
|
205
207
|
})
|
|
206
208
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
// ---------------------------------------------------------------------------
|
|
212
|
-
|
|
213
|
-
const handleServerMessage =
|
|
214
|
-
(
|
|
215
|
-
queue: Queue.Queue<TranscriptEvent, Cause.Done>,
|
|
216
|
-
mapEvent: (msg: typeof ServerMessage.Type) => TranscriptEvent | undefined,
|
|
217
|
-
) =>
|
|
218
|
-
(raw: string) =>
|
|
209
|
+
/** One raw text frame to at most one event; unknown or malformed frames yield `undefined`. */
|
|
210
|
+
const frameToEvent =
|
|
211
|
+
(mapEvent: (msg: typeof ServerMessage.Type) => TranscriptEvent | undefined) =>
|
|
212
|
+
(raw: string): Effect.Effect<TranscriptEvent | undefined> =>
|
|
219
213
|
Effect.gen(function* () {
|
|
220
214
|
const json = yield* JSONL.parseSafe(raw)
|
|
221
|
-
if (json === undefined) return
|
|
215
|
+
if (json === undefined) return undefined
|
|
222
216
|
const decoded = yield* decodeServerMessage(json).pipe(Effect.option)
|
|
223
|
-
|
|
224
|
-
const event = mapEvent(decoded.value)
|
|
225
|
-
if (event !== undefined) yield* Queue.offer(queue, event)
|
|
217
|
+
return Option.match(decoded, { onNone: () => undefined, onSome: mapEvent })
|
|
226
218
|
})
|
|
227
219
|
|
|
228
220
|
// ---------------------------------------------------------------------------
|
|
@@ -261,27 +253,25 @@ export const streamTranscription =
|
|
|
261
253
|
...(request.biasingTerms !== undefined && { keyterms: request.biasingTerms }),
|
|
262
254
|
includeTimestamps,
|
|
263
255
|
})
|
|
264
|
-
const socket = yield* Socket.makeWebSocket(url
|
|
265
|
-
// Effect's Socket treats all close codes as errors by default — that
|
|
266
|
-
// surfaces a clean server-side close as a stream failure. Whitelist
|
|
267
|
-
// standard clean-close codes (1000 / 1001 / 1005).
|
|
268
|
-
closeCodeIsError: (code) => code !== 1000 && code !== 1001 && code !== 1005,
|
|
269
|
-
})
|
|
270
|
-
const queue = yield* Queue.bounded<TranscriptEvent, Cause.Done>(64)
|
|
256
|
+
const socket = yield* Socket.makeWebSocket(url)
|
|
271
257
|
const sampleRate = request.inputFormat.sampleRate
|
|
258
|
+
const { write } = yield* socket.writer
|
|
259
|
+
const asAiError = WebSocketSession.toAiError("elevenlabs")
|
|
272
260
|
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
261
|
+
const events = Stream.fromPull(Socket.readerString(socket)).pipe(
|
|
262
|
+
Stream.scoped,
|
|
263
|
+
Stream.catchIf(WebSocketSession.isCleanClose, () => Stream.empty),
|
|
264
|
+
Stream.mapError(asAiError),
|
|
265
|
+
Stream.mapEffect(frameToEvent(wireToEvent(includeTimestamps))),
|
|
266
|
+
Stream.filter(Predicate.isNotUndefined),
|
|
267
|
+
)
|
|
277
268
|
|
|
278
|
-
//
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
.runString(handleServerMessage(queue, wireToEvent(includeTimestamps)))
|
|
283
|
-
.pipe(Effect.ensuring(Queue.end(queue)), Effect.forkScoped)
|
|
269
|
+
// Writes wait for the reader to connect.
|
|
270
|
+
const outgoing = Stream.map(audioIn, (bytes) => encodeAudioFrame(bytes, sampleRate)).pipe(
|
|
271
|
+
Stream.mapEffect((frame) => Effect.mapError(write(frame), asAiError)),
|
|
272
|
+
)
|
|
284
273
|
|
|
285
|
-
|
|
274
|
+
// The server's close ends the transcript; the audio running out does not.
|
|
275
|
+
return Stream.merge(events, Stream.drain(outgoing), { haltStrategy: "left" })
|
|
286
276
|
}),
|
|
287
277
|
)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, it } from "@effect/vitest"
|
|
2
|
+
import { Effect, Exit, Fiber, Redacted, Stream } from "effect"
|
|
3
|
+
import { Base64 } from "effect/encoding"
|
|
4
|
+
import { expect } from "vitest"
|
|
5
|
+
import * as FakeWebSocket from "@effect-uai/core/testing/FakeWebSocket"
|
|
6
|
+
import { streamSynthesis } from "./realtimeTts.js"
|
|
7
|
+
|
|
8
|
+
const request = { voiceId: "voice-1" }
|
|
9
|
+
|
|
10
|
+
const synthesizing = FakeWebSocket.make({
|
|
11
|
+
greeting: [
|
|
12
|
+
{ audio: Base64.encode(new Uint8Array([1, 2])), isFinal: null },
|
|
13
|
+
{ audio: null, isFinal: true },
|
|
14
|
+
],
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
/** Run a synthesis against `server`, closing it, if asked, once the frames are in. */
|
|
18
|
+
const synthesize = <E>(
|
|
19
|
+
server: FakeWebSocket.FakeWebSocketServer,
|
|
20
|
+
textIn: Stream.Stream<string, E>,
|
|
21
|
+
close?: { readonly code: number; readonly afterSent?: number },
|
|
22
|
+
) =>
|
|
23
|
+
Effect.gen(function* () {
|
|
24
|
+
const run = streamSynthesis({ apiKey: Redacted.make("test-key") })(textIn, request).pipe(
|
|
25
|
+
Stream.provide(server.layer),
|
|
26
|
+
)
|
|
27
|
+
const collector = yield* Effect.forkChild(Effect.exit(Stream.runCollect(run)))
|
|
28
|
+
if (close !== undefined) {
|
|
29
|
+
yield* server.greeted
|
|
30
|
+
yield* server.awaitSent(close.afterSent ?? 0)
|
|
31
|
+
yield* server.close(close.code)
|
|
32
|
+
}
|
|
33
|
+
return yield* Fiber.join(collector)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
describe("ElevenLabs realtime TTS socket", () => {
|
|
37
|
+
it.live("sends BOS before any text and EOS after it, and ends on a clean close", () =>
|
|
38
|
+
Effect.gen(function* () {
|
|
39
|
+
const server = yield* synthesizing
|
|
40
|
+
const exit = yield* synthesize(server, Stream.make("Hello", "", "world "), {
|
|
41
|
+
code: 1000,
|
|
42
|
+
afterSent: 4,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
expect(exit).toEqual(Exit.succeed([{ bytes: new Uint8Array([1, 2]) }]))
|
|
46
|
+
expect(yield* server.sent).toEqual([
|
|
47
|
+
{ text: " ", "xi-api-key": "test-key" },
|
|
48
|
+
{ text: "Hello " },
|
|
49
|
+
{ text: "world " },
|
|
50
|
+
{ text: "" },
|
|
51
|
+
])
|
|
52
|
+
}).pipe(Effect.scoped),
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
it.live("fails rather than ending when the connection drops", () =>
|
|
56
|
+
Effect.gen(function* () {
|
|
57
|
+
const server = yield* synthesizing
|
|
58
|
+
const exit = yield* synthesize(server, Stream.never, { code: 1011 })
|
|
59
|
+
|
|
60
|
+
expect(Exit.isFailure(exit) && JSON.stringify(exit.cause)).toContain("Unavailable")
|
|
61
|
+
}).pipe(Effect.scoped),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
it.live("fails with the text input's own error", () =>
|
|
65
|
+
Effect.gen(function* () {
|
|
66
|
+
const server = yield* synthesizing
|
|
67
|
+
const exit = yield* synthesize(server, Stream.fail("llm stream broke"))
|
|
68
|
+
|
|
69
|
+
expect(Exit.isFailure(exit) && JSON.stringify(exit.cause)).toContain("llm stream broke")
|
|
70
|
+
}).pipe(Effect.scoped),
|
|
71
|
+
)
|
|
72
|
+
})
|
package/src/realtimeTts.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import { Effect, Option, Predicate, Redacted, Result, Schema, Stream } from "effect"
|
|
2
|
+
import { Base64 } from "effect/encoding"
|
|
3
|
+
import * as Socket from "effect/socket/Socket"
|
|
3
4
|
import * as AiError from "@effect-uai/core/AiError"
|
|
4
5
|
import type { AudioChunk, AudioFormat } from "@effect-uai/core/Audio"
|
|
5
6
|
import * as JSONL from "@effect-uai/core/JSONL"
|
|
6
7
|
import type { CustomPronunciation } from "@effect-uai/core/SpeechSynthesizer"
|
|
8
|
+
import * as WebSocketSession from "@effect-uai/core/WebSocketSession"
|
|
7
9
|
import {
|
|
8
10
|
defaultFormat,
|
|
9
11
|
formatToOutputSlug,
|
|
@@ -85,7 +87,7 @@ const ServerFrame = Schema.Struct({
|
|
|
85
87
|
const decodeServerFrame = Schema.decodeUnknownEffect(ServerFrame)
|
|
86
88
|
|
|
87
89
|
const decodeAudio = (b64: string): Effect.Effect<Uint8Array, AiError.AiError> =>
|
|
88
|
-
Result.match(
|
|
90
|
+
Result.match(Base64.decode(b64), {
|
|
89
91
|
onSuccess: Effect.succeed,
|
|
90
92
|
onFailure: (cause) =>
|
|
91
93
|
Effect.fail(
|
|
@@ -96,23 +98,24 @@ const decodeAudio = (b64: string): Effect.Effect<Uint8Array, AiError.AiError> =>
|
|
|
96
98
|
),
|
|
97
99
|
})
|
|
98
100
|
|
|
99
|
-
|
|
101
|
+
/** One raw text frame to at most one chunk; error frames are logged and dropped. */
|
|
102
|
+
const frameToChunk = (raw: string): Effect.Effect<AudioChunk | undefined> =>
|
|
100
103
|
Effect.gen(function* () {
|
|
101
104
|
const json = yield* JSONL.parseSafe(raw)
|
|
102
|
-
if (json === undefined) return
|
|
105
|
+
if (json === undefined) return undefined
|
|
103
106
|
const decoded = yield* decodeServerFrame(json).pipe(Effect.option)
|
|
104
|
-
if (decoded._tag === "None") return
|
|
107
|
+
if (decoded._tag === "None") return undefined
|
|
105
108
|
const frame = decoded.value
|
|
106
109
|
if (frame.error !== undefined) {
|
|
107
110
|
yield* Effect.logWarning("[elevenlabs-tts] server error frame", {
|
|
108
111
|
error: frame.error,
|
|
109
112
|
message: frame.message,
|
|
110
113
|
})
|
|
111
|
-
return
|
|
114
|
+
return undefined
|
|
112
115
|
}
|
|
113
|
-
if (frame.audio == null || frame.audio === "") return
|
|
116
|
+
if (frame.audio == null || frame.audio === "") return undefined
|
|
114
117
|
const bytes = yield* decodeAudio(frame.audio).pipe(Effect.option)
|
|
115
|
-
|
|
118
|
+
return Option.match(bytes, { onNone: () => undefined, onSome: (b) => ({ bytes: b }) })
|
|
116
119
|
})
|
|
117
120
|
|
|
118
121
|
// ---------------------------------------------------------------------------
|
|
@@ -129,35 +132,31 @@ export const streamSynthesis =
|
|
|
129
132
|
Effect.gen(function* () {
|
|
130
133
|
yield* rejectInlinePronunciations(request.pronunciations)
|
|
131
134
|
const slug = yield* formatToOutputSlug(request.outputFormat ?? defaultFormat)
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
.runString(handleServerFrame(queue))
|
|
159
|
-
.pipe(Effect.ensuring(Queue.end(queue)), Effect.forkScoped)
|
|
160
|
-
|
|
161
|
-
return Stream.fromQueue(queue)
|
|
135
|
+
const socket = yield* Socket.makeWebSocket(buildWsUrl(cfg, request, slug))
|
|
136
|
+
const { write } = yield* socket.writer
|
|
137
|
+
const asAiError = WebSocketSession.toAiError("elevenlabs")
|
|
138
|
+
|
|
139
|
+
// ElevenLabs closes `/stream-input` with 1000 after the final audio chunk.
|
|
140
|
+
const chunks = Stream.fromPull(Socket.readerString(socket)).pipe(
|
|
141
|
+
Stream.scoped,
|
|
142
|
+
Stream.catchIf(WebSocketSession.isCleanClose, () => Stream.empty),
|
|
143
|
+
Stream.mapError(asAiError),
|
|
144
|
+
Stream.mapEffect(frameToChunk),
|
|
145
|
+
Stream.filter(Predicate.isNotUndefined),
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
// BOS, then the text, then EOS. Writes wait for the reader to connect.
|
|
149
|
+
const outgoing = Stream.concat(
|
|
150
|
+
Stream.make(bosFrame(cfg, request)),
|
|
151
|
+
Stream.concat(
|
|
152
|
+
textIn.pipe(
|
|
153
|
+
Stream.filter((text) => text.length > 0),
|
|
154
|
+
Stream.map(textFrame),
|
|
155
|
+
),
|
|
156
|
+
Stream.make(eosFrame),
|
|
157
|
+
),
|
|
158
|
+
).pipe(Stream.mapEffect((frame) => Effect.mapError(write(frame), asAiError)))
|
|
159
|
+
|
|
160
|
+
return Stream.merge(chunks, Stream.drain(outgoing), { haltStrategy: "left" })
|
|
162
161
|
}),
|
|
163
162
|
)
|
package/dist/dist-C0AvqZTC.mjs
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import { t as __commonJSMin } from "./rolldown-runtime-EAsCxrDo.mjs";
|
|
2
|
-
import "./test.DNmyFkvJ-N3QqMRwZ.mjs";
|
|
3
|
-
//#region ../../../node_modules/.pnpm/vite@7.3.2_@types+node@26.2.0_jiti@2.6.1_lightningcss@1.32.0_tsx@4.23.12_yaml@2.9.0/node_modules/vite/dist/node/module-runner.js
|
|
4
|
-
let SOURCEMAPPING_URL = "sourceMa";
|
|
5
|
-
SOURCEMAPPING_URL += "ppingURL";
|
|
6
|
-
typeof process < "u" && process.platform;
|
|
7
|
-
(async function() {}).constructor;
|
|
8
|
-
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
9
|
-
var intToChar = /* @__PURE__ */ new Uint8Array(64);
|
|
10
|
-
var charToInt = /* @__PURE__ */ new Uint8Array(128);
|
|
11
|
-
for (let i = 0; i < chars.length; i++) {
|
|
12
|
-
let c = chars.charCodeAt(i);
|
|
13
|
-
intToChar[i] = c, charToInt[c] = i;
|
|
14
|
-
}
|
|
15
|
-
`${SOURCEMAPPING_URL}`;
|
|
16
|
-
Error.prepareStackTrace;
|
|
17
|
-
const customizationHookNamespace = "vite-module-runner:import-meta-resolve/v1/";
|
|
18
|
-
`${JSON.stringify(customizationHookNamespace)}${JSON.stringify(customizationHookNamespace)}`;
|
|
19
|
-
new Proxy({}, { get(_, p) {
|
|
20
|
-
throw Error(`[module runner] Dynamic access of "import.meta.env" is not supported. Please, use "import.meta.env.${String(p)}" instead.`);
|
|
21
|
-
} });
|
|
22
|
-
//#endregion
|
|
23
|
-
//#region ../../../node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/branding.js
|
|
24
|
-
var require_branding = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
25
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
}));
|
|
27
|
-
//#endregion
|
|
28
|
-
//#region ../../../node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/messages.js
|
|
29
|
-
var require_messages = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
30
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
|
-
}));
|
|
32
|
-
//#endregion
|
|
33
|
-
//#region ../../../node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/overloads.js
|
|
34
|
-
var require_overloads = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
}));
|
|
37
|
-
//#endregion
|
|
38
|
-
//#region ../../../node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/utils.js
|
|
39
|
-
var require_utils = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
40
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
-
}));
|
|
42
|
-
//#endregion
|
|
43
|
-
//#region ../../../node_modules/.pnpm/vitest@4.1.11_@types+node@26.2.0_vite@7.3.2_@types+node@26.2.0_jiti@2.6.1_lightningcss@1.32.0_tsx@4.23.12_yaml@2.9.0_/node_modules/vitest/dist/index.js
|
|
44
|
-
var import_dist = (/* @__PURE__ */ __commonJSMin(((exports) => {
|
|
45
|
-
var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
|
|
46
|
-
if (k2 === void 0) k2 = k;
|
|
47
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
48
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) desc = {
|
|
49
|
-
enumerable: true,
|
|
50
|
-
get: function() {
|
|
51
|
-
return m[k];
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
Object.defineProperty(o, k2, desc);
|
|
55
|
-
}) : (function(o, m, k, k2) {
|
|
56
|
-
if (k2 === void 0) k2 = k;
|
|
57
|
-
o[k2] = m[k];
|
|
58
|
-
}));
|
|
59
|
-
var __exportStar = exports && exports.__exportStar || function(m, exports$1) {
|
|
60
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports$1, p)) __createBinding(exports$1, m, p);
|
|
61
|
-
};
|
|
62
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
63
|
-
exports.expectTypeOf = void 0;
|
|
64
|
-
__exportStar(require_branding(), exports);
|
|
65
|
-
__exportStar(require_messages(), exports);
|
|
66
|
-
__exportStar(require_overloads(), exports);
|
|
67
|
-
__exportStar(require_utils(), exports);
|
|
68
|
-
const fn = () => true;
|
|
69
|
-
/**
|
|
70
|
-
* Similar to Jest's `expect`, but with type-awareness.
|
|
71
|
-
* Gives you access to a number of type-matchers that let you make assertions about the
|
|
72
|
-
* form of a reference or generic type parameter.
|
|
73
|
-
*
|
|
74
|
-
* @example
|
|
75
|
-
* ```ts
|
|
76
|
-
* import { foo, bar } from '../foo'
|
|
77
|
-
* import { expectTypeOf } from 'expect-type'
|
|
78
|
-
*
|
|
79
|
-
* test('foo types', () => {
|
|
80
|
-
* // make sure `foo` has type { a: number }
|
|
81
|
-
* expectTypeOf(foo).toMatchTypeOf({ a: 1 })
|
|
82
|
-
* expectTypeOf(foo).toHaveProperty('a').toBeNumber()
|
|
83
|
-
*
|
|
84
|
-
* // make sure `bar` is a function taking a string:
|
|
85
|
-
* expectTypeOf(bar).parameter(0).toBeString()
|
|
86
|
-
* expectTypeOf(bar).returns.not.toBeAny()
|
|
87
|
-
* })
|
|
88
|
-
* ```
|
|
89
|
-
*
|
|
90
|
-
* @description
|
|
91
|
-
* See the [full docs](https://npmjs.com/package/expect-type#documentation) for lots more examples.
|
|
92
|
-
*/
|
|
93
|
-
const expectTypeOf = (_actual) => {
|
|
94
|
-
const nonFunctionProperties = [
|
|
95
|
-
"parameters",
|
|
96
|
-
"returns",
|
|
97
|
-
"resolves",
|
|
98
|
-
"not",
|
|
99
|
-
"items",
|
|
100
|
-
"constructorParameters",
|
|
101
|
-
"thisParameter",
|
|
102
|
-
"instance",
|
|
103
|
-
"guards",
|
|
104
|
-
"asserts",
|
|
105
|
-
"branded"
|
|
106
|
-
];
|
|
107
|
-
const obj = {
|
|
108
|
-
toBeAny: fn,
|
|
109
|
-
toBeUnknown: fn,
|
|
110
|
-
toBeNever: fn,
|
|
111
|
-
toBeFunction: fn,
|
|
112
|
-
toBeObject: fn,
|
|
113
|
-
toBeArray: fn,
|
|
114
|
-
toBeString: fn,
|
|
115
|
-
toBeNumber: fn,
|
|
116
|
-
toBeBoolean: fn,
|
|
117
|
-
toBeVoid: fn,
|
|
118
|
-
toBeSymbol: fn,
|
|
119
|
-
toBeNull: fn,
|
|
120
|
-
toBeUndefined: fn,
|
|
121
|
-
toBeNullable: fn,
|
|
122
|
-
toBeBigInt: fn,
|
|
123
|
-
toMatchTypeOf: fn,
|
|
124
|
-
toEqualTypeOf: fn,
|
|
125
|
-
toBeConstructibleWith: fn,
|
|
126
|
-
toMatchObjectType: fn,
|
|
127
|
-
toExtend: fn,
|
|
128
|
-
map: exports.expectTypeOf,
|
|
129
|
-
toBeCallableWith: exports.expectTypeOf,
|
|
130
|
-
extract: exports.expectTypeOf,
|
|
131
|
-
exclude: exports.expectTypeOf,
|
|
132
|
-
pick: exports.expectTypeOf,
|
|
133
|
-
omit: exports.expectTypeOf,
|
|
134
|
-
toHaveProperty: exports.expectTypeOf,
|
|
135
|
-
parameter: exports.expectTypeOf
|
|
136
|
-
};
|
|
137
|
-
nonFunctionProperties.forEach((prop) => Object.defineProperty(obj, prop, { get: () => (0, exports.expectTypeOf)({}) }));
|
|
138
|
-
return obj;
|
|
139
|
-
};
|
|
140
|
-
exports.expectTypeOf = expectTypeOf;
|
|
141
|
-
})))();
|
|
142
|
-
//#endregion
|
|
143
|
-
export { import_dist as t };
|
|
144
|
-
|
|
145
|
-
//# sourceMappingURL=dist-C0AvqZTC.mjs.map
|