@effect-uai/google 0.4.0 → 0.5.1
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 +41 -21
- package/dist/Gemini.mjs.map +1 -1
- package/dist/GeminiEmbedding.d.mts.map +1 -1
- package/dist/GeminiEmbedding.mjs +1 -1
- package/dist/GeminiEmbedding.mjs.map +1 -1
- package/dist/GeminiSynthesizer.d.mts +3 -13
- package/dist/GeminiSynthesizer.d.mts.map +1 -1
- package/dist/GeminiSynthesizer.mjs +1 -1
- package/dist/GeminiSynthesizer.mjs.map +1 -1
- package/dist/GeminiTranscriber.d.mts +2 -2
- package/dist/GeminiTranscriber.d.mts.map +1 -1
- package/dist/GeminiTranscriber.mjs +1 -1
- package/dist/GeminiTranscriber.mjs.map +1 -1
- package/dist/LyriaGenerator.d.mts.map +1 -1
- package/dist/LyriaGenerator.mjs +5 -5
- package/dist/LyriaGenerator.mjs.map +1 -1
- package/dist/codec.d.mts +51 -6
- package/dist/codec.d.mts.map +1 -1
- package/dist/codec.mjs +150 -26
- package/dist/codec.mjs.map +1 -1
- package/package.json +2 -2
- package/src/Gemini.ts +53 -18
- package/src/GeminiEmbedding.ts +23 -5
- package/src/GeminiSynthesizer.ts +1 -1
- package/src/GeminiTranscriber.ts +1 -1
- package/src/LyriaGenerator.ts +15 -19
- package/src/codec.ts +312 -36
package/dist/Gemini.d.mts
CHANGED
|
@@ -23,6 +23,12 @@ declare const ProviderEvent: Schema.Struct<{
|
|
|
23
23
|
readonly parts: Schema.optional<Schema.$Array<Schema.Union<readonly [Schema.Struct<{
|
|
24
24
|
readonly text: Schema.String;
|
|
25
25
|
readonly thought: Schema.optional<Schema.Boolean>;
|
|
26
|
+
}>, Schema.Struct<{
|
|
27
|
+
readonly functionCall: Schema.Struct<{
|
|
28
|
+
readonly id: Schema.optional<Schema.String>;
|
|
29
|
+
readonly name: Schema.String;
|
|
30
|
+
readonly args: Schema.optional<Schema.Unknown>;
|
|
31
|
+
}>;
|
|
26
32
|
}>]>>>;
|
|
27
33
|
}>>;
|
|
28
34
|
readonly finishReason: Schema.optional<Schema.String>;
|
|
@@ -82,11 +88,6 @@ type Config = {
|
|
|
82
88
|
readonly apiKey: Redacted.Redacted;
|
|
83
89
|
readonly baseUrl?: string;
|
|
84
90
|
};
|
|
85
|
-
/**
|
|
86
|
-
* Project a stream of native `ProviderEvent`s into canonical `TurnEvent`s.
|
|
87
|
-
* Threads a fresh `Accumulator` so chunk-level text/usage merging works
|
|
88
|
-
* across the run.
|
|
89
|
-
*/
|
|
90
91
|
declare const toCanonical: <E, R>(s: Stream.Stream<ProviderEvent, E, R>) => Stream.Stream<TurnEvent, E, R>;
|
|
91
92
|
/**
|
|
92
93
|
* Build a `GeminiService` value. For Layer-based setup, prefer `layer`.
|
package/dist/Gemini.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Gemini.d.mts","names":[],"sources":["../src/Gemini.ts"],"mappings":";;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"Gemini.d.mts","names":[],"sources":["../src/Gemini.ts"],"mappings":";;;;;;;;;;;;;;;;;;cA6Ba,aAAA,EAAa,MAAA,CAAA,MAAA;EAAA;;;;;;;;;;;;;;;;;;;;;;;;;KACd,aAAA,GAAgB,SAAA;AAAA,KAMhB,aAAA,GAAgB,IAAA,CAAK,aAAA;;;;;WAKtB,KAAA,EAAO,WAAA;;;;;;WAMP,cAAA;AAAA;AAAA,KAGC,aAAA;;;;;;;;WAQD,YAAA,GAAe,OAAA,EAAS,aAAA,KAAkB,MAAA,CAAO,MAAA,CAAO,aAAA,EAAe,OAAA,CAAQ,OAAA;EA7BhE;;;;EAAA,SAkCf,UAAA,GAAa,OAAA,EAAS,aAAA,KAAkB,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,OAAA,CAAQ,OAAA;;;;;;WAMzE,WAAA,SACP,CAAA,EAAG,MAAA,CAAO,MAAA,CAAO,aAAA,EAAe,CAAA,EAAG,CAAA,MAChC,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,CAAA,EAAG,CAAA;AAAA;AAAA,cAClC,WAAA;;;;;;cAOY,MAAA,SAAe,WAAA;AAAA,KAIhB,MAAA;EAAA,SACD,MAAA,EAAQ,QAAA,CAAS,QAAA;EAAA,SACjB,OAAA;AAAA;AAAA,cA8IE,WAAA,SACX,CAAA,EAAG,MAAA,CAAO,MAAA,CAAO,aAAA,EAAe,CAAA,EAAG,CAAA,MAClC,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,CAAA,EAAG,CAAA;;;;cAoClB,IAAA,GAAQ,GAAA,EAAK,MAAA,KAAS,MAAA,CAAO,MAAA,CAAO,aAAA,SAAsB,UAAA,CAAW,UAAA;;;;;;;;cAkBrE,KAAA,GACX,GAAA,EAAK,MAAA,KACJ,KAAA,CAAM,KAAA,CAAM,MAAA,GAAS,aAAA,SAAsB,UAAA,CAAW,UAAA"}
|
package/dist/Gemini.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { n as __exportAll } from "./chunk-uyGKjUfl.mjs";
|
|
2
2
|
import { WireChunk, accumulatorToTurn, buildRequestBody, emptyAccumulator, ingestChunk } from "./codec.mjs";
|
|
3
|
-
import { Context, Effect, Layer, Option, Redacted, Schema, Stream } from "effect";
|
|
3
|
+
import { Context, Effect, Layer, Match, Option, Redacted, Schema, Stream } from "effect";
|
|
4
4
|
import { HttpClient, HttpClientRequest } from "effect/unstable/http";
|
|
5
5
|
import * as AiError from "@effect-uai/core/AiError";
|
|
6
6
|
import { LanguageModel } from "@effect-uai/core/LanguageModel";
|
|
7
7
|
import { JsonParseError } from "@effect-uai/core/JSONL";
|
|
8
8
|
import * as SSE from "@effect-uai/core/SSE";
|
|
9
|
+
import { TurnEvent } from "@effect-uai/core/Turn";
|
|
9
10
|
//#region src/Gemini.ts
|
|
10
11
|
var Gemini_exports = /* @__PURE__ */ __exportAll({
|
|
11
12
|
Gemini: () => Gemini,
|
|
@@ -48,13 +49,11 @@ const decodeChunk = Schema.decodeUnknownEffect(WireChunk);
|
|
|
48
49
|
* an `_unknown` variant, so JSON parse / schema decode failures flow
|
|
49
50
|
* through as transport errors rather than being silently dropped.
|
|
50
51
|
*/
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
})
|
|
57
|
-
}).pipe(Effect.flatMap(decodeChunk));
|
|
52
|
+
const parseJsonUnknown = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown));
|
|
53
|
+
const sseEventToChunk = (ev) => parseJsonUnknown(ev.data).pipe(Effect.mapError((cause) => new JsonParseError({
|
|
54
|
+
line: ev.data,
|
|
55
|
+
cause
|
|
56
|
+
})), Effect.flatMap(decodeChunk));
|
|
58
57
|
const httpStatusError = (status, body) => {
|
|
59
58
|
const provider = "gemini";
|
|
60
59
|
const raw = body;
|
|
@@ -101,7 +100,7 @@ const buildNativeStream = (cfg) => {
|
|
|
101
100
|
const client = yield* HttpClient.HttpClient;
|
|
102
101
|
const url = `${baseUrl}/models/${request.model}:streamGenerateContent?alt=sse`;
|
|
103
102
|
const generationConfig = buildGenerationConfig(request);
|
|
104
|
-
const body = buildRequestBody(request.history, generationConfig);
|
|
103
|
+
const body = buildRequestBody(request.history, generationConfig, request.tools ?? []);
|
|
105
104
|
const httpRequest = HttpClientRequest.post(url).pipe(HttpClientRequest.setHeader("x-goog-api-key", Redacted.value(cfg.apiKey)), HttpClientRequest.bodyJsonUnsafe(body), HttpClientRequest.accept("text/event-stream"));
|
|
106
105
|
const response = yield* client.execute(httpRequest).pipe(Effect.mapError((cause) => new AiError.Unavailable({
|
|
107
106
|
provider: "gemini",
|
|
@@ -125,20 +124,41 @@ const buildNativeStream = (cfg) => {
|
|
|
125
124
|
* Threads a fresh `Accumulator` so chunk-level text/usage merging works
|
|
126
125
|
* across the run.
|
|
127
126
|
*/
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
127
|
+
/**
|
|
128
|
+
* Map one `ChunkPart` to the `TurnEvent`(s) it produces. Function calls
|
|
129
|
+
* emit two events back-to-back since Gemini delivers args whole: a
|
|
130
|
+
* `tool_call_start` immediately followed by a `tool_call_args_delta` with
|
|
131
|
+
* the full JSON-encoded args. The `call_id` is the synthesized id from
|
|
132
|
+
* the accumulator (Gemini-3 wire id when present, `<name>_<index>` otherwise).
|
|
133
|
+
*/
|
|
134
|
+
const partToTurnEvents = (part, callIdAt) => Match.value(part).pipe(Match.discriminatorsExhaustive("kind")({
|
|
135
|
+
text: (p) => [TurnEvent.TextDelta({ text: p.text })],
|
|
136
|
+
reasoning: (p) => [TurnEvent.ReasoningDelta({
|
|
135
137
|
text: p.text,
|
|
136
138
|
kind: "trace"
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
139
|
+
})],
|
|
140
|
+
function_call: (p) => {
|
|
141
|
+
const call_id = callIdAt(p.name, 0);
|
|
142
|
+
return [TurnEvent.ToolCallStart({
|
|
143
|
+
call_id,
|
|
144
|
+
name: p.name
|
|
145
|
+
}), TurnEvent.ToolCallArgsDelta({
|
|
146
|
+
call_id,
|
|
147
|
+
delta: JSON.stringify(p.args ?? {})
|
|
148
|
+
})];
|
|
149
|
+
}
|
|
150
|
+
}));
|
|
151
|
+
const toCanonical = (s) => s.pipe(Stream.mapAccum(() => emptyAccumulator, (acc, chunk) => {
|
|
152
|
+
const result = ingestChunk(acc, chunk);
|
|
153
|
+
const newCalls = result.parts.filter((p) => p.kind === "function_call");
|
|
154
|
+
const tailCalls = result.accumulator.functionCalls.slice(result.accumulator.functionCalls.length - newCalls.length);
|
|
155
|
+
let consumedCalls = 0;
|
|
156
|
+
const partDeltas = result.parts.flatMap((p) => partToTurnEvents(p, () => {
|
|
157
|
+
const id = tailCalls[consumedCalls]?.callId ?? p.kind;
|
|
158
|
+
consumedCalls += 1;
|
|
159
|
+
return id;
|
|
160
|
+
}));
|
|
161
|
+
const deltas = result.finished ? [...partDeltas, TurnEvent.TurnComplete({ turn: accumulatorToTurn(result.accumulator) })] : partDeltas;
|
|
142
162
|
return [result.accumulator, deltas];
|
|
143
163
|
}));
|
|
144
164
|
/**
|
package/dist/Gemini.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Gemini.mjs","names":[],"sources":["../src/Gemini.ts"],"sourcesContent":["import { Context, Effect, Layer, Option, Redacted, Schema, Stream } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport {\n type CommonRequest,\n LanguageModel,\n type LanguageModelService,\n} from \"@effect-uai/core/LanguageModel\"\nimport { JsonParseError } from \"@effect-uai/core/JSONL\"\nimport * as SSE from \"@effect-uai/core/SSE\"\nimport type { TurnEvent } from \"@effect-uai/core/Turn\"\nimport {\n type GenerationConfig,\n WireChunk,\n accumulatorToTurn,\n buildRequestBody,\n emptyAccumulator,\n ingestChunk,\n} from \"./codec.js\"\nimport type { GoogleModel } from \"./models.js\"\n\n/**\n * Gemini's native event vocabulary. Aliased from `WireChunk` so the public\n * surface matches `@effect-uai/responses` and `@effect-uai/anthropic`. Each\n * `ProviderEvent` is a full `GenerateContentResponse` chunk (not a per-field\n * delta) - that's how Gemini's SSE works.\n */\nexport const ProviderEvent = WireChunk\nexport type ProviderEvent = WireChunk\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport type GeminiRequest = Omit<CommonRequest, \"model\"> & {\n /**\n * Narrows `CommonRequest.model` (`string`) to the typed `GoogleModel`\n * literal union for autocomplete.\n */\n readonly model: GoogleModel\n /**\n * Gemini 2.5 thinking budget. `0` disables thinking entirely (lowest\n * latency); higher values let the model think longer. Forwarded as\n * `generationConfig.thinkingConfig.thinkingBudget`.\n */\n readonly thinkingBudget?: number\n}\n\nexport type GeminiService = {\n /**\n * Stream the provider's native event vocabulary (post-SSE-decode). For\n * Gemini, each event is a full `GenerateContentResponse` chunk. Use this\n * when you need access to fields the canonical view doesn't surface\n * (`thoughtSignature`, granular `safetyRatings`, etc.). For\n * provider-portable code, use `streamTurn`.\n */\n readonly streamNative: (request: GeminiRequest) => Stream.Stream<ProviderEvent, AiError.AiError>\n /**\n * Stream canonical `TurnEvent`s. Implemented as\n * `streamNative |> toCanonical`.\n */\n readonly streamTurn: (request: GeminiRequest) => Stream.Stream<TurnEvent, AiError.AiError>\n /**\n * Project a stream of native `ProviderEvent`s into canonical `TurnEvent`s.\n * Threads a fresh `Accumulator` so chunk-level text/usage merging happens\n * per stream.\n */\n readonly toCanonical: <E, R>(\n s: Stream.Stream<ProviderEvent, E, R>,\n ) => Stream.Stream<TurnEvent, E, R>\n}\n\n/**\n * Provider-typed service tag. Yield this when you want Gemini-specific\n * options (`thinkingBudget`); yield the generic `LanguageModel` tag for\n * provider-portable code. Both are registered by `layer`.\n */\nexport class Gemini extends Context.Service<Gemini, GeminiService>()(\n \"@betalyra/effect-uai/providers/google/Gemini\",\n) {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n}\n\n// ---------------------------------------------------------------------------\n// Request body\n// ---------------------------------------------------------------------------\n\nconst buildGenerationConfig = (request: GeminiRequest): Option.Option<GenerationConfig> => {\n const cfg: GenerationConfig = {\n ...(request.temperature !== undefined && { temperature: request.temperature }),\n ...(request.maxOutputTokens !== undefined && { maxOutputTokens: request.maxOutputTokens }),\n ...(request.topP !== undefined && { topP: request.topP }),\n ...(request.thinkingBudget !== undefined && {\n thinkingConfig: { thinkingBudget: request.thinkingBudget },\n }),\n ...(request.structured !== undefined && {\n responseMimeType: \"application/json\",\n responseJsonSchema: request.structured.schema[\"~standard\"].jsonSchema.input({\n target: \"draft-2020-12\",\n }),\n }),\n }\n return Object.keys(cfg).length === 0 ? Option.none() : Option.some(cfg)\n}\n\n// ---------------------------------------------------------------------------\n// SSE event → wire chunk\n// ---------------------------------------------------------------------------\n\nconst decodeChunk = Schema.decodeUnknownEffect(WireChunk)\n\n/**\n * Parse one SSE event's `data` payload into a `WireChunk`. Unlike the other\n * providers, Gemini's wire vocabulary is non-discriminated (a single\n * permissive struct, all fields optional) - there's no natural shape for\n * an `_unknown` variant, so JSON parse / schema decode failures flow\n * through as transport errors rather than being silently dropped.\n */\nconst sseEventToChunk = (ev: SSE.Event) =>\n Effect.try({\n try: () => JSON.parse(ev.data) as unknown,\n catch: (cause) => new JsonParseError({ line: ev.data, cause }),\n }).pipe(Effect.flatMap(decodeChunk))\n\n// ---------------------------------------------------------------------------\n// Service implementation\n// ---------------------------------------------------------------------------\n\nconst httpStatusError = (status: number, body: string): AiError.AiError => {\n const provider = \"gemini\"\n const raw = body\n if (status === 429) return new AiError.RateLimited({ provider, raw })\n if (status === 408 || status === 504) return new AiError.Timeout({ provider, raw })\n if (status === 401) return new AiError.AuthFailed({ provider, subtype: \"auth\", raw })\n if (status === 403) return new AiError.AuthFailed({ provider, subtype: \"permission\", raw })\n if (status === 402) return new AiError.AuthFailed({ provider, subtype: \"billing\", raw })\n if (status === 413) return new AiError.ContextLengthExceeded({ provider, raw })\n if (status >= 500) return new AiError.Unavailable({ provider, status, raw })\n return new AiError.InvalidRequest({ provider, raw })\n}\n\nconst buildNativeStream = (cfg: Config) => {\n const baseUrl = cfg.baseUrl ?? \"https://generativelanguage.googleapis.com/v1beta\"\n return (\n request: GeminiRequest,\n ): Stream.Stream<ProviderEvent, AiError.AiError, HttpClient.HttpClient> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const url = `${baseUrl}/models/${request.model}:streamGenerateContent?alt=sse`\n const generationConfig = buildGenerationConfig(request)\n const body = buildRequestBody(request.history, generationConfig)\n const httpRequest = HttpClientRequest.post(url).pipe(\n HttpClientRequest.setHeader(\"x-goog-api-key\", Redacted.value(cfg.apiKey)),\n HttpClientRequest.bodyJsonUnsafe(body),\n HttpClientRequest.accept(\"text/event-stream\"),\n )\n const response = yield* client\n .execute(httpRequest)\n .pipe(\n Effect.mapError(\n (cause): AiError.AiError =>\n new AiError.Unavailable({ provider: \"gemini\", raw: cause }),\n ),\n )\n if (response.status >= 400) {\n const text = yield* response.text.pipe(Effect.orElseSucceed(() => \"\"))\n return Stream.fail(httpStatusError(response.status, text))\n }\n\n return response.stream.pipe(\n Stream.mapError(\n (cause): AiError.AiError => new AiError.Unavailable({ provider: \"gemini\", raw: cause }),\n ),\n SSE.fromBytes,\n Stream.mapEffect((ev) =>\n sseEventToChunk(ev).pipe(\n Effect.mapError(\n (cause): AiError.AiError =>\n new AiError.Unavailable({ provider: \"gemini\", raw: cause }),\n ),\n ),\n ),\n )\n }),\n )\n}\n\n/**\n * Project a stream of native `ProviderEvent`s into canonical `TurnEvent`s.\n * Threads a fresh `Accumulator` so chunk-level text/usage merging works\n * across the run.\n */\nexport const toCanonical = <E, R>(\n s: Stream.Stream<ProviderEvent, E, R>,\n): Stream.Stream<TurnEvent, E, R> =>\n s.pipe(\n Stream.mapAccum(\n () => emptyAccumulator,\n (acc, chunk) => {\n const result = ingestChunk(acc, chunk)\n const partDeltas = result.parts.map((p) =>\n p.kind === \"text\"\n ? { type: \"text_delta\" as const, text: p.text }\n : { type: \"reasoning_delta\" as const, text: p.text, kind: \"trace\" as const },\n )\n const deltas = result.finished\n ? [\n ...partDeltas,\n {\n type: \"turn_complete\" as const,\n turn: accumulatorToTurn(result.accumulator),\n },\n ]\n : partDeltas\n return [result.accumulator, deltas] as const\n },\n ),\n )\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\n/**\n * Build a `GeminiService` value. For Layer-based setup, prefer `layer`.\n */\nexport const make = (cfg: Config): Effect.Effect<GeminiService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient.asEffect(), (client) => {\n const streamNative: GeminiService[\"streamNative\"] = (request) =>\n buildNativeStream(cfg)(request).pipe(Stream.provideService(HttpClient.HttpClient, client))\n return {\n streamNative,\n streamTurn: (request) => toCanonical(streamNative(request)),\n toCanonical,\n }\n })\n\n/**\n * Layer that registers both the provider-specific `Gemini` tag and the\n * generic `LanguageModel` tag, sharing one underlying implementation.\n *\n * The generic tag accepts `CommonRequest`; the typed tag accepts the full\n * `GeminiRequest` surface.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<Gemini | LanguageModel, never, HttpClient.HttpClient> => {\n const typed = Layer.effect(Gemini, make(cfg))\n const generic = Layer.effect(\n LanguageModel,\n Effect.map(\n make(cfg),\n (s): LanguageModelService => ({\n streamTurn: (request) => s.streamTurn(request as GeminiRequest),\n }),\n ),\n )\n return Layer.merge(typed, generic)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Gemini.mjs","names":[],"sources":["../src/Gemini.ts"],"sourcesContent":["import { Context, Effect, Layer, Option, Redacted, Schema, Stream } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport {\n type CommonRequest,\n LanguageModel,\n type LanguageModelService,\n} from \"@effect-uai/core/LanguageModel\"\nimport { JsonParseError } from \"@effect-uai/core/JSONL\"\nimport * as SSE from \"@effect-uai/core/SSE\"\nimport { TurnEvent } from \"@effect-uai/core/Turn\"\nimport {\n type ChunkPart,\n type GenerationConfig,\n WireChunk,\n accumulatorToTurn,\n buildRequestBody,\n emptyAccumulator,\n ingestChunk,\n} from \"./codec.js\"\nimport { Match } from \"effect\"\nimport type { GoogleModel } from \"./models.js\"\n\n/**\n * Gemini's native event vocabulary. Aliased from `WireChunk` so the public\n * surface matches `@effect-uai/responses` and `@effect-uai/anthropic`. Each\n * `ProviderEvent` is a full `GenerateContentResponse` chunk (not a per-field\n * delta) - that's how Gemini's SSE works.\n */\nexport const ProviderEvent = WireChunk\nexport type ProviderEvent = WireChunk\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport type GeminiRequest = Omit<CommonRequest, \"model\"> & {\n /**\n * Narrows `CommonRequest.model` (`string`) to the typed `GoogleModel`\n * literal union for autocomplete.\n */\n readonly model: GoogleModel\n /**\n * Gemini 2.5 thinking budget. `0` disables thinking entirely (lowest\n * latency); higher values let the model think longer. Forwarded as\n * `generationConfig.thinkingConfig.thinkingBudget`.\n */\n readonly thinkingBudget?: number\n}\n\nexport type GeminiService = {\n /**\n * Stream the provider's native event vocabulary (post-SSE-decode). For\n * Gemini, each event is a full `GenerateContentResponse` chunk. Use this\n * when you need access to fields the canonical view doesn't surface\n * (`thoughtSignature`, granular `safetyRatings`, etc.). For\n * provider-portable code, use `streamTurn`.\n */\n readonly streamNative: (request: GeminiRequest) => Stream.Stream<ProviderEvent, AiError.AiError>\n /**\n * Stream canonical `TurnEvent`s. Implemented as\n * `streamNative |> toCanonical`.\n */\n readonly streamTurn: (request: GeminiRequest) => Stream.Stream<TurnEvent, AiError.AiError>\n /**\n * Project a stream of native `ProviderEvent`s into canonical `TurnEvent`s.\n * Threads a fresh `Accumulator` so chunk-level text/usage merging happens\n * per stream.\n */\n readonly toCanonical: <E, R>(\n s: Stream.Stream<ProviderEvent, E, R>,\n ) => Stream.Stream<TurnEvent, E, R>\n}\n\n/**\n * Provider-typed service tag. Yield this when you want Gemini-specific\n * options (`thinkingBudget`); yield the generic `LanguageModel` tag for\n * provider-portable code. Both are registered by `layer`.\n */\nexport class Gemini extends Context.Service<Gemini, GeminiService>()(\n \"@betalyra/effect-uai/providers/google/Gemini\",\n) {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n}\n\n// ---------------------------------------------------------------------------\n// Request body\n// ---------------------------------------------------------------------------\n\nconst buildGenerationConfig = (request: GeminiRequest): Option.Option<GenerationConfig> => {\n const cfg: GenerationConfig = {\n ...(request.temperature !== undefined && { temperature: request.temperature }),\n ...(request.maxOutputTokens !== undefined && { maxOutputTokens: request.maxOutputTokens }),\n ...(request.topP !== undefined && { topP: request.topP }),\n ...(request.thinkingBudget !== undefined && {\n thinkingConfig: { thinkingBudget: request.thinkingBudget },\n }),\n ...(request.structured !== undefined && {\n responseMimeType: \"application/json\",\n responseJsonSchema: request.structured.schema[\"~standard\"].jsonSchema.input({\n target: \"draft-2020-12\",\n }),\n }),\n }\n return Object.keys(cfg).length === 0 ? Option.none() : Option.some(cfg)\n}\n\n// ---------------------------------------------------------------------------\n// SSE event → wire chunk\n// ---------------------------------------------------------------------------\n\nconst decodeChunk = Schema.decodeUnknownEffect(WireChunk)\n\n/**\n * Parse one SSE event's `data` payload into a `WireChunk`. Unlike the other\n * providers, Gemini's wire vocabulary is non-discriminated (a single\n * permissive struct, all fields optional) - there's no natural shape for\n * an `_unknown` variant, so JSON parse / schema decode failures flow\n * through as transport errors rather than being silently dropped.\n */\nconst parseJsonUnknown = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown))\n\nconst sseEventToChunk = (ev: SSE.Event) =>\n parseJsonUnknown(ev.data).pipe(\n Effect.mapError((cause) => new JsonParseError({ line: ev.data, cause })),\n Effect.flatMap(decodeChunk),\n )\n\n// ---------------------------------------------------------------------------\n// Service implementation\n// ---------------------------------------------------------------------------\n\nconst httpStatusError = (status: number, body: string): AiError.AiError => {\n const provider = \"gemini\"\n const raw = body\n if (status === 429) return new AiError.RateLimited({ provider, raw })\n if (status === 408 || status === 504) return new AiError.Timeout({ provider, raw })\n if (status === 401) return new AiError.AuthFailed({ provider, subtype: \"auth\", raw })\n if (status === 403) return new AiError.AuthFailed({ provider, subtype: \"permission\", raw })\n if (status === 402) return new AiError.AuthFailed({ provider, subtype: \"billing\", raw })\n if (status === 413) return new AiError.ContextLengthExceeded({ provider, raw })\n if (status >= 500) return new AiError.Unavailable({ provider, status, raw })\n return new AiError.InvalidRequest({ provider, raw })\n}\n\nconst buildNativeStream = (cfg: Config) => {\n const baseUrl = cfg.baseUrl ?? \"https://generativelanguage.googleapis.com/v1beta\"\n return (\n request: GeminiRequest,\n ): Stream.Stream<ProviderEvent, AiError.AiError, HttpClient.HttpClient> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const url = `${baseUrl}/models/${request.model}:streamGenerateContent?alt=sse`\n const generationConfig = buildGenerationConfig(request)\n const body = buildRequestBody(request.history, generationConfig, request.tools ?? [])\n const httpRequest = HttpClientRequest.post(url).pipe(\n HttpClientRequest.setHeader(\"x-goog-api-key\", Redacted.value(cfg.apiKey)),\n HttpClientRequest.bodyJsonUnsafe(body),\n HttpClientRequest.accept(\"text/event-stream\"),\n )\n const response = yield* client\n .execute(httpRequest)\n .pipe(\n Effect.mapError(\n (cause): AiError.AiError =>\n new AiError.Unavailable({ provider: \"gemini\", raw: cause }),\n ),\n )\n if (response.status >= 400) {\n const text = yield* response.text.pipe(Effect.orElseSucceed(() => \"\"))\n return Stream.fail(httpStatusError(response.status, text))\n }\n\n return response.stream.pipe(\n Stream.mapError(\n (cause): AiError.AiError => new AiError.Unavailable({ provider: \"gemini\", raw: cause }),\n ),\n SSE.fromBytes,\n Stream.mapEffect((ev) =>\n sseEventToChunk(ev).pipe(\n Effect.mapError(\n (cause): AiError.AiError =>\n new AiError.Unavailable({ provider: \"gemini\", raw: cause }),\n ),\n ),\n ),\n )\n }),\n )\n}\n\n/**\n * Project a stream of native `ProviderEvent`s into canonical `TurnEvent`s.\n * Threads a fresh `Accumulator` so chunk-level text/usage merging works\n * across the run.\n */\n/**\n * Map one `ChunkPart` to the `TurnEvent`(s) it produces. Function calls\n * emit two events back-to-back since Gemini delivers args whole: a\n * `tool_call_start` immediately followed by a `tool_call_args_delta` with\n * the full JSON-encoded args. The `call_id` is the synthesized id from\n * the accumulator (Gemini-3 wire id when present, `<name>_<index>` otherwise).\n */\nconst partToTurnEvents = (\n part: ChunkPart,\n callIdAt: (name: string, indexFromTail: number) => string,\n): ReadonlyArray<TurnEvent> =>\n Match.value(part).pipe(\n Match.discriminatorsExhaustive(\"kind\")({\n text: (p): ReadonlyArray<TurnEvent> => [TurnEvent.TextDelta({ text: p.text })],\n reasoning: (p): ReadonlyArray<TurnEvent> => [\n TurnEvent.ReasoningDelta({ text: p.text, kind: \"trace\" }),\n ],\n function_call: (p): ReadonlyArray<TurnEvent> => {\n const call_id = callIdAt(p.name, 0)\n return [\n TurnEvent.ToolCallStart({ call_id, name: p.name }),\n TurnEvent.ToolCallArgsDelta({ call_id, delta: JSON.stringify(p.args ?? {}) }),\n ]\n },\n }),\n )\n\nexport const toCanonical = <E, R>(\n s: Stream.Stream<ProviderEvent, E, R>,\n): Stream.Stream<TurnEvent, E, R> =>\n s.pipe(\n Stream.mapAccum(\n () => emptyAccumulator,\n (acc, chunk) => {\n const result = ingestChunk(acc, chunk)\n // The accumulator already contains the synthesized call_ids for any\n // function_call parts in *this* chunk - they are the tail. Walk back\n // by chunk-local function-call index to recover them.\n const newCalls = result.parts.filter((p) => p.kind === \"function_call\")\n const tailCalls = result.accumulator.functionCalls.slice(\n result.accumulator.functionCalls.length - newCalls.length,\n )\n let consumedCalls = 0\n const partDeltas: ReadonlyArray<TurnEvent> = result.parts.flatMap((p) =>\n partToTurnEvents(p, () => {\n const id = tailCalls[consumedCalls]?.callId ?? p.kind\n consumedCalls += 1\n return id\n }),\n )\n const deltas: ReadonlyArray<TurnEvent> = result.finished\n ? [...partDeltas, TurnEvent.TurnComplete({ turn: accumulatorToTurn(result.accumulator) })]\n : partDeltas\n return [result.accumulator, deltas] as const\n },\n ),\n )\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\n/**\n * Build a `GeminiService` value. For Layer-based setup, prefer `layer`.\n */\nexport const make = (cfg: Config): Effect.Effect<GeminiService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient.asEffect(), (client) => {\n const streamNative: GeminiService[\"streamNative\"] = (request) =>\n buildNativeStream(cfg)(request).pipe(Stream.provideService(HttpClient.HttpClient, client))\n return {\n streamNative,\n streamTurn: (request) => toCanonical(streamNative(request)),\n toCanonical,\n }\n })\n\n/**\n * Layer that registers both the provider-specific `Gemini` tag and the\n * generic `LanguageModel` tag, sharing one underlying implementation.\n *\n * The generic tag accepts `CommonRequest`; the typed tag accepts the full\n * `GeminiRequest` surface.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<Gemini | LanguageModel, never, HttpClient.HttpClient> => {\n const typed = Layer.effect(Gemini, make(cfg))\n const generic = Layer.effect(\n LanguageModel,\n Effect.map(\n make(cfg),\n (s): LanguageModelService => ({\n streamTurn: (request) => s.streamTurn(request as GeminiRequest),\n }),\n ),\n )\n return Layer.merge(typed, generic)\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GeminiEmbedding.d.mts","names":[],"sources":["../src/GeminiEmbedding.ts"],"mappings":";;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"GeminiEmbedding.d.mts","names":[],"sources":["../src/GeminiEmbedding.ts"],"mappings":";;;;;;;;;;;;;;;;;KA+BY,mBAAA;AAAA,KAUA,kBAAA,GAAqB,IAAA,CAAK,kBAAA;+EAE3B,KAAA,EAAO,oBAAA;;;;AAZlB;;WAkBW,IAAA,GAAO,mBAAA;EAlBa;;AAU/B;;EAV+B,SAuBpB,KAAA;AAAA;AAAA,KAGC,sBAAA,GAAyB,IAAA,CAAK,kBAAA;EAAA,SAC/B,MAAA,EAAQ,aAAA,CAAc,UAAA;AAAA;AAAA,KAGrB,sBAAA;EAAA,SACD,KAAA,GAAQ,OAAA,EAAS,kBAAA,KAAuB,MAAA,CAAO,MAAA,CAAO,aAAA,EAAe,OAAA,CAAQ,OAAA;EAAA,SAC7E,SAAA,GACP,OAAA,EAAS,sBAAA,KACN,MAAA,CAAO,MAAA,CAAO,iBAAA,EAAmB,OAAA,CAAQ,OAAA;AAAA;AAAA,cAC/C,oBAAA;;;;;;cAOY,eAAA,SAAwB,oBAAA;AAAA,KAIzB,MAAA;EAAA,SACD,MAAA,EAAQ,QAAA,CAAS,QAAA;EAAA,SACjB,OAAA;AAAA;;;;;cAuPE,IAAA,GACX,GAAA,EAAK,MAAA,KACJ,MAAA,CAAO,MAAA,CAAO,sBAAA,SAA+B,UAAA,CAAW,UAAA;;;;;;cAa9C,KAAA,GACX,GAAA,EAAK,MAAA,KACJ,KAAA,CAAM,KAAA,CAAM,eAAA,GAAkB,cAAA,SAAuB,UAAA,CAAW,UAAA"}
|
package/dist/GeminiEmbedding.mjs
CHANGED
|
@@ -125,7 +125,7 @@ const postJson = (cfg, url, body) => Effect.gen(function* () {
|
|
|
125
125
|
const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure));
|
|
126
126
|
if (response.status >= 400) {
|
|
127
127
|
const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""));
|
|
128
|
-
return yield*
|
|
128
|
+
return yield* httpStatusError(response.status, text);
|
|
129
129
|
}
|
|
130
130
|
return yield* response.json.pipe(Effect.mapError(transportFailure));
|
|
131
131
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GeminiEmbedding.mjs","names":[],"sources":["../src/GeminiEmbedding.ts"],"sourcesContent":["import { Context, Effect, Encoding, Layer, Match, Redacted, Schema } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport type { Embedding, EmbedContentPart, EmbedInput, Usage } from \"@effect-uai/core/Embedding\"\nimport {\n type CommonEmbedManyRequest,\n type CommonEmbedRequest,\n EmbeddingModel,\n type EmbeddingModelService,\n type EmbedManyResponse,\n type EmbedResponse,\n} from \"@effect-uai/core/EmbeddingModel\"\nimport type { ImageSource } from \"@effect-uai/core/Image\"\nimport type { GoogleEmbeddingModel } from \"./models.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Gemini's task-type enum, exposed under our friendly names. Mapped to the\n * wire `taskType` values inside the codec. Honoured by\n * `gemini-embedding-001`; ignored by `gemini-embedding-2` (which expects\n * task instructions in the prompt text instead).\n */\nexport type GoogleEmbeddingTask =\n | \"query\"\n | \"document\"\n | \"similarity\"\n | \"classification\"\n | \"clustering\"\n | \"qa\"\n | \"fact_verification\"\n | \"code_query\"\n\nexport type GeminiEmbedRequest = Omit<CommonEmbedRequest, \"model\" | \"task\" | \"encoding\"> & {\n /** Narrows `CommonEmbedRequest.model` to the typed Google union. */\n readonly model: GoogleEmbeddingModel\n /**\n * Widens the cross-provider `\"query\" | \"document\"` to the full Google\n * task enum. Optional. Honoured by `gemini-embedding-001`; ignored by\n * `gemini-embedding-2`.\n */\n readonly task?: GoogleEmbeddingTask\n /**\n * Optional document title. Only meaningful for `RETRIEVAL_DOCUMENT`\n * tasks on `gemini-embedding-001`.\n */\n readonly title?: string\n}\n\nexport type GeminiEmbedManyRequest = Omit<GeminiEmbedRequest, \"input\"> & {\n readonly inputs: ReadonlyArray<EmbedInput>\n}\n\nexport type GeminiEmbeddingService = {\n readonly embed: (request: GeminiEmbedRequest) => Effect.Effect<EmbedResponse, AiError.AiError>\n readonly embedMany: (\n request: GeminiEmbedManyRequest,\n ) => Effect.Effect<EmbedManyResponse, AiError.AiError>\n}\n\n/**\n * Provider-typed service tag. Yield this when you want Gemini-specific\n * options (full `task` enum, `title`); yield the generic `EmbeddingModel`\n * tag for provider-portable code. Both are registered by `layer`.\n */\nexport class GeminiEmbedding extends Context.Service<GeminiEmbedding, GeminiEmbeddingService>()(\n \"@betalyra/effect-uai/providers/google/GeminiEmbedding\",\n) {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n}\n\n// ---------------------------------------------------------------------------\n// Codec - request body\n// ---------------------------------------------------------------------------\n\nconst taskToWire: Record<GoogleEmbeddingTask, string> = {\n query: \"RETRIEVAL_QUERY\",\n document: \"RETRIEVAL_DOCUMENT\",\n similarity: \"SEMANTIC_SIMILARITY\",\n classification: \"CLASSIFICATION\",\n clustering: \"CLUSTERING\",\n qa: \"QUESTION_ANSWERING\",\n fact_verification: \"FACT_VERIFICATION\",\n code_query: \"CODE_RETRIEVAL_QUERY\",\n}\n\ntype WireTextPart = {\n readonly text: string\n}\ntype WireInlineDataPart = {\n readonly inlineData: { readonly mimeType: string; readonly data: string }\n}\ntype WirePart = WireTextPart | WireInlineDataPart\ntype WireContent = {\n readonly parts: ReadonlyArray<WirePart>\n}\n\nconst urlRejected = (param: string): AiError.AiError =>\n new AiError.InvalidRequest({\n provider: \"gemini\",\n param,\n raw: \"URL-form image inputs aren't supported; pass base64 or bytes\",\n })\n\n/**\n * Pre-uploading via Google's Files API isn't free, so we reject `url`\n * sources up front rather than silently dropping them. Users with HTTPS\n * URLs should fetch + pass `bytes` (or `base64`).\n */\nconst imageSourceToPart = (param: string) =>\n Match.type<ImageSource>().pipe(\n Match.tag(\n \"base64\",\n (s): Effect.Effect<WirePart, AiError.AiError> =>\n Effect.succeed({ inlineData: { mimeType: s.mimeType, data: s.base64 } }),\n ),\n Match.tag(\n \"bytes\",\n (s): Effect.Effect<WirePart, AiError.AiError> =>\n Effect.succeed({\n inlineData: { mimeType: s.mimeType, data: Encoding.encodeBase64(s.bytes) },\n }),\n ),\n Match.tag(\n \"url\",\n (): Effect.Effect<WirePart, AiError.AiError> => Effect.fail(urlRejected(param)),\n ),\n Match.exhaustive,\n )\n\nconst contentPartToPart = (\n param: string,\n part: EmbedContentPart,\n): Effect.Effect<WirePart, AiError.AiError> =>\n \"text\" in part ? Effect.succeed({ text: part.text }) : imageSourceToPart(param)(part.image)\n\nconst inputToContent = (input: EmbedInput): Effect.Effect<WireContent, AiError.AiError> => {\n if (typeof input === \"string\") return Effect.succeed({ parts: [{ text: input }] })\n if (\"text\" in input) return Effect.succeed({ parts: [{ text: input.text }] })\n if (\"image\" in input) {\n return imageSourceToPart(\"input.image\")(input.image).pipe(\n Effect.map((part) => ({ parts: [part] })),\n )\n }\n return Effect.forEach(input.content, (p) => contentPartToPart(\"input.content[].image\", p)).pipe(\n Effect.map((parts) => ({ parts })),\n )\n}\n\ntype WireSingleBody = {\n readonly content: WireContent\n readonly taskType?: string\n readonly outputDimensionality?: number\n readonly title?: string\n}\n\nconst wireOptions = (\n request: Pick<GeminiEmbedRequest, \"task\" | \"dimensions\" | \"title\">,\n): Omit<WireSingleBody, \"content\"> => ({\n ...(request.task !== undefined && { taskType: taskToWire[request.task] }),\n ...(request.dimensions !== undefined && { outputDimensionality: request.dimensions }),\n ...(request.title !== undefined && { title: request.title }),\n})\n\nconst buildSingleBody = (\n request: GeminiEmbedRequest,\n): Effect.Effect<WireSingleBody, AiError.AiError> =>\n inputToContent(request.input).pipe(\n Effect.map((content) => ({ content, ...wireOptions(request) })),\n )\n\ntype WireBatchEntry = WireSingleBody & {\n readonly model: string\n}\ntype WireBatchBody = {\n readonly requests: ReadonlyArray<WireBatchEntry>\n}\n\nconst buildBatchBody = (\n request: GeminiEmbedManyRequest,\n): Effect.Effect<WireBatchBody, AiError.AiError> => {\n const modelPath = `models/${request.model}`\n const options = wireOptions(request)\n return Effect.forEach(request.inputs, (input) =>\n inputToContent(input).pipe(\n Effect.map((content): WireBatchEntry => ({ model: modelPath, content, ...options })),\n ),\n ).pipe(Effect.map((requests) => ({ requests })))\n}\n\n// ---------------------------------------------------------------------------\n// Codec - response\n// ---------------------------------------------------------------------------\n\nconst WireEmbedding = Schema.Struct({\n values: Schema.Array(Schema.Number),\n})\n\nconst WireSingleResponse = Schema.Struct({\n embedding: WireEmbedding,\n})\n\nconst WireBatchResponse = Schema.Struct({\n embeddings: Schema.Array(WireEmbedding),\n})\n\nconst valuesToEmbedding = (values: ReadonlyArray<number>): Embedding => ({\n _tag: \"float32\",\n vector: Float32Array.from(values),\n})\n\n// Gemini's embed endpoints don't return token-count metadata. Estimating\n// client-side would be provider-specific cruft; honest `undefined` is better.\nconst emptyUsage: Usage = {}\n\n// ---------------------------------------------------------------------------\n// HTTP errors\n// ---------------------------------------------------------------------------\n\nconst httpStatusError = (status: number, body: string): AiError.AiError => {\n const provider = \"gemini\"\n const raw = body\n if (status === 429) return new AiError.RateLimited({ provider, raw })\n if (status === 408 || status === 504) return new AiError.Timeout({ provider, raw })\n if (status === 401) return new AiError.AuthFailed({ provider, subtype: \"auth\", raw })\n if (status === 403) return new AiError.AuthFailed({ provider, subtype: \"permission\", raw })\n if (status === 402) return new AiError.AuthFailed({ provider, subtype: \"billing\", raw })\n if (status === 413) return new AiError.ContextLengthExceeded({ provider, raw })\n if (status >= 500) return new AiError.Unavailable({ provider, status, raw })\n return new AiError.InvalidRequest({ provider, raw })\n}\n\nconst transportFailure = (cause: unknown): AiError.AiError =>\n new AiError.Unavailable({ provider: \"gemini\", raw: cause })\n\n// ---------------------------------------------------------------------------\n// HTTP plumbing - send a JSON body, error-map status, return parsed JSON.\n// ---------------------------------------------------------------------------\n\nconst postJson = (\n cfg: Config,\n url: string,\n body: unknown,\n): Effect.Effect<unknown, AiError.AiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const httpRequest = HttpClientRequest.post(url).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 return yield* response.json.pipe(Effect.mapError(transportFailure))\n })\n\nconst decodeSingle = (json: unknown) =>\n Schema.decodeUnknownEffect(WireSingleResponse)(json).pipe(Effect.mapError(transportFailure))\n\nconst decodeBatch = (json: unknown) =>\n Schema.decodeUnknownEffect(WireBatchResponse)(json).pipe(Effect.mapError(transportFailure))\n\nconst baseUrl = (cfg: Config): string =>\n cfg.baseUrl ?? \"https://generativelanguage.googleapis.com/v1beta\"\n\n// ---------------------------------------------------------------------------\n// Service implementation\n// ---------------------------------------------------------------------------\n\nconst embedImpl =\n (cfg: Config) =>\n (\n request: GeminiEmbedRequest,\n ): Effect.Effect<EmbedResponse, AiError.AiError, HttpClient.HttpClient> =>\n buildSingleBody(request).pipe(\n Effect.flatMap((body) =>\n postJson(cfg, `${baseUrl(cfg)}/models/${request.model}:embedContent`, body),\n ),\n Effect.flatMap(decodeSingle),\n Effect.map(\n (decoded): EmbedResponse => ({\n embedding: valuesToEmbedding(decoded.embedding.values),\n usage: emptyUsage,\n }),\n ),\n )\n\nconst embedManyImpl =\n (cfg: Config) =>\n (\n request: GeminiEmbedManyRequest,\n ): Effect.Effect<EmbedManyResponse, AiError.AiError, HttpClient.HttpClient> =>\n buildBatchBody(request).pipe(\n Effect.flatMap((body) =>\n postJson(cfg, `${baseUrl(cfg)}/models/${request.model}:batchEmbedContents`, body),\n ),\n Effect.flatMap(decodeBatch),\n Effect.map(\n (decoded): EmbedManyResponse => ({\n embeddings: decoded.embeddings.map((e) => valuesToEmbedding(e.values)),\n usage: emptyUsage,\n }),\n ),\n )\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\n/**\n * Build a `GeminiEmbeddingService` value. For Layer-based setup, prefer\n * `layer`.\n */\nexport const make = (\n cfg: Config,\n): Effect.Effect<GeminiEmbeddingService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient.asEffect(), (client) => ({\n embed: (request) =>\n embedImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n embedMany: (request) =>\n embedManyImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n }))\n\n/**\n * Layer that registers both the provider-specific `GeminiEmbedding` tag\n * and the generic `EmbeddingModel` tag, sharing one underlying\n * implementation.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<GeminiEmbedding | EmbeddingModel, never, HttpClient.HttpClient> => {\n const typed = Layer.effect(GeminiEmbedding, make(cfg))\n const generic = Layer.effect(\n EmbeddingModel,\n Effect.map(\n make(cfg),\n (s): EmbeddingModelService => ({\n embed: (req: CommonEmbedRequest) => s.embed(req as GeminiEmbedRequest),\n embedMany: (req: CommonEmbedManyRequest) => s.embedMany(req as GeminiEmbedManyRequest),\n }),\n ),\n )\n return Layer.merge(typed, generic)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"GeminiEmbedding.mjs","names":[],"sources":["../src/GeminiEmbedding.ts"],"sourcesContent":["import { Context, Effect, Encoding, Layer, Match, Redacted, Schema } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport type {\n EmbedContentPart,\n EmbedInput,\n Float32Embedding,\n Usage,\n} from \"@effect-uai/core/Embedding\"\nimport {\n type CommonEmbedManyRequest,\n type CommonEmbedRequest,\n type EmbedEncoding,\n EmbeddingModel,\n type EmbeddingModelService,\n type EmbedManyResponse,\n type EmbedResponse,\n} from \"@effect-uai/core/EmbeddingModel\"\nimport type { ImageSource } from \"@effect-uai/core/Image\"\nimport type { GoogleEmbeddingModel } from \"./models.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Gemini's task-type enum, exposed under our friendly names. Mapped to the\n * wire `taskType` values inside the codec. Honoured by\n * `gemini-embedding-001`; ignored by `gemini-embedding-2` (which expects\n * task instructions in the prompt text instead).\n */\nexport type GoogleEmbeddingTask =\n | \"query\"\n | \"document\"\n | \"similarity\"\n | \"classification\"\n | \"clustering\"\n | \"qa\"\n | \"fact_verification\"\n | \"code_query\"\n\nexport type GeminiEmbedRequest = Omit<CommonEmbedRequest, \"model\" | \"task\" | \"encoding\"> & {\n /** Narrows `CommonEmbedRequest.model` to the typed Google union. */\n readonly model: GoogleEmbeddingModel\n /**\n * Widens the cross-provider `\"query\" | \"document\"` to the full Google\n * task enum. Optional. Honoured by `gemini-embedding-001`; ignored by\n * `gemini-embedding-2`.\n */\n readonly task?: GoogleEmbeddingTask\n /**\n * Optional document title. Only meaningful for `RETRIEVAL_DOCUMENT`\n * tasks on `gemini-embedding-001`.\n */\n readonly title?: string\n}\n\nexport type GeminiEmbedManyRequest = Omit<GeminiEmbedRequest, \"input\"> & {\n readonly inputs: ReadonlyArray<EmbedInput>\n}\n\nexport type GeminiEmbeddingService = {\n readonly embed: (request: GeminiEmbedRequest) => Effect.Effect<EmbedResponse, AiError.AiError>\n readonly embedMany: (\n request: GeminiEmbedManyRequest,\n ) => Effect.Effect<EmbedManyResponse, AiError.AiError>\n}\n\n/**\n * Provider-typed service tag. Yield this when you want Gemini-specific\n * options (full `task` enum, `title`); yield the generic `EmbeddingModel`\n * tag for provider-portable code. Both are registered by `layer`.\n */\nexport class GeminiEmbedding extends Context.Service<GeminiEmbedding, GeminiEmbeddingService>()(\n \"@betalyra/effect-uai/providers/google/GeminiEmbedding\",\n) {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n}\n\n// ---------------------------------------------------------------------------\n// Codec - request body\n// ---------------------------------------------------------------------------\n\nconst taskToWire: Record<GoogleEmbeddingTask, string> = {\n query: \"RETRIEVAL_QUERY\",\n document: \"RETRIEVAL_DOCUMENT\",\n similarity: \"SEMANTIC_SIMILARITY\",\n classification: \"CLASSIFICATION\",\n clustering: \"CLUSTERING\",\n qa: \"QUESTION_ANSWERING\",\n fact_verification: \"FACT_VERIFICATION\",\n code_query: \"CODE_RETRIEVAL_QUERY\",\n}\n\ntype WireTextPart = {\n readonly text: string\n}\ntype WireInlineDataPart = {\n readonly inlineData: { readonly mimeType: string; readonly data: string }\n}\ntype WirePart = WireTextPart | WireInlineDataPart\ntype WireContent = {\n readonly parts: ReadonlyArray<WirePart>\n}\n\nconst urlRejected = (param: string): AiError.AiError =>\n new AiError.InvalidRequest({\n provider: \"gemini\",\n param,\n raw: \"URL-form image inputs aren't supported; pass base64 or bytes\",\n })\n\n/**\n * Pre-uploading via Google's Files API isn't free, so we reject `url`\n * sources up front rather than silently dropping them. Users with HTTPS\n * URLs should fetch + pass `bytes` (or `base64`).\n */\nconst imageSourceToPart = (param: string) =>\n Match.type<ImageSource>().pipe(\n Match.tag(\n \"base64\",\n (s): Effect.Effect<WirePart, AiError.AiError> =>\n Effect.succeed({ inlineData: { mimeType: s.mimeType, data: s.base64 } }),\n ),\n Match.tag(\n \"bytes\",\n (s): Effect.Effect<WirePart, AiError.AiError> =>\n Effect.succeed({\n inlineData: { mimeType: s.mimeType, data: Encoding.encodeBase64(s.bytes) },\n }),\n ),\n Match.tag(\n \"url\",\n (): Effect.Effect<WirePart, AiError.AiError> => Effect.fail(urlRejected(param)),\n ),\n Match.exhaustive,\n )\n\nconst contentPartToPart = (\n param: string,\n part: EmbedContentPart,\n): Effect.Effect<WirePart, AiError.AiError> =>\n \"text\" in part ? Effect.succeed({ text: part.text }) : imageSourceToPart(param)(part.image)\n\nconst inputToContent = (input: EmbedInput): Effect.Effect<WireContent, AiError.AiError> => {\n if (typeof input === \"string\") return Effect.succeed({ parts: [{ text: input }] })\n if (\"text\" in input) return Effect.succeed({ parts: [{ text: input.text }] })\n if (\"image\" in input) {\n return imageSourceToPart(\"input.image\")(input.image).pipe(\n Effect.map((part) => ({ parts: [part] })),\n )\n }\n return Effect.forEach(input.content, (p) => contentPartToPart(\"input.content[].image\", p)).pipe(\n Effect.map((parts) => ({ parts })),\n )\n}\n\ntype WireSingleBody = {\n readonly content: WireContent\n readonly taskType?: string\n readonly outputDimensionality?: number\n readonly title?: string\n}\n\nconst wireOptions = (\n request: Pick<GeminiEmbedRequest, \"task\" | \"dimensions\" | \"title\">,\n): Omit<WireSingleBody, \"content\"> => ({\n ...(request.task !== undefined && { taskType: taskToWire[request.task] }),\n ...(request.dimensions !== undefined && { outputDimensionality: request.dimensions }),\n ...(request.title !== undefined && { title: request.title }),\n})\n\nconst buildSingleBody = (\n request: GeminiEmbedRequest,\n): Effect.Effect<WireSingleBody, AiError.AiError> =>\n inputToContent(request.input).pipe(\n Effect.map((content) => ({ content, ...wireOptions(request) })),\n )\n\ntype WireBatchEntry = WireSingleBody & {\n readonly model: string\n}\ntype WireBatchBody = {\n readonly requests: ReadonlyArray<WireBatchEntry>\n}\n\nconst buildBatchBody = (\n request: GeminiEmbedManyRequest,\n): Effect.Effect<WireBatchBody, AiError.AiError> => {\n const modelPath = `models/${request.model}`\n const options = wireOptions(request)\n return Effect.forEach(request.inputs, (input) =>\n inputToContent(input).pipe(\n Effect.map((content): WireBatchEntry => ({ model: modelPath, content, ...options })),\n ),\n ).pipe(Effect.map((requests) => ({ requests })))\n}\n\n// ---------------------------------------------------------------------------\n// Codec - response\n// ---------------------------------------------------------------------------\n\nconst WireEmbedding = Schema.Struct({\n values: Schema.Array(Schema.Number),\n})\n\nconst WireSingleResponse = Schema.Struct({\n embedding: WireEmbedding,\n})\n\nconst WireBatchResponse = Schema.Struct({\n embeddings: Schema.Array(WireEmbedding),\n})\n\nconst valuesToEmbedding = (values: ReadonlyArray<number>): Float32Embedding => ({\n _tag: \"float32\",\n vector: Float32Array.from(values),\n})\n\n// Gemini's embed endpoints don't return token-count metadata. Estimating\n// client-side would be provider-specific cruft; honest `undefined` is better.\nconst emptyUsage: Usage = {}\n\n// ---------------------------------------------------------------------------\n// HTTP errors\n// ---------------------------------------------------------------------------\n\nconst httpStatusError = (status: number, body: string): AiError.AiError => {\n const provider = \"gemini\"\n const raw = body\n if (status === 429) return new AiError.RateLimited({ provider, raw })\n if (status === 408 || status === 504) return new AiError.Timeout({ provider, raw })\n if (status === 401) return new AiError.AuthFailed({ provider, subtype: \"auth\", raw })\n if (status === 403) return new AiError.AuthFailed({ provider, subtype: \"permission\", raw })\n if (status === 402) return new AiError.AuthFailed({ provider, subtype: \"billing\", raw })\n if (status === 413) return new AiError.ContextLengthExceeded({ provider, raw })\n if (status >= 500) return new AiError.Unavailable({ provider, status, raw })\n return new AiError.InvalidRequest({ provider, raw })\n}\n\nconst transportFailure = (cause: unknown): AiError.AiError =>\n new AiError.Unavailable({ provider: \"gemini\", raw: cause })\n\n// ---------------------------------------------------------------------------\n// HTTP plumbing - send a JSON body, error-map status, return parsed JSON.\n// ---------------------------------------------------------------------------\n\nconst postJson = (\n cfg: Config,\n url: string,\n body: unknown,\n): Effect.Effect<unknown, AiError.AiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const httpRequest = HttpClientRequest.post(url).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 return yield* response.json.pipe(Effect.mapError(transportFailure))\n })\n\nconst decodeSingle = (json: unknown) =>\n Schema.decodeUnknownEffect(WireSingleResponse)(json).pipe(Effect.mapError(transportFailure))\n\nconst decodeBatch = (json: unknown) =>\n Schema.decodeUnknownEffect(WireBatchResponse)(json).pipe(Effect.mapError(transportFailure))\n\nconst baseUrl = (cfg: Config): string =>\n cfg.baseUrl ?? \"https://generativelanguage.googleapis.com/v1beta\"\n\n// ---------------------------------------------------------------------------\n// Service implementation\n// ---------------------------------------------------------------------------\n\nconst embedImpl =\n (cfg: Config) =>\n (\n request: GeminiEmbedRequest,\n ): Effect.Effect<EmbedResponse, AiError.AiError, HttpClient.HttpClient> =>\n buildSingleBody(request).pipe(\n Effect.flatMap((body) =>\n postJson(cfg, `${baseUrl(cfg)}/models/${request.model}:embedContent`, body),\n ),\n Effect.flatMap(decodeSingle),\n Effect.map(\n (decoded): EmbedResponse => ({\n embedding: valuesToEmbedding(decoded.embedding.values),\n usage: emptyUsage,\n }),\n ),\n )\n\nconst embedManyImpl =\n (cfg: Config) =>\n (\n request: GeminiEmbedManyRequest,\n ): Effect.Effect<EmbedManyResponse, AiError.AiError, HttpClient.HttpClient> =>\n buildBatchBody(request).pipe(\n Effect.flatMap((body) =>\n postJson(cfg, `${baseUrl(cfg)}/models/${request.model}:batchEmbedContents`, body),\n ),\n Effect.flatMap(decodeBatch),\n Effect.map(\n (decoded): EmbedManyResponse => ({\n embeddings: decoded.embeddings.map((e) => valuesToEmbedding(e.values)),\n usage: emptyUsage,\n }),\n ),\n )\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\n/**\n * Build a `GeminiEmbeddingService` value. For Layer-based setup, prefer\n * `layer`.\n */\nexport const make = (\n cfg: Config,\n): Effect.Effect<GeminiEmbeddingService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient.asEffect(), (client) => ({\n embed: (request) =>\n embedImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n embedMany: (request) =>\n embedManyImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n }))\n\n/**\n * Layer that registers both the provider-specific `GeminiEmbedding` tag\n * and the generic `EmbeddingModel` tag, sharing one underlying\n * implementation.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<GeminiEmbedding | EmbeddingModel, never, HttpClient.HttpClient> => {\n const typed = Layer.effect(GeminiEmbedding, make(cfg))\n const generic = Layer.effect(\n EmbeddingModel,\n Effect.map(\n make(cfg),\n (s): EmbeddingModelService => ({\n // Gemini's endpoints only emit float32; the cast is sound for the\n // 99% case. A caller who asks for `encoding: \"int8\"` via the\n // generic tag gets the type they requested but the runtime returns\n // float32 (Gemini ignores the field).\n embed: <E extends EmbedEncoding | undefined = undefined>(\n req: Omit<CommonEmbedRequest, \"encoding\"> & { readonly encoding?: E },\n ) => s.embed(req as GeminiEmbedRequest) as Effect.Effect<EmbedResponse<E>, AiError.AiError>,\n embedMany: <E extends EmbedEncoding | undefined = undefined>(\n req: Omit<CommonEmbedManyRequest, \"encoding\"> & { readonly encoding?: E },\n ) =>\n s.embedMany(req as GeminiEmbedManyRequest) as Effect.Effect<\n EmbedManyResponse<E>,\n AiError.AiError\n >,\n }),\n ),\n )\n return Layer.merge(typed, generic)\n}\n"],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import { GeminiTtsModel, GeminiVoiceName } from "./models.mjs";
|
|
|
2
2
|
import { Context, Effect, Layer, Redacted, Stream } from "effect";
|
|
3
3
|
import { HttpClient } from "effect/unstable/http";
|
|
4
4
|
import * as AiError from "@effect-uai/core/AiError";
|
|
5
|
-
import { CommonSynthesizeRequest,
|
|
5
|
+
import { CommonSynthesizeRequest, SpeechSynthesizerService } from "@effect-uai/core/SpeechSynthesizer";
|
|
6
6
|
import { AudioBlob, AudioChunk } from "@effect-uai/core/Audio";
|
|
7
7
|
|
|
8
8
|
//#region src/GeminiSynthesizer.d.ts
|
|
@@ -24,17 +24,7 @@ type Config = {
|
|
|
24
24
|
readonly apiKey: Redacted.Redacted;
|
|
25
25
|
readonly baseUrl?: string;
|
|
26
26
|
};
|
|
27
|
-
declare const realizeOutput:
|
|
28
|
-
readonly container: "raw";
|
|
29
|
-
readonly encoding: "pcm_s16le";
|
|
30
|
-
readonly sampleRate: 24000;
|
|
31
|
-
readonly channels: 1;
|
|
32
|
-
}, (b: Uint8Array) => Uint8Array<ArrayBufferLike>] | readonly [{
|
|
33
|
-
readonly container: "wav";
|
|
34
|
-
readonly encoding: "pcm_s16le";
|
|
35
|
-
readonly sampleRate: 24000;
|
|
36
|
-
readonly channels: 1;
|
|
37
|
-
}, (b: Uint8Array) => Uint8Array<ArrayBufferLike>], AiError.Unsupported, never>;
|
|
27
|
+
declare const realizeOutput: never;
|
|
38
28
|
declare const make: (cfg: Config) => Effect.Effect<GeminiSynthesizerService, never, HttpClient.HttpClient>;
|
|
39
29
|
/**
|
|
40
30
|
* Layer registers both `GeminiSynthesizer` and the generic
|
|
@@ -42,7 +32,7 @@ declare const make: (cfg: Config) => Effect.Effect<GeminiSynthesizerService, nev
|
|
|
42
32
|
* TTS is sync-only at the wire level, so `streamSynthesisFrom` against
|
|
43
33
|
* this Layer alone is a compile-time error.
|
|
44
34
|
*/
|
|
45
|
-
declare const layer: (cfg: Config) => Layer.Layer<
|
|
35
|
+
declare const layer: (cfg: Config) => Layer.Layer<unknown, never, HttpClient.HttpClient>;
|
|
46
36
|
//#endregion
|
|
47
37
|
export { Config, GeminiSynthesizeRequest, GeminiSynthesizer, GeminiSynthesizerService, layer, make, realizeOutput, GeminiSynthesizer_d_exports as t };
|
|
48
38
|
//# sourceMappingURL=GeminiSynthesizer.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GeminiSynthesizer.d.mts","names":[],"sources":["../src/GeminiSynthesizer.ts"],"mappings":";;;;;;;;;;;KAsBY,uBAAA,GAA0B,IAAA,CAAK,uBAAA;EAAA,SAChC,KAAA,EAAO,cAAA;EAAA,SACP,OAAA,EAAS,eAAA;AAAA;AAAA,KAGR,wBAAA;EAAA,SACD,UAAA,GAAa,CAAA,EAAG,uBAAA,KAA4B,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,OAAA,CAAQ,OAAA;EAAA,SAC7E,eAAA,GACP,CAAA,EAAG,uBAAA,KACA,MAAA,CAAO,MAAA,CAAO,UAAA,EAAY,OAAA,CAAQ,OAAA;EAAA,SAC9B,mBAAA,EAAqB,wBAAA;AAAA;AAAA,cAC/B,sBAAA;cAEY,iBAAA,SAA0B,sBAAA;AAAA,KAK3B,MAAA;EAAA,SAAoB,MAAA,EAAQ,QAAA,CAAS,QAAA;EAAA,SAAmB,OAAA;AAAA;AAAA,cAUvD,aAAA
|
|
1
|
+
{"version":3,"file":"GeminiSynthesizer.d.mts","names":[],"sources":["../src/GeminiSynthesizer.ts"],"mappings":";;;;;;;;;;;KAsBY,uBAAA,GAA0B,IAAA,CAAK,uBAAA;EAAA,SAChC,KAAA,EAAO,cAAA;EAAA,SACP,OAAA,EAAS,eAAA;AAAA;AAAA,KAGR,wBAAA;EAAA,SACD,UAAA,GAAa,CAAA,EAAG,uBAAA,KAA4B,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,OAAA,CAAQ,OAAA;EAAA,SAC7E,eAAA,GACP,CAAA,EAAG,uBAAA,KACA,MAAA,CAAO,MAAA,CAAO,UAAA,EAAY,OAAA,CAAQ,OAAA;EAAA,SAC9B,mBAAA,EAAqB,wBAAA;AAAA;AAAA,cAC/B,sBAAA;cAEY,iBAAA,SAA0B,sBAAA;AAAA,KAK3B,MAAA;EAAA,SAAoB,MAAA,EAAQ,QAAA,CAAS,QAAA;EAAA,SAAmB,OAAA;AAAA;AAAA,cAUvD,aAAA;AAAA,cA8HA,IAAA,GAAQ,GAAA,EAAK,MAAA,KAAM,MAAA,CAAA,MAAA,CAAA,wBAAA,SAAA,UAAA,CAAA,UAAA;;;;;;;cAqBnB,KAAA,GAAS,GAAA,EAAK,MAAA,KAAM,KAAA,CAAA,KAAA,iBAAA,UAAA,CAAA,UAAA"}
|
|
@@ -50,7 +50,7 @@ const synthesizeImpl = (cfg) => (request) => Effect.gen(function* () {
|
|
|
50
50
|
const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure));
|
|
51
51
|
if (response.status >= 400) {
|
|
52
52
|
const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""));
|
|
53
|
-
return yield*
|
|
53
|
+
return yield* httpStatusError(response.status, text);
|
|
54
54
|
}
|
|
55
55
|
const json = yield* response.json.pipe(Effect.mapError(transportFailure));
|
|
56
56
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GeminiSynthesizer.mjs","names":[],"sources":["../src/GeminiSynthesizer.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 { AudioBlob, AudioChunk, AudioFormat } from \"@effect-uai/core/Audio\"\nimport {\n type CommonStreamSynthesizeRequest,\n type CommonSynthesizeRequest,\n SpeechSynthesizer,\n type SpeechSynthesizerService,\n} from \"@effect-uai/core/SpeechSynthesizer\"\nimport {\n decodeBase64Audio,\n httpStatusError,\n transportFailure,\n wrapPcmAsWav,\n} from \"./geminiSpeechCodec.js\"\nimport type { GeminiTtsModel, GeminiVoiceName } from \"./models.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport type GeminiSynthesizeRequest = Omit<CommonSynthesizeRequest, \"model\" | \"voiceId\"> & {\n readonly model: GeminiTtsModel\n readonly voiceId: GeminiVoiceName\n}\n\nexport type GeminiSynthesizerService = {\n readonly synthesize: (r: GeminiSynthesizeRequest) => Effect.Effect<AudioBlob, AiError.AiError>\n readonly streamSynthesis: (\n r: GeminiSynthesizeRequest,\n ) => Stream.Stream<AudioChunk, AiError.AiError>\n readonly streamSynthesisFrom: SpeechSynthesizerService[\"streamSynthesisFrom\"]\n}\n\nexport class GeminiSynthesizer extends Context.Service<\n GeminiSynthesizer,\n GeminiSynthesizerService\n>()(\"@betalyra/effect-uai/providers/google/GeminiSynthesizer\") {}\n\nexport type Config = { readonly apiKey: Redacted.Redacted; readonly baseUrl?: string }\n\n// ---------------------------------------------------------------------------\n// Codec — Gemini TTS always emits 24 kHz mono signed-16 PCM. Caller picks\n// raw bytes or WAV-wrapped.\n// ---------------------------------------------------------------------------\n\nconst PCM_RATE = 24000\nconst PCM_CHANNELS = 1\n\nexport const realizeOutput = Match.type<AudioFormat[\"container\"]>().pipe(\n Match.when(\"raw\", () =>\n Effect.succeed([\n { container: \"raw\", encoding: \"pcm_s16le\", sampleRate: PCM_RATE, channels: PCM_CHANNELS },\n (b: Uint8Array) => b,\n ] as const),\n ),\n Match.when(\"wav\", () =>\n Effect.succeed([\n { container: \"wav\", encoding: \"pcm_s16le\", sampleRate: PCM_RATE, channels: PCM_CHANNELS },\n (b: Uint8Array) => wrapPcmAsWav(b, PCM_RATE, PCM_CHANNELS),\n ] as const),\n ),\n Match.whenOr(\"mp3\", \"opus\", \"aac\", \"flac\", \"ogg\", \"webm\", (c) =>\n Effect.fail(\n new AiError.Unsupported({\n provider: \"gemini\",\n capability: \"outputFormat\",\n reason: `Gemini TTS emits raw 16-bit PCM at 24 kHz. Request \"raw\" or \"wav\"; ${c} is unavailable.`,\n }),\n ),\n ),\n Match.exhaustive,\n)\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(\n Schema.Struct({\n inlineData: Schema.optional(\n Schema.Struct({\n mimeType: Schema.optional(Schema.String),\n data: Schema.String,\n }),\n ),\n }),\n ),\n ),\n }),\n ),\n }),\n ),\n ),\n})\nconst decodeWire = Schema.decodeUnknownEffect(Wire)\n\nconst findInline = (wire: typeof Wire.Type) =>\n (wire.candidates ?? [])\n .flatMap((c) => c.content?.parts ?? [])\n .find((p) => p.inlineData !== undefined)?.inlineData\n\nconst ttsBody = (r: GeminiSynthesizeRequest) => ({\n contents: [{ parts: [{ text: r.text }] }],\n generationConfig: {\n responseModalities: [\"AUDIO\"],\n speechConfig: { voiceConfig: { prebuiltVoiceConfig: { voiceName: r.voiceId } } },\n },\n})\n\n// ---------------------------------------------------------------------------\n// Service implementation\n// ---------------------------------------------------------------------------\n\nconst baseUrl = (cfg: Config) => cfg.baseUrl ?? \"https://generativelanguage.googleapis.com/v1beta\"\n\nconst synthesizeImpl = (cfg: Config) => (request: GeminiSynthesizeRequest) =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const [format, wrap] = yield* realizeOutput(request.outputFormat?.container ?? \"raw\")\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(ttsBody(request)),\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 inline = yield* decodeWire(json).pipe(\n Effect.map(findInline),\n Effect.mapError((cause) => new AiError.GenerationFailed({ provider: \"gemini\", raw: cause })),\n Effect.flatMap((found) =>\n found === undefined\n ? Effect.fail(\n new AiError.GenerationFailed({\n provider: \"gemini\",\n raw: {\n message: \"Gemini TTS response had no inlineData audio part.\",\n hint: \"Likely a prompt-safety rejection or the model didn't honour responseModalities.\",\n response: json,\n },\n }),\n )\n : Effect.succeed(found),\n ),\n )\n const pcm = yield* decodeBase64Audio(inline.data)\n return { format, bytes: wrap(pcm) } satisfies AudioBlob\n })\n\nconst unsupportedStreamFrom: SpeechSynthesizerService[\"streamSynthesisFrom\"] = () =>\n Stream.fail(\n new AiError.Unsupported({\n provider: \"gemini\",\n capability: \"streamSynthesisFrom\",\n reason:\n \"Gemini TTS via generateContent is sync-only. Use Cloud Text-to-Speech (Chirp 3 HD) for incremental text-in.\",\n }),\n )\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\nexport const make = (cfg: Config) =>\n Effect.map(\n HttpClient.HttpClient.asEffect(),\n (client): GeminiSynthesizerService => ({\n synthesize: (r) =>\n synthesizeImpl(cfg)(r).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n streamSynthesis: (r) =>\n Stream.fromEffect(synthesizeImpl(cfg)(r)).pipe(\n Stream.map((b): AudioChunk => ({ bytes: b.bytes })),\n Stream.provideService(HttpClient.HttpClient, client),\n ),\n streamSynthesisFrom: unsupportedStreamFrom,\n }),\n )\n\n/**\n * Layer registers both `GeminiSynthesizer` and the generic\n * `SpeechSynthesizer`. Does NOT register `TtsIncrementalText` — Gemini\n * TTS is sync-only at the wire level, so `streamSynthesisFrom` against\n * this Layer alone is a compile-time error.\n */\nexport const layer = (cfg: Config) =>\n Layer.merge(\n Layer.effect(GeminiSynthesizer, make(cfg)),\n Layer.effect(\n SpeechSynthesizer,\n Effect.map(\n make(cfg),\n (s): SpeechSynthesizerService => ({\n synthesize: (req: CommonSynthesizeRequest) =>\n s.synthesize(req as GeminiSynthesizeRequest),\n streamSynthesis: (req: CommonSynthesizeRequest) =>\n s.streamSynthesis(req as GeminiSynthesizeRequest),\n streamSynthesisFrom: s.streamSynthesisFrom,\n }),\n ),\n ),\n )\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"GeminiSynthesizer.mjs","names":[],"sources":["../src/GeminiSynthesizer.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 { AudioBlob, AudioChunk, AudioFormat } from \"@effect-uai/core/Audio\"\nimport {\n type CommonStreamSynthesizeRequest,\n type CommonSynthesizeRequest,\n SpeechSynthesizer,\n type SpeechSynthesizerService,\n} from \"@effect-uai/core/SpeechSynthesizer\"\nimport {\n decodeBase64Audio,\n httpStatusError,\n transportFailure,\n wrapPcmAsWav,\n} from \"./geminiSpeechCodec.js\"\nimport type { GeminiTtsModel, GeminiVoiceName } from \"./models.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport type GeminiSynthesizeRequest = Omit<CommonSynthesizeRequest, \"model\" | \"voiceId\"> & {\n readonly model: GeminiTtsModel\n readonly voiceId: GeminiVoiceName\n}\n\nexport type GeminiSynthesizerService = {\n readonly synthesize: (r: GeminiSynthesizeRequest) => Effect.Effect<AudioBlob, AiError.AiError>\n readonly streamSynthesis: (\n r: GeminiSynthesizeRequest,\n ) => Stream.Stream<AudioChunk, AiError.AiError>\n readonly streamSynthesisFrom: SpeechSynthesizerService[\"streamSynthesisFrom\"]\n}\n\nexport class GeminiSynthesizer extends Context.Service<\n GeminiSynthesizer,\n GeminiSynthesizerService\n>()(\"@betalyra/effect-uai/providers/google/GeminiSynthesizer\") {}\n\nexport type Config = { readonly apiKey: Redacted.Redacted; readonly baseUrl?: string }\n\n// ---------------------------------------------------------------------------\n// Codec — Gemini TTS always emits 24 kHz mono signed-16 PCM. Caller picks\n// raw bytes or WAV-wrapped.\n// ---------------------------------------------------------------------------\n\nconst PCM_RATE = 24000\nconst PCM_CHANNELS = 1\n\nexport const realizeOutput = Match.type<AudioFormat[\"container\"]>().pipe(\n Match.when(\"raw\", () =>\n Effect.succeed([\n { container: \"raw\", encoding: \"pcm_s16le\", sampleRate: PCM_RATE, channels: PCM_CHANNELS },\n (b: Uint8Array) => b,\n ] as const),\n ),\n Match.when(\"wav\", () =>\n Effect.succeed([\n { container: \"wav\", encoding: \"pcm_s16le\", sampleRate: PCM_RATE, channels: PCM_CHANNELS },\n (b: Uint8Array) => wrapPcmAsWav(b, PCM_RATE, PCM_CHANNELS),\n ] as const),\n ),\n Match.whenOr(\"mp3\", \"opus\", \"aac\", \"flac\", \"ogg\", \"webm\", (c) =>\n Effect.fail(\n new AiError.Unsupported({\n provider: \"gemini\",\n capability: \"outputFormat\",\n reason: `Gemini TTS emits raw 16-bit PCM at 24 kHz. Request \"raw\" or \"wav\"; ${c} is unavailable.`,\n }),\n ),\n ),\n Match.exhaustive,\n)\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(\n Schema.Struct({\n inlineData: Schema.optional(\n Schema.Struct({\n mimeType: Schema.optional(Schema.String),\n data: Schema.String,\n }),\n ),\n }),\n ),\n ),\n }),\n ),\n }),\n ),\n ),\n})\nconst decodeWire = Schema.decodeUnknownEffect(Wire)\n\nconst findInline = (wire: typeof Wire.Type) =>\n (wire.candidates ?? [])\n .flatMap((c) => c.content?.parts ?? [])\n .find((p) => p.inlineData !== undefined)?.inlineData\n\nconst ttsBody = (r: GeminiSynthesizeRequest) => ({\n contents: [{ parts: [{ text: r.text }] }],\n generationConfig: {\n responseModalities: [\"AUDIO\"],\n speechConfig: { voiceConfig: { prebuiltVoiceConfig: { voiceName: r.voiceId } } },\n },\n})\n\n// ---------------------------------------------------------------------------\n// Service implementation\n// ---------------------------------------------------------------------------\n\nconst baseUrl = (cfg: Config) => cfg.baseUrl ?? \"https://generativelanguage.googleapis.com/v1beta\"\n\nconst synthesizeImpl = (cfg: Config) => (request: GeminiSynthesizeRequest) =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const [format, wrap] = yield* realizeOutput(request.outputFormat?.container ?? \"raw\")\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(ttsBody(request)),\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 inline = yield* decodeWire(json).pipe(\n Effect.map(findInline),\n Effect.mapError((cause) => new AiError.GenerationFailed({ provider: \"gemini\", raw: cause })),\n Effect.flatMap((found) =>\n found === undefined\n ? Effect.fail(\n new AiError.GenerationFailed({\n provider: \"gemini\",\n raw: {\n message: \"Gemini TTS response had no inlineData audio part.\",\n hint: \"Likely a prompt-safety rejection or the model didn't honour responseModalities.\",\n response: json,\n },\n }),\n )\n : Effect.succeed(found),\n ),\n )\n const pcm = yield* decodeBase64Audio(inline.data)\n return { format, bytes: wrap(pcm) } satisfies AudioBlob\n })\n\nconst unsupportedStreamFrom: SpeechSynthesizerService[\"streamSynthesisFrom\"] = () =>\n Stream.fail(\n new AiError.Unsupported({\n provider: \"gemini\",\n capability: \"streamSynthesisFrom\",\n reason:\n \"Gemini TTS via generateContent is sync-only. Use Cloud Text-to-Speech (Chirp 3 HD) for incremental text-in.\",\n }),\n )\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\nexport const make = (cfg: Config) =>\n Effect.map(\n HttpClient.HttpClient.asEffect(),\n (client): GeminiSynthesizerService => ({\n synthesize: (r) =>\n synthesizeImpl(cfg)(r).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n streamSynthesis: (r) =>\n Stream.fromEffect(synthesizeImpl(cfg)(r)).pipe(\n Stream.map((b): AudioChunk => ({ bytes: b.bytes })),\n Stream.provideService(HttpClient.HttpClient, client),\n ),\n streamSynthesisFrom: unsupportedStreamFrom,\n }),\n )\n\n/**\n * Layer registers both `GeminiSynthesizer` and the generic\n * `SpeechSynthesizer`. Does NOT register `TtsIncrementalText` — Gemini\n * TTS is sync-only at the wire level, so `streamSynthesisFrom` against\n * this Layer alone is a compile-time error.\n */\nexport const layer = (cfg: Config) =>\n Layer.merge(\n Layer.effect(GeminiSynthesizer, make(cfg)),\n Layer.effect(\n SpeechSynthesizer,\n Effect.map(\n make(cfg),\n (s): SpeechSynthesizerService => ({\n synthesize: (req: CommonSynthesizeRequest) =>\n s.synthesize(req as GeminiSynthesizeRequest),\n streamSynthesis: (req: CommonSynthesizeRequest) =>\n s.streamSynthesis(req as GeminiSynthesizeRequest),\n streamSynthesisFrom: s.streamSynthesisFrom,\n }),\n ),\n ),\n )\n"],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import { GeminiSttModel } from "./models.mjs";
|
|
|
2
2
|
import { Context, Effect, Layer, Redacted } from "effect";
|
|
3
3
|
import { HttpClient } from "effect/unstable/http";
|
|
4
4
|
import * as AiError from "@effect-uai/core/AiError";
|
|
5
|
-
import { CommonTranscribeRequest,
|
|
5
|
+
import { CommonTranscribeRequest, TranscriberService } from "@effect-uai/core/Transcriber";
|
|
6
6
|
import { TranscriptResult } from "@effect-uai/core/Transcript";
|
|
7
7
|
|
|
8
8
|
//#region src/GeminiTranscriber.d.ts
|
|
@@ -36,7 +36,7 @@ declare const make: (cfg: Config) => Effect.Effect<GeminiTranscriberService, nev
|
|
|
36
36
|
* is sync-only, so `streamTranscriptionFrom` against this Layer alone
|
|
37
37
|
* is a compile-time error.
|
|
38
38
|
*/
|
|
39
|
-
declare const layer: (cfg: Config) => Layer.Layer<
|
|
39
|
+
declare const layer: (cfg: Config) => Layer.Layer<unknown, never, HttpClient.HttpClient>;
|
|
40
40
|
//#endregion
|
|
41
41
|
export { Config, GeminiTranscribeRequest, GeminiTranscriber, GeminiTranscriberService, buildPrompt, layer, make, GeminiTranscriber_d_exports as t };
|
|
42
42
|
//# sourceMappingURL=GeminiTranscriber.d.mts.map
|
|
@@ -1 +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,
|
|
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"}
|
|
@@ -36,7 +36,7 @@ const transcribeImpl = (cfg) => (request) => Effect.gen(function* () {
|
|
|
36
36
|
const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure));
|
|
37
37
|
if (response.status >= 400) {
|
|
38
38
|
const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""));
|
|
39
|
-
return yield*
|
|
39
|
+
return yield* httpStatusError(response.status, text);
|
|
40
40
|
}
|
|
41
41
|
const json = yield* response.json.pipe(Effect.mapError(transportFailure));
|
|
42
42
|
return {
|
|
@@ -1 +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* Effect.fail(httpStatusError(response.status, text))\n }\n const json = yield* response.json.pipe(Effect.mapError(transportFailure))\n const text = yield* decodeWire(json).pipe(\n Effect.map(collectText),\n Effect.mapError((cause) => new AiError.GenerationFailed({ provider: \"gemini\", raw: cause })),\n Effect.flatMap((t) =>\n t.length === 0\n ? Effect.fail(\n new AiError.GenerationFailed({\n provider: \"gemini\",\n raw: {\n message: \"Gemini transcription response had no text part.\",\n hint: \"Likely a prompt-safety rejection or the model returned only metadata.\",\n response: json,\n },\n }),\n )\n : Effect.succeed(t),\n ),\n )\n return {\n text,\n ...(request.language !== undefined && { languageCode: request.language }),\n raw: json,\n } satisfies TranscriptResult\n })\n\nconst unsupportedStreamFrom: TranscriberService[\"streamTranscriptionFrom\"] = () =>\n Stream.fail(\n new AiError.Unsupported({\n provider: \"gemini\",\n capability: \"streamTranscriptionFrom\",\n reason:\n \"Gemini transcription via generateContent is sync-only. Use Cloud Speech-to-Text (StreamingRecognize) for live transcription.\",\n }),\n )\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\nexport const make = (cfg: Config) =>\n Effect.map(\n HttpClient.HttpClient.asEffect(),\n (client): GeminiTranscriberService => ({\n transcribe: (r) =>\n transcribeImpl(cfg)(r).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n streamTranscriptionFrom: unsupportedStreamFrom,\n }),\n )\n\n/**\n * Layer registers both `GeminiTranscriber` and the generic\n * `Transcriber`. Does NOT register `SttStreaming` — Gemini's transcription\n * is sync-only, so `streamTranscriptionFrom` against this Layer alone\n * is a compile-time error.\n */\nexport const layer = (cfg: Config) =>\n Layer.merge(\n Layer.effect(GeminiTranscriber, make(cfg)),\n Layer.effect(\n Transcriber,\n Effect.map(\n make(cfg),\n (s): TranscriberService => ({\n transcribe: (req: CommonTranscribeRequest) =>\n s.transcribe(req as GeminiTranscribeRequest),\n streamTranscriptionFrom: s.streamTranscriptionFrom,\n }),\n ),\n ),\n )\n"],"mappings":";;;;;;;;;;;;;AAiCA,IAAa,oBAAb,cAAuC,QAAQ,SAG5C,CAAC,0DAA0D,CAAC;AAQ/D,MAAM,eAAe,YAAoB,WACvC,IAAI,QAAQ,YAAY;CAAE,UAAU;CAAU;CAAY;CAAQ,CAAC;AAErE,MAAM,kBAAkB,MAAM,MAA+B,CAAC,KAC5D,MAAM,KAAK,EAAE,gBAAgB,MAAM,QACjC,OAAO,KACL,YACE,kBACA,wIACD,CACF,CACF,EACD,MAAM,KAAK,EAAE,aAAa,MAAM,QAC9B,OAAO,KACL,YACE,eACA,yHACD,CACF,CACF,EACD,MAAM,aAAa,OAAO,KAAK,CAChC;AAED,MAAM,eAAe,MAA+B;AAClD,KAAI,EAAE,WAAW,KAAA,EAAW,QAAO;AACnC,KAAI,OAAO,EAAE,WAAW,SAAU,QAAO,cAAc,EAAE;AACzD,QAAO,EAAE,OAAO,MAAM,WAAW,IAC7B,KACA,sDAAsD,EAAE,OAAO,MAAM,KAAK,KAAK,CAAC;;AAGtF,MAAa,eAAe,OACzB,EAAE,aAAa,KAAA,IACZ,oCAAoC,EAAE,SAAS,mDAC/C,kFACJ,YAAY,EAAE;AAMhB,MAAM,OAAO,OAAO,OAAO,EACzB,YAAY,OAAO,SACjB,OAAO,MACL,OAAO,OAAO,EACZ,SAAS,OAAO,SACd,OAAO,OAAO,EACZ,OAAO,OAAO,SACZ,OAAO,MAAM,OAAO,OAAO,EAAE,MAAM,OAAO,SAAS,OAAO,OAAO,EAAE,CAAC,CAAC,CACtE,EACF,CAAC,CACH,EACF,CAAC,CACH,CACF,EACF,CAAC;AACF,MAAM,aAAa,OAAO,oBAAoB,KAAK;AAEnD,MAAM,eAAe,UAClB,KAAK,cAAc,EAAE,EACnB,SAAS,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC,CACtC,SAAS,MAAO,EAAE,SAAS,KAAA,IAAY,CAAC,EAAE,KAAK,GAAG,EAAE,CAAE,CACtD,KAAK,KAAK,CACV,MAAM;AAMX,MAAM,WAAW,QAAgB,IAAI,WAAW;AAEhD,MAAM,kBAAkB,SAAiB,YACvC,OAAO,IAAI,aAAa;AACtB,QAAO,gBAAgB,QAAQ;CAC/B,MAAM,SAAS,OAAO,WAAW;CACjC,MAAM,SAAS,OAAO,wBAAwB,QAAQ,MAAM;CAC5D,MAAM,cAAc,kBAAkB,KACpC,GAAG,QAAQ,IAAI,CAAC,UAAU,QAAQ,MAAM,kBACzC,CAAC,KACA,kBAAkB,UAAU,kBAAkB,SAAS,MAAM,IAAI,OAAO,CAAC,EACzE,kBAAkB,eAAe,EAC/B,UAAU,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,YAAY,QAAQ,EAAE,EAAE,EAAE,YAAY,QAAQ,CAAC,EAAE,CAAC,EAChF,CAAC,CACH;CACD,MAAM,WAAW,OAAO,OAAO,QAAQ,YAAY,CAAC,KAAK,OAAO,SAAS,iBAAiB,CAAC;AAC3F,KAAI,SAAS,UAAU,KAAK;EAC1B,MAAM,OAAO,OAAO,SAAS,KAAK,KAAK,OAAO,oBAAoB,GAAG,CAAC;AACtE,SAAO,OAAO,OAAO,KAAK,gBAAgB,SAAS,QAAQ,KAAK,CAAC;;CAEnE,MAAM,OAAO,OAAO,SAAS,KAAK,KAAK,OAAO,SAAS,iBAAiB,CAAC;AAmBzE,QAAO;EACL,MAAA,OAnBkB,WAAW,KAAK,CAAC,KACnC,OAAO,IAAI,YAAY,EACvB,OAAO,UAAU,UAAU,IAAI,QAAQ,iBAAiB;GAAE,UAAU;GAAU,KAAK;GAAO,CAAC,CAAC,EAC5F,OAAO,SAAS,MACd,EAAE,WAAW,IACT,OAAO,KACL,IAAI,QAAQ,iBAAiB;GAC3B,UAAU;GACV,KAAK;IACH,SAAS;IACT,MAAM;IACN,UAAU;IACX;GACF,CAAC,CACH,GACD,OAAO,QAAQ,EAAE,CACtB,CACF;EAGC,GAAI,QAAQ,aAAa,KAAA,KAAa,EAAE,cAAc,QAAQ,UAAU;EACxE,KAAK;EACN;EACD;AAEJ,MAAM,8BACJ,OAAO,KACL,IAAI,QAAQ,YAAY;CACtB,UAAU;CACV,YAAY;CACZ,QACE;CACH,CAAC,CACH;AAMH,MAAa,QAAQ,QACnB,OAAO,IACL,WAAW,WAAW,UAAU,GAC/B,YAAsC;CACrC,aAAa,MACX,eAAe,IAAI,CAAC,EAAE,CAAC,KAAK,OAAO,eAAe,WAAW,YAAY,OAAO,CAAC;CACnF,yBAAyB;CAC1B,EACF;;;;;;;AAQH,MAAa,SAAS,QACpB,MAAM,MACJ,MAAM,OAAO,mBAAmB,KAAK,IAAI,CAAC,EAC1C,MAAM,OACJ,aACA,OAAO,IACL,KAAK,IAAI,GACR,OAA2B;CAC1B,aAAa,QACX,EAAE,WAAW,IAA+B;CAC9C,yBAAyB,EAAE;CAC5B,EACF,CACF,CACF"}
|
|
1
|
+
{"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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"}
|
|
@@ -1 +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,
|
|
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"}
|
package/dist/LyriaGenerator.mjs
CHANGED
|
@@ -121,11 +121,11 @@ const extractParts = (wire) => {
|
|
|
121
121
|
const generateImpl = (cfg) => (request) => Effect.gen(function* () {
|
|
122
122
|
const client = yield* HttpClient.HttpClient;
|
|
123
123
|
const mimeType = yield* containerToMimeType(request.outputFormat?.container ?? "mp3");
|
|
124
|
-
if (isClipModel(request.model) && mimeType === "audio/wav") return yield*
|
|
124
|
+
if (isClipModel(request.model) && mimeType === "audio/wav") return yield* new AiError.Unsupported({
|
|
125
125
|
provider: "lyria",
|
|
126
126
|
capability: "outputFormat",
|
|
127
127
|
reason: `lyria-3-clip-preview is fixed at audio/mp3. Use lyria-3-pro-preview for audio/wav output.`
|
|
128
|
-
})
|
|
128
|
+
});
|
|
129
129
|
const promptText = buildPrompt(request);
|
|
130
130
|
const body = isClipModel(request.model) ? { contents: [{ parts: [{ text: promptText }] }] } : {
|
|
131
131
|
contents: [{ parts: [{ text: promptText }] }],
|
|
@@ -138,14 +138,14 @@ const generateImpl = (cfg) => (request) => Effect.gen(function* () {
|
|
|
138
138
|
const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure));
|
|
139
139
|
if (response.status >= 400) {
|
|
140
140
|
const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""));
|
|
141
|
-
return yield*
|
|
141
|
+
return yield* httpStatusError(response.status, text);
|
|
142
142
|
}
|
|
143
143
|
const json = yield* response.json.pipe(Effect.mapError(transportFailure));
|
|
144
144
|
const { inline, text } = extractParts(yield* decodeWire(json).pipe(Effect.mapError((cause) => new AiError.GenerationFailed({
|
|
145
145
|
provider: "lyria",
|
|
146
146
|
raw: cause
|
|
147
147
|
}))));
|
|
148
|
-
if (inline === void 0) return yield*
|
|
148
|
+
if (inline === void 0) return yield* new AiError.GenerationFailed({
|
|
149
149
|
provider: "lyria",
|
|
150
150
|
raw: {
|
|
151
151
|
message: "Lyria response had no inlineData audio part.",
|
|
@@ -153,7 +153,7 @@ const generateImpl = (cfg) => (request) => Effect.gen(function* () {
|
|
|
153
153
|
textParts: text,
|
|
154
154
|
response: json
|
|
155
155
|
}
|
|
156
|
-
})
|
|
156
|
+
});
|
|
157
157
|
const bytes = yield* decodeBase64ToBytes(inline.data);
|
|
158
158
|
return {
|
|
159
159
|
format: realizedFormat(mimeType),
|