@effect-uai/google 0.3.0 → 0.5.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 +6 -5
- package/dist/Gemini.d.mts.map +1 -1
- package/dist/Gemini.mjs +42 -22
- package/dist/Gemini.mjs.map +1 -1
- package/dist/GeminiEmbedding.d.mts.map +1 -1
- package/dist/GeminiEmbedding.mjs +2 -2
- package/dist/GeminiEmbedding.mjs.map +1 -1
- package/dist/GeminiSynthesizer.d.mts +38 -0
- package/dist/GeminiSynthesizer.d.mts.map +1 -0
- package/dist/GeminiSynthesizer.mjs +95 -0
- package/dist/GeminiSynthesizer.mjs.map +1 -0
- package/dist/GeminiSynthesizer.test.d.mts +1 -0
- package/dist/GeminiSynthesizer.test.mjs +84 -0
- package/dist/GeminiSynthesizer.test.mjs.map +1 -0
- package/dist/GeminiTranscriber.d.mts +42 -0
- package/dist/GeminiTranscriber.d.mts.map +1 -0
- package/dist/GeminiTranscriber.mjs +80 -0
- package/dist/GeminiTranscriber.mjs.map +1 -0
- package/dist/GeminiTranscriber.test.d.mts +1 -0
- package/dist/GeminiTranscriber.test.mjs +125 -0
- package/dist/GeminiTranscriber.test.mjs.map +1 -0
- package/dist/LyriaGenerator.d.mts +57 -0
- package/dist/LyriaGenerator.d.mts.map +1 -0
- package/dist/LyriaGenerator.mjs +202 -0
- package/dist/LyriaGenerator.mjs.map +1 -0
- package/dist/LyriaGenerator.test.d.mts +1 -0
- package/dist/LyriaGenerator.test.mjs +138 -0
- package/dist/LyriaGenerator.test.mjs.map +1 -0
- package/dist/{chunk-CfYAbeIz.mjs → chunk-uyGKjUfl.mjs} +2 -1
- package/dist/codec.d.mts +51 -6
- package/dist/codec.d.mts.map +1 -1
- package/dist/codec.mjs +151 -27
- package/dist/codec.mjs.map +1 -1
- package/dist/dist-DMsg8YLb.mjs +13782 -0
- package/dist/dist-DMsg8YLb.mjs.map +1 -0
- package/dist/geminiSpeechCodec.d.mts +37 -0
- package/dist/geminiSpeechCodec.d.mts.map +1 -0
- package/dist/geminiSpeechCodec.mjs +110 -0
- package/dist/geminiSpeechCodec.mjs.map +1 -0
- package/dist/geminiSpeechCodec.test.d.mts +1 -0
- package/dist/geminiSpeechCodec.test.mjs +78 -0
- package/dist/geminiSpeechCodec.test.mjs.map +1 -0
- package/dist/index.d.mts +5 -2
- package/dist/index.mjs +4 -1
- package/dist/magic-string.es-CaWgF-3p.mjs +1013 -0
- package/dist/magic-string.es-CaWgF-3p.mjs.map +1 -0
- package/dist/models.d.mts +35 -1
- package/dist/models.d.mts.map +1 -1
- package/package.json +14 -2
- package/src/Gemini.ts +53 -18
- package/src/GeminiEmbedding.ts +23 -5
- package/src/GeminiSynthesizer.test.ts +93 -0
- package/src/GeminiSynthesizer.ts +214 -0
- package/src/GeminiTranscriber.test.ts +136 -0
- package/src/GeminiTranscriber.ts +204 -0
- package/src/LyriaGenerator.test.ts +162 -0
- package/src/LyriaGenerator.ts +390 -0
- package/src/codec.ts +312 -36
- package/src/geminiSpeechCodec.test.ts +56 -0
- package/src/geminiSpeechCodec.ts +137 -0
- package/src/index.ts +3 -0
- package/src/models.ts +84 -0
|
@@ -0,0 +1,42 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { i as it, n as globalExpect, r as describe, t as import_dist } from "./dist-DMsg8YLb.mjs";
|
|
2
|
+
import { GeminiTranscriber, buildPrompt, layer } from "./GeminiTranscriber.mjs";
|
|
3
|
+
import { Effect, Layer, Redacted, Stream } from "effect";
|
|
4
|
+
import { FetchHttpClient } from "effect/unstable/http";
|
|
5
|
+
import * as Transcriber$1 from "@effect-uai/core/Transcriber";
|
|
6
|
+
//#region src/GeminiTranscriber.test.ts
|
|
7
|
+
const cfg = { apiKey: Redacted.make("test-key") };
|
|
8
|
+
const live = Layer.provide(layer(cfg), FetchHttpClient.layer);
|
|
9
|
+
const dummyAudio = {
|
|
10
|
+
_tag: "bytes",
|
|
11
|
+
bytes: new Uint8Array([
|
|
12
|
+
0,
|
|
13
|
+
1,
|
|
14
|
+
2,
|
|
15
|
+
3
|
|
16
|
+
]),
|
|
17
|
+
mimeType: "audio/wav"
|
|
18
|
+
};
|
|
19
|
+
describe("GeminiTranscriber.buildPrompt", () => {
|
|
20
|
+
it("uses the language-agnostic prompt when no language is set", () => {
|
|
21
|
+
globalExpect(buildPrompt({
|
|
22
|
+
audio: dummyAudio,
|
|
23
|
+
model: "gemini-2.5-flash"
|
|
24
|
+
})).toContain("Transcribe the audio verbatim. Return only the transcript text");
|
|
25
|
+
});
|
|
26
|
+
it("includes the language when set", () => {
|
|
27
|
+
globalExpect(buildPrompt({
|
|
28
|
+
audio: dummyAudio,
|
|
29
|
+
model: "gemini-2.5-flash",
|
|
30
|
+
language: "pt-BR"
|
|
31
|
+
})).toContain("in pt-BR");
|
|
32
|
+
});
|
|
33
|
+
it("appends a free-text prompt as context", () => {
|
|
34
|
+
globalExpect(buildPrompt({
|
|
35
|
+
audio: dummyAudio,
|
|
36
|
+
model: "gemini-2.5-flash",
|
|
37
|
+
prompt: "Engineering meeting; expect technical jargon."
|
|
38
|
+
})).toContain("Context: Engineering meeting");
|
|
39
|
+
});
|
|
40
|
+
it("appends a terms list as biasing vocabulary", () => {
|
|
41
|
+
globalExpect(buildPrompt({
|
|
42
|
+
audio: dummyAudio,
|
|
43
|
+
model: "gemini-2.5-flash",
|
|
44
|
+
prompt: { terms: [
|
|
45
|
+
"Effect",
|
|
46
|
+
"Lyria",
|
|
47
|
+
"MusicGenerator"
|
|
48
|
+
] }
|
|
49
|
+
})).toContain("Effect, Lyria, MusicGenerator");
|
|
50
|
+
});
|
|
51
|
+
it("omits the biasing block when the terms list is empty", () => {
|
|
52
|
+
globalExpect(buildPrompt({
|
|
53
|
+
audio: dummyAudio,
|
|
54
|
+
model: "gemini-2.5-flash",
|
|
55
|
+
prompt: { terms: [] }
|
|
56
|
+
})).not.toContain("terms / names");
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
describe("GeminiTranscriber capability guards (runtime)", () => {
|
|
60
|
+
it("fails Unsupported for wordTimestamps", async () => {
|
|
61
|
+
const program = GeminiTranscriber.use((s) => s.transcribe({
|
|
62
|
+
audio: dummyAudio,
|
|
63
|
+
model: "gemini-2.5-flash",
|
|
64
|
+
wordTimestamps: true
|
|
65
|
+
}));
|
|
66
|
+
const exit = await Effect.runPromiseExit(program.pipe(Effect.provide(live)));
|
|
67
|
+
globalExpect(exit._tag).toBe("Failure");
|
|
68
|
+
if (exit._tag === "Failure") {
|
|
69
|
+
globalExpect(JSON.stringify(exit.cause)).toContain("Unsupported");
|
|
70
|
+
globalExpect(JSON.stringify(exit.cause)).toContain("wordTimestamps");
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
it("fails Unsupported for diarization", async () => {
|
|
74
|
+
const program = GeminiTranscriber.use((s) => s.transcribe({
|
|
75
|
+
audio: dummyAudio,
|
|
76
|
+
model: "gemini-2.5-flash",
|
|
77
|
+
diarization: true
|
|
78
|
+
}));
|
|
79
|
+
const exit = await Effect.runPromiseExit(program.pipe(Effect.provide(live)));
|
|
80
|
+
globalExpect(exit._tag).toBe("Failure");
|
|
81
|
+
if (exit._tag === "Failure") {
|
|
82
|
+
globalExpect(JSON.stringify(exit.cause)).toContain("Unsupported");
|
|
83
|
+
globalExpect(JSON.stringify(exit.cause)).toContain("diarization");
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
it("streamTranscriptionFrom returns an Unsupported stream", async () => {
|
|
87
|
+
const program = GeminiTranscriber.use((s) => Stream.runDrain(s.streamTranscriptionFrom(Stream.fromIterable([new Uint8Array([0])]), {
|
|
88
|
+
model: "gemini-2.5-flash",
|
|
89
|
+
inputFormat: {
|
|
90
|
+
container: "raw",
|
|
91
|
+
encoding: "pcm_s16le",
|
|
92
|
+
sampleRate: 16e3
|
|
93
|
+
}
|
|
94
|
+
})));
|
|
95
|
+
const exit = await Effect.runPromiseExit(program.pipe(Effect.provide(live)));
|
|
96
|
+
globalExpect(exit._tag).toBe("Failure");
|
|
97
|
+
if (exit._tag === "Failure") {
|
|
98
|
+
globalExpect(JSON.stringify(exit.cause)).toContain("Unsupported");
|
|
99
|
+
globalExpect(JSON.stringify(exit.cause)).toContain("streamTranscriptionFrom");
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
describe("GeminiTranscriber Layer (compile-time)", () => {
|
|
104
|
+
it("leaves `SttStreaming` unsatisfied when using `streamTranscriptionFrom` against this Layer", () => {
|
|
105
|
+
const events = Stream.fromIterable([new Uint8Array([0])]).pipe(Transcriber$1.streamTranscriptionFrom({
|
|
106
|
+
model: "gemini-2.5-flash",
|
|
107
|
+
inputFormat: {
|
|
108
|
+
container: "raw",
|
|
109
|
+
encoding: "pcm_s16le",
|
|
110
|
+
sampleRate: 16e3
|
|
111
|
+
}
|
|
112
|
+
}));
|
|
113
|
+
(0, import_dist.expectTypeOf)(Stream.runDrain(events).pipe(Effect.provide(live))).toEqualTypeOf();
|
|
114
|
+
});
|
|
115
|
+
it("sync `transcribe` requires no marker", () => {
|
|
116
|
+
(0, import_dist.expectTypeOf)(Transcriber$1.transcribe({
|
|
117
|
+
audio: dummyAudio,
|
|
118
|
+
model: "gemini-2.5-flash"
|
|
119
|
+
}).pipe(Effect.provide(live))).toEqualTypeOf();
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
//#endregion
|
|
123
|
+
export {};
|
|
124
|
+
|
|
125
|
+
//# sourceMappingURL=GeminiTranscriber.test.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GeminiTranscriber.test.mjs","names":["GeminiTranscriber.layer","GeminiTranscriber.buildPrompt","Transcriber"],"sources":["../src/GeminiTranscriber.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 type { AudioSource } from \"@effect-uai/core/Audio\"\nimport type { TranscriptResult } from \"@effect-uai/core/Transcript\"\nimport * as Transcriber from \"@effect-uai/core/Transcriber\"\nimport * as GeminiTranscriber from \"./GeminiTranscriber.js\"\n\nconst cfg: GeminiTranscriber.Config = { apiKey: Redacted.make(\"test-key\") }\nconst live = Layer.provide(GeminiTranscriber.layer(cfg), FetchHttpClient.layer)\n\nconst dummyAudio: AudioSource = {\n _tag: \"bytes\",\n bytes: new Uint8Array([0, 1, 2, 3]),\n mimeType: \"audio/wav\",\n}\n\ndescribe(\"GeminiTranscriber.buildPrompt\", () => {\n it(\"uses the language-agnostic prompt when no language is set\", () => {\n expect(\n GeminiTranscriber.buildPrompt({ audio: dummyAudio, model: \"gemini-2.5-flash\" }),\n ).toContain(\"Transcribe the audio verbatim. Return only the transcript text\")\n })\n\n it(\"includes the language when set\", () => {\n expect(\n GeminiTranscriber.buildPrompt({\n audio: dummyAudio,\n model: \"gemini-2.5-flash\",\n language: \"pt-BR\",\n }),\n ).toContain(\"in pt-BR\")\n })\n\n it(\"appends a free-text prompt as context\", () => {\n const out = GeminiTranscriber.buildPrompt({\n audio: dummyAudio,\n model: \"gemini-2.5-flash\",\n prompt: \"Engineering meeting; expect technical jargon.\",\n })\n expect(out).toContain(\"Context: Engineering meeting\")\n })\n\n it(\"appends a terms list as biasing vocabulary\", () => {\n const out = GeminiTranscriber.buildPrompt({\n audio: dummyAudio,\n model: \"gemini-2.5-flash\",\n prompt: { terms: [\"Effect\", \"Lyria\", \"MusicGenerator\"] },\n })\n expect(out).toContain(\"Effect, Lyria, MusicGenerator\")\n })\n\n it(\"omits the biasing block when the terms list is empty\", () => {\n const out = GeminiTranscriber.buildPrompt({\n audio: dummyAudio,\n model: \"gemini-2.5-flash\",\n prompt: { terms: [] },\n })\n expect(out).not.toContain(\"terms / names\")\n })\n})\n\ndescribe(\"GeminiTranscriber capability guards (runtime)\", () => {\n it(\"fails Unsupported for wordTimestamps\", async () => {\n const program = GeminiTranscriber.GeminiTranscriber.use((s) =>\n s.transcribe({\n audio: dummyAudio,\n model: \"gemini-2.5-flash\",\n wordTimestamps: true,\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(\"wordTimestamps\")\n }\n })\n\n it(\"fails Unsupported for diarization\", async () => {\n const program = GeminiTranscriber.GeminiTranscriber.use((s) =>\n s.transcribe({\n audio: dummyAudio,\n model: \"gemini-2.5-flash\",\n diarization: true,\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(\"diarization\")\n }\n })\n\n it(\"streamTranscriptionFrom returns an Unsupported stream\", async () => {\n const program = GeminiTranscriber.GeminiTranscriber.use((s) =>\n Stream.runDrain(\n s.streamTranscriptionFrom(Stream.fromIterable([new Uint8Array([0])]), {\n model: \"gemini-2.5-flash\",\n inputFormat: { container: \"raw\", encoding: \"pcm_s16le\", sampleRate: 16000 },\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(\"streamTranscriptionFrom\")\n }\n })\n})\n\ndescribe(\"GeminiTranscriber Layer (compile-time)\", () => {\n it(\"leaves `SttStreaming` unsatisfied when using `streamTranscriptionFrom` against this Layer\", () => {\n const audio: Stream.Stream<Uint8Array> = Stream.fromIterable([new Uint8Array([0])])\n const events = audio.pipe(\n Transcriber.streamTranscriptionFrom({\n model: \"gemini-2.5-flash\",\n inputFormat: { container: \"raw\", encoding: \"pcm_s16le\", sampleRate: 16000 },\n }),\n )\n const provided = Stream.runDrain(events).pipe(Effect.provide(live))\n expectTypeOf(provided).toEqualTypeOf<\n Effect.Effect<void, AiError.AiError, Transcriber.SttStreaming>\n >()\n })\n\n it(\"sync `transcribe` requires no marker\", () => {\n const t = Transcriber.transcribe({ audio: dummyAudio, model: \"gemini-2.5-flash\" }).pipe(\n Effect.provide(live),\n )\n expectTypeOf(t).toEqualTypeOf<Effect.Effect<TranscriptResult, AiError.AiError, never>>()\n })\n})\n"],"mappings":"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"}
|
|
@@ -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,cA0OxB,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* 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* 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* 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* 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 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* 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* 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 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 { };
|