@effect-uai/google 0.17.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/Gemini.d.mts +1 -1
- package/dist/Gemini.mjs +1 -1
- package/dist/Gemini.mjs.map +1 -1
- package/dist/GeminiEmbedding.d.mts +1 -1
- package/dist/GeminiEmbedding.d.mts.map +1 -1
- package/dist/GeminiEmbedding.mjs +4 -3
- package/dist/GeminiEmbedding.mjs.map +1 -1
- package/dist/GeminiImageGenerator.d.mts +1 -1
- package/dist/GeminiImageGenerator.d.mts.map +1 -1
- package/dist/GeminiImageGenerator.mjs +4 -3
- package/dist/GeminiImageGenerator.mjs.map +1 -1
- package/dist/GeminiLiveSession.d.mts +1 -1
- package/dist/GeminiSynthesizer.d.mts +1 -1
- package/dist/GeminiSynthesizer.mjs +1 -1
- package/dist/GeminiSynthesizer.mjs.map +1 -1
- package/dist/GeminiSynthesizer.test.mjs +1 -1
- package/dist/GoogleDeepResearch.d.mts +1 -1
- package/dist/GoogleDeepResearch.mjs +1 -1
- package/dist/GoogleDeepResearch.mjs.map +1 -1
- package/dist/GoogleDeepResearch.test.mjs +1 -1
- package/dist/LyriaGenerator.d.mts +1 -1
- package/dist/LyriaGenerator.d.mts.map +1 -1
- package/dist/LyriaGenerator.mjs +4 -3
- package/dist/LyriaGenerator.mjs.map +1 -1
- package/dist/LyriaGenerator.test.mjs +4 -142
- package/dist/LyriaGenerator.test.mjs.map +1 -1
- package/dist/codec.d.mts.map +1 -1
- package/dist/codec.mjs +3 -2
- package/dist/codec.mjs.map +1 -1
- package/dist/dist-DHer1cql.mjs +1533 -0
- package/dist/dist-DHer1cql.mjs.map +1 -0
- package/dist/geminiSpeechCodec.d.mts.map +1 -1
- package/dist/geminiSpeechCodec.mjs +4 -3
- package/dist/geminiSpeechCodec.mjs.map +1 -1
- package/dist/geminiSpeechCodec.test.mjs +5 -4
- package/dist/geminiSpeechCodec.test.mjs.map +1 -1
- package/dist/geminiTools.test.mjs +1 -1
- package/dist/{test.DNmyFkvJ-BPz3G1HS.mjs → index.C4tKZ0yW-DjxCdpW_.mjs} +9115 -5993
- package/dist/index.C4tKZ0yW-DjxCdpW_.mjs.map +1 -0
- package/dist/{realtimeSession-7HSIjWh4.d.mts → realtimeSession-BlBgLDdp.d.mts} +2 -2
- package/dist/{realtimeSession-7HSIjWh4.d.mts.map → realtimeSession-BlBgLDdp.d.mts.map} +1 -1
- package/dist/realtimeSession.d.mts +1 -1
- package/dist/realtimeSession.mjs +6 -5
- package/dist/realtimeSession.mjs.map +1 -1
- package/dist/realtimeSession.test.mjs +69 -78
- package/dist/realtimeSession.test.mjs.map +1 -1
- package/package.json +4 -4
- package/src/Gemini.ts +1 -1
- package/src/GeminiEmbedding.ts +4 -3
- package/src/GeminiImageGenerator.ts +4 -3
- package/src/GeminiSynthesizer.ts +1 -1
- package/src/GoogleDeepResearch.ts +1 -1
- package/src/LyriaGenerator.test.ts +1 -1
- package/src/LyriaGenerator.ts +4 -14
- package/src/codec.ts +3 -2
- package/src/geminiSpeechCodec.test.ts +4 -3
- package/src/geminiSpeechCodec.ts +4 -3
- package/src/realtimeSession.test.ts +4 -3
- package/src/realtimeSession.ts +5 -5
- package/dist/magic-string.es-CUAnjKYC.mjs +0 -1014
- package/dist/magic-string.es-CUAnjKYC.mjs.map +0 -1
- package/dist/test.DNmyFkvJ-BPz3G1HS.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -20,18 +20,18 @@ One key reaches the whole Gemini family, each at its own subpath:
|
|
|
20
20
|
pnpm add @effect-uai/google @effect-uai/core effect
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
ESM-only. Requires `effect
|
|
23
|
+
ESM-only. Requires `effect@>=4.0.1` and `@effect-uai/core` as peers.
|
|
24
24
|
|
|
25
25
|
## Usage
|
|
26
26
|
|
|
27
27
|
```ts
|
|
28
28
|
import { Config, Effect, Layer } from "effect"
|
|
29
|
-
import { FetchHttpClient } from "effect/
|
|
29
|
+
import { FetchHttpClient } from "effect/http"
|
|
30
30
|
import { layer as googleLayer } from "@effect-uai/google"
|
|
31
31
|
|
|
32
32
|
const provider = Layer.unwrap(
|
|
33
33
|
Effect.gen(function* () {
|
|
34
|
-
const apiKey = yield* Config.
|
|
34
|
+
const apiKey = yield* Config.Redacted("GOOGLE_API_KEY")
|
|
35
35
|
return googleLayer({ apiKey })
|
|
36
36
|
}),
|
|
37
37
|
)
|
package/dist/Gemini.d.mts
CHANGED
|
@@ -2,7 +2,7 @@ import { ImageConfig, WireChunk } from "./codec.mjs";
|
|
|
2
2
|
import { codeExecutionTool, googleSearchTool, urlContextTool } from "./GeminiTools.mjs";
|
|
3
3
|
import { s as GoogleModel } from "./models-BVSKJwCx.mjs";
|
|
4
4
|
import { Context, Effect, Layer, Redacted, Schema, Stream } from "effect";
|
|
5
|
-
import { HttpClient } from "effect/
|
|
5
|
+
import { HttpClient } from "effect/http";
|
|
6
6
|
import * as AiError from "@effect-uai/core/AiError";
|
|
7
7
|
import { CommonRequest, LanguageModel } from "@effect-uai/core/LanguageModel";
|
|
8
8
|
import { Turn, TurnEvent } from "@effect-uai/core/Turn";
|
package/dist/Gemini.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { n as __exportAll } from "./rolldown-runtime-EAsCxrDo.mjs";
|
|
|
2
2
|
import { codeExecutionTool, googleSearchTool, renderProviderTools, urlContextTool } from "./GeminiTools.mjs";
|
|
3
3
|
import { WireChunk, accumulatorToTurn, buildRequestBody, emptyAccumulator, hasUrlImageSource, httpStatusError as httpStatusError$1, ingestChunk } from "./codec.mjs";
|
|
4
4
|
import { Context, Effect, Layer, Match, Option, Redacted, Result, Schema, Stream } from "effect";
|
|
5
|
-
import { HttpClient, HttpClientRequest } from "effect/
|
|
5
|
+
import { HttpClient, HttpClientRequest } from "effect/http";
|
|
6
6
|
import * as AiError from "@effect-uai/core/AiError";
|
|
7
7
|
import { LanguageModel, turnFromStream } from "@effect-uai/core/LanguageModel";
|
|
8
8
|
import { JsonParseError } from "@effect-uai/core/JSONL";
|
package/dist/Gemini.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Gemini.mjs","names":["sharedHttpStatusError"],"sources":["../src/Gemini.ts"],"sourcesContent":["import { Context, Effect, Layer, Option, Redacted, Result, 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 turnFromStream,\n} from \"@effect-uai/core/LanguageModel\"\nimport { JsonParseError } from \"@effect-uai/core/JSONL\"\nimport * as SSE from \"@effect-uai/core/SSE\"\nimport { descriptorsOf, providerToolsOf } from \"@effect-uai/core/Tool\"\nimport { type Turn, TurnEvent } from \"@effect-uai/core/Turn\"\nimport { renderProviderTools } from \"./GeminiTools.js\"\n\nexport { codeExecutionTool, googleSearchTool, urlContextTool } from \"./GeminiTools.js\"\nimport {\n type ChunkPart,\n type GenerationConfig,\n type ImageConfig,\n WireChunk,\n accumulatorToTurn,\n buildRequestBody,\n emptyAccumulator,\n hasUrlImageSource,\n httpStatusError as sharedHttpStatusError,\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 * What the model may answer with. Image models default to\n * `[\"TEXT\", \"IMAGE\"]`; set this to ask a non-image model for images, or\n * to hold an image model to prose.\n */\n readonly responseModalities?: ReadonlyArray<\"TEXT\" | \"IMAGE\">\n /** Shape and size of images the model draws. Image models only. */\n readonly imageConfig?: ImageConfig\n}\n\n/**\n * Image models answer in prose unless the IMAGE modality is asked for,\n * which is a silent disappointment rather than an error, so the adapter\n * asks on their behalf. Detection is Google's naming convention, which\n * holds across the family and keeps a future id working without an SDK\n * update; an explicit `responseModalities` always wins.\n */\nconst drawsImages = (model: string): boolean => model.endsWith(\"-image\")\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 * Drain a single turn and return the assembled `Turn`. Derived from\n * `streamTurn` — Gemini's non-streaming `generateContent` could\n * back this directly, but routing through streaming keeps a single\n * accumulator path.\n */\n readonly turn: (request: GeminiRequest) => Effect.Effect<Turn, 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 TEXT_AND_IMAGE: ReadonlyArray<\"TEXT\" | \"IMAGE\"> = [\"TEXT\", \"IMAGE\"]\n\nconst buildGenerationConfig = (request: GeminiRequest): Option.Option<GenerationConfig> => {\n const modalities =\n request.responseModalities ?? (drawsImages(request.model) ? TEXT_AND_IMAGE : undefined)\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 ...(modalities !== undefined && { responseModalities: modalities }),\n ...(request.imageConfig !== undefined && { imageConfig: request.imageConfig }),\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((json) =>\n // A `SchemaError` alone says nothing about which part shape we failed\n // to read, and image models add part shapes text-only ones never send.\n // Carry the line so the mismatch is legible.\n decodeChunk(json).pipe(\n Effect.mapError((cause) => new JsonParseError({ line: ev.data.slice(0, 2000), cause })),\n ),\n ),\n )\n\n// ---------------------------------------------------------------------------\n// Service implementation\n// ---------------------------------------------------------------------------\n\nconst httpStatusError = sharedHttpStatusError(\"gemini\")\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 if (hasUrlImageSource(request.history)) {\n return yield* new AiError.Unsupported({\n provider: \"gemini\",\n capability: \"imageInput\",\n reason:\n \"Gemini needs URL images pre-uploaded via the Files API; pass the image as base64 or raw bytes instead.\",\n })\n }\n const url = `${baseUrl}/models/${request.model}:streamGenerateContent?alt=sse`\n const generationConfig = buildGenerationConfig(request)\n const providerToolEntries = yield* Result.match(\n renderProviderTools(providerToolsOf(request.tools)),\n {\n onFailure: (e) =>\n Effect.fail(\n new AiError.Unsupported({\n provider: \"gemini\",\n capability: e.capability,\n reason: e.reason,\n }),\n ),\n onSuccess: (entries) => Effect.succeed(entries),\n },\n )\n const body = buildRequestBody(\n request.history,\n generationConfig,\n descriptorsOf(request.tools),\n providerToolEntries,\n request.toolChoice,\n )\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 // Gemini sends the image whole, so there is no `partialIndex`: the\n // one event that arrives is the finished picture.\n image: (p): ReadonlyArray<TurnEvent> => [TurnEvent.ImageOutput({ image: p.source })],\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, (client) => {\n const streamNative: GeminiService[\"streamNative\"] = (request) =>\n buildNativeStream(cfg)(request).pipe(Stream.provideService(HttpClient.HttpClient, client))\n const streamTurn: GeminiService[\"streamTurn\"] = (request) => toCanonical(streamNative(request))\n return {\n streamNative,\n streamTurn,\n turn: turnFromStream(streamTurn),\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(make(cfg), (s): LanguageModelService => ({\n streamTurn: (request) => s.streamTurn(request as GeminiRequest),\n turn: (request) => s.turn(request as GeminiRequest),\n })),\n )\n return Layer.merge(typed, generic)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Gemini.mjs","names":["sharedHttpStatusError"],"sources":["../src/Gemini.ts"],"sourcesContent":["import { Context, Effect, Layer, Option, Redacted, Result, Schema, Stream } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport {\n type CommonRequest,\n LanguageModel,\n type LanguageModelService,\n turnFromStream,\n} from \"@effect-uai/core/LanguageModel\"\nimport { JsonParseError } from \"@effect-uai/core/JSONL\"\nimport * as SSE from \"@effect-uai/core/SSE\"\nimport { descriptorsOf, providerToolsOf } from \"@effect-uai/core/Tool\"\nimport { type Turn, TurnEvent } from \"@effect-uai/core/Turn\"\nimport { renderProviderTools } from \"./GeminiTools.js\"\n\nexport { codeExecutionTool, googleSearchTool, urlContextTool } from \"./GeminiTools.js\"\nimport {\n type ChunkPart,\n type GenerationConfig,\n type ImageConfig,\n WireChunk,\n accumulatorToTurn,\n buildRequestBody,\n emptyAccumulator,\n hasUrlImageSource,\n httpStatusError as sharedHttpStatusError,\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 * What the model may answer with. Image models default to\n * `[\"TEXT\", \"IMAGE\"]`; set this to ask a non-image model for images, or\n * to hold an image model to prose.\n */\n readonly responseModalities?: ReadonlyArray<\"TEXT\" | \"IMAGE\">\n /** Shape and size of images the model draws. Image models only. */\n readonly imageConfig?: ImageConfig\n}\n\n/**\n * Image models answer in prose unless the IMAGE modality is asked for,\n * which is a silent disappointment rather than an error, so the adapter\n * asks on their behalf. Detection is Google's naming convention, which\n * holds across the family and keeps a future id working without an SDK\n * update; an explicit `responseModalities` always wins.\n */\nconst drawsImages = (model: string): boolean => model.endsWith(\"-image\")\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 * Drain a single turn and return the assembled `Turn`. Derived from\n * `streamTurn` — Gemini's non-streaming `generateContent` could\n * back this directly, but routing through streaming keeps a single\n * accumulator path.\n */\n readonly turn: (request: GeminiRequest) => Effect.Effect<Turn, 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 TEXT_AND_IMAGE: ReadonlyArray<\"TEXT\" | \"IMAGE\"> = [\"TEXT\", \"IMAGE\"]\n\nconst buildGenerationConfig = (request: GeminiRequest): Option.Option<GenerationConfig> => {\n const modalities =\n request.responseModalities ?? (drawsImages(request.model) ? TEXT_AND_IMAGE : undefined)\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 ...(modalities !== undefined && { responseModalities: modalities }),\n ...(request.imageConfig !== undefined && { imageConfig: request.imageConfig }),\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((json) =>\n // A `SchemaError` alone says nothing about which part shape we failed\n // to read, and image models add part shapes text-only ones never send.\n // Carry the line so the mismatch is legible.\n decodeChunk(json).pipe(\n Effect.mapError((cause) => new JsonParseError({ line: ev.data.slice(0, 2000), cause })),\n ),\n ),\n )\n\n// ---------------------------------------------------------------------------\n// Service implementation\n// ---------------------------------------------------------------------------\n\nconst httpStatusError = sharedHttpStatusError(\"gemini\")\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 if (hasUrlImageSource(request.history)) {\n return yield* new AiError.Unsupported({\n provider: \"gemini\",\n capability: \"imageInput\",\n reason:\n \"Gemini needs URL images pre-uploaded via the Files API; pass the image as base64 or raw bytes instead.\",\n })\n }\n const url = `${baseUrl}/models/${request.model}:streamGenerateContent?alt=sse`\n const generationConfig = buildGenerationConfig(request)\n const providerToolEntries = yield* Result.match(\n renderProviderTools(providerToolsOf(request.tools)),\n {\n onFailure: (e) =>\n Effect.fail(\n new AiError.Unsupported({\n provider: \"gemini\",\n capability: e.capability,\n reason: e.reason,\n }),\n ),\n onSuccess: (entries) => Effect.succeed(entries),\n },\n )\n const body = buildRequestBody(\n request.history,\n generationConfig,\n descriptorsOf(request.tools),\n providerToolEntries,\n request.toolChoice,\n )\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 // Gemini sends the image whole, so there is no `partialIndex`: the\n // one event that arrives is the finished picture.\n image: (p): ReadonlyArray<TurnEvent> => [TurnEvent.ImageOutput({ image: p.source })],\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, (client) => {\n const streamNative: GeminiService[\"streamNative\"] = (request) =>\n buildNativeStream(cfg)(request).pipe(Stream.provideService(HttpClient.HttpClient, client))\n const streamTurn: GeminiService[\"streamTurn\"] = (request) => toCanonical(streamNative(request))\n return {\n streamNative,\n streamTurn,\n turn: turnFromStream(streamTurn),\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(make(cfg), (s): LanguageModelService => ({\n streamTurn: (request) => s.streamTurn(request as GeminiRequest),\n turn: (request) => s.turn(request as GeminiRequest),\n })),\n )\n return Layer.merge(typed, generic)\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { o as GoogleEmbeddingModel } from "./models-BVSKJwCx.mjs";
|
|
2
2
|
import { Context, Effect, Layer, Redacted } from "effect";
|
|
3
|
-
import { HttpClient } from "effect/
|
|
3
|
+
import { HttpClient } from "effect/http";
|
|
4
4
|
import * as AiError from "@effect-uai/core/AiError";
|
|
5
5
|
import { CommonEmbedRequest, EmbedManyResponse, EmbedResponse, EmbeddingModel } from "@effect-uai/core/EmbeddingModel";
|
|
6
6
|
import { EmbedInput } from "@effect-uai/core/Embedding";
|
|
@@ -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":";;;;;;;;;;;;;;;KAiCY;KAUA,qBAAqB,KAAK;;WAE3B,OAAO;;;;;;WAMP,OAAO;;;;;WAKP;;KAGC,yBAAyB,KAAK;WAC/B,QAAQ,cAAc;;KAGrB;WACD,QAAQ,SAAS,uBAAuB,OAAO,OAAO,eAAe,QAAQ;WAC7E,YACP,SAAS,2BACN,OAAO,OAAO,mBAAmB,QAAQ;;;;;;;;cAQnC,wBAAwB;KAIzB;WACD,QAAQ,SAAS;WACjB;;;;;;cA8OE,OAAI,KACV,WACJ,OAAO,OAAO,+BAA+B,WAAW;;;;;;cAa9C,QAAK,KACX,WACJ,MAAM,MAAM,kBAAkB,uBAAuB,WAAW"}
|
package/dist/GeminiEmbedding.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { n as __exportAll } from "./rolldown-runtime-EAsCxrDo.mjs";
|
|
2
|
-
import { Context, Effect,
|
|
3
|
-
import { HttpClient, HttpClientRequest } from "effect/
|
|
2
|
+
import { Context, Effect, Layer, Match, Redacted, Schema } from "effect";
|
|
3
|
+
import { HttpClient, HttpClientRequest } from "effect/http";
|
|
4
4
|
import * as AiError from "@effect-uai/core/AiError";
|
|
5
|
+
import { Base64 } from "effect/encoding";
|
|
5
6
|
import { EmbeddingModel, assertEncoding } from "@effect-uai/core/EmbeddingModel";
|
|
6
7
|
//#region src/GeminiEmbedding.ts
|
|
7
8
|
var GeminiEmbedding_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -40,7 +41,7 @@ const imageSourceToPart = (param) => Match.type().pipe(Match.tag("base64", (s) =
|
|
|
40
41
|
data: s.base64
|
|
41
42
|
} })), Match.tag("bytes", (s) => Effect.succeed({ inlineData: {
|
|
42
43
|
mimeType: s.mimeType,
|
|
43
|
-
data:
|
|
44
|
+
data: Base64.encode(s.bytes)
|
|
44
45
|
} })), Match.tag("url", () => Effect.fail(urlRejected(param))), Match.exhaustive);
|
|
45
46
|
const contentPartToPart = (param, part) => "text" in part ? Effect.succeed({ text: part.text }) : imageSourceToPart(param)(part.image);
|
|
46
47
|
const inputToContent = (input) => {
|
|
@@ -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 {\n EmbedContentPart,\n EmbedInput,\n Float32Embedding,\n Usage,\n} from \"@effect-uai/core/Embedding\"\nimport {\n assertEncoding,\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(\"base64\", (s): Effect.Effect<WirePart, AiError.AiError> =>\n Effect.succeed({ inlineData: { mimeType: s.mimeType, data: s.base64 } }),\n ),\n Match.tag(\"bytes\", (s): Effect.Effect<WirePart, AiError.AiError> =>\n Effect.succeed({\n inlineData: { mimeType: s.mimeType, data: Encoding.encodeBase64(s.bytes) },\n }),\n ),\n Match.tag(\"url\", (): Effect.Effect<WirePart, AiError.AiError> =>\n 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((decoded): EmbedResponse => ({\n embedding: valuesToEmbedding(decoded.embedding.values),\n usage: emptyUsage,\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((decoded): EmbedManyResponse => ({\n embeddings: decoded.embeddings.map((e) => valuesToEmbedding(e.values)),\n usage: emptyUsage,\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, (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(make(cfg), (s): EmbeddingModelService => ({\n // Gemini's embed endpoints emit float32 only. Reject a non-float32\n // `encoding` (bucket 1) so a float32 vector isn't handed back\n // mislabeled as the requested type. `task` is left silent: it's a\n // per-model gap (honored by gemini-embedding-001, ignored by\n // gemini-embedding-2 server-side), and per capabilities.md §2.3 we\n // don't maintain per-model tables; there's no wire error to translate.\n embed: <E extends EmbedEncoding | undefined = undefined>(\n req: Omit<CommonEmbedRequest, \"encoding\"> & { readonly encoding?: E },\n ) =>\n assertEncoding(req.encoding, [\"float32\"], \"gemini\").pipe(\n Effect.andThen(s.embed(req as GeminiEmbedRequest)),\n ) as Effect.Effect<EmbedResponse<E>, AiError.AiError>,\n embedMany: <E extends EmbedEncoding | undefined = undefined>(\n req: Omit<CommonEmbedManyRequest, \"encoding\"> & { readonly encoding?: E },\n ) =>\n assertEncoding(req.encoding, [\"float32\"], \"gemini\").pipe(\n Effect.andThen(s.embedMany(req as GeminiEmbedManyRequest)),\n ) as Effect.Effect<EmbedManyResponse<E>, AiError.AiError>,\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, Layer, Match, Redacted, Schema } from \"effect\"\nimport { Base64 } from \"effect/encoding\"\nimport { HttpClient, HttpClientRequest } from \"effect/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 assertEncoding,\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(\"base64\", (s): Effect.Effect<WirePart, AiError.AiError> =>\n Effect.succeed({ inlineData: { mimeType: s.mimeType, data: s.base64 } }),\n ),\n Match.tag(\"bytes\", (s): Effect.Effect<WirePart, AiError.AiError> =>\n Effect.succeed({\n inlineData: { mimeType: s.mimeType, data: Base64.encode(s.bytes) },\n }),\n ),\n Match.tag(\"url\", (): Effect.Effect<WirePart, AiError.AiError> =>\n 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((decoded): EmbedResponse => ({\n embedding: valuesToEmbedding(decoded.embedding.values),\n usage: emptyUsage,\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((decoded): EmbedManyResponse => ({\n embeddings: decoded.embeddings.map((e) => valuesToEmbedding(e.values)),\n usage: emptyUsage,\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, (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(make(cfg), (s): EmbeddingModelService => ({\n // Gemini's embed endpoints emit float32 only. Reject a non-float32\n // `encoding` (bucket 1) so a float32 vector isn't handed back\n // mislabeled as the requested type. `task` is left silent: it's a\n // per-model gap (honored by gemini-embedding-001, ignored by\n // gemini-embedding-2 server-side), and per capabilities.md §2.3 we\n // don't maintain per-model tables; there's no wire error to translate.\n embed: <E extends EmbedEncoding | undefined = undefined>(\n req: Omit<CommonEmbedRequest, \"encoding\"> & { readonly encoding?: E },\n ) =>\n assertEncoding(req.encoding, [\"float32\"], \"gemini\").pipe(\n Effect.andThen(s.embed(req as GeminiEmbedRequest)),\n ) as Effect.Effect<EmbedResponse<E>, AiError.AiError>,\n embedMany: <E extends EmbedEncoding | undefined = undefined>(\n req: Omit<CommonEmbedManyRequest, \"encoding\"> & { readonly encoding?: E },\n ) =>\n assertEncoding(req.encoding, [\"float32\"], \"gemini\").pipe(\n Effect.andThen(s.embedMany(req as GeminiEmbedManyRequest)),\n ) as Effect.Effect<EmbedManyResponse<E>, AiError.AiError>,\n })),\n )\n return Layer.merge(typed, generic)\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as GeminiImageModel } from "./models-BVSKJwCx.mjs";
|
|
2
2
|
import { Context, Effect, Layer, Redacted, Schema } from "effect";
|
|
3
|
-
import { HttpClient } from "effect/
|
|
3
|
+
import { HttpClient } from "effect/http";
|
|
4
4
|
import * as AiError from "@effect-uai/core/AiError";
|
|
5
5
|
import { ImageBase64Source, ImageBytesSource, ImageSource } from "@effect-uai/core/Image";
|
|
6
6
|
import { CommonImageEditRequest, CommonImageGenerateRequest, ImageGenerator, ImageGeneratorService, ImageResponse } from "@effect-uai/core/ImageGenerator";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GeminiImageGenerator.d.mts","names":[],"sources":["../src/GeminiImageGenerator.ts"],"mappings":";;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"GeminiImageGenerator.d.mts","names":[],"sources":["../src/GeminiImageGenerator.ts"],"mappings":";;;;;;;;;;KA+BK;;;;;WAKM;;WAEA;;WAEA;;KAGC,6BAA6B,KAAK;WACnC,OAAO;IACd;;;;;;;;KASQ,iBAAiB,oBAAoB;KAErC,yBAAyB,KAAK;WAC/B,OAAO;;WAEP,QAAQ,cAAc;IAC7B;KAEQ;WACD,WACP,SAAS,+BACN,OAAO,OAAO,eAAe,QAAQ;WACjC,OAAO,SAAS,2BAA2B,OAAO,OAAO,eAAe,QAAQ;;WAEhF,kBAAkB;WAClB,YAAY;;;;;;;;cAQV,6BAA6B;KAK9B;WACD,QAAQ,SAAS;WACjB;;KAaN;WACM;WACA;;KAGN;WACU;;WACA;aAAuB;aAA2B;;;KAErD;WACD,UAAU;aACR;aACA,OAAO,cAAc;;WAEvB;aACE,oBAAoB;aACpB,cAAc;aACd;eAA4B;;;WAE9B,QAAQ;aAAyB;;;;cAyC/B,cAAW,SACb,KAAK,4DACb,OAAO;cAaG,YAAS,SACX,4BAA0B,QAC3B,cAAc,iBACrB,OAAO,OAAO,UAAU,QAAQ;cAkD7B,MAAI,OAAA;;;;;;;;;;;;;;;;;;;;;;;;KAKL,cAAc,KAAK;cAEX,aAAU,gBAAA,qCAAA,iBAAA,OAAA;;;;;;;;;;;;;;;;;;;;;;GAAmC,OAAA;cAgC7C,aAAU,MAAU,SAAO,OAAO,OAAO,eAAe,QAAQ;cAuFhE,OAAI,KACV,WACJ,OAAO,OAAO,oCAAoC,WAAW;;;;;;;;;;cAqBnD,QAAK,KACX,WACJ,MAAM,MAAM,uBAAuB,uBAAuB,WAAW"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { n as __exportAll } from "./rolldown-runtime-EAsCxrDo.mjs";
|
|
2
2
|
import { httpStatusError, transportFailure } from "./codec.mjs";
|
|
3
|
-
import { Context, Effect,
|
|
4
|
-
import { HttpClient, HttpClientRequest } from "effect/
|
|
3
|
+
import { Context, Effect, Layer, Match, Redacted, Schema, Stream } from "effect";
|
|
4
|
+
import { HttpClient, HttpClientRequest } from "effect/http";
|
|
5
5
|
import * as AiError from "@effect-uai/core/AiError";
|
|
6
|
+
import { Base64 } from "effect/encoding";
|
|
6
7
|
import { imageBase64 } from "@effect-uai/core/Image";
|
|
7
8
|
import * as Capabilities from "@effect-uai/core/Capabilities";
|
|
8
9
|
import { ImageGenerator } from "@effect-uai/core/ImageGenerator";
|
|
@@ -33,7 +34,7 @@ const referencePart = Match.type().pipe(Match.tag("base64", (i) => Effect.succee
|
|
|
33
34
|
data: i.base64
|
|
34
35
|
} })), Match.tag("bytes", (i) => Effect.succeed({ inlineData: {
|
|
35
36
|
mimeType: i.mimeType,
|
|
36
|
-
data:
|
|
37
|
+
data: Base64.encode(i.bytes)
|
|
37
38
|
} })), Match.tag("url", () => Effect.fail(urlRefNotSupported)), Match.exhaustive);
|
|
38
39
|
/**
|
|
39
40
|
* More than one image per call is bucket 1: `candidateCount` is a generic
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GeminiImageGenerator.mjs","names":[],"sources":["../src/GeminiImageGenerator.ts"],"sourcesContent":["import { Context, Effect, Encoding, Layer, Match, Redacted, Schema, Stream } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport * as Capabilities from \"@effect-uai/core/Capabilities\"\nimport type {\n GeneratedImage,\n ImageBase64Source,\n ImageBytesSource,\n ImageSource,\n} from \"@effect-uai/core/Image\"\nimport { imageBase64 } from \"@effect-uai/core/Image\"\nimport type {\n CommonImageEditRequest,\n CommonImageGenerateRequest,\n CommonStreamImageEditRequest,\n CommonStreamImageRequest,\n ImageGeneratorService,\n ImageResponse,\n ImageStreamEvent,\n ImageUsage,\n} from \"@effect-uai/core/ImageGenerator\"\nimport { ImageGenerator } from \"@effect-uai/core/ImageGenerator\"\nimport { httpStatusError, transportFailure } from \"./codec.js\"\nimport type { GeminiImageModel } from \"./models.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/** Wire knobs `generateContent` accepts alongside the portable request. */\ntype GeminiImageKnobs = {\n /**\n * Sent as `imageConfig.imageSize`. Takes precedence over `resolution`,\n * which is then warn-dropped. `\"512\"` is Flash-only.\n */\n readonly imageSize?: \"512\" | \"1K\" | \"2K\" | \"4K\"\n /** Thinking is always on for image models; this only sets how long. */\n readonly thinkingLevel?: \"minimal\" | \"low\" | \"medium\" | \"high\"\n /** Ground the image in web results. Flash and Pro only. */\n readonly googleSearch?: boolean\n}\n\nexport type GeminiImageGenerateRequest = Omit<CommonImageGenerateRequest, \"model\"> & {\n readonly model: GeminiImageModel\n} & GeminiImageKnobs\n\n/**\n * Reference images for an edit. Narrowed off `ImageSource`: references\n * ride in the request body as base64 `inlineData`, so a `url` variant has\n * no wire representation short of pre-uploading through the Files API.\n * A compile-time error rather than a 400; fetch it yourself and pass the\n * bytes.\n */\nexport type GeminiImageRef = ImageBase64Source | ImageBytesSource\n\nexport type GeminiImageEditRequest = Omit<CommonImageEditRequest, \"model\" | \"images\"> & {\n readonly model: GeminiImageModel\n /** 14 total, split between object / character / style refs per model. */\n readonly images: ReadonlyArray<GeminiImageRef>\n} & GeminiImageKnobs\n\nexport type GeminiImageGeneratorService = {\n readonly generate: (\n request: GeminiImageGenerateRequest,\n ) => Effect.Effect<ImageResponse, AiError.AiError>\n readonly edit: (request: GeminiImageEditRequest) => Effect.Effect<ImageResponse, AiError.AiError>\n /** Both always `Unsupported`: `ImageStreaming` is not registered by {@link layer}. */\n readonly streamGeneration: ImageGeneratorService[\"streamGeneration\"]\n readonly streamEdit: ImageGeneratorService[\"streamEdit\"]\n}\n\n/**\n * Provider-typed service tag. Yield this for `imageSize`, `thinkingLevel`\n * and search grounding; yield the generic `ImageGenerator` tag for\n * provider-portable code. Both are registered by {@link layer}.\n */\nexport class GeminiImageGenerator extends Context.Service<\n GeminiImageGenerator,\n GeminiImageGeneratorService\n>()(\"@betalyra/effect-uai/providers/google/GeminiImageGenerator\") {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n}\n\nconst PROVIDER = \"gemini\"\n\n// ---------------------------------------------------------------------------\n// Codec - request\n//\n// Both ratio and tier are native here, so nothing is derived. Whether a\n// given model accepts the pair is the server's call: per-model limits\n// churn and we do not table them.\n// ---------------------------------------------------------------------------\n\ntype ImageConfig = {\n readonly aspectRatio?: string\n readonly imageSize?: string\n}\n\ntype RequestPart =\n | { readonly text: string }\n | { readonly inlineData: { readonly mimeType: string; readonly data: string } }\n\nexport type WireBody = {\n readonly contents: ReadonlyArray<{\n readonly role: \"user\"\n readonly parts: ReadonlyArray<RequestPart>\n }>\n readonly generationConfig: {\n readonly responseModalities: ReadonlyArray<\"TEXT\" | \"IMAGE\">\n readonly imageConfig?: ImageConfig\n readonly thinkingConfig?: { readonly thinkingLevel: string }\n }\n readonly tools?: ReadonlyArray<{ readonly googleSearch: {} }>\n}\n\nconst urlRefNotSupported: AiError.AiError = new AiError.InvalidRequest({\n provider: PROVIDER,\n param: \"images\",\n raw: 'Reference images ride in the request body as base64, so a URL reference cannot be sent without pre-uploading it through the Files API. Fetch the URL yourself and pass `{ _tag: \"bytes\", bytes, mimeType }`.',\n})\n\nconst referencePart: (image: ImageSource) => Effect.Effect<RequestPart, AiError.AiError> =\n Match.type<ImageSource>().pipe(\n Match.tag(\"base64\", (i) =>\n Effect.succeed<RequestPart>({ inlineData: { mimeType: i.mimeType, data: i.base64 } }),\n ),\n Match.tag(\"bytes\", (i) =>\n Effect.succeed<RequestPart>({\n inlineData: { mimeType: i.mimeType, data: Encoding.encodeBase64(i.bytes) },\n }),\n ),\n Match.tag(\"url\", () => Effect.fail(urlRefNotSupported)),\n Match.exhaustive,\n )\n\n/**\n * More than one image per call is bucket 1: `candidateCount` is a generic\n * `generationConfig` field whose behaviour on image models is undocumented,\n * and silently returning one image where several were asked for is a\n * different result, not a smaller one.\n */\nconst rejectMultiple = (n: number | undefined): Effect.Effect<void, AiError.AiError> =>\n n === undefined || n <= 1\n ? Effect.void\n : Effect.fail(\n new AiError.Unsupported({\n provider: PROVIDER,\n capability: \"n\",\n reason: `Gemini image models return one image per call; \\`n: ${n}\\` has no wire field. Run the request ${n} times, concurrently if you want them at once.`,\n }),\n )\n\n/** The typed `imageSize` wins over the portable tier, which is then dropped. */\nexport const imageSizeOf = (\n request: Pick<GeminiImageGenerateRequest, \"imageSize\" | \"resolution\">,\n): Effect.Effect<string | undefined> =>\n request.imageSize === undefined\n ? Effect.succeed(request.resolution)\n : Effect.as(\n Capabilities.warnDroppedWhen(request.resolution, {\n provider: PROVIDER,\n capability: \"resolution\",\n field: \"resolution\",\n reason: `\\`imageSize: \"${request.imageSize}\"\\` sets the tier directly.`,\n }),\n request.imageSize,\n )\n\nexport const buildBody = (\n request: GeminiImageGenerateRequest,\n images: ReadonlyArray<ImageSource>,\n): Effect.Effect<WireBody, AiError.AiError> =>\n Effect.gen(function* () {\n yield* rejectMultiple(request.n)\n const imageSize = yield* imageSizeOf(request)\n const refs = yield* Effect.forEach(images, referencePart)\n const imageConfig: ImageConfig = {\n ...(request.aspectRatio !== undefined && { aspectRatio: request.aspectRatio }),\n ...(imageSize !== undefined && { imageSize }),\n }\n return {\n contents: [{ role: \"user\", parts: [{ text: request.prompt }, ...refs] }],\n generationConfig: {\n responseModalities: [\"TEXT\", \"IMAGE\"],\n ...(Object.keys(imageConfig).length > 0 && { imageConfig }),\n ...(request.thinkingLevel !== undefined && {\n thinkingConfig: { thinkingLevel: request.thinkingLevel.toUpperCase() },\n }),\n },\n ...(request.googleSearch === true && { tools: [{ googleSearch: {} }] }),\n }\n })\n\n// ---------------------------------------------------------------------------\n// Codec - response\n// ---------------------------------------------------------------------------\n\nconst InlineData = Schema.Struct({\n mimeType: Schema.optional(Schema.String),\n data: Schema.String,\n})\n\nconst Part = Schema.Struct({\n text: Schema.optional(Schema.String),\n inlineData: Schema.optional(InlineData),\n /** Interim images the model drew while thinking. Not the answer. */\n thought: Schema.optional(Schema.Boolean),\n})\n\nconst Candidate = Schema.Struct({\n content: Schema.optional(Schema.Struct({ parts: Schema.optional(Schema.Array(Part)) })),\n finishReason: Schema.optional(Schema.String),\n})\n\nconst UsageMetadata = Schema.Struct({\n promptTokenCount: Schema.optional(Schema.Number),\n candidatesTokenCount: Schema.optional(Schema.Number),\n totalTokenCount: Schema.optional(Schema.Number),\n})\ntype UsageMetadata = typeof UsageMetadata.Type\n\nconst Wire = Schema.Struct({\n candidates: Schema.optional(Schema.Array(Candidate)),\n promptFeedback: Schema.optional(Schema.Struct({ blockReason: Schema.optional(Schema.String) })),\n usageMetadata: Schema.optional(UsageMetadata),\n})\ntype Wire = typeof Wire.Type\n\nexport const decodeWire = Schema.decodeUnknownEffect(Wire)\n\n/**\n * Finish reasons that are a content decision rather than a failure. The\n * generic safety codes appear when the prompt trips a filter, the\n * `IMAGE_`-prefixed ones when the drawn image does. `NO_IMAGE` and\n * `IMAGE_OTHER` are not here: they say the model produced nothing, not\n * that it refused.\n */\nconst FILTERED_FINISH_REASONS: ReadonlySet<string> = new Set([\n \"SAFETY\",\n \"RECITATION\",\n \"BLOCKLIST\",\n \"PROHIBITED_CONTENT\",\n \"SPII\",\n \"IMAGE_SAFETY\",\n \"IMAGE_PROHIBITED_CONTENT\",\n \"IMAGE_RECITATION\",\n])\n\nconst usageOf = (usage: UsageMetadata | undefined): ImageUsage => ({\n ...(usage?.promptTokenCount !== undefined && { inputTokens: usage.promptTokenCount }),\n ...(usage?.candidatesTokenCount !== undefined && { outputTokens: usage.candidatesTokenCount }),\n ...(usage?.totalTokenCount !== undefined && { totalTokens: usage.totalTokenCount }),\n})\n\n/** Every Gemini image carries SynthID; Lite adds a C2PA manifest on top. */\nconst generatedImage = (inline: typeof InlineData.Type): GeneratedImage => ({\n image: imageBase64(inline.data, inline.mimeType ?? \"image/png\"),\n watermark: \"synthid\",\n})\n\nexport const toResponse = (wire: Wire): Effect.Effect<ImageResponse, AiError.AiError> => {\n const blockReason = wire.promptFeedback?.blockReason\n const finishReason = wire.candidates?.[0]?.finishReason\n // A prompt-level block names itself; an output-level one shows up as the\n // candidate's finish reason with no parts attached.\n const filtered =\n blockReason ??\n (finishReason !== undefined && FILTERED_FINISH_REASONS.has(finishReason)\n ? finishReason\n : undefined)\n if (filtered !== undefined) {\n return Effect.fail(\n new AiError.ContentFiltered({ provider: PROVIDER, reason: filtered, raw: wire }),\n )\n }\n const images = (wire.candidates ?? [])\n .flatMap((candidate) => candidate.content?.parts ?? [])\n .flatMap((part) =>\n part.thought === true || part.inlineData === undefined\n ? []\n : [generatedImage(part.inlineData)],\n )\n return images.length === 0\n ? Effect.fail(\n new AiError.GenerationFailed({\n provider: PROVIDER,\n ...(finishReason !== undefined && { code: finishReason }),\n message: \"Gemini returned no image part.\",\n raw: wire,\n }),\n )\n : Effect.succeed({ images, usage: usageOf(wire.usageMetadata) })\n}\n\n// ---------------------------------------------------------------------------\n// HTTP\n// ---------------------------------------------------------------------------\n\nconst host = (cfg: Config): string =>\n cfg.baseUrl ?? \"https://generativelanguage.googleapis.com/v1beta\"\n\nconst requestImages = (\n cfg: Config,\n request: GeminiImageGenerateRequest,\n images: ReadonlyArray<ImageSource>,\n): Effect.Effect<ImageResponse, AiError.AiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const body = yield* buildBody(request, images)\n const response = yield* client\n .execute(\n HttpClientRequest.post(`${host(cfg)}/models/${request.model}:generateContent`).pipe(\n HttpClientRequest.setHeader(\"x-goog-api-key\", Redacted.value(cfg.apiKey)),\n HttpClientRequest.bodyJsonUnsafe(body),\n ),\n )\n .pipe(Effect.mapError(transportFailure(PROVIDER)))\n if (response.status >= 400) {\n const text = yield* response.text.pipe(Effect.orElseSucceed(() => \"\"))\n return yield* httpStatusError(PROVIDER)(response.status, text)\n }\n const json = yield* response.json.pipe(Effect.mapError(transportFailure(PROVIDER)))\n const wire = yield* decodeWire(json).pipe(Effect.mapError(transportFailure(PROVIDER)))\n return yield* toResponse(wire)\n })\n\n/**\n * `generateContent` has no partial-image wire, so this never succeeds and\n * {@link layer} does not register `ImageStreaming`. Code calling the\n * top-level `streamGeneration` helper against this Layer alone fails to\n * typecheck, which is the intended UX; this branch covers the service\n * being used directly.\n */\nconst streamUnsupported = (capability: string): Stream.Stream<ImageStreamEvent, AiError.AiError> =>\n Stream.fail(\n new AiError.Unsupported({\n provider: PROVIDER,\n capability,\n reason:\n \"Gemini image models have no partial-image wire; the image arrives whole. Use `generate` or `edit` and render when it resolves.\",\n }),\n )\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\nexport const make = (\n cfg: Config,\n): Effect.Effect<GeminiImageGeneratorService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient, (client): GeminiImageGeneratorService => ({\n generate: (request) =>\n requestImages(cfg, request, []).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n edit: (request) =>\n requestImages(cfg, request, request.images).pipe(\n Effect.provideService(HttpClient.HttpClient, client),\n ),\n streamGeneration: () => streamUnsupported(\"streamGeneration\"),\n streamEdit: () => streamUnsupported(\"streamEdit\"),\n }))\n\n/**\n * Layer registering the provider-typed tag and the generic\n * `ImageGenerator`. A `CommonImageGenerateRequest` is structurally a\n * `GeminiImageGenerateRequest` with no vendor knobs set, so the generic\n * registration forwards directly; a URL reference slipping through the\n * generic `edit` fails `InvalidRequest` at encode time.\n *\n * Does NOT register `ImageStreaming`.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<GeminiImageGenerator | ImageGenerator, never, HttpClient.HttpClient> =>\n Layer.merge(\n Layer.effect(GeminiImageGenerator, make(cfg)),\n Layer.effect(\n ImageGenerator,\n Effect.map(make(cfg), (s): ImageGeneratorService => ({\n generate: (request: CommonImageGenerateRequest) =>\n s.generate(request as GeminiImageGenerateRequest),\n edit: (request: CommonImageEditRequest) => s.edit(request as GeminiImageEditRequest),\n streamGeneration: (_request: CommonStreamImageRequest) =>\n streamUnsupported(\"streamGeneration\"),\n streamEdit: (_request: CommonStreamImageEditRequest) => streamUnsupported(\"streamEdit\"),\n })),\n ),\n )\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"GeminiImageGenerator.mjs","names":[],"sources":["../src/GeminiImageGenerator.ts"],"sourcesContent":["import { Context, Effect, Layer, Match, Redacted, Schema, Stream } from \"effect\"\nimport { Base64 } from \"effect/encoding\"\nimport { HttpClient, HttpClientRequest } from \"effect/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport * as Capabilities from \"@effect-uai/core/Capabilities\"\nimport type {\n GeneratedImage,\n ImageBase64Source,\n ImageBytesSource,\n ImageSource,\n} from \"@effect-uai/core/Image\"\nimport { imageBase64 } from \"@effect-uai/core/Image\"\nimport type {\n CommonImageEditRequest,\n CommonImageGenerateRequest,\n CommonStreamImageEditRequest,\n CommonStreamImageRequest,\n ImageGeneratorService,\n ImageResponse,\n ImageStreamEvent,\n ImageUsage,\n} from \"@effect-uai/core/ImageGenerator\"\nimport { ImageGenerator } from \"@effect-uai/core/ImageGenerator\"\nimport { httpStatusError, transportFailure } from \"./codec.js\"\nimport type { GeminiImageModel } from \"./models.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/** Wire knobs `generateContent` accepts alongside the portable request. */\ntype GeminiImageKnobs = {\n /**\n * Sent as `imageConfig.imageSize`. Takes precedence over `resolution`,\n * which is then warn-dropped. `\"512\"` is Flash-only.\n */\n readonly imageSize?: \"512\" | \"1K\" | \"2K\" | \"4K\"\n /** Thinking is always on for image models; this only sets how long. */\n readonly thinkingLevel?: \"minimal\" | \"low\" | \"medium\" | \"high\"\n /** Ground the image in web results. Flash and Pro only. */\n readonly googleSearch?: boolean\n}\n\nexport type GeminiImageGenerateRequest = Omit<CommonImageGenerateRequest, \"model\"> & {\n readonly model: GeminiImageModel\n} & GeminiImageKnobs\n\n/**\n * Reference images for an edit. Narrowed off `ImageSource`: references\n * ride in the request body as base64 `inlineData`, so a `url` variant has\n * no wire representation short of pre-uploading through the Files API.\n * A compile-time error rather than a 400; fetch it yourself and pass the\n * bytes.\n */\nexport type GeminiImageRef = ImageBase64Source | ImageBytesSource\n\nexport type GeminiImageEditRequest = Omit<CommonImageEditRequest, \"model\" | \"images\"> & {\n readonly model: GeminiImageModel\n /** 14 total, split between object / character / style refs per model. */\n readonly images: ReadonlyArray<GeminiImageRef>\n} & GeminiImageKnobs\n\nexport type GeminiImageGeneratorService = {\n readonly generate: (\n request: GeminiImageGenerateRequest,\n ) => Effect.Effect<ImageResponse, AiError.AiError>\n readonly edit: (request: GeminiImageEditRequest) => Effect.Effect<ImageResponse, AiError.AiError>\n /** Both always `Unsupported`: `ImageStreaming` is not registered by {@link layer}. */\n readonly streamGeneration: ImageGeneratorService[\"streamGeneration\"]\n readonly streamEdit: ImageGeneratorService[\"streamEdit\"]\n}\n\n/**\n * Provider-typed service tag. Yield this for `imageSize`, `thinkingLevel`\n * and search grounding; yield the generic `ImageGenerator` tag for\n * provider-portable code. Both are registered by {@link layer}.\n */\nexport class GeminiImageGenerator extends Context.Service<\n GeminiImageGenerator,\n GeminiImageGeneratorService\n>()(\"@betalyra/effect-uai/providers/google/GeminiImageGenerator\") {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n}\n\nconst PROVIDER = \"gemini\"\n\n// ---------------------------------------------------------------------------\n// Codec - request\n//\n// Both ratio and tier are native here, so nothing is derived. Whether a\n// given model accepts the pair is the server's call: per-model limits\n// churn and we do not table them.\n// ---------------------------------------------------------------------------\n\ntype ImageConfig = {\n readonly aspectRatio?: string\n readonly imageSize?: string\n}\n\ntype RequestPart =\n | { readonly text: string }\n | { readonly inlineData: { readonly mimeType: string; readonly data: string } }\n\nexport type WireBody = {\n readonly contents: ReadonlyArray<{\n readonly role: \"user\"\n readonly parts: ReadonlyArray<RequestPart>\n }>\n readonly generationConfig: {\n readonly responseModalities: ReadonlyArray<\"TEXT\" | \"IMAGE\">\n readonly imageConfig?: ImageConfig\n readonly thinkingConfig?: { readonly thinkingLevel: string }\n }\n readonly tools?: ReadonlyArray<{ readonly googleSearch: {} }>\n}\n\nconst urlRefNotSupported: AiError.AiError = new AiError.InvalidRequest({\n provider: PROVIDER,\n param: \"images\",\n raw: 'Reference images ride in the request body as base64, so a URL reference cannot be sent without pre-uploading it through the Files API. Fetch the URL yourself and pass `{ _tag: \"bytes\", bytes, mimeType }`.',\n})\n\nconst referencePart: (image: ImageSource) => Effect.Effect<RequestPart, AiError.AiError> =\n Match.type<ImageSource>().pipe(\n Match.tag(\"base64\", (i) =>\n Effect.succeed<RequestPart>({ inlineData: { mimeType: i.mimeType, data: i.base64 } }),\n ),\n Match.tag(\"bytes\", (i) =>\n Effect.succeed<RequestPart>({\n inlineData: { mimeType: i.mimeType, data: Base64.encode(i.bytes) },\n }),\n ),\n Match.tag(\"url\", () => Effect.fail(urlRefNotSupported)),\n Match.exhaustive,\n )\n\n/**\n * More than one image per call is bucket 1: `candidateCount` is a generic\n * `generationConfig` field whose behaviour on image models is undocumented,\n * and silently returning one image where several were asked for is a\n * different result, not a smaller one.\n */\nconst rejectMultiple = (n: number | undefined): Effect.Effect<void, AiError.AiError> =>\n n === undefined || n <= 1\n ? Effect.void\n : Effect.fail(\n new AiError.Unsupported({\n provider: PROVIDER,\n capability: \"n\",\n reason: `Gemini image models return one image per call; \\`n: ${n}\\` has no wire field. Run the request ${n} times, concurrently if you want them at once.`,\n }),\n )\n\n/** The typed `imageSize` wins over the portable tier, which is then dropped. */\nexport const imageSizeOf = (\n request: Pick<GeminiImageGenerateRequest, \"imageSize\" | \"resolution\">,\n): Effect.Effect<string | undefined> =>\n request.imageSize === undefined\n ? Effect.succeed(request.resolution)\n : Effect.as(\n Capabilities.warnDroppedWhen(request.resolution, {\n provider: PROVIDER,\n capability: \"resolution\",\n field: \"resolution\",\n reason: `\\`imageSize: \"${request.imageSize}\"\\` sets the tier directly.`,\n }),\n request.imageSize,\n )\n\nexport const buildBody = (\n request: GeminiImageGenerateRequest,\n images: ReadonlyArray<ImageSource>,\n): Effect.Effect<WireBody, AiError.AiError> =>\n Effect.gen(function* () {\n yield* rejectMultiple(request.n)\n const imageSize = yield* imageSizeOf(request)\n const refs = yield* Effect.forEach(images, referencePart)\n const imageConfig: ImageConfig = {\n ...(request.aspectRatio !== undefined && { aspectRatio: request.aspectRatio }),\n ...(imageSize !== undefined && { imageSize }),\n }\n return {\n contents: [{ role: \"user\", parts: [{ text: request.prompt }, ...refs] }],\n generationConfig: {\n responseModalities: [\"TEXT\", \"IMAGE\"],\n ...(Object.keys(imageConfig).length > 0 && { imageConfig }),\n ...(request.thinkingLevel !== undefined && {\n thinkingConfig: { thinkingLevel: request.thinkingLevel.toUpperCase() },\n }),\n },\n ...(request.googleSearch === true && { tools: [{ googleSearch: {} }] }),\n }\n })\n\n// ---------------------------------------------------------------------------\n// Codec - response\n// ---------------------------------------------------------------------------\n\nconst InlineData = Schema.Struct({\n mimeType: Schema.optional(Schema.String),\n data: Schema.String,\n})\n\nconst Part = Schema.Struct({\n text: Schema.optional(Schema.String),\n inlineData: Schema.optional(InlineData),\n /** Interim images the model drew while thinking. Not the answer. */\n thought: Schema.optional(Schema.Boolean),\n})\n\nconst Candidate = Schema.Struct({\n content: Schema.optional(Schema.Struct({ parts: Schema.optional(Schema.Array(Part)) })),\n finishReason: Schema.optional(Schema.String),\n})\n\nconst UsageMetadata = Schema.Struct({\n promptTokenCount: Schema.optional(Schema.Number),\n candidatesTokenCount: Schema.optional(Schema.Number),\n totalTokenCount: Schema.optional(Schema.Number),\n})\ntype UsageMetadata = typeof UsageMetadata.Type\n\nconst Wire = Schema.Struct({\n candidates: Schema.optional(Schema.Array(Candidate)),\n promptFeedback: Schema.optional(Schema.Struct({ blockReason: Schema.optional(Schema.String) })),\n usageMetadata: Schema.optional(UsageMetadata),\n})\ntype Wire = typeof Wire.Type\n\nexport const decodeWire = Schema.decodeUnknownEffect(Wire)\n\n/**\n * Finish reasons that are a content decision rather than a failure. The\n * generic safety codes appear when the prompt trips a filter, the\n * `IMAGE_`-prefixed ones when the drawn image does. `NO_IMAGE` and\n * `IMAGE_OTHER` are not here: they say the model produced nothing, not\n * that it refused.\n */\nconst FILTERED_FINISH_REASONS: ReadonlySet<string> = new Set([\n \"SAFETY\",\n \"RECITATION\",\n \"BLOCKLIST\",\n \"PROHIBITED_CONTENT\",\n \"SPII\",\n \"IMAGE_SAFETY\",\n \"IMAGE_PROHIBITED_CONTENT\",\n \"IMAGE_RECITATION\",\n])\n\nconst usageOf = (usage: UsageMetadata | undefined): ImageUsage => ({\n ...(usage?.promptTokenCount !== undefined && { inputTokens: usage.promptTokenCount }),\n ...(usage?.candidatesTokenCount !== undefined && { outputTokens: usage.candidatesTokenCount }),\n ...(usage?.totalTokenCount !== undefined && { totalTokens: usage.totalTokenCount }),\n})\n\n/** Every Gemini image carries SynthID; Lite adds a C2PA manifest on top. */\nconst generatedImage = (inline: typeof InlineData.Type): GeneratedImage => ({\n image: imageBase64(inline.data, inline.mimeType ?? \"image/png\"),\n watermark: \"synthid\",\n})\n\nexport const toResponse = (wire: Wire): Effect.Effect<ImageResponse, AiError.AiError> => {\n const blockReason = wire.promptFeedback?.blockReason\n const finishReason = wire.candidates?.[0]?.finishReason\n // A prompt-level block names itself; an output-level one shows up as the\n // candidate's finish reason with no parts attached.\n const filtered =\n blockReason ??\n (finishReason !== undefined && FILTERED_FINISH_REASONS.has(finishReason)\n ? finishReason\n : undefined)\n if (filtered !== undefined) {\n return Effect.fail(\n new AiError.ContentFiltered({ provider: PROVIDER, reason: filtered, raw: wire }),\n )\n }\n const images = (wire.candidates ?? [])\n .flatMap((candidate) => candidate.content?.parts ?? [])\n .flatMap((part) =>\n part.thought === true || part.inlineData === undefined\n ? []\n : [generatedImage(part.inlineData)],\n )\n return images.length === 0\n ? Effect.fail(\n new AiError.GenerationFailed({\n provider: PROVIDER,\n ...(finishReason !== undefined && { code: finishReason }),\n message: \"Gemini returned no image part.\",\n raw: wire,\n }),\n )\n : Effect.succeed({ images, usage: usageOf(wire.usageMetadata) })\n}\n\n// ---------------------------------------------------------------------------\n// HTTP\n// ---------------------------------------------------------------------------\n\nconst host = (cfg: Config): string =>\n cfg.baseUrl ?? \"https://generativelanguage.googleapis.com/v1beta\"\n\nconst requestImages = (\n cfg: Config,\n request: GeminiImageGenerateRequest,\n images: ReadonlyArray<ImageSource>,\n): Effect.Effect<ImageResponse, AiError.AiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const body = yield* buildBody(request, images)\n const response = yield* client\n .execute(\n HttpClientRequest.post(`${host(cfg)}/models/${request.model}:generateContent`).pipe(\n HttpClientRequest.setHeader(\"x-goog-api-key\", Redacted.value(cfg.apiKey)),\n HttpClientRequest.bodyJsonUnsafe(body),\n ),\n )\n .pipe(Effect.mapError(transportFailure(PROVIDER)))\n if (response.status >= 400) {\n const text = yield* response.text.pipe(Effect.orElseSucceed(() => \"\"))\n return yield* httpStatusError(PROVIDER)(response.status, text)\n }\n const json = yield* response.json.pipe(Effect.mapError(transportFailure(PROVIDER)))\n const wire = yield* decodeWire(json).pipe(Effect.mapError(transportFailure(PROVIDER)))\n return yield* toResponse(wire)\n })\n\n/**\n * `generateContent` has no partial-image wire, so this never succeeds and\n * {@link layer} does not register `ImageStreaming`. Code calling the\n * top-level `streamGeneration` helper against this Layer alone fails to\n * typecheck, which is the intended UX; this branch covers the service\n * being used directly.\n */\nconst streamUnsupported = (capability: string): Stream.Stream<ImageStreamEvent, AiError.AiError> =>\n Stream.fail(\n new AiError.Unsupported({\n provider: PROVIDER,\n capability,\n reason:\n \"Gemini image models have no partial-image wire; the image arrives whole. Use `generate` or `edit` and render when it resolves.\",\n }),\n )\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\nexport const make = (\n cfg: Config,\n): Effect.Effect<GeminiImageGeneratorService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient, (client): GeminiImageGeneratorService => ({\n generate: (request) =>\n requestImages(cfg, request, []).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n edit: (request) =>\n requestImages(cfg, request, request.images).pipe(\n Effect.provideService(HttpClient.HttpClient, client),\n ),\n streamGeneration: () => streamUnsupported(\"streamGeneration\"),\n streamEdit: () => streamUnsupported(\"streamEdit\"),\n }))\n\n/**\n * Layer registering the provider-typed tag and the generic\n * `ImageGenerator`. A `CommonImageGenerateRequest` is structurally a\n * `GeminiImageGenerateRequest` with no vendor knobs set, so the generic\n * registration forwards directly; a URL reference slipping through the\n * generic `edit` fails `InvalidRequest` at encode time.\n *\n * Does NOT register `ImageStreaming`.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<GeminiImageGenerator | ImageGenerator, never, HttpClient.HttpClient> =>\n Layer.merge(\n Layer.effect(GeminiImageGenerator, make(cfg)),\n Layer.effect(\n ImageGenerator,\n Effect.map(make(cfg), (s): ImageGeneratorService => ({\n generate: (request: CommonImageGenerateRequest) =>\n s.generate(request as GeminiImageGenerateRequest),\n edit: (request: CommonImageEditRequest) => s.edit(request as GeminiImageEditRequest),\n streamGeneration: (_request: CommonStreamImageRequest) =>\n streamUnsupported(\"streamGeneration\"),\n streamEdit: (_request: CommonStreamImageEditRequest) => streamUnsupported(\"streamEdit\"),\n })),\n ),\n )\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as GeminiLiveRequest, t as Config } from "./realtimeSession-
|
|
1
|
+
import { n as GeminiLiveRequest, t as Config } from "./realtimeSession-BlBgLDdp.mjs";
|
|
2
2
|
import { Context, Effect, Layer, Scope } from "effect";
|
|
3
3
|
import * as AiError from "@effect-uai/core/AiError";
|
|
4
4
|
import { RealtimeSession, RealtimeSessionHandle, RealtimeVideoInput } from "@effect-uai/core/RealtimeSession";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { i as GeminiVoiceName, r as GeminiTtsModel } from "./models-BVSKJwCx.mjs";
|
|
2
2
|
import { Context, Effect, Layer, Redacted, Result, Stream } from "effect";
|
|
3
|
-
import { HttpClient } from "effect/
|
|
3
|
+
import { HttpClient } from "effect/http";
|
|
4
4
|
import * as AiError from "@effect-uai/core/AiError";
|
|
5
5
|
import { CommonSynthesizeRequest, SpeechSynthesizer, SpeechSynthesizerService } from "@effect-uai/core/SpeechSynthesizer";
|
|
6
6
|
import { AudioBlob, AudioChunk } from "@effect-uai/core/Audio";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { n as __exportAll } from "./rolldown-runtime-EAsCxrDo.mjs";
|
|
2
2
|
import { decodeBase64Audio, httpStatusError, transportFailure, wrapPcmAsWav } from "./geminiSpeechCodec.mjs";
|
|
3
3
|
import { Context, Effect, Layer, Match, Redacted, Result, Schema, Stream } from "effect";
|
|
4
|
-
import { HttpClient, HttpClientRequest } from "effect/
|
|
4
|
+
import { HttpClient, HttpClientRequest } from "effect/http";
|
|
5
5
|
import * as AiError from "@effect-uai/core/AiError";
|
|
6
6
|
import * as Capabilities from "@effect-uai/core/Capabilities";
|
|
7
7
|
import { SpeechSynthesizer } from "@effect-uai/core/SpeechSynthesizer";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GeminiSynthesizer.mjs","names":[],"sources":["../src/GeminiSynthesizer.ts"],"sourcesContent":["import { Context, Effect, Layer, Match, Redacted, Result, 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 * as Capabilities from \"@effect-uai/core/Capabilities\"\nimport {\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 readonly synthesizeDialogue: SpeechSynthesizerService[\"synthesizeDialogue\"]\n readonly streamSynthesizeDialogue: SpeechSynthesizerService[\"streamSynthesizeDialogue\"]\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 Result.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 Result.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 Result.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 // Gemini `:generateContent` TTS has no phoneme field. Pronunciations\n // are load-bearing (bucket 1), so reject rather than mispronounce.\n if (request.pronunciations !== undefined && request.pronunciations.length > 0) {\n return yield* new AiError.Unsupported({\n provider: \"gemini\",\n capability: \"pronunciations\",\n reason:\n \"Gemini `:generateContent` TTS has no phoneme field. Use Cloud Text-to-Speech (Chirp 3 HD) or a provider with inline phonemes (Inworld) for pronunciation overrides.\",\n })\n }\n // No speaking-rate or language parameters on this endpoint; bucket 2.\n yield* Capabilities.warnDroppedWhen(request.speed, {\n provider: \"gemini\",\n capability: \"speed\",\n field: \"speed\",\n reason: \"Gemini `:generateContent` TTS has no speaking-rate parameter.\",\n })\n yield* Capabilities.warnDroppedWhen(request.languageCode, {\n provider: \"gemini\",\n capability: \"languageCode\",\n field: \"languageCode\",\n reason: \"Gemini `:generateContent` TTS has no language parameter.\",\n })\n const client = yield* HttpClient.HttpClient\n const [format, wrap] = yield* Effect.fromResult(\n realizeOutput(request.outputFormat?.container ?? \"raw\"),\n )\n const httpRequest = HttpClientRequest.post(\n `${baseUrl(cfg)}/models/${request.model}:generateContent`,\n ).pipe(\n HttpClientRequest.setHeader(\"x-goog-api-key\", Redacted.value(cfg.apiKey)),\n HttpClientRequest.bodyJsonUnsafe(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\nconst unsupportedDialogue: SpeechSynthesizerService[\"synthesizeDialogue\"] = () =>\n Effect.fail(\n new AiError.Unsupported({\n provider: \"gemini\",\n capability: \"synthesizeDialogue\",\n reason:\n \"Gemini API (generativelanguage.googleapis.com) does not expose a multi-speaker endpoint. Use Cloud Text-to-Speech (`@effect-uai/google-speech`) with Gemini TTS for multi-speaker.\",\n }),\n )\n\nconst unsupportedStreamDialogue: SpeechSynthesizerService[\"streamSynthesizeDialogue\"] = () =>\n Stream.fail(\n new AiError.Unsupported({\n provider: \"gemini\",\n capability: \"streamSynthesizeDialogue\",\n reason:\n \"Gemini API (generativelanguage.googleapis.com) does not expose a multi-speaker endpoint. Use Cloud Text-to-Speech (`@effect-uai/google-speech`) with Gemini TTS for multi-speaker.\",\n }),\n )\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\nexport const make = (cfg: Config) =>\n Effect.map(HttpClient.HttpClient, (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 synthesizeDialogue: unsupportedDialogue,\n streamSynthesizeDialogue: unsupportedStreamDialogue,\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(make(cfg), (s): SpeechSynthesizerService => ({\n synthesize: (req: CommonSynthesizeRequest) => s.synthesize(req as GeminiSynthesizeRequest),\n streamSynthesis: (req: CommonSynthesizeRequest) =>\n s.streamSynthesis(req as GeminiSynthesizeRequest),\n streamSynthesisFrom: s.streamSynthesisFrom,\n synthesizeDialogue: s.synthesizeDialogue,\n streamSynthesizeDialogue: s.streamSynthesizeDialogue,\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, Result, Schema, Stream } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport type { AudioBlob, AudioChunk, AudioFormat } from \"@effect-uai/core/Audio\"\nimport * as Capabilities from \"@effect-uai/core/Capabilities\"\nimport {\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 readonly synthesizeDialogue: SpeechSynthesizerService[\"synthesizeDialogue\"]\n readonly streamSynthesizeDialogue: SpeechSynthesizerService[\"streamSynthesizeDialogue\"]\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 Result.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 Result.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 Result.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 // Gemini `:generateContent` TTS has no phoneme field. Pronunciations\n // are load-bearing (bucket 1), so reject rather than mispronounce.\n if (request.pronunciations !== undefined && request.pronunciations.length > 0) {\n return yield* new AiError.Unsupported({\n provider: \"gemini\",\n capability: \"pronunciations\",\n reason:\n \"Gemini `:generateContent` TTS has no phoneme field. Use Cloud Text-to-Speech (Chirp 3 HD) or a provider with inline phonemes (Inworld) for pronunciation overrides.\",\n })\n }\n // No speaking-rate or language parameters on this endpoint; bucket 2.\n yield* Capabilities.warnDroppedWhen(request.speed, {\n provider: \"gemini\",\n capability: \"speed\",\n field: \"speed\",\n reason: \"Gemini `:generateContent` TTS has no speaking-rate parameter.\",\n })\n yield* Capabilities.warnDroppedWhen(request.languageCode, {\n provider: \"gemini\",\n capability: \"languageCode\",\n field: \"languageCode\",\n reason: \"Gemini `:generateContent` TTS has no language parameter.\",\n })\n const client = yield* HttpClient.HttpClient\n const [format, wrap] = yield* Effect.fromResult(\n realizeOutput(request.outputFormat?.container ?? \"raw\"),\n )\n const httpRequest = HttpClientRequest.post(\n `${baseUrl(cfg)}/models/${request.model}:generateContent`,\n ).pipe(\n HttpClientRequest.setHeader(\"x-goog-api-key\", Redacted.value(cfg.apiKey)),\n HttpClientRequest.bodyJsonUnsafe(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\nconst unsupportedDialogue: SpeechSynthesizerService[\"synthesizeDialogue\"] = () =>\n Effect.fail(\n new AiError.Unsupported({\n provider: \"gemini\",\n capability: \"synthesizeDialogue\",\n reason:\n \"Gemini API (generativelanguage.googleapis.com) does not expose a multi-speaker endpoint. Use Cloud Text-to-Speech (`@effect-uai/google-speech`) with Gemini TTS for multi-speaker.\",\n }),\n )\n\nconst unsupportedStreamDialogue: SpeechSynthesizerService[\"streamSynthesizeDialogue\"] = () =>\n Stream.fail(\n new AiError.Unsupported({\n provider: \"gemini\",\n capability: \"streamSynthesizeDialogue\",\n reason:\n \"Gemini API (generativelanguage.googleapis.com) does not expose a multi-speaker endpoint. Use Cloud Text-to-Speech (`@effect-uai/google-speech`) with Gemini TTS for multi-speaker.\",\n }),\n )\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\nexport const make = (cfg: Config) =>\n Effect.map(HttpClient.HttpClient, (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 synthesizeDialogue: unsupportedDialogue,\n streamSynthesizeDialogue: unsupportedStreamDialogue,\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(make(cfg), (s): SpeechSynthesizerService => ({\n synthesize: (req: CommonSynthesizeRequest) => s.synthesize(req as GeminiSynthesizeRequest),\n streamSynthesis: (req: CommonSynthesizeRequest) =>\n s.streamSynthesis(req as GeminiSynthesizeRequest),\n streamSynthesisFrom: s.streamSynthesisFrom,\n synthesizeDialogue: s.synthesizeDialogue,\n streamSynthesizeDialogue: s.streamSynthesizeDialogue,\n })),\n ),\n )\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { c as it, n as globalExpect, s as describe } from "./index.C4tKZ0yW-DjxCdpW_.mjs";
|
|
2
2
|
import { realizeOutput } from "./GeminiSynthesizer.mjs";
|
|
3
3
|
import { Result } from "effect";
|
|
4
4
|
//#region src/GeminiSynthesizer.test.ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as GoogleDeepResearchModel } from "./models-BVSKJwCx.mjs";
|
|
2
2
|
import { Context, Effect, Layer, Option, Redacted, Schema } from "effect";
|
|
3
|
-
import { HttpClient } from "effect/
|
|
3
|
+
import { HttpClient } from "effect/http";
|
|
4
4
|
import { DeepResearch, DeepResearchServiceShape } from "@effect-uai/core/DeepResearch";
|
|
5
5
|
import * as Items from "@effect-uai/core/Items";
|
|
6
6
|
import * as Job from "@effect-uai/core/Job";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { n as __exportAll } from "./rolldown-runtime-EAsCxrDo.mjs";
|
|
2
2
|
import { GroundingMetadata, groundingToAnnotations } from "./codec.mjs";
|
|
3
3
|
import { Array, Context, Effect, Layer, Match, Option, Redacted, Ref, Result, Schema, Stream, pipe } from "effect";
|
|
4
|
-
import { HttpClient, HttpClientRequest } from "effect/
|
|
4
|
+
import { HttpClient, HttpClientRequest } from "effect/http";
|
|
5
5
|
import * as AiError from "@effect-uai/core/AiError";
|
|
6
6
|
import * as SSE from "@effect-uai/core/SSE";
|
|
7
7
|
import { TurnEvent } from "@effect-uai/core/Turn";
|