@effect-uai/google 0.6.0 → 0.8.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/Gemini.d.mts.map +1 -1
- package/dist/Gemini.mjs +7 -2
- package/dist/Gemini.mjs.map +1 -1
- package/dist/GeminiEmbedding.d.mts.map +1 -1
- package/dist/GeminiEmbedding.mjs +3 -3
- package/dist/GeminiEmbedding.mjs.map +1 -1
- package/dist/GeminiSynthesizer.d.mts.map +1 -1
- package/dist/GeminiSynthesizer.mjs +22 -4
- package/dist/GeminiSynthesizer.mjs.map +1 -1
- package/dist/GeminiSynthesizer.test.mjs +14 -48
- package/dist/GeminiSynthesizer.test.mjs.map +1 -1
- package/dist/LyriaGenerator.d.mts +18 -18
- package/dist/LyriaGenerator.d.mts.map +1 -1
- package/dist/LyriaGenerator.mjs +59 -42
- package/dist/LyriaGenerator.mjs.map +1 -1
- package/dist/LyriaGenerator.test.mjs +26 -72
- package/dist/LyriaGenerator.test.mjs.map +1 -1
- package/dist/codec.d.mts +11 -3
- package/dist/codec.d.mts.map +1 -1
- package/dist/codec.mjs +20 -3
- package/dist/codec.mjs.map +1 -1
- package/dist/index.d.mts +2 -3
- package/dist/index.mjs +1 -2
- package/dist/models.d.mts +1 -10
- package/dist/models.d.mts.map +1 -1
- package/package.json +2 -6
- package/src/Gemini.ts +15 -1
- package/src/GeminiEmbedding.ts +14 -9
- package/src/GeminiSynthesizer.test.ts +13 -67
- package/src/GeminiSynthesizer.ts +30 -4
- package/src/LyriaGenerator.test.ts +34 -78
- package/src/LyriaGenerator.ts +92 -73
- package/src/codec.ts +31 -1
- package/src/index.ts +0 -1
- package/src/models.ts +0 -17
- package/dist/GeminiTranscriber.d.mts +0 -42
- package/dist/GeminiTranscriber.d.mts.map +0 -1
- package/dist/GeminiTranscriber.mjs +0 -80
- package/dist/GeminiTranscriber.mjs.map +0 -1
- package/dist/GeminiTranscriber.test.d.mts +0 -1
- package/dist/GeminiTranscriber.test.mjs +0 -125
- package/dist/GeminiTranscriber.test.mjs.map +0 -1
- package/src/GeminiTranscriber.test.ts +0 -136
- package/src/GeminiTranscriber.ts +0 -204
package/src/LyriaGenerator.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
-
Array as Arr,
|
|
3
2
|
Context,
|
|
3
|
+
Duration,
|
|
4
4
|
Effect,
|
|
5
5
|
Encoding,
|
|
6
6
|
Layer,
|
|
7
7
|
Match,
|
|
8
|
-
Option,
|
|
9
8
|
Redacted,
|
|
10
9
|
Result,
|
|
11
10
|
Schema,
|
|
@@ -14,13 +13,16 @@ import {
|
|
|
14
13
|
import { HttpClient, HttpClientRequest } from "effect/unstable/http"
|
|
15
14
|
import * as AiError from "@effect-uai/core/AiError"
|
|
16
15
|
import type { AudioChunk, AudioFormat } from "@effect-uai/core/Audio"
|
|
16
|
+
import { warnDroppedWhen } from "@effect-uai/core/Capabilities"
|
|
17
17
|
import type {
|
|
18
18
|
CommonGenerateMusicRequest,
|
|
19
19
|
CommonStreamGenerateMusicRequest,
|
|
20
|
+
GenerateResult,
|
|
20
21
|
MusicResult,
|
|
21
22
|
MusicSessionInput,
|
|
22
|
-
|
|
23
|
+
MusicStreamEvent,
|
|
23
24
|
} from "@effect-uai/core/Music"
|
|
25
|
+
import { singleVariant } from "@effect-uai/core/Music"
|
|
24
26
|
import { MusicGenerator, type MusicGeneratorService } from "@effect-uai/core/MusicGenerator"
|
|
25
27
|
import type { LyriaModel } from "./models.js"
|
|
26
28
|
|
|
@@ -29,25 +31,34 @@ import type { LyriaModel } from "./models.js"
|
|
|
29
31
|
// ---------------------------------------------------------------------------
|
|
30
32
|
|
|
31
33
|
/**
|
|
32
|
-
* Lyria-typed generate request. Narrows `model` to `LyriaModel`.
|
|
33
|
-
*
|
|
34
|
-
* `
|
|
35
|
-
* `
|
|
36
|
-
*
|
|
34
|
+
* Lyria-typed generate request. Narrows `model` to `LyriaModel`.
|
|
35
|
+
*
|
|
36
|
+
* Lyria 3 sync `generateContent` has no structured wire fields for
|
|
37
|
+
* `lyrics` / `duration` / `seed` / `instrumental` / `bpm` / `scale` /
|
|
38
|
+
* etc. Those fields on the Common request are logged as
|
|
39
|
+
* dropped-bucket-2 hints; the adapter never rewrites or augments the
|
|
40
|
+
* caller's prompt on their behalf. If you want lyrics or vocal
|
|
41
|
+
* suppression, include them in your `prompt` text yourself.
|
|
42
|
+
*
|
|
43
|
+
* Output format: only `audio/mp3` and `audio/wav` are accepted; other
|
|
44
|
+
* containers fail `Unsupported`. The Clip model variant is fixed at
|
|
45
|
+
* `audio/mp3`.
|
|
37
46
|
*/
|
|
38
47
|
export type LyriaGenerateRequest = Omit<CommonGenerateMusicRequest, "model"> & {
|
|
39
48
|
readonly model: LyriaModel
|
|
40
49
|
}
|
|
41
50
|
|
|
42
51
|
export type LyriaGeneratorService = {
|
|
43
|
-
readonly generate: (
|
|
52
|
+
readonly generate: (
|
|
53
|
+
request: LyriaGenerateRequest,
|
|
54
|
+
) => Effect.Effect<GenerateResult, AiError.AiError>
|
|
44
55
|
readonly streamGeneration: (
|
|
45
56
|
request: LyriaGenerateRequest,
|
|
46
57
|
) => Stream.Stream<AudioChunk, AiError.AiError>
|
|
47
58
|
readonly streamGenerationFrom: <E, R>(
|
|
48
59
|
input: Stream.Stream<MusicSessionInput, E, R>,
|
|
49
60
|
request: CommonStreamGenerateMusicRequest,
|
|
50
|
-
) => Stream.Stream<
|
|
61
|
+
) => Stream.Stream<MusicStreamEvent, AiError.AiError | E, R>
|
|
51
62
|
}
|
|
52
63
|
|
|
53
64
|
export class LyriaGenerator extends Context.Service<LyriaGenerator, LyriaGeneratorService>()(
|
|
@@ -60,7 +71,7 @@ export type Config = {
|
|
|
60
71
|
}
|
|
61
72
|
|
|
62
73
|
// ---------------------------------------------------------------------------
|
|
63
|
-
// Codec
|
|
74
|
+
// Codec: AudioFormat → Lyria mimeType
|
|
64
75
|
// ---------------------------------------------------------------------------
|
|
65
76
|
|
|
66
77
|
export type LyriaResponseMimeType = "audio/mp3" | "audio/wav"
|
|
@@ -85,10 +96,10 @@ export const containerToMimeType: (
|
|
|
85
96
|
)
|
|
86
97
|
|
|
87
98
|
/**
|
|
88
|
-
* Is this a Lyria 3 Clip model variant? Clip is fixed at 30 s MP3
|
|
99
|
+
* Is this a Lyria 3 Clip model variant? Clip is fixed at 30 s MP3, it
|
|
89
100
|
* does not accept a `responseFormat` on `generationConfig` (the live
|
|
90
|
-
* API rejects unknown fields
|
|
91
|
-
*
|
|
101
|
+
* API rejects unknown fields). Pro accepts the documented
|
|
102
|
+
* `responseFormat` knob.
|
|
92
103
|
*/
|
|
93
104
|
const isClipModel = (model: string): boolean => model.includes("clip")
|
|
94
105
|
|
|
@@ -110,38 +121,51 @@ export const realizedFormat: (mime: LyriaResponseMimeType) => AudioFormat =
|
|
|
110
121
|
Match.exhaustive,
|
|
111
122
|
)
|
|
112
123
|
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
// Bucket-2 warn-and-drop for Common fields Lyria 3 can't honor
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
|
|
113
128
|
/**
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
129
|
+
* Warn (don't fail) when the caller passed Common fields Lyria 3 has
|
|
130
|
+
* no wire field for. Bucket-2 per capabilities policy: provider has
|
|
131
|
+
* no structured interpretation; the output is still valid music, just
|
|
132
|
+
* less aligned with the caller's hint. We never embed these into the
|
|
133
|
+
* prompt on the caller's behalf, prompt construction is the
|
|
134
|
+
* developer's job.
|
|
118
135
|
*/
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
136
|
+
const warnDroppedHints = (request: LyriaGenerateRequest): Effect.Effect<void> =>
|
|
137
|
+
Effect.all(
|
|
138
|
+
[
|
|
139
|
+
warnDroppedWhen(request.lyrics, {
|
|
140
|
+
provider: "lyria",
|
|
141
|
+
capability: "lyrics",
|
|
142
|
+
field: "lyrics",
|
|
143
|
+
reason:
|
|
144
|
+
"Lyria 3 sync has no `lyrics` wire field. If you want vocals to follow specific lyrics, embed them in your prompt yourself (e.g. with `[Verse]` / `[Chorus]` tags).",
|
|
145
|
+
}),
|
|
146
|
+
warnDroppedWhen(
|
|
147
|
+
request.duration !== undefined ? Duration.toMillis(request.duration) : undefined,
|
|
148
|
+
{
|
|
149
|
+
provider: "lyria",
|
|
150
|
+
capability: "duration",
|
|
151
|
+
field: "duration",
|
|
152
|
+
reason:
|
|
153
|
+
"Lyria 3 clip is fixed at 30 s; pro derives duration from prompt content. There is no wire field for duration. If you want to steer length, mention it in your prompt.",
|
|
154
|
+
},
|
|
155
|
+
),
|
|
156
|
+
warnDroppedWhen(request.seed, {
|
|
157
|
+
provider: "lyria",
|
|
158
|
+
capability: "seed",
|
|
159
|
+
field: "seed",
|
|
160
|
+
reason:
|
|
161
|
+
"Lyria 3 (Gemini surface) does not expose a seed parameter. Output is non-deterministic across identical prompts. Use Lyria 2 (Vertex) for seeded generation.",
|
|
162
|
+
}),
|
|
163
|
+
],
|
|
164
|
+
{ discard: true },
|
|
127
165
|
)
|
|
128
|
-
const hints = Arr.getSomes([
|
|
129
|
-
request.instrumental === true ? Option.some("Instrumental only — no vocals.") : Option.none(),
|
|
130
|
-
request.bpm !== undefined ? Option.some(`BPM: ${request.bpm}.`) : Option.none(),
|
|
131
|
-
request.scale !== undefined ? Option.some(`Key/scale: ${request.scale}.`) : Option.none(),
|
|
132
|
-
request.durationSeconds !== undefined
|
|
133
|
-
? Option.some(`Target duration: ${request.durationSeconds}s.`)
|
|
134
|
-
: Option.none(),
|
|
135
|
-
])
|
|
136
|
-
const lyricsBlock =
|
|
137
|
-
request.lyrics !== undefined && request.instrumental !== true
|
|
138
|
-
? `\n\nLyrics:\n${request.lyrics}`
|
|
139
|
-
: ""
|
|
140
|
-
return [promptText, ...hints].join(" ") + lyricsBlock
|
|
141
|
-
}
|
|
142
166
|
|
|
143
167
|
// ---------------------------------------------------------------------------
|
|
144
|
-
// Wire response schema
|
|
168
|
+
// Wire response schema
|
|
145
169
|
// ---------------------------------------------------------------------------
|
|
146
170
|
|
|
147
171
|
const InlineData = Schema.Struct({
|
|
@@ -245,26 +269,23 @@ const generateImpl =
|
|
|
245
269
|
(cfg: Config) =>
|
|
246
270
|
(
|
|
247
271
|
request: LyriaGenerateRequest,
|
|
248
|
-
): Effect.Effect<
|
|
272
|
+
): Effect.Effect<GenerateResult, AiError.AiError, HttpClient.HttpClient> =>
|
|
249
273
|
Effect.gen(function* () {
|
|
250
274
|
const client = yield* HttpClient.HttpClient
|
|
251
275
|
const requestedContainer = (request.outputFormat?.container ??
|
|
252
276
|
"mp3") satisfies AudioFormat["container"]
|
|
253
|
-
const
|
|
254
|
-
//
|
|
255
|
-
//
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
})
|
|
262
|
-
}
|
|
263
|
-
const promptText = buildPrompt(request)
|
|
277
|
+
const requestedMime = yield* containerToMimeType(requestedContainer)
|
|
278
|
+
// lyria-3-clip-preview has no `responseFormat` wire field and always
|
|
279
|
+
// emits mp3, so there is no format to send and no 400 to surface. Rather
|
|
280
|
+
// than keep a per-model table that rejects `wav`, report the format the
|
|
281
|
+
// model actually produces (mp3) so the result is never mislabeled.
|
|
282
|
+
// lyria-3-pro honours the requested container.
|
|
283
|
+
const mimeType = isClipModel(request.model) ? ("audio/mp3" as const) : requestedMime
|
|
284
|
+
yield* warnDroppedHints(request)
|
|
264
285
|
const body = isClipModel(request.model)
|
|
265
|
-
? { contents: [{ parts: [{ text:
|
|
286
|
+
? { contents: [{ parts: [{ text: request.prompt }] }] }
|
|
266
287
|
: {
|
|
267
|
-
contents: [{ parts: [{ text:
|
|
288
|
+
contents: [{ parts: [{ text: request.prompt }] }],
|
|
268
289
|
generationConfig: {
|
|
269
290
|
responseModalities: ["AUDIO", "TEXT"],
|
|
270
291
|
responseFormat: { audio: { mimeType } },
|
|
@@ -290,10 +311,6 @@ const generateImpl =
|
|
|
290
311
|
)
|
|
291
312
|
const { inline, text } = extractParts(wire)
|
|
292
313
|
if (inline === undefined) {
|
|
293
|
-
// No audio came back. Most common cause: Lyria filtered the prompt
|
|
294
|
-
// (named-artist references, copyrighted lyrics, etc.). Surface the
|
|
295
|
-
// full response body — text parts, finish reasons, prompt-feedback
|
|
296
|
-
// blocks — so the caller can see why.
|
|
297
314
|
return yield* new AiError.GenerationFailed({
|
|
298
315
|
provider: "lyria",
|
|
299
316
|
raw: {
|
|
@@ -305,12 +322,13 @@ const generateImpl =
|
|
|
305
322
|
})
|
|
306
323
|
}
|
|
307
324
|
const bytes = yield* decodeBase64ToBytes(inline.data)
|
|
308
|
-
|
|
309
|
-
format: realizedFormat(mimeType),
|
|
310
|
-
|
|
325
|
+
const result: MusicResult = {
|
|
326
|
+
audio: { format: realizedFormat(mimeType), bytes },
|
|
327
|
+
provider: "lyria",
|
|
311
328
|
...(text !== undefined && { lyrics: text }),
|
|
312
|
-
watermark:
|
|
313
|
-
}
|
|
329
|
+
watermark: "synthid",
|
|
330
|
+
}
|
|
331
|
+
return singleVariant(result)
|
|
314
332
|
})
|
|
315
333
|
|
|
316
334
|
const streamGenerationImpl =
|
|
@@ -320,22 +338,22 @@ const streamGenerationImpl =
|
|
|
320
338
|
): Stream.Stream<AudioChunk, AiError.AiError, HttpClient.HttpClient> =>
|
|
321
339
|
Stream.unwrap(
|
|
322
340
|
Effect.map(generateImpl(cfg)(request), (result) =>
|
|
323
|
-
Stream.succeed<AudioChunk>({ bytes: result.bytes }),
|
|
341
|
+
Stream.succeed<AudioChunk>({ bytes: result.primary.audio.bytes }),
|
|
324
342
|
),
|
|
325
343
|
)
|
|
326
344
|
|
|
327
345
|
/**
|
|
328
|
-
* Lyria 3 sync has no bidirectional session at the wire level
|
|
329
|
-
* Lyria RealTime (
|
|
330
|
-
*
|
|
346
|
+
* Lyria 3 sync has no bidirectional session at the wire level, that's
|
|
347
|
+
* Lyria RealTime (separate Layer). The provider Layer here also does
|
|
348
|
+
* NOT register `MusicInteractiveSession`, so callers using
|
|
331
349
|
* `MusicGenerator.streamGenerationFrom` against only this Layer get a
|
|
332
350
|
* compile-time error before this runtime fallback ever fires.
|
|
333
351
|
*/
|
|
334
352
|
const streamGenerationFromUnsupported = <E, R>(
|
|
335
353
|
_input: Stream.Stream<MusicSessionInput, E, R>,
|
|
336
354
|
_request: CommonStreamGenerateMusicRequest,
|
|
337
|
-
): Stream.Stream<
|
|
338
|
-
const fail: Stream.Stream<
|
|
355
|
+
): Stream.Stream<MusicStreamEvent, AiError.AiError | E, R> => {
|
|
356
|
+
const fail: Stream.Stream<MusicStreamEvent, AiError.AiError | E, R> = Stream.fail(
|
|
339
357
|
new AiError.Unsupported({
|
|
340
358
|
provider: "lyria",
|
|
341
359
|
capability: "streamGenerationFrom",
|
|
@@ -365,7 +383,7 @@ export const make = (
|
|
|
365
383
|
* Layer that registers both the provider-specific `LyriaGenerator` tag
|
|
366
384
|
* and the generic `MusicGenerator` tag.
|
|
367
385
|
*
|
|
368
|
-
* Does NOT register the `MusicInteractiveSession` capability marker
|
|
386
|
+
* Does NOT register the `MusicInteractiveSession` capability marker,
|
|
369
387
|
* Lyria 3 sync has no bidirectional session at the wire level. Code
|
|
370
388
|
* calling `MusicGenerator.streamGenerationFrom` will fail to typecheck
|
|
371
389
|
* against this Layer alone, which is the intended UX.
|
|
@@ -380,9 +398,10 @@ export const layer = (
|
|
|
380
398
|
Effect.map(
|
|
381
399
|
make(cfg),
|
|
382
400
|
(s): MusicGeneratorService => ({
|
|
383
|
-
generate: (req: CommonGenerateMusicRequest) =>
|
|
401
|
+
generate: (req: CommonGenerateMusicRequest) =>
|
|
402
|
+
s.generate({ ...req, model: req.model as LyriaModel }),
|
|
384
403
|
streamGeneration: (req: CommonStreamGenerateMusicRequest) =>
|
|
385
|
-
s.streamGeneration(req as
|
|
404
|
+
s.streamGeneration({ ...req, model: req.model as LyriaModel }),
|
|
386
405
|
streamGenerationFrom: s.streamGenerationFrom,
|
|
387
406
|
}),
|
|
388
407
|
),
|
package/src/codec.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
} from "@effect-uai/core/Items"
|
|
9
9
|
import type { ToolDescriptor } from "@effect-uai/core/Tool"
|
|
10
10
|
import type { Turn } from "@effect-uai/core/Turn"
|
|
11
|
+
import type { CommonRequest } from "@effect-uai/core/LanguageModel"
|
|
11
12
|
|
|
12
13
|
// ---------------------------------------------------------------------------
|
|
13
14
|
// Wire schemas - the subset of Gemini's streamGenerateContent payload we
|
|
@@ -341,10 +342,39 @@ const toolDescriptorsToTools = (tools: ReadonlyArray<ToolDescriptor>): ReadonlyA
|
|
|
341
342
|
},
|
|
342
343
|
]
|
|
343
344
|
|
|
345
|
+
/**
|
|
346
|
+
* Map our cross-provider `toolChoice` onto Gemini's `functionCallingConfig`.
|
|
347
|
+
* Gemini honours the same three modes plus a function allow-list, so the
|
|
348
|
+
* choice is mapped, not dropped. `undefined` keeps the API default (`AUTO`).
|
|
349
|
+
*/
|
|
350
|
+
const toolChoiceToFunctionCallingConfig = (
|
|
351
|
+
toolChoice: CommonRequest["toolChoice"],
|
|
352
|
+
): RequestToolConfig["functionCallingConfig"] =>
|
|
353
|
+
Match.value(toolChoice ?? "auto").pipe(
|
|
354
|
+
Match.when("auto", () => ({ mode: "AUTO" as const })),
|
|
355
|
+
Match.when("required", () => ({ mode: "ANY" as const })),
|
|
356
|
+
Match.when("none", () => ({ mode: "NONE" as const })),
|
|
357
|
+
Match.orElse((tc) => ({ mode: "ANY" as const, allowedFunctionNames: [tc.name] })),
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* URL-form images need Gemini's Files API (upload, then `fileData`); we
|
|
362
|
+
* don't pre-upload, so such a part would otherwise vanish from the request.
|
|
363
|
+
* The caller fails the request with `Unsupported` instead of silently
|
|
364
|
+
* dropping the image.
|
|
365
|
+
*/
|
|
366
|
+
export const hasUrlImageSource = (history: ReadonlyArray<HistoryItem>): boolean =>
|
|
367
|
+
history.some(
|
|
368
|
+
(item) =>
|
|
369
|
+
item.type === "message" &&
|
|
370
|
+
item.content.some((b) => b.type === "input_image" && b.source._tag === "url"),
|
|
371
|
+
)
|
|
372
|
+
|
|
344
373
|
export const buildRequestBody = (
|
|
345
374
|
history: ReadonlyArray<HistoryItem>,
|
|
346
375
|
generationConfig: Option.Option<GenerationConfig>,
|
|
347
376
|
tools: ReadonlyArray<ToolDescriptor> = [],
|
|
377
|
+
toolChoice: CommonRequest["toolChoice"] = undefined,
|
|
348
378
|
): RequestBody => {
|
|
349
379
|
const systemTexts = pipe(allMessages(history), Arr.filterMap(systemMessageText))
|
|
350
380
|
const contents = pipe(history, Arr.filterMap(itemToContent(history)))
|
|
@@ -360,7 +390,7 @@ export const buildRequestBody = (
|
|
|
360
390
|
}),
|
|
361
391
|
...(requestTools.length > 0 && {
|
|
362
392
|
tools: requestTools,
|
|
363
|
-
toolConfig: { functionCallingConfig:
|
|
393
|
+
toolConfig: { functionCallingConfig: toolChoiceToFunctionCallingConfig(toolChoice) },
|
|
364
394
|
}),
|
|
365
395
|
}
|
|
366
396
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export * as Gemini from "./Gemini.js"
|
|
2
2
|
export * as GeminiEmbedding from "./GeminiEmbedding.js"
|
|
3
3
|
export * as GeminiSynthesizer from "./GeminiSynthesizer.js"
|
|
4
|
-
export * as GeminiTranscriber from "./GeminiTranscriber.js"
|
|
5
4
|
export * as LyriaGenerator from "./LyriaGenerator.js"
|
|
6
5
|
export * as codec from "./codec.js"
|
|
7
6
|
export * from "./models.js"
|
package/src/models.ts
CHANGED
|
@@ -44,23 +44,6 @@ export type GeminiTtsModel =
|
|
|
44
44
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
45
45
|
| (string & {})
|
|
46
46
|
|
|
47
|
-
/**
|
|
48
|
-
* Known Gemini models that accept audio input via `inlineData` /
|
|
49
|
-
* `fileData` on `generateContent`. Used by `GeminiTranscriber` —
|
|
50
|
-
* transcription is prompt-driven on top of these models, not a
|
|
51
|
-
* dedicated endpoint.
|
|
52
|
-
*
|
|
53
|
-
* Reference: https://ai.google.dev/gemini-api/docs/audio
|
|
54
|
-
*/
|
|
55
|
-
export type GeminiSttModel =
|
|
56
|
-
| "gemini-3-flash-preview"
|
|
57
|
-
| "gemini-3.1-pro-preview"
|
|
58
|
-
| "gemini-3.1-flash-lite-preview"
|
|
59
|
-
| "gemini-2.5-flash"
|
|
60
|
-
| "gemini-2.5-pro"
|
|
61
|
-
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
62
|
-
| (string & {})
|
|
63
|
-
|
|
64
47
|
/**
|
|
65
48
|
* 30 prebuilt voice names for Gemini TTS. No custom-voice / cloning path
|
|
66
49
|
* is exposed on this surface, so the tail is stock-only (no
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { GeminiSttModel } from "./models.mjs";
|
|
2
|
-
import { Context, Effect, Layer, Redacted } from "effect";
|
|
3
|
-
import { HttpClient } from "effect/unstable/http";
|
|
4
|
-
import * as AiError from "@effect-uai/core/AiError";
|
|
5
|
-
import { CommonTranscribeRequest, TranscriberService } from "@effect-uai/core/Transcriber";
|
|
6
|
-
import { TranscriptResult } from "@effect-uai/core/Transcript";
|
|
7
|
-
|
|
8
|
-
//#region src/GeminiTranscriber.d.ts
|
|
9
|
-
declare namespace GeminiTranscriber_d_exports {
|
|
10
|
-
export { Config, GeminiTranscribeRequest, GeminiTranscriber, GeminiTranscriberService, buildPrompt, layer, make };
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Gemini transcription rides on `:generateContent` — the model receives
|
|
14
|
-
* audio plus a textual "transcribe verbatim" prompt and returns plain
|
|
15
|
-
* text. There is no native structured timestamp / diarization channel,
|
|
16
|
-
* so `wordTimestamps` / `diarization` short-circuit with `Unsupported`.
|
|
17
|
-
*/
|
|
18
|
-
type GeminiTranscribeRequest = Omit<CommonTranscribeRequest, "model"> & {
|
|
19
|
-
readonly model: GeminiSttModel;
|
|
20
|
-
};
|
|
21
|
-
type GeminiTranscriberService = {
|
|
22
|
-
readonly transcribe: (r: GeminiTranscribeRequest) => Effect.Effect<TranscriptResult, AiError.AiError>;
|
|
23
|
-
readonly streamTranscriptionFrom: TranscriberService["streamTranscriptionFrom"];
|
|
24
|
-
};
|
|
25
|
-
declare const GeminiTranscriber_base: Context.ServiceClass<GeminiTranscriber, "@betalyra/effect-uai/providers/google/GeminiTranscriber", GeminiTranscriberService>;
|
|
26
|
-
declare class GeminiTranscriber extends GeminiTranscriber_base {}
|
|
27
|
-
type Config = {
|
|
28
|
-
readonly apiKey: Redacted.Redacted;
|
|
29
|
-
readonly baseUrl?: string;
|
|
30
|
-
};
|
|
31
|
-
declare const buildPrompt: (r: GeminiTranscribeRequest) => string;
|
|
32
|
-
declare const make: (cfg: Config) => Effect.Effect<GeminiTranscriberService, never, HttpClient.HttpClient>;
|
|
33
|
-
/**
|
|
34
|
-
* Layer registers both `GeminiTranscriber` and the generic
|
|
35
|
-
* `Transcriber`. Does NOT register `SttStreaming` — Gemini's transcription
|
|
36
|
-
* is sync-only, so `streamTranscriptionFrom` against this Layer alone
|
|
37
|
-
* is a compile-time error.
|
|
38
|
-
*/
|
|
39
|
-
declare const layer: (cfg: Config) => Layer.Layer<unknown, never, HttpClient.HttpClient>;
|
|
40
|
-
//#endregion
|
|
41
|
-
export { Config, GeminiTranscribeRequest, GeminiTranscriber, GeminiTranscriberService, buildPrompt, layer, make, GeminiTranscriber_d_exports as t };
|
|
42
|
-
//# sourceMappingURL=GeminiTranscriber.d.mts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GeminiTranscriber.d.mts","names":[],"sources":["../src/GeminiTranscriber.ts"],"mappings":";;;;;;;;;;;;;;;;;KAsBY,uBAAA,GAA0B,IAAA,CAAK,uBAAA;EAAA,SAChC,KAAA,EAAO,cAAA;AAAA;AAAA,KAGN,wBAAA;EAAA,SACD,UAAA,GACP,CAAA,EAAG,uBAAA,KACA,MAAA,CAAO,MAAA,CAAO,gBAAA,EAAkB,OAAA,CAAQ,OAAA;EAAA,SACpC,uBAAA,EAAyB,kBAAA;AAAA;AAAA,cACnC,sBAAA;cAEY,iBAAA,SAA0B,sBAAA;AAAA,KAK3B,MAAA;EAAA,SAAoB,MAAA,EAAQ,QAAA,CAAS,QAAA;EAAA,SAAmB,OAAA;AAAA;AAAA,cAqCvD,WAAA,GAAe,CAAA,EAAG,uBAAA;AAAA,cAkGlB,IAAA,GAAQ,GAAA,EAAK,MAAA,KAAM,MAAA,CAAA,MAAA,CAAA,wBAAA,SAAA,UAAA,CAAA,UAAA;;;;;AAnJhC;;cAmKa,KAAA,GAAS,GAAA,EAAK,MAAA,KAAM,KAAA,CAAA,KAAA,iBAAA,UAAA,CAAA,UAAA"}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { n as __exportAll } from "./chunk-uyGKjUfl.mjs";
|
|
2
|
-
import { audioSourceToInlineData, httpStatusError, transportFailure } from "./geminiSpeechCodec.mjs";
|
|
3
|
-
import { Context, Effect, Layer, Match, Redacted, Schema, Stream } from "effect";
|
|
4
|
-
import { HttpClient, HttpClientRequest } from "effect/unstable/http";
|
|
5
|
-
import * as AiError from "@effect-uai/core/AiError";
|
|
6
|
-
import { Transcriber } from "@effect-uai/core/Transcriber";
|
|
7
|
-
//#region src/GeminiTranscriber.ts
|
|
8
|
-
var GeminiTranscriber_exports = /* @__PURE__ */ __exportAll({
|
|
9
|
-
GeminiTranscriber: () => GeminiTranscriber,
|
|
10
|
-
buildPrompt: () => buildPrompt,
|
|
11
|
-
layer: () => layer,
|
|
12
|
-
make: () => make
|
|
13
|
-
});
|
|
14
|
-
var GeminiTranscriber = class extends Context.Service()("@betalyra/effect-uai/providers/google/GeminiTranscriber") {};
|
|
15
|
-
const unsupported = (capability, reason) => new AiError.Unsupported({
|
|
16
|
-
provider: "gemini",
|
|
17
|
-
capability,
|
|
18
|
-
reason
|
|
19
|
-
});
|
|
20
|
-
const ensureSupported = Match.type().pipe(Match.when({ wordTimestamps: true }, () => Effect.fail(unsupported("wordTimestamps", "Gemini's prompt-driven transcription returns plain text — no per-word timing. Use Cloud Speech-to-Text (Chirp 2) for word timestamps."))), Match.when({ diarization: true }, () => Effect.fail(unsupported("diarization", "Gemini's prompt-driven transcription does not surface speaker IDs. Use Cloud Speech-to-Text (Chirp 2) for diarization."))), Match.orElse(() => Effect.void));
|
|
21
|
-
const biasingHint = (r) => {
|
|
22
|
-
if (r.prompt === void 0) return "";
|
|
23
|
-
if (typeof r.prompt === "string") return `\nContext: ${r.prompt}`;
|
|
24
|
-
return r.prompt.terms.length === 0 ? "" : `\nThe following terms / names appear in the audio: ${r.prompt.terms.join(", ")}.`;
|
|
25
|
-
};
|
|
26
|
-
const buildPrompt = (r) => (r.language !== void 0 ? `Transcribe the audio verbatim in ${r.language}. Return only the transcript text, no preamble.` : "Transcribe the audio verbatim. Return only the transcript text, no preamble.") + biasingHint(r);
|
|
27
|
-
const Wire = Schema.Struct({ candidates: Schema.optional(Schema.Array(Schema.Struct({ content: Schema.optional(Schema.Struct({ parts: Schema.optional(Schema.Array(Schema.Struct({ text: Schema.optional(Schema.String) }))) })) }))) });
|
|
28
|
-
const decodeWire = Schema.decodeUnknownEffect(Wire);
|
|
29
|
-
const collectText = (wire) => (wire.candidates ?? []).flatMap((c) => c.content?.parts ?? []).flatMap((p) => p.text !== void 0 ? [p.text] : []).join("\n").trim();
|
|
30
|
-
const baseUrl = (cfg) => cfg.baseUrl ?? "https://generativelanguage.googleapis.com/v1beta";
|
|
31
|
-
const transcribeImpl = (cfg) => (request) => Effect.gen(function* () {
|
|
32
|
-
yield* ensureSupported(request);
|
|
33
|
-
const client = yield* HttpClient.HttpClient;
|
|
34
|
-
const inline = yield* audioSourceToInlineData(request.audio);
|
|
35
|
-
const httpRequest = HttpClientRequest.post(`${baseUrl(cfg)}/models/${request.model}:generateContent`).pipe(HttpClientRequest.setHeader("x-goog-api-key", Redacted.value(cfg.apiKey)), HttpClientRequest.bodyJsonUnsafe({ contents: [{ parts: [{ text: buildPrompt(request) }, { inlineData: inline }] }] }));
|
|
36
|
-
const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure));
|
|
37
|
-
if (response.status >= 400) {
|
|
38
|
-
const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""));
|
|
39
|
-
return yield* httpStatusError(response.status, text);
|
|
40
|
-
}
|
|
41
|
-
const json = yield* response.json.pipe(Effect.mapError(transportFailure));
|
|
42
|
-
return {
|
|
43
|
-
text: yield* decodeWire(json).pipe(Effect.map(collectText), Effect.mapError((cause) => new AiError.GenerationFailed({
|
|
44
|
-
provider: "gemini",
|
|
45
|
-
raw: cause
|
|
46
|
-
})), Effect.flatMap((t) => t.length === 0 ? Effect.fail(new AiError.GenerationFailed({
|
|
47
|
-
provider: "gemini",
|
|
48
|
-
raw: {
|
|
49
|
-
message: "Gemini transcription response had no text part.",
|
|
50
|
-
hint: "Likely a prompt-safety rejection or the model returned only metadata.",
|
|
51
|
-
response: json
|
|
52
|
-
}
|
|
53
|
-
})) : Effect.succeed(t))),
|
|
54
|
-
...request.language !== void 0 && { languageCode: request.language },
|
|
55
|
-
raw: json
|
|
56
|
-
};
|
|
57
|
-
});
|
|
58
|
-
const unsupportedStreamFrom = () => Stream.fail(new AiError.Unsupported({
|
|
59
|
-
provider: "gemini",
|
|
60
|
-
capability: "streamTranscriptionFrom",
|
|
61
|
-
reason: "Gemini transcription via generateContent is sync-only. Use Cloud Speech-to-Text (StreamingRecognize) for live transcription."
|
|
62
|
-
}));
|
|
63
|
-
const make = (cfg) => Effect.map(HttpClient.HttpClient.asEffect(), (client) => ({
|
|
64
|
-
transcribe: (r) => transcribeImpl(cfg)(r).pipe(Effect.provideService(HttpClient.HttpClient, client)),
|
|
65
|
-
streamTranscriptionFrom: unsupportedStreamFrom
|
|
66
|
-
}));
|
|
67
|
-
/**
|
|
68
|
-
* Layer registers both `GeminiTranscriber` and the generic
|
|
69
|
-
* `Transcriber`. Does NOT register `SttStreaming` — Gemini's transcription
|
|
70
|
-
* is sync-only, so `streamTranscriptionFrom` against this Layer alone
|
|
71
|
-
* is a compile-time error.
|
|
72
|
-
*/
|
|
73
|
-
const layer = (cfg) => Layer.merge(Layer.effect(GeminiTranscriber, make(cfg)), Layer.effect(Transcriber, Effect.map(make(cfg), (s) => ({
|
|
74
|
-
transcribe: (req) => s.transcribe(req),
|
|
75
|
-
streamTranscriptionFrom: s.streamTranscriptionFrom
|
|
76
|
-
}))));
|
|
77
|
-
//#endregion
|
|
78
|
-
export { GeminiTranscriber, buildPrompt, layer, make, GeminiTranscriber_exports as t };
|
|
79
|
-
|
|
80
|
-
//# sourceMappingURL=GeminiTranscriber.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GeminiTranscriber.mjs","names":[],"sources":["../src/GeminiTranscriber.ts"],"sourcesContent":["import { Context, Effect, Layer, Match, Redacted, Schema, Stream } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport type { TranscriptResult } from \"@effect-uai/core/Transcript\"\nimport {\n type CommonTranscribeRequest,\n Transcriber,\n type TranscriberService,\n} from \"@effect-uai/core/Transcriber\"\nimport { audioSourceToInlineData, httpStatusError, transportFailure } from \"./geminiSpeechCodec.js\"\nimport type { GeminiSttModel } from \"./models.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Gemini transcription rides on `:generateContent` — the model receives\n * audio plus a textual \"transcribe verbatim\" prompt and returns plain\n * text. There is no native structured timestamp / diarization channel,\n * so `wordTimestamps` / `diarization` short-circuit with `Unsupported`.\n */\nexport type GeminiTranscribeRequest = Omit<CommonTranscribeRequest, \"model\"> & {\n readonly model: GeminiSttModel\n}\n\nexport type GeminiTranscriberService = {\n readonly transcribe: (\n r: GeminiTranscribeRequest,\n ) => Effect.Effect<TranscriptResult, AiError.AiError>\n readonly streamTranscriptionFrom: TranscriberService[\"streamTranscriptionFrom\"]\n}\n\nexport class GeminiTranscriber extends Context.Service<\n GeminiTranscriber,\n GeminiTranscriberService\n>()(\"@betalyra/effect-uai/providers/google/GeminiTranscriber\") {}\n\nexport type Config = { readonly apiKey: Redacted.Redacted; readonly baseUrl?: string }\n\n// ---------------------------------------------------------------------------\n// Prompt builder + capability guards\n// ---------------------------------------------------------------------------\n\nconst unsupported = (capability: string, reason: string) =>\n new AiError.Unsupported({ provider: \"gemini\", capability, reason })\n\nconst ensureSupported = Match.type<GeminiTranscribeRequest>().pipe(\n Match.when({ wordTimestamps: true }, () =>\n Effect.fail(\n unsupported(\n \"wordTimestamps\",\n \"Gemini's prompt-driven transcription returns plain text — no per-word timing. Use Cloud Speech-to-Text (Chirp 2) for word timestamps.\",\n ),\n ),\n ),\n Match.when({ diarization: true }, () =>\n Effect.fail(\n unsupported(\n \"diarization\",\n \"Gemini's prompt-driven transcription does not surface speaker IDs. Use Cloud Speech-to-Text (Chirp 2) for diarization.\",\n ),\n ),\n ),\n Match.orElse(() => Effect.void),\n)\n\nconst biasingHint = (r: GeminiTranscribeRequest) => {\n if (r.prompt === undefined) return \"\"\n if (typeof r.prompt === \"string\") return `\\nContext: ${r.prompt}`\n return r.prompt.terms.length === 0\n ? \"\"\n : `\\nThe following terms / names appear in the audio: ${r.prompt.terms.join(\", \")}.`\n}\n\nexport const buildPrompt = (r: GeminiTranscribeRequest) =>\n (r.language !== undefined\n ? `Transcribe the audio verbatim in ${r.language}. Return only the transcript text, no preamble.`\n : \"Transcribe the audio verbatim. Return only the transcript text, no preamble.\") +\n biasingHint(r)\n\n// ---------------------------------------------------------------------------\n// Wire\n// ---------------------------------------------------------------------------\n\nconst Wire = Schema.Struct({\n candidates: Schema.optional(\n Schema.Array(\n Schema.Struct({\n content: Schema.optional(\n Schema.Struct({\n parts: Schema.optional(\n Schema.Array(Schema.Struct({ text: Schema.optional(Schema.String) })),\n ),\n }),\n ),\n }),\n ),\n ),\n})\nconst decodeWire = Schema.decodeUnknownEffect(Wire)\n\nconst collectText = (wire: typeof Wire.Type) =>\n (wire.candidates ?? [])\n .flatMap((c) => c.content?.parts ?? [])\n .flatMap((p) => (p.text !== undefined ? [p.text] : []))\n .join(\"\\n\")\n .trim()\n\n// ---------------------------------------------------------------------------\n// Service implementation\n// ---------------------------------------------------------------------------\n\nconst baseUrl = (cfg: Config) => cfg.baseUrl ?? \"https://generativelanguage.googleapis.com/v1beta\"\n\nconst transcribeImpl = (cfg: Config) => (request: GeminiTranscribeRequest) =>\n Effect.gen(function* () {\n yield* ensureSupported(request)\n const client = yield* HttpClient.HttpClient\n const inline = yield* audioSourceToInlineData(request.audio)\n const httpRequest = HttpClientRequest.post(\n `${baseUrl(cfg)}/models/${request.model}:generateContent`,\n ).pipe(\n HttpClientRequest.setHeader(\"x-goog-api-key\", Redacted.value(cfg.apiKey)),\n HttpClientRequest.bodyJsonUnsafe({\n contents: [{ parts: [{ text: buildPrompt(request) }, { inlineData: inline }] }],\n }),\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* httpStatusError(response.status, text)\n }\n const json = yield* response.json.pipe(Effect.mapError(transportFailure))\n const text = yield* decodeWire(json).pipe(\n Effect.map(collectText),\n Effect.mapError((cause) => new AiError.GenerationFailed({ provider: \"gemini\", raw: cause })),\n Effect.flatMap((t) =>\n t.length === 0\n ? Effect.fail(\n new AiError.GenerationFailed({\n provider: \"gemini\",\n raw: {\n message: \"Gemini transcription response had no text part.\",\n hint: \"Likely a prompt-safety rejection or the model returned only metadata.\",\n response: json,\n },\n }),\n )\n : Effect.succeed(t),\n ),\n )\n return {\n text,\n ...(request.language !== undefined && { languageCode: request.language }),\n raw: json,\n } satisfies TranscriptResult\n })\n\nconst unsupportedStreamFrom: TranscriberService[\"streamTranscriptionFrom\"] = () =>\n Stream.fail(\n new AiError.Unsupported({\n provider: \"gemini\",\n capability: \"streamTranscriptionFrom\",\n reason:\n \"Gemini transcription via generateContent is sync-only. Use Cloud Speech-to-Text (StreamingRecognize) for live transcription.\",\n }),\n )\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\nexport const make = (cfg: Config) =>\n Effect.map(\n HttpClient.HttpClient.asEffect(),\n (client): GeminiTranscriberService => ({\n transcribe: (r) =>\n transcribeImpl(cfg)(r).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n streamTranscriptionFrom: unsupportedStreamFrom,\n }),\n )\n\n/**\n * Layer registers both `GeminiTranscriber` and the generic\n * `Transcriber`. Does NOT register `SttStreaming` — Gemini's transcription\n * is sync-only, so `streamTranscriptionFrom` against this Layer alone\n * is a compile-time error.\n */\nexport const layer = (cfg: Config) =>\n Layer.merge(\n Layer.effect(GeminiTranscriber, 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 GeminiTranscribeRequest),\n streamTranscriptionFrom: s.streamTranscriptionFrom,\n }),\n ),\n ),\n )\n"],"mappings":";;;;;;;;;;;;;AAiCA,IAAa,oBAAb,cAAuC,QAAQ,SAG5C,CAAC,0DAA0D,CAAC;AAQ/D,MAAM,eAAe,YAAoB,WACvC,IAAI,QAAQ,YAAY;CAAE,UAAU;CAAU;CAAY;CAAQ,CAAC;AAErE,MAAM,kBAAkB,MAAM,MAA+B,CAAC,KAC5D,MAAM,KAAK,EAAE,gBAAgB,MAAM,QACjC,OAAO,KACL,YACE,kBACA,wIACD,CACF,CACF,EACD,MAAM,KAAK,EAAE,aAAa,MAAM,QAC9B,OAAO,KACL,YACE,eACA,yHACD,CACF,CACF,EACD,MAAM,aAAa,OAAO,KAAK,CAChC;AAED,MAAM,eAAe,MAA+B;AAClD,KAAI,EAAE,WAAW,KAAA,EAAW,QAAO;AACnC,KAAI,OAAO,EAAE,WAAW,SAAU,QAAO,cAAc,EAAE;AACzD,QAAO,EAAE,OAAO,MAAM,WAAW,IAC7B,KACA,sDAAsD,EAAE,OAAO,MAAM,KAAK,KAAK,CAAC;;AAGtF,MAAa,eAAe,OACzB,EAAE,aAAa,KAAA,IACZ,oCAAoC,EAAE,SAAS,mDAC/C,kFACJ,YAAY,EAAE;AAMhB,MAAM,OAAO,OAAO,OAAO,EACzB,YAAY,OAAO,SACjB,OAAO,MACL,OAAO,OAAO,EACZ,SAAS,OAAO,SACd,OAAO,OAAO,EACZ,OAAO,OAAO,SACZ,OAAO,MAAM,OAAO,OAAO,EAAE,MAAM,OAAO,SAAS,OAAO,OAAO,EAAE,CAAC,CAAC,CACtE,EACF,CAAC,CACH,EACF,CAAC,CACH,CACF,EACF,CAAC;AACF,MAAM,aAAa,OAAO,oBAAoB,KAAK;AAEnD,MAAM,eAAe,UAClB,KAAK,cAAc,EAAE,EACnB,SAAS,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC,CACtC,SAAS,MAAO,EAAE,SAAS,KAAA,IAAY,CAAC,EAAE,KAAK,GAAG,EAAE,CAAE,CACtD,KAAK,KAAK,CACV,MAAM;AAMX,MAAM,WAAW,QAAgB,IAAI,WAAW;AAEhD,MAAM,kBAAkB,SAAiB,YACvC,OAAO,IAAI,aAAa;AACtB,QAAO,gBAAgB,QAAQ;CAC/B,MAAM,SAAS,OAAO,WAAW;CACjC,MAAM,SAAS,OAAO,wBAAwB,QAAQ,MAAM;CAC5D,MAAM,cAAc,kBAAkB,KACpC,GAAG,QAAQ,IAAI,CAAC,UAAU,QAAQ,MAAM,kBACzC,CAAC,KACA,kBAAkB,UAAU,kBAAkB,SAAS,MAAM,IAAI,OAAO,CAAC,EACzE,kBAAkB,eAAe,EAC/B,UAAU,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,YAAY,QAAQ,EAAE,EAAE,EAAE,YAAY,QAAQ,CAAC,EAAE,CAAC,EAChF,CAAC,CACH;CACD,MAAM,WAAW,OAAO,OAAO,QAAQ,YAAY,CAAC,KAAK,OAAO,SAAS,iBAAiB,CAAC;AAC3F,KAAI,SAAS,UAAU,KAAK;EAC1B,MAAM,OAAO,OAAO,SAAS,KAAK,KAAK,OAAO,oBAAoB,GAAG,CAAC;AACtE,SAAO,OAAO,gBAAgB,SAAS,QAAQ,KAAK;;CAEtD,MAAM,OAAO,OAAO,SAAS,KAAK,KAAK,OAAO,SAAS,iBAAiB,CAAC;AAmBzE,QAAO;EACL,MAAA,OAnBkB,WAAW,KAAK,CAAC,KACnC,OAAO,IAAI,YAAY,EACvB,OAAO,UAAU,UAAU,IAAI,QAAQ,iBAAiB;GAAE,UAAU;GAAU,KAAK;GAAO,CAAC,CAAC,EAC5F,OAAO,SAAS,MACd,EAAE,WAAW,IACT,OAAO,KACL,IAAI,QAAQ,iBAAiB;GAC3B,UAAU;GACV,KAAK;IACH,SAAS;IACT,MAAM;IACN,UAAU;IACX;GACF,CAAC,CACH,GACD,OAAO,QAAQ,EAAE,CACtB,CACF;EAGC,GAAI,QAAQ,aAAa,KAAA,KAAa,EAAE,cAAc,QAAQ,UAAU;EACxE,KAAK;EACN;EACD;AAEJ,MAAM,8BACJ,OAAO,KACL,IAAI,QAAQ,YAAY;CACtB,UAAU;CACV,YAAY;CACZ,QACE;CACH,CAAC,CACH;AAMH,MAAa,QAAQ,QACnB,OAAO,IACL,WAAW,WAAW,UAAU,GAC/B,YAAsC;CACrC,aAAa,MACX,eAAe,IAAI,CAAC,EAAE,CAAC,KAAK,OAAO,eAAe,WAAW,YAAY,OAAO,CAAC;CACnF,yBAAyB;CAC1B,EACF;;;;;;;AAQH,MAAa,SAAS,QACpB,MAAM,MACJ,MAAM,OAAO,mBAAmB,KAAK,IAAI,CAAC,EAC1C,MAAM,OACJ,aACA,OAAO,IACL,KAAK,IAAI,GACR,OAA2B;CAC1B,aAAa,QACX,EAAE,WAAW,IAA+B;CAC9C,yBAAyB,EAAE;CAC5B,EACF,CACF,CACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { };
|