@effect-uai/google 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/Gemini.mjs +1 -1
  2. package/dist/GeminiEmbedding.mjs +1 -1
  3. package/dist/GeminiSynthesizer.d.mts +48 -0
  4. package/dist/GeminiSynthesizer.d.mts.map +1 -0
  5. package/dist/GeminiSynthesizer.mjs +95 -0
  6. package/dist/GeminiSynthesizer.mjs.map +1 -0
  7. package/dist/GeminiSynthesizer.test.d.mts +1 -0
  8. package/dist/GeminiSynthesizer.test.mjs +84 -0
  9. package/dist/GeminiSynthesizer.test.mjs.map +1 -0
  10. package/dist/GeminiTranscriber.d.mts +42 -0
  11. package/dist/GeminiTranscriber.d.mts.map +1 -0
  12. package/dist/GeminiTranscriber.mjs +80 -0
  13. package/dist/GeminiTranscriber.mjs.map +1 -0
  14. package/dist/GeminiTranscriber.test.d.mts +1 -0
  15. package/dist/GeminiTranscriber.test.mjs +125 -0
  16. package/dist/GeminiTranscriber.test.mjs.map +1 -0
  17. package/dist/LyriaGenerator.d.mts +57 -0
  18. package/dist/LyriaGenerator.d.mts.map +1 -0
  19. package/dist/LyriaGenerator.mjs +202 -0
  20. package/dist/LyriaGenerator.mjs.map +1 -0
  21. package/dist/LyriaGenerator.test.d.mts +1 -0
  22. package/dist/LyriaGenerator.test.mjs +138 -0
  23. package/dist/LyriaGenerator.test.mjs.map +1 -0
  24. package/dist/{chunk-CfYAbeIz.mjs → chunk-uyGKjUfl.mjs} +2 -1
  25. package/dist/codec.mjs +1 -1
  26. package/dist/dist-DMsg8YLb.mjs +13782 -0
  27. package/dist/dist-DMsg8YLb.mjs.map +1 -0
  28. package/dist/geminiSpeechCodec.d.mts +37 -0
  29. package/dist/geminiSpeechCodec.d.mts.map +1 -0
  30. package/dist/geminiSpeechCodec.mjs +110 -0
  31. package/dist/geminiSpeechCodec.mjs.map +1 -0
  32. package/dist/geminiSpeechCodec.test.d.mts +1 -0
  33. package/dist/geminiSpeechCodec.test.mjs +78 -0
  34. package/dist/geminiSpeechCodec.test.mjs.map +1 -0
  35. package/dist/index.d.mts +5 -2
  36. package/dist/index.mjs +4 -1
  37. package/dist/magic-string.es-CaWgF-3p.mjs +1013 -0
  38. package/dist/magic-string.es-CaWgF-3p.mjs.map +1 -0
  39. package/dist/models.d.mts +35 -1
  40. package/dist/models.d.mts.map +1 -1
  41. package/package.json +14 -2
  42. package/src/GeminiSynthesizer.test.ts +93 -0
  43. package/src/GeminiSynthesizer.ts +214 -0
  44. package/src/GeminiTranscriber.test.ts +136 -0
  45. package/src/GeminiTranscriber.ts +204 -0
  46. package/src/LyriaGenerator.test.ts +162 -0
  47. package/src/LyriaGenerator.ts +394 -0
  48. package/src/geminiSpeechCodec.test.ts +56 -0
  49. package/src/geminiSpeechCodec.ts +137 -0
  50. package/src/index.ts +3 -0
  51. package/src/models.ts +84 -0
@@ -0,0 +1,57 @@
1
+ import { LyriaModel } from "./models.mjs";
2
+ import { Context, Effect, Layer, Redacted, Stream } from "effect";
3
+ import { HttpClient } from "effect/unstable/http";
4
+ import * as AiError from "@effect-uai/core/AiError";
5
+ import { MusicGenerator } from "@effect-uai/core/MusicGenerator";
6
+ import { CommonGenerateMusicRequest, CommonStreamGenerateMusicRequest, MusicResult, MusicSessionInput } from "@effect-uai/core/Music";
7
+ import { AudioChunk, AudioFormat } from "@effect-uai/core/Audio";
8
+
9
+ //#region src/LyriaGenerator.d.ts
10
+ declare namespace LyriaGenerator_d_exports {
11
+ export { Config, LyriaGenerateRequest, LyriaGenerator, LyriaGeneratorService, LyriaResponseMimeType, buildPrompt, containerToMimeType, layer, make, realizedFormat };
12
+ }
13
+ /**
14
+ * Lyria-typed generate request. Narrows `model` to `LyriaModel`. The
15
+ * Lyria sync API (`generateContent`) accepts only `audio/mp3` and
16
+ * `audio/wav` output; `bpm`, `scale`, `instrumental` etc. on the
17
+ * `CommonGenerateMusicRequest` are flattened into the prompt text
18
+ * because the public API does not expose them as structured fields.
19
+ */
20
+ type LyriaGenerateRequest = Omit<CommonGenerateMusicRequest, "model"> & {
21
+ readonly model: LyriaModel;
22
+ };
23
+ type LyriaGeneratorService = {
24
+ readonly generate: (request: LyriaGenerateRequest) => Effect.Effect<MusicResult, AiError.AiError>;
25
+ readonly streamGeneration: (request: LyriaGenerateRequest) => Stream.Stream<AudioChunk, AiError.AiError>;
26
+ readonly streamGenerationFrom: <E, R>(input: Stream.Stream<MusicSessionInput, E, R>, request: CommonStreamGenerateMusicRequest) => Stream.Stream<AudioChunk, AiError.AiError | E, R>;
27
+ };
28
+ declare const LyriaGenerator_base: Context.ServiceClass<LyriaGenerator, "@betalyra/effect-uai/providers/google/LyriaGenerator", LyriaGeneratorService>;
29
+ declare class LyriaGenerator extends LyriaGenerator_base {}
30
+ type Config = {
31
+ readonly apiKey: Redacted.Redacted;
32
+ readonly baseUrl?: string;
33
+ };
34
+ type LyriaResponseMimeType = "audio/mp3" | "audio/wav";
35
+ declare const containerToMimeType: (container: AudioFormat["container"]) => Effect.Effect<LyriaResponseMimeType, AiError.AiError>;
36
+ declare const realizedFormat: (mime: LyriaResponseMimeType) => AudioFormat;
37
+ /**
38
+ * Collapse `prompts | WeightedPrompt[]` into a single prompt string and
39
+ * splice in non-structural hints (`lyrics`, `bpm`, `scale`,
40
+ * `instrumental`, `durationSeconds`). The Lyria 3 sync API only takes
41
+ * one text part — every condition lives inline.
42
+ */
43
+ declare const buildPrompt: (request: CommonGenerateMusicRequest) => string;
44
+ declare const make: (cfg: Config) => Effect.Effect<LyriaGeneratorService, never, HttpClient.HttpClient>;
45
+ /**
46
+ * Layer that registers both the provider-specific `LyriaGenerator` tag
47
+ * and the generic `MusicGenerator` tag.
48
+ *
49
+ * Does NOT register the `MusicInteractiveSession` capability marker —
50
+ * Lyria 3 sync has no bidirectional session at the wire level. Code
51
+ * calling `MusicGenerator.streamGenerationFrom` will fail to typecheck
52
+ * against this Layer alone, which is the intended UX.
53
+ */
54
+ declare const layer: (cfg: Config) => Layer.Layer<LyriaGenerator | MusicGenerator, never, HttpClient.HttpClient>;
55
+ //#endregion
56
+ export { Config, LyriaGenerateRequest, LyriaGenerator, LyriaGeneratorService, LyriaResponseMimeType, buildPrompt, containerToMimeType, layer, make, realizedFormat, LyriaGenerator_d_exports as t };
57
+ //# sourceMappingURL=LyriaGenerator.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LyriaGenerator.d.mts","names":[],"sources":["../src/LyriaGenerator.ts"],"mappings":";;;;;;;;;;;;;;;;;;;KAqCY,oBAAA,GAAuB,IAAA,CAAK,0BAAA;EAAA,SAC7B,KAAA,EAAO,UAAA;AAAA;AAAA,KAGN,qBAAA;EAAA,SACD,QAAA,GAAW,OAAA,EAAS,oBAAA,KAAyB,MAAA,CAAO,MAAA,CAAO,WAAA,EAAa,OAAA,CAAQ,OAAA;EAAA,SAChF,gBAAA,GACP,OAAA,EAAS,oBAAA,KACN,MAAA,CAAO,MAAA,CAAO,UAAA,EAAY,OAAA,CAAQ,OAAA;EAAA,SAC9B,oBAAA,SACP,KAAA,EAAO,MAAA,CAAO,MAAA,CAAO,iBAAA,EAAmB,CAAA,EAAG,CAAA,GAC3C,OAAA,EAAS,gCAAA,KACN,MAAA,CAAO,MAAA,CAAO,UAAA,EAAY,OAAA,CAAQ,OAAA,GAAU,CAAA,EAAG,CAAA;AAAA;AAAA,cACrD,mBAAA;cAEY,cAAA,SAAuB,mBAAA;AAAA,KAIxB,MAAA;EAAA,SACD,MAAA,EAAQ,QAAA,CAAS,QAAA;EAAA,SACjB,OAAA;AAAA;AAAA,KAOC,qBAAA;AAAA,cAEC,mBAAA,GACX,SAAA,EAAW,WAAA,kBACR,MAAA,CAAO,MAAA,CAAO,qBAAA,EAAuB,OAAA,CAAQ,OAAA;AAAA,cAyBrC,cAAA,GAAiB,IAAA,EAAM,qBAAA,KAA0B,WAAA;;;;;AArD9D;;cA6Ea,WAAA,GAAe,OAAA,EAAS,0BAAA;AAAA,cA8OxB,IAAA,GACX,GAAA,EAAK,MAAA,KACJ,MAAA,CAAO,MAAA,CAAO,qBAAA,SAA8B,UAAA,CAAW,UAAA;;;;;;;;;;cAkB7C,KAAA,GACX,GAAA,EAAK,MAAA,KACJ,KAAA,CAAM,KAAA,CAAM,cAAA,GAAiB,cAAA,SAAuB,UAAA,CAAW,UAAA"}
@@ -0,0 +1,202 @@
1
+ import { n as __exportAll } from "./chunk-uyGKjUfl.mjs";
2
+ import { Array, Context, Effect, Encoding, Layer, Match, Option, Redacted, Result, Schema, Stream } from "effect";
3
+ import { HttpClient, HttpClientRequest } from "effect/unstable/http";
4
+ import * as AiError from "@effect-uai/core/AiError";
5
+ import { MusicGenerator } from "@effect-uai/core/MusicGenerator";
6
+ //#region src/LyriaGenerator.ts
7
+ var LyriaGenerator_exports = /* @__PURE__ */ __exportAll({
8
+ LyriaGenerator: () => LyriaGenerator,
9
+ buildPrompt: () => buildPrompt,
10
+ containerToMimeType: () => containerToMimeType,
11
+ layer: () => layer,
12
+ make: () => make,
13
+ realizedFormat: () => realizedFormat
14
+ });
15
+ var LyriaGenerator = class extends Context.Service()("@betalyra/effect-uai/providers/google/LyriaGenerator") {};
16
+ const containerToMimeType = Match.type().pipe(Match.when("mp3", () => Effect.succeed("audio/mp3")), Match.when("wav", () => Effect.succeed("audio/wav")), Match.whenOr("ogg", "opus", "flac", "aac", "webm", "raw", (c) => Effect.fail(new AiError.Unsupported({
17
+ provider: "lyria",
18
+ capability: "outputFormat",
19
+ reason: `Lyria 3 only produces audio/mp3 or audio/wav; ${c} is not available. Use container: "mp3" or "wav".`
20
+ }))), Match.exhaustive);
21
+ /**
22
+ * Is this a Lyria 3 Clip model variant? Clip is fixed at 30 s MP3 — it
23
+ * does not accept a `responseFormat` on `generationConfig` (the live
24
+ * API rejects unknown fields, despite what the docs imply). Pro
25
+ * accepts the documented `responseFormat` knob.
26
+ */
27
+ const isClipModel = (model) => model.includes("clip");
28
+ const realizedFormat = Match.type().pipe(Match.when("audio/mp3", () => ({
29
+ container: "mp3",
30
+ encoding: "mp3",
31
+ sampleRate: 44100,
32
+ channels: 2
33
+ })), Match.when("audio/wav", () => ({
34
+ container: "wav",
35
+ encoding: "pcm_s16le",
36
+ sampleRate: 44100,
37
+ channels: 2
38
+ })), Match.exhaustive);
39
+ /**
40
+ * Collapse `prompts | WeightedPrompt[]` into a single prompt string and
41
+ * splice in non-structural hints (`lyrics`, `bpm`, `scale`,
42
+ * `instrumental`, `durationSeconds`). The Lyria 3 sync API only takes
43
+ * one text part — every condition lives inline.
44
+ */
45
+ const buildPrompt = (request) => {
46
+ const promptText = Match.value(request.prompts).pipe(Match.when(Match.string, (s) => s), Match.orElse((ws) => Array.map(ws, (w) => w.weight !== void 0 && w.weight !== 1 ? `${w.text} (weight ${w.weight})` : w.text).join(". ")));
47
+ const hints = Array.getSomes([
48
+ request.instrumental === true ? Option.some("Instrumental only — no vocals.") : Option.none(),
49
+ request.bpm !== void 0 ? Option.some(`BPM: ${request.bpm}.`) : Option.none(),
50
+ request.scale !== void 0 ? Option.some(`Key/scale: ${request.scale}.`) : Option.none(),
51
+ request.durationSeconds !== void 0 ? Option.some(`Target duration: ${request.durationSeconds}s.`) : Option.none()
52
+ ]);
53
+ const lyricsBlock = request.lyrics !== void 0 && request.instrumental !== true ? `\n\nLyrics:\n${request.lyrics}` : "";
54
+ return [promptText, ...hints].join(" ") + lyricsBlock;
55
+ };
56
+ const InlineData = Schema.Struct({
57
+ mimeType: Schema.optional(Schema.String),
58
+ data: Schema.String
59
+ });
60
+ const Part = Schema.Struct({
61
+ text: Schema.optional(Schema.String),
62
+ inlineData: Schema.optional(InlineData)
63
+ });
64
+ const Content = Schema.Struct({ parts: Schema.optional(Schema.Array(Part)) });
65
+ const Candidate = Schema.Struct({ content: Schema.optional(Content) });
66
+ const Wire = Schema.Struct({ candidates: Schema.optional(Schema.Array(Candidate)) });
67
+ const decodeWire = Schema.decodeUnknownEffect(Wire);
68
+ const baseUrl = (cfg) => cfg.baseUrl ?? "https://generativelanguage.googleapis.com/v1beta";
69
+ const transportFailure = (cause) => new AiError.Unavailable({
70
+ provider: "lyria",
71
+ raw: cause
72
+ });
73
+ const httpStatusError = (status, body) => Match.value(status).pipe(Match.when(429, () => new AiError.RateLimited({
74
+ provider: "lyria",
75
+ raw: body
76
+ })), Match.whenOr(408, 504, () => new AiError.Timeout({
77
+ provider: "lyria",
78
+ raw: body
79
+ })), Match.when(401, () => new AiError.AuthFailed({
80
+ provider: "lyria",
81
+ subtype: "auth",
82
+ raw: body
83
+ })), Match.when(403, () => new AiError.AuthFailed({
84
+ provider: "lyria",
85
+ subtype: "permission",
86
+ raw: body
87
+ })), Match.when(402, () => new AiError.AuthFailed({
88
+ provider: "lyria",
89
+ subtype: "billing",
90
+ raw: body
91
+ })), Match.when((n) => n >= 500, (n) => new AiError.Unavailable({
92
+ provider: "lyria",
93
+ status: n,
94
+ raw: body
95
+ })), Match.orElse(() => new AiError.InvalidRequest({
96
+ provider: "lyria",
97
+ raw: body
98
+ })));
99
+ const decodeBase64ToBytes = (b64) => Result.match(Encoding.decodeBase64(b64), {
100
+ onSuccess: Effect.succeed,
101
+ onFailure: (cause) => Effect.fail(new AiError.InvalidRequest({
102
+ provider: "lyria",
103
+ param: "audio",
104
+ raw: cause
105
+ }))
106
+ });
107
+ /**
108
+ * Find the first inline-data audio part and the concatenated text parts
109
+ * from a Wire response.
110
+ */
111
+ const extractParts = (wire) => {
112
+ const allParts = (wire.candidates ?? []).flatMap((c) => c.content?.parts ?? []);
113
+ const inline = allParts.find((p) => p.inlineData !== void 0)?.inlineData;
114
+ const textParts = allParts.flatMap((p) => p.text !== void 0 ? [p.text] : []);
115
+ const text = textParts.length === 0 ? void 0 : textParts.join("\n");
116
+ return {
117
+ ...inline !== void 0 && { inline },
118
+ ...text !== void 0 && { text }
119
+ };
120
+ };
121
+ const generateImpl = (cfg) => (request) => Effect.gen(function* () {
122
+ const client = yield* HttpClient.HttpClient;
123
+ const mimeType = yield* containerToMimeType(request.outputFormat?.container ?? "mp3");
124
+ if (isClipModel(request.model) && mimeType === "audio/wav") return yield* Effect.fail(new AiError.Unsupported({
125
+ provider: "lyria",
126
+ capability: "outputFormat",
127
+ reason: `lyria-3-clip-preview is fixed at audio/mp3. Use lyria-3-pro-preview for audio/wav output.`
128
+ }));
129
+ const promptText = buildPrompt(request);
130
+ const body = isClipModel(request.model) ? { contents: [{ parts: [{ text: promptText }] }] } : {
131
+ contents: [{ parts: [{ text: promptText }] }],
132
+ generationConfig: {
133
+ responseModalities: ["AUDIO", "TEXT"],
134
+ responseFormat: { audio: { mimeType } }
135
+ }
136
+ };
137
+ const httpRequest = HttpClientRequest.post(`${baseUrl(cfg)}/models/${request.model}:generateContent`).pipe(HttpClientRequest.setHeader("x-goog-api-key", Redacted.value(cfg.apiKey)), HttpClientRequest.bodyJsonUnsafe(body));
138
+ const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure));
139
+ if (response.status >= 400) {
140
+ const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""));
141
+ return yield* Effect.fail(httpStatusError(response.status, text));
142
+ }
143
+ const json = yield* response.json.pipe(Effect.mapError(transportFailure));
144
+ const { inline, text } = extractParts(yield* decodeWire(json).pipe(Effect.mapError((cause) => new AiError.GenerationFailed({
145
+ provider: "lyria",
146
+ raw: cause
147
+ }))));
148
+ if (inline === void 0) return yield* Effect.fail(new AiError.GenerationFailed({
149
+ provider: "lyria",
150
+ raw: {
151
+ message: "Lyria response had no inlineData audio part.",
152
+ hint: "Likely a prompt-filter rejection. Lyria filters references to real artists / copyrighted song lyrics. Rephrase with descriptive style language instead of artist names.",
153
+ textParts: text,
154
+ response: json
155
+ }
156
+ }));
157
+ const bytes = yield* decodeBase64ToBytes(inline.data);
158
+ return {
159
+ format: realizedFormat(mimeType),
160
+ bytes,
161
+ ...text !== void 0 && { lyrics: text },
162
+ watermark: { kind: "synthid" }
163
+ };
164
+ });
165
+ const streamGenerationImpl = (cfg) => (request) => Stream.unwrap(Effect.map(generateImpl(cfg)(request), (result) => Stream.succeed({ bytes: result.bytes })));
166
+ /**
167
+ * Lyria 3 sync has no bidirectional session at the wire level — that's
168
+ * Lyria RealTime (Phase 1b, separate Layer). The provider Layer here
169
+ * also does NOT register `MusicInteractiveSession`, so callers using
170
+ * `MusicGenerator.streamGenerationFrom` against only this Layer get a
171
+ * compile-time error before this runtime fallback ever fires.
172
+ */
173
+ const streamGenerationFromUnsupported = (_input, _request) => {
174
+ return Stream.fail(new AiError.Unsupported({
175
+ provider: "lyria",
176
+ capability: "streamGenerationFrom",
177
+ reason: "Lyria 3 sync does not support bidirectional sessions. Use the Lyria RealTime Layer (WebSocket BidiGenerateMusic) for interactive mid-session prompts."
178
+ }));
179
+ };
180
+ const make = (cfg) => Effect.map(HttpClient.HttpClient.asEffect(), (client) => ({
181
+ generate: (request) => generateImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),
182
+ streamGeneration: (request) => streamGenerationImpl(cfg)(request).pipe(Stream.provideService(HttpClient.HttpClient, client)),
183
+ streamGenerationFrom: streamGenerationFromUnsupported
184
+ }));
185
+ /**
186
+ * Layer that registers both the provider-specific `LyriaGenerator` tag
187
+ * and the generic `MusicGenerator` tag.
188
+ *
189
+ * Does NOT register the `MusicInteractiveSession` capability marker —
190
+ * Lyria 3 sync has no bidirectional session at the wire level. Code
191
+ * calling `MusicGenerator.streamGenerationFrom` will fail to typecheck
192
+ * against this Layer alone, which is the intended UX.
193
+ */
194
+ const layer = (cfg) => Layer.merge(Layer.effect(LyriaGenerator, make(cfg)), Layer.effect(MusicGenerator, Effect.map(make(cfg), (s) => ({
195
+ generate: (req) => s.generate(req),
196
+ streamGeneration: (req) => s.streamGeneration(req),
197
+ streamGenerationFrom: s.streamGenerationFrom
198
+ }))));
199
+ //#endregion
200
+ export { LyriaGenerator, buildPrompt, containerToMimeType, layer, make, realizedFormat, LyriaGenerator_exports as t };
201
+
202
+ //# sourceMappingURL=LyriaGenerator.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LyriaGenerator.mjs","names":["Arr"],"sources":["../src/LyriaGenerator.ts"],"sourcesContent":["import {\n Array as Arr,\n Context,\n Effect,\n Encoding,\n Layer,\n Match,\n Option,\n Redacted,\n Result,\n Schema,\n Stream,\n} from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport type { AudioChunk, AudioFormat } from \"@effect-uai/core/Audio\"\nimport type {\n CommonGenerateMusicRequest,\n CommonStreamGenerateMusicRequest,\n MusicResult,\n MusicSessionInput,\n WeightedPrompt,\n} from \"@effect-uai/core/Music\"\nimport { MusicGenerator, type MusicGeneratorService } from \"@effect-uai/core/MusicGenerator\"\nimport type { LyriaModel } from \"./models.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Lyria-typed generate request. Narrows `model` to `LyriaModel`. The\n * Lyria sync API (`generateContent`) accepts only `audio/mp3` and\n * `audio/wav` output; `bpm`, `scale`, `instrumental` etc. on the\n * `CommonGenerateMusicRequest` are flattened into the prompt text\n * because the public API does not expose them as structured fields.\n */\nexport type LyriaGenerateRequest = Omit<CommonGenerateMusicRequest, \"model\"> & {\n readonly model: LyriaModel\n}\n\nexport type LyriaGeneratorService = {\n readonly generate: (request: LyriaGenerateRequest) => Effect.Effect<MusicResult, AiError.AiError>\n readonly streamGeneration: (\n request: LyriaGenerateRequest,\n ) => Stream.Stream<AudioChunk, AiError.AiError>\n readonly streamGenerationFrom: <E, R>(\n input: Stream.Stream<MusicSessionInput, E, R>,\n request: CommonStreamGenerateMusicRequest,\n ) => Stream.Stream<AudioChunk, AiError.AiError | E, R>\n}\n\nexport class LyriaGenerator extends Context.Service<LyriaGenerator, LyriaGeneratorService>()(\n \"@betalyra/effect-uai/providers/google/LyriaGenerator\",\n) {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n}\n\n// ---------------------------------------------------------------------------\n// Codec — request → prompt text + response_format\n// ---------------------------------------------------------------------------\n\nexport type LyriaResponseMimeType = \"audio/mp3\" | \"audio/wav\"\n\nexport const containerToMimeType: (\n container: AudioFormat[\"container\"],\n) => Effect.Effect<LyriaResponseMimeType, AiError.AiError> = Match.type<\n AudioFormat[\"container\"]\n>().pipe(\n Match.when(\"mp3\", () => Effect.succeed<LyriaResponseMimeType>(\"audio/mp3\")),\n Match.when(\"wav\", () => Effect.succeed<LyriaResponseMimeType>(\"audio/wav\")),\n Match.whenOr(\"ogg\", \"opus\", \"flac\", \"aac\", \"webm\", \"raw\", (c) =>\n Effect.fail(\n new AiError.Unsupported({\n provider: \"lyria\",\n capability: \"outputFormat\",\n reason: `Lyria 3 only produces audio/mp3 or audio/wav; ${c} is not available. Use container: \"mp3\" or \"wav\".`,\n }),\n ),\n ),\n Match.exhaustive,\n)\n\n/**\n * Is this a Lyria 3 Clip model variant? Clip is fixed at 30 s MP3 — it\n * does not accept a `responseFormat` on `generationConfig` (the live\n * API rejects unknown fields, despite what the docs imply). Pro\n * accepts the documented `responseFormat` knob.\n */\nconst isClipModel = (model: string): boolean => model.includes(\"clip\")\n\nexport const realizedFormat: (mime: LyriaResponseMimeType) => AudioFormat =\n Match.type<LyriaResponseMimeType>().pipe(\n Match.when(\n \"audio/mp3\",\n (): AudioFormat => ({ container: \"mp3\", encoding: \"mp3\", sampleRate: 44100, channels: 2 }),\n ),\n Match.when(\n \"audio/wav\",\n (): AudioFormat => ({\n container: \"wav\",\n encoding: \"pcm_s16le\",\n sampleRate: 44100,\n channels: 2,\n }),\n ),\n Match.exhaustive,\n )\n\n/**\n * Collapse `prompts | WeightedPrompt[]` into a single prompt string and\n * splice in non-structural hints (`lyrics`, `bpm`, `scale`,\n * `instrumental`, `durationSeconds`). The Lyria 3 sync API only takes\n * one text part — every condition lives inline.\n */\nexport const buildPrompt = (request: CommonGenerateMusicRequest): string => {\n const promptText = Match.value(request.prompts).pipe(\n Match.when(Match.string, (s) => s),\n Match.orElse((ws: ReadonlyArray<WeightedPrompt>) =>\n Arr.map(ws, (w) =>\n w.weight !== undefined && w.weight !== 1 ? `${w.text} (weight ${w.weight})` : w.text,\n ).join(\". \"),\n ),\n )\n const hints = Arr.getSomes([\n request.instrumental === true ? Option.some(\"Instrumental only — no vocals.\") : Option.none(),\n request.bpm !== undefined ? Option.some(`BPM: ${request.bpm}.`) : Option.none(),\n request.scale !== undefined ? Option.some(`Key/scale: ${request.scale}.`) : Option.none(),\n request.durationSeconds !== undefined\n ? Option.some(`Target duration: ${request.durationSeconds}s.`)\n : Option.none(),\n ])\n const lyricsBlock =\n request.lyrics !== undefined && request.instrumental !== true\n ? `\\n\\nLyrics:\\n${request.lyrics}`\n : \"\"\n return [promptText, ...hints].join(\" \") + lyricsBlock\n}\n\n// ---------------------------------------------------------------------------\n// Wire response schema — just enough to find audio + optional lyrics text\n// ---------------------------------------------------------------------------\n\nconst InlineData = Schema.Struct({\n mimeType: Schema.optional(Schema.String),\n data: Schema.String,\n})\n\nconst Part = Schema.Struct({\n text: Schema.optional(Schema.String),\n inlineData: Schema.optional(InlineData),\n})\n\nconst Content = Schema.Struct({\n parts: Schema.optional(Schema.Array(Part)),\n})\n\nconst Candidate = Schema.Struct({\n content: Schema.optional(Content),\n})\n\nconst Wire = Schema.Struct({\n candidates: Schema.optional(Schema.Array(Candidate)),\n})\n\nconst decodeWire = Schema.decodeUnknownEffect(Wire)\n\n// ---------------------------------------------------------------------------\n// HTTP plumbing\n// ---------------------------------------------------------------------------\n\nconst baseUrl = (cfg: Config): string =>\n cfg.baseUrl ?? \"https://generativelanguage.googleapis.com/v1beta\"\n\nconst transportFailure = (cause: unknown): AiError.AiError =>\n new AiError.Unavailable({ provider: \"lyria\", raw: cause })\n\nconst httpStatusError: (status: number, body: string) => AiError.AiError = (status, body) =>\n Match.value(status).pipe(\n Match.when(\n 429,\n (): AiError.AiError => new AiError.RateLimited({ provider: \"lyria\", raw: body }),\n ),\n Match.whenOr(\n 408,\n 504,\n (): AiError.AiError => new AiError.Timeout({ provider: \"lyria\", raw: body }),\n ),\n Match.when(\n 401,\n (): AiError.AiError =>\n new AiError.AuthFailed({ provider: \"lyria\", subtype: \"auth\", raw: body }),\n ),\n Match.when(\n 403,\n (): AiError.AiError =>\n new AiError.AuthFailed({ provider: \"lyria\", subtype: \"permission\", raw: body }),\n ),\n Match.when(\n 402,\n (): AiError.AiError =>\n new AiError.AuthFailed({ provider: \"lyria\", subtype: \"billing\", raw: body }),\n ),\n Match.when(\n (n) => n >= 500,\n (n): AiError.AiError => new AiError.Unavailable({ provider: \"lyria\", status: n, raw: body }),\n ),\n Match.orElse(\n (): AiError.AiError => new AiError.InvalidRequest({ provider: \"lyria\", raw: body }),\n ),\n )\n\nconst decodeBase64ToBytes = (b64: string): Effect.Effect<Uint8Array, AiError.AiError> =>\n Result.match(Encoding.decodeBase64(b64), {\n onSuccess: Effect.succeed,\n onFailure: (cause) =>\n Effect.fail(new AiError.InvalidRequest({ provider: \"lyria\", param: \"audio\", raw: cause })),\n })\n\n/**\n * Find the first inline-data audio part and the concatenated text parts\n * from a Wire response.\n */\nconst extractParts = (\n wire: typeof Wire.Type,\n): { readonly inline?: typeof InlineData.Type; readonly text?: string } => {\n const allParts = (wire.candidates ?? []).flatMap((c) => c.content?.parts ?? [])\n const inline = allParts.find((p) => p.inlineData !== undefined)?.inlineData\n const textParts = allParts.flatMap((p) => (p.text !== undefined ? [p.text] : []))\n const text = textParts.length === 0 ? undefined : textParts.join(\"\\n\")\n return {\n ...(inline !== undefined && { inline }),\n ...(text !== undefined && { text }),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Service implementation\n// ---------------------------------------------------------------------------\n\nconst generateImpl =\n (cfg: Config) =>\n (\n request: LyriaGenerateRequest,\n ): Effect.Effect<MusicResult, AiError.AiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const requestedContainer = (request.outputFormat?.container ??\n \"mp3\") satisfies AudioFormat[\"container\"]\n const mimeType = yield* containerToMimeType(requestedContainer)\n // Clip is fixed 30 s mp3 — reject wav up front rather than sending\n // a `responseFormat` that the live API ignores or rejects.\n if (isClipModel(request.model) && mimeType === \"audio/wav\") {\n return yield* Effect.fail(\n new AiError.Unsupported({\n provider: \"lyria\",\n capability: \"outputFormat\",\n reason: `lyria-3-clip-preview is fixed at audio/mp3. Use lyria-3-pro-preview for audio/wav output.`,\n }),\n )\n }\n const promptText = buildPrompt(request)\n const body = isClipModel(request.model)\n ? { contents: [{ parts: [{ text: promptText }] }] }\n : {\n contents: [{ parts: [{ text: promptText }] }],\n generationConfig: {\n responseModalities: [\"AUDIO\", \"TEXT\"],\n responseFormat: { audio: { mimeType } },\n },\n }\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(body),\n )\n const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure))\n if (response.status >= 400) {\n const text = yield* response.text.pipe(Effect.orElseSucceed(() => \"\"))\n return yield* Effect.fail(httpStatusError(response.status, text))\n }\n const json = yield* response.json.pipe(Effect.mapError(transportFailure))\n const wire = yield* decodeWire(json).pipe(\n Effect.mapError(\n (cause): AiError.AiError =>\n new AiError.GenerationFailed({ provider: \"lyria\", raw: cause }),\n ),\n )\n const { inline, text } = extractParts(wire)\n if (inline === undefined) {\n // No audio came back. Most common cause: Lyria filtered the prompt\n // (named-artist references, copyrighted lyrics, etc.). Surface the\n // full response body — text parts, finish reasons, prompt-feedback\n // blocks — so the caller can see why.\n return yield* Effect.fail(\n new AiError.GenerationFailed({\n provider: \"lyria\",\n raw: {\n message: \"Lyria response had no inlineData audio part.\",\n hint: \"Likely a prompt-filter rejection. Lyria filters references to real artists / copyrighted song lyrics. Rephrase with descriptive style language instead of artist names.\",\n textParts: text,\n response: json,\n },\n }),\n )\n }\n const bytes = yield* decodeBase64ToBytes(inline.data)\n return {\n format: realizedFormat(mimeType),\n bytes,\n ...(text !== undefined && { lyrics: text }),\n watermark: { kind: \"synthid\" },\n } satisfies MusicResult\n })\n\nconst streamGenerationImpl =\n (cfg: Config) =>\n (\n request: LyriaGenerateRequest,\n ): Stream.Stream<AudioChunk, AiError.AiError, HttpClient.HttpClient> =>\n Stream.unwrap(\n Effect.map(generateImpl(cfg)(request), (result) =>\n Stream.succeed<AudioChunk>({ bytes: result.bytes }),\n ),\n )\n\n/**\n * Lyria 3 sync has no bidirectional session at the wire level — that's\n * Lyria RealTime (Phase 1b, separate Layer). The provider Layer here\n * also does NOT register `MusicInteractiveSession`, so callers using\n * `MusicGenerator.streamGenerationFrom` against only this Layer get a\n * compile-time error before this runtime fallback ever fires.\n */\nconst streamGenerationFromUnsupported = <E, R>(\n _input: Stream.Stream<MusicSessionInput, E, R>,\n _request: CommonStreamGenerateMusicRequest,\n): Stream.Stream<AudioChunk, AiError.AiError | E, R> => {\n const fail: Stream.Stream<AudioChunk, AiError.AiError | E, R> = Stream.fail(\n new AiError.Unsupported({\n provider: \"lyria\",\n capability: \"streamGenerationFrom\",\n reason:\n \"Lyria 3 sync does not support bidirectional sessions. Use the Lyria RealTime Layer (WebSocket BidiGenerateMusic) for interactive mid-session prompts.\",\n }),\n )\n return fail\n}\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\nexport const make = (\n cfg: Config,\n): Effect.Effect<LyriaGeneratorService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient.asEffect(), (client) => ({\n generate: (request) =>\n generateImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n streamGeneration: (request) =>\n streamGenerationImpl(cfg)(request).pipe(Stream.provideService(HttpClient.HttpClient, client)),\n streamGenerationFrom: streamGenerationFromUnsupported,\n }))\n\n/**\n * Layer that registers both the provider-specific `LyriaGenerator` tag\n * and the generic `MusicGenerator` tag.\n *\n * Does NOT register the `MusicInteractiveSession` capability marker —\n * Lyria 3 sync has no bidirectional session at the wire level. Code\n * calling `MusicGenerator.streamGenerationFrom` will fail to typecheck\n * against this Layer alone, which is the intended UX.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<LyriaGenerator | MusicGenerator, never, HttpClient.HttpClient> =>\n Layer.merge(\n Layer.effect(LyriaGenerator, make(cfg)),\n Layer.effect(\n MusicGenerator,\n Effect.map(\n make(cfg),\n (s): MusicGeneratorService => ({\n generate: (req: CommonGenerateMusicRequest) => s.generate(req as LyriaGenerateRequest),\n streamGeneration: (req: CommonStreamGenerateMusicRequest) =>\n s.streamGeneration(req as LyriaGenerateRequest),\n streamGenerationFrom: s.streamGenerationFrom,\n }),\n ),\n ),\n )\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,138 @@
1
+ import { i as it, n as globalExpect, r as describe, t as import_dist } from "./dist-DMsg8YLb.mjs";
2
+ import { LyriaGenerator, buildPrompt, containerToMimeType, layer, realizedFormat } from "./LyriaGenerator.mjs";
3
+ import { Effect, Layer, Redacted, Stream } from "effect";
4
+ import { FetchHttpClient } from "effect/unstable/http";
5
+ import * as MusicGenerator$1 from "@effect-uai/core/MusicGenerator";
6
+ import { promptsInput } from "@effect-uai/core/Music";
7
+ //#region src/LyriaGenerator.test.ts
8
+ const cfg = { apiKey: Redacted.make("test-key") };
9
+ const live = Layer.provide(layer(cfg), FetchHttpClient.layer);
10
+ describe("buildPrompt", () => {
11
+ it("returns a plain string when prompts is a string", () => {
12
+ globalExpect(buildPrompt({
13
+ model: "lyria-3-clip-preview",
14
+ prompts: "upbeat indie pop"
15
+ })).toBe("upbeat indie pop");
16
+ });
17
+ it("joins weighted prompts with weight annotations", () => {
18
+ const out = buildPrompt({
19
+ model: "lyria-3-clip-preview",
20
+ prompts: [{
21
+ text: "minimal techno",
22
+ weight: 1
23
+ }, {
24
+ text: "1980s synthwave",
25
+ weight: .3
26
+ }]
27
+ });
28
+ globalExpect(out).toContain("minimal techno");
29
+ globalExpect(out).toContain("1980s synthwave (weight 0.3)");
30
+ });
31
+ it("omits the weight annotation for weight === 1", () => {
32
+ globalExpect(buildPrompt({
33
+ model: "lyria-3-clip-preview",
34
+ prompts: [{
35
+ text: "ambient",
36
+ weight: 1
37
+ }]
38
+ })).toBe("ambient");
39
+ });
40
+ it("splices in bpm, scale, duration, and instrumental hints", () => {
41
+ const out = buildPrompt({
42
+ model: "lyria-3-clip-preview",
43
+ prompts: "house",
44
+ bpm: 124,
45
+ scale: "C_MAJOR",
46
+ durationSeconds: 30,
47
+ instrumental: true
48
+ });
49
+ globalExpect(out).toContain("Instrumental only");
50
+ globalExpect(out).toContain("BPM: 124");
51
+ globalExpect(out).toContain("Key/scale: C_MAJOR");
52
+ globalExpect(out).toContain("Target duration: 30s");
53
+ });
54
+ it("appends a lyrics block when lyrics is set and instrumental is not true", () => {
55
+ const out = buildPrompt({
56
+ model: "lyria-3-clip-preview",
57
+ prompts: "indie rock",
58
+ lyrics: "[Verse]\nhello world"
59
+ });
60
+ globalExpect(out).toContain("Lyrics:");
61
+ globalExpect(out).toContain("[Verse]");
62
+ });
63
+ it("drops the lyrics block when instrumental is true", () => {
64
+ globalExpect(buildPrompt({
65
+ model: "lyria-3-clip-preview",
66
+ prompts: "indie rock",
67
+ lyrics: "[Verse]\nhello world",
68
+ instrumental: true
69
+ })).not.toContain("Lyrics:");
70
+ });
71
+ });
72
+ describe("containerToMimeType", () => {
73
+ it("maps mp3 and wav", async () => {
74
+ globalExpect(await Effect.runPromise(containerToMimeType("mp3"))).toBe("audio/mp3");
75
+ globalExpect(await Effect.runPromise(containerToMimeType("wav"))).toBe("audio/wav");
76
+ });
77
+ it("fails Unsupported for ogg / opus / flac / aac / webm / raw", async () => {
78
+ const exit = await Effect.runPromiseExit(containerToMimeType("ogg"));
79
+ globalExpect(exit._tag).toBe("Failure");
80
+ if (exit._tag === "Failure") {
81
+ globalExpect(JSON.stringify(exit.cause)).toContain("Unsupported");
82
+ globalExpect(JSON.stringify(exit.cause)).toContain("outputFormat");
83
+ }
84
+ });
85
+ });
86
+ describe("realizedFormat", () => {
87
+ it("reports 44.1 kHz stereo for both mp3 and wav", () => {
88
+ globalExpect(realizedFormat("audio/mp3")).toEqual({
89
+ container: "mp3",
90
+ encoding: "mp3",
91
+ sampleRate: 44100,
92
+ channels: 2
93
+ });
94
+ globalExpect(realizedFormat("audio/wav")).toEqual({
95
+ container: "wav",
96
+ encoding: "pcm_s16le",
97
+ sampleRate: 44100,
98
+ channels: 2
99
+ });
100
+ });
101
+ });
102
+ describe("LyriaGenerator capability guards (runtime)", () => {
103
+ it("streamGenerationFrom returns an Unsupported stream", async () => {
104
+ const program = LyriaGenerator.use((s) => Stream.runDrain(s.streamGenerationFrom(Stream.fromIterable([promptsInput([{ text: "x" }])]), {
105
+ model: "lyria-3-clip-preview",
106
+ prompts: ""
107
+ })));
108
+ const exit = await Effect.runPromiseExit(program.pipe(Effect.provide(live)));
109
+ globalExpect(exit._tag).toBe("Failure");
110
+ if (exit._tag === "Failure") {
111
+ globalExpect(JSON.stringify(exit.cause)).toContain("Unsupported");
112
+ globalExpect(JSON.stringify(exit.cause)).toContain("streamGenerationFrom");
113
+ }
114
+ });
115
+ });
116
+ describe("LyriaGenerator Layer (compile-time)", () => {
117
+ it("leaves `MusicInteractiveSession` unsatisfied when using `streamGenerationFrom` against this Layer", () => {
118
+ const audio = Stream.fromIterable([promptsInput([{ text: "techno" }])]).pipe(MusicGenerator$1.streamGenerationFrom({
119
+ model: "lyria-3-clip-preview",
120
+ prompts: ""
121
+ }));
122
+ (0, import_dist.expectTypeOf)(Stream.runDrain(audio).pipe(Effect.provide(live))).toEqualTypeOf();
123
+ });
124
+ it("sync `generate` and chunked `streamGeneration` require no marker", () => {
125
+ (0, import_dist.expectTypeOf)(MusicGenerator$1.generate({
126
+ model: "lyria-3-clip-preview",
127
+ prompts: "ambient"
128
+ }).pipe(Effect.provide(live))).toEqualTypeOf();
129
+ globalExpect(Stream.runCollect(MusicGenerator$1.streamGeneration({
130
+ model: "lyria-3-clip-preview",
131
+ prompts: "ambient"
132
+ })).pipe(Effect.provide(live))).toBeDefined();
133
+ });
134
+ });
135
+ //#endregion
136
+ export {};
137
+
138
+ //# sourceMappingURL=LyriaGenerator.test.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LyriaGenerator.test.mjs","names":["LyriaGenerator.layer","LyriaGenerator.buildPrompt","LyriaGenerator.containerToMimeType","LyriaGenerator.realizedFormat","MusicGenerator"],"sources":["../src/LyriaGenerator.test.ts"],"sourcesContent":["import { Effect, Layer, Redacted, Stream } from \"effect\"\nimport { FetchHttpClient } from \"effect/unstable/http\"\nimport { describe, expect, expectTypeOf, it } from \"vitest\"\nimport type * as AiError from \"@effect-uai/core/AiError\"\nimport * as MusicGenerator from \"@effect-uai/core/MusicGenerator\"\nimport { promptsInput } from \"@effect-uai/core/Music\"\nimport * as LyriaGenerator from \"./LyriaGenerator.js\"\n\nconst cfg: LyriaGenerator.Config = { apiKey: Redacted.make(\"test-key\") }\n// FetchHttpClient is required for `make`, but these tests only exercise the\n// compile-time gating and runtime Unsupported branches — no real HTTP is made.\nconst live = Layer.provide(LyriaGenerator.layer(cfg), FetchHttpClient.layer)\n\ndescribe(\"buildPrompt\", () => {\n it(\"returns a plain string when prompts is a string\", () => {\n const out = LyriaGenerator.buildPrompt({\n model: \"lyria-3-clip-preview\",\n prompts: \"upbeat indie pop\",\n })\n expect(out).toBe(\"upbeat indie pop\")\n })\n\n it(\"joins weighted prompts with weight annotations\", () => {\n const out = LyriaGenerator.buildPrompt({\n model: \"lyria-3-clip-preview\",\n prompts: [\n { text: \"minimal techno\", weight: 1.0 },\n { text: \"1980s synthwave\", weight: 0.3 },\n ],\n })\n expect(out).toContain(\"minimal techno\")\n expect(out).toContain(\"1980s synthwave (weight 0.3)\")\n })\n\n it(\"omits the weight annotation for weight === 1\", () => {\n const out = LyriaGenerator.buildPrompt({\n model: \"lyria-3-clip-preview\",\n prompts: [{ text: \"ambient\", weight: 1 }],\n })\n expect(out).toBe(\"ambient\")\n })\n\n it(\"splices in bpm, scale, duration, and instrumental hints\", () => {\n const out = LyriaGenerator.buildPrompt({\n model: \"lyria-3-clip-preview\",\n prompts: \"house\",\n bpm: 124,\n scale: \"C_MAJOR\",\n durationSeconds: 30,\n instrumental: true,\n })\n expect(out).toContain(\"Instrumental only\")\n expect(out).toContain(\"BPM: 124\")\n expect(out).toContain(\"Key/scale: C_MAJOR\")\n expect(out).toContain(\"Target duration: 30s\")\n })\n\n it(\"appends a lyrics block when lyrics is set and instrumental is not true\", () => {\n const out = LyriaGenerator.buildPrompt({\n model: \"lyria-3-clip-preview\",\n prompts: \"indie rock\",\n lyrics: \"[Verse]\\nhello world\",\n })\n expect(out).toContain(\"Lyrics:\")\n expect(out).toContain(\"[Verse]\")\n })\n\n it(\"drops the lyrics block when instrumental is true\", () => {\n const out = LyriaGenerator.buildPrompt({\n model: \"lyria-3-clip-preview\",\n prompts: \"indie rock\",\n lyrics: \"[Verse]\\nhello world\",\n instrumental: true,\n })\n expect(out).not.toContain(\"Lyrics:\")\n })\n})\n\ndescribe(\"containerToMimeType\", () => {\n it(\"maps mp3 and wav\", async () => {\n expect(await Effect.runPromise(LyriaGenerator.containerToMimeType(\"mp3\"))).toBe(\"audio/mp3\")\n expect(await Effect.runPromise(LyriaGenerator.containerToMimeType(\"wav\"))).toBe(\"audio/wav\")\n })\n\n it(\"fails Unsupported for ogg / opus / flac / aac / webm / raw\", async () => {\n const exit = await Effect.runPromiseExit(LyriaGenerator.containerToMimeType(\"ogg\"))\n expect(exit._tag).toBe(\"Failure\")\n if (exit._tag === \"Failure\") {\n expect(JSON.stringify(exit.cause)).toContain(\"Unsupported\")\n expect(JSON.stringify(exit.cause)).toContain(\"outputFormat\")\n }\n })\n})\n\ndescribe(\"realizedFormat\", () => {\n it(\"reports 44.1 kHz stereo for both mp3 and wav\", () => {\n expect(LyriaGenerator.realizedFormat(\"audio/mp3\")).toEqual({\n container: \"mp3\",\n encoding: \"mp3\",\n sampleRate: 44100,\n channels: 2,\n })\n expect(LyriaGenerator.realizedFormat(\"audio/wav\")).toEqual({\n container: \"wav\",\n encoding: \"pcm_s16le\",\n sampleRate: 44100,\n channels: 2,\n })\n })\n})\n\ndescribe(\"LyriaGenerator capability guards (runtime)\", () => {\n it(\"streamGenerationFrom returns an Unsupported stream\", async () => {\n const program = LyriaGenerator.LyriaGenerator.use((s) =>\n Stream.runDrain(\n s.streamGenerationFrom(Stream.fromIterable([promptsInput([{ text: \"x\" }])]), {\n model: \"lyria-3-clip-preview\",\n prompts: \"\",\n }),\n ),\n )\n const exit = await Effect.runPromiseExit(program.pipe(Effect.provide(live)))\n expect(exit._tag).toBe(\"Failure\")\n if (exit._tag === \"Failure\") {\n expect(JSON.stringify(exit.cause)).toContain(\"Unsupported\")\n expect(JSON.stringify(exit.cause)).toContain(\"streamGenerationFrom\")\n }\n })\n})\n\ndescribe(\"LyriaGenerator Layer (compile-time)\", () => {\n it(\"leaves `MusicInteractiveSession` unsatisfied when using `streamGenerationFrom` against this Layer\", () => {\n const inputs = Stream.fromIterable([promptsInput([{ text: \"techno\" }])])\n const audio = inputs.pipe(\n MusicGenerator.streamGenerationFrom({\n model: \"lyria-3-clip-preview\",\n prompts: \"\",\n }),\n )\n const provided = Stream.runDrain(audio).pipe(Effect.provide(live))\n expectTypeOf(provided).toEqualTypeOf<\n Effect.Effect<void, AiError.AiError, MusicGenerator.MusicInteractiveSession>\n >()\n })\n\n it(\"sync `generate` and chunked `streamGeneration` require no marker\", () => {\n const gen = MusicGenerator.generate({\n model: \"lyria-3-clip-preview\",\n prompts: \"ambient\",\n }).pipe(Effect.provide(live))\n expectTypeOf(gen).toEqualTypeOf<\n Effect.Effect<import(\"@effect-uai/core/Music\").MusicResult, AiError.AiError, never>\n >()\n const _stream = Stream.runCollect(\n MusicGenerator.streamGeneration({\n model: \"lyria-3-clip-preview\",\n prompts: \"ambient\",\n }),\n ).pipe(Effect.provide(live))\n expect(_stream).toBeDefined()\n })\n})\n"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  //#region \0rolldown/runtime.js
2
2
  var __defProp = Object.defineProperty;
3
+ var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
3
4
  var __exportAll = (all, no_symbols) => {
4
5
  let target = {};
5
6
  for (var name in all) __defProp(target, name, {
@@ -10,4 +11,4 @@ var __exportAll = (all, no_symbols) => {
10
11
  return target;
11
12
  };
12
13
  //#endregion
13
- export { __exportAll as t };
14
+ export { __exportAll as n, __commonJSMin as t };
package/dist/codec.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as __exportAll } from "./chunk-CfYAbeIz.mjs";
1
+ import { n as __exportAll } from "./chunk-uyGKjUfl.mjs";
2
2
  import { Array, Encoding, Match, Option, Result, Schema, pipe } from "effect";
3
3
  //#region src/codec.ts
4
4
  var codec_exports = /* @__PURE__ */ __exportAll({