@effect-uai/google 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Gemini.d.mts +15 -1
- package/dist/Gemini.d.mts.map +1 -1
- package/dist/Gemini.mjs +19 -42
- package/dist/Gemini.mjs.map +1 -1
- package/dist/GeminiImageGenerator.d.mts +149 -0
- package/dist/GeminiImageGenerator.d.mts.map +1 -0
- package/dist/GeminiImageGenerator.mjs +198 -0
- package/dist/GeminiImageGenerator.mjs.map +1 -0
- package/dist/LyriaGenerator.d.mts.map +1 -1
- package/dist/LyriaGenerator.mjs +3 -30
- package/dist/LyriaGenerator.mjs.map +1 -1
- package/dist/codec.d.mts +26 -2
- package/dist/codec.d.mts.map +1 -1
- package/dist/codec.mjs +87 -11
- package/dist/codec.mjs.map +1 -1
- package/dist/index.d.mts +3 -2
- package/dist/index.mjs +2 -1
- package/dist/models.d.mts +15 -2
- package/dist/models.d.mts.map +1 -1
- package/package.json +6 -2
- package/src/Gemini.ts +37 -13
- package/src/GeminiImageGenerator.ts +390 -0
- package/src/LyriaGenerator.ts +6 -36
- package/src/codec.ts +122 -21
- package/src/index.ts +1 -0
- package/src/models.ts +24 -0
package/dist/Gemini.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WireChunk } from "./codec.mjs";
|
|
1
|
+
import { ImageConfig, WireChunk } from "./codec.mjs";
|
|
2
2
|
import { codeExecutionTool, googleSearchTool, urlContextTool } from "./GeminiTools.mjs";
|
|
3
3
|
import { GoogleModel } from "./models.mjs";
|
|
4
4
|
import { Context, Effect, Layer, Redacted, Schema, Stream } from "effect";
|
|
@@ -29,6 +29,12 @@ declare const ProviderEvent: Schema.Struct<{
|
|
|
29
29
|
readonly args: Schema.optional<Schema.Unknown>;
|
|
30
30
|
}>;
|
|
31
31
|
readonly thoughtSignature: Schema.optional<Schema.String>;
|
|
32
|
+
}>, Schema.Struct<{
|
|
33
|
+
readonly inlineData: Schema.Struct<{
|
|
34
|
+
readonly mimeType: Schema.optional<Schema.String>;
|
|
35
|
+
readonly data: Schema.String;
|
|
36
|
+
}>;
|
|
37
|
+
readonly thought: Schema.optional<Schema.Boolean>;
|
|
32
38
|
}>]>>>;
|
|
33
39
|
}>>;
|
|
34
40
|
readonly groundingMetadata: Schema.optional<Schema.Struct<{
|
|
@@ -63,6 +69,14 @@ type GeminiRequest = Omit<CommonRequest, "model"> & {
|
|
|
63
69
|
* `generationConfig.thinkingConfig.thinkingBudget`.
|
|
64
70
|
*/
|
|
65
71
|
readonly thinkingBudget?: number;
|
|
72
|
+
/**
|
|
73
|
+
* What the model may answer with. Image models default to
|
|
74
|
+
* `["TEXT", "IMAGE"]`; set this to ask a non-image model for images, or
|
|
75
|
+
* to hold an image model to prose.
|
|
76
|
+
*/
|
|
77
|
+
readonly responseModalities?: ReadonlyArray<"TEXT" | "IMAGE">;
|
|
78
|
+
/** Shape and size of images the model draws. Image models only. */
|
|
79
|
+
readonly imageConfig?: ImageConfig;
|
|
66
80
|
};
|
|
67
81
|
type GeminiService = {
|
|
68
82
|
/**
|
package/dist/Gemini.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Gemini.d.mts","names":[],"sources":["../src/Gemini.ts"],"mappings":";;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"Gemini.d.mts","names":[],"sources":["../src/Gemini.ts"],"mappings":";;;;;;;;;;;;;;;;;cAqCa,eAAa,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KACd,gBAAgB;KAMhB,gBAAgB,KAAK;;;;;WAKtB,OAAO;;;;;;WAMP;;;;;;WAMA,qBAAqB;;WAErB,cAAc;;KAYb;;;;;;;;WAQD,eAAe,SAAS,kBAAkB,OAAO,OAAO,eAAe,QAAQ;;;;;WAK/E,aAAa,SAAS,kBAAkB,OAAO,OAAO,WAAW,QAAQ;;;;;;;WAOzE,OAAO,SAAS,kBAAkB,OAAO,OAAO,MAAM,QAAQ;;;;;;WAM9D,cAAc,GAAG,GACxB,GAAG,OAAO,OAAO,eAAe,GAAG,OAChC,OAAO,OAAO,WAAW,GAAG;;;;;;;;cAQtB,eAAe;KAIhB;WACD,QAAQ,SAAS;WACjB;;cA+KE,cAAe,GAAG,GAAC,GAC3B,OAAO,OAAO,eAAe,GAAG,OAClC,OAAO,OAAO,WAAW,GAAG;;;;cAoClB,OAAI,KAAS,WAAS,OAAO,OAAO,sBAAsB,WAAW;;;;;;;;cAoBrE,QAAK,KACX,WACJ,MAAM,MAAM,SAAS,sBAAsB,WAAW"}
|
package/dist/Gemini.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { n as __exportAll } from "./rolldown-runtime-EAsCxrDo.mjs";
|
|
2
2
|
import { codeExecutionTool, googleSearchTool, renderProviderTools, urlContextTool } from "./GeminiTools.mjs";
|
|
3
|
-
import { WireChunk, accumulatorToTurn, buildRequestBody, emptyAccumulator, hasUrlImageSource, ingestChunk } from "./codec.mjs";
|
|
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
5
|
import { HttpClient, HttpClientRequest } from "effect/unstable/http";
|
|
6
6
|
import * as AiError from "@effect-uai/core/AiError";
|
|
@@ -28,17 +28,29 @@ var Gemini_exports = /* @__PURE__ */ __exportAll({
|
|
|
28
28
|
*/
|
|
29
29
|
const ProviderEvent = WireChunk;
|
|
30
30
|
/**
|
|
31
|
+
* Image models answer in prose unless the IMAGE modality is asked for,
|
|
32
|
+
* which is a silent disappointment rather than an error, so the adapter
|
|
33
|
+
* asks on their behalf. Detection is Google's naming convention, which
|
|
34
|
+
* holds across the family and keeps a future id working without an SDK
|
|
35
|
+
* update; an explicit `responseModalities` always wins.
|
|
36
|
+
*/
|
|
37
|
+
const drawsImages = (model) => model.endsWith("-image");
|
|
38
|
+
/**
|
|
31
39
|
* Provider-typed service tag. Yield this when you want Gemini-specific
|
|
32
40
|
* options (`thinkingBudget`); yield the generic `LanguageModel` tag for
|
|
33
41
|
* provider-portable code. Both are registered by `layer`.
|
|
34
42
|
*/
|
|
35
43
|
var Gemini = class extends Context.Service()("@betalyra/effect-uai/providers/google/Gemini") {};
|
|
44
|
+
const TEXT_AND_IMAGE = ["TEXT", "IMAGE"];
|
|
36
45
|
const buildGenerationConfig = (request) => {
|
|
46
|
+
const modalities = request.responseModalities ?? (drawsImages(request.model) ? TEXT_AND_IMAGE : void 0);
|
|
37
47
|
const cfg = {
|
|
38
48
|
...request.temperature !== void 0 && { temperature: request.temperature },
|
|
39
49
|
...request.maxOutputTokens !== void 0 && { maxOutputTokens: request.maxOutputTokens },
|
|
40
50
|
...request.topP !== void 0 && { topP: request.topP },
|
|
41
51
|
...request.thinkingBudget !== void 0 && { thinkingConfig: { thinkingBudget: request.thinkingBudget } },
|
|
52
|
+
...modalities !== void 0 && { responseModalities: modalities },
|
|
53
|
+
...request.imageConfig !== void 0 && { imageConfig: request.imageConfig },
|
|
42
54
|
...request.structured !== void 0 && {
|
|
43
55
|
responseMimeType: "application/json",
|
|
44
56
|
responseJsonSchema: request.structured.schema["~standard"].jsonSchema.input({ target: "draft-2020-12" })
|
|
@@ -58,47 +70,11 @@ const parseJsonUnknown = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema
|
|
|
58
70
|
const sseEventToChunk = (ev) => parseJsonUnknown(ev.data).pipe(Effect.mapError((cause) => new JsonParseError({
|
|
59
71
|
line: ev.data,
|
|
60
72
|
cause
|
|
61
|
-
})), Effect.flatMap(decodeChunk))
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
provider,
|
|
67
|
-
raw
|
|
68
|
-
});
|
|
69
|
-
if (status === 408 || status === 504) return new AiError.Timeout({
|
|
70
|
-
provider,
|
|
71
|
-
raw
|
|
72
|
-
});
|
|
73
|
-
if (status === 401) return new AiError.AuthFailed({
|
|
74
|
-
provider,
|
|
75
|
-
subtype: "auth",
|
|
76
|
-
raw
|
|
77
|
-
});
|
|
78
|
-
if (status === 403) return new AiError.AuthFailed({
|
|
79
|
-
provider,
|
|
80
|
-
subtype: "permission",
|
|
81
|
-
raw
|
|
82
|
-
});
|
|
83
|
-
if (status === 402) return new AiError.AuthFailed({
|
|
84
|
-
provider,
|
|
85
|
-
subtype: "billing",
|
|
86
|
-
raw
|
|
87
|
-
});
|
|
88
|
-
if (status === 413) return new AiError.ContextLengthExceeded({
|
|
89
|
-
provider,
|
|
90
|
-
raw
|
|
91
|
-
});
|
|
92
|
-
if (status >= 500) return new AiError.Unavailable({
|
|
93
|
-
provider,
|
|
94
|
-
status,
|
|
95
|
-
raw
|
|
96
|
-
});
|
|
97
|
-
return new AiError.InvalidRequest({
|
|
98
|
-
provider,
|
|
99
|
-
raw
|
|
100
|
-
});
|
|
101
|
-
};
|
|
73
|
+
})), Effect.flatMap((json) => decodeChunk(json).pipe(Effect.mapError((cause) => new JsonParseError({
|
|
74
|
+
line: ev.data.slice(0, 2e3),
|
|
75
|
+
cause
|
|
76
|
+
})))));
|
|
77
|
+
const httpStatusError = httpStatusError$1("gemini");
|
|
102
78
|
const buildNativeStream = (cfg) => {
|
|
103
79
|
const baseUrl = cfg.baseUrl ?? "https://generativelanguage.googleapis.com/v1beta";
|
|
104
80
|
return (request) => Stream.unwrap(Effect.gen(function* () {
|
|
@@ -155,6 +131,7 @@ const partToTurnEvents = (part, callIdAt) => Match.value(part).pipe(Match.discri
|
|
|
155
131
|
text: p.text,
|
|
156
132
|
kind: "trace"
|
|
157
133
|
})],
|
|
134
|
+
image: (p) => [TurnEvent.ImageOutput({ image: p.source })],
|
|
158
135
|
function_call: (p) => {
|
|
159
136
|
const call_id = callIdAt(p.name, 0);
|
|
160
137
|
return [TurnEvent.ToolCallStart({
|
package/dist/Gemini.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Gemini.mjs","names":[],"sources":["../src/Gemini.ts"],"sourcesContent":["import { Context, Effect, Layer, Option, Redacted, 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 WireChunk,\n accumulatorToTurn,\n buildRequestBody,\n emptyAccumulator,\n hasUrlImageSource,\n ingestChunk,\n} from \"./codec.js\"\nimport { Match } from \"effect\"\nimport type { GoogleModel } from \"./models.js\"\n\n/**\n * Gemini's native event vocabulary. Aliased from `WireChunk` so the public\n * surface matches `@effect-uai/responses` and `@effect-uai/anthropic`. Each\n * `ProviderEvent` is a full `GenerateContentResponse` chunk (not a per-field\n * delta) - that's how Gemini's SSE works.\n */\nexport const ProviderEvent = WireChunk\nexport type ProviderEvent = WireChunk\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport type GeminiRequest = Omit<CommonRequest, \"model\"> & {\n /**\n * Narrows `CommonRequest.model` (`string`) to the typed `GoogleModel`\n * literal union for autocomplete.\n */\n readonly model: GoogleModel\n /**\n * Gemini 2.5 thinking budget. `0` disables thinking entirely (lowest\n * latency); higher values let the model think longer. Forwarded as\n * `generationConfig.thinkingConfig.thinkingBudget`.\n */\n readonly thinkingBudget?: number\n}\n\nexport type GeminiService = {\n /**\n * Stream the provider's native event vocabulary (post-SSE-decode). For\n * Gemini, each event is a full `GenerateContentResponse` chunk. Use this\n * when you need access to fields the canonical view doesn't surface\n * (`thoughtSignature`, granular `safetyRatings`, etc.). For\n * provider-portable code, use `streamTurn`.\n */\n readonly streamNative: (request: GeminiRequest) => Stream.Stream<ProviderEvent, AiError.AiError>\n /**\n * Stream canonical `TurnEvent`s. Implemented as\n * `streamNative |> toCanonical`.\n */\n readonly streamTurn: (request: GeminiRequest) => Stream.Stream<TurnEvent, AiError.AiError>\n /**\n * 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 buildGenerationConfig = (request: GeminiRequest): Option.Option<GenerationConfig> => {\n const cfg: GenerationConfig = {\n ...(request.temperature !== undefined && { temperature: request.temperature }),\n ...(request.maxOutputTokens !== undefined && { maxOutputTokens: request.maxOutputTokens }),\n ...(request.topP !== undefined && { topP: request.topP }),\n ...(request.thinkingBudget !== undefined && {\n thinkingConfig: { thinkingBudget: request.thinkingBudget },\n }),\n ...(request.structured !== undefined && {\n responseMimeType: \"application/json\",\n responseJsonSchema: request.structured.schema[\"~standard\"].jsonSchema.input({\n target: \"draft-2020-12\",\n }),\n }),\n }\n return Object.keys(cfg).length === 0 ? Option.none() : Option.some(cfg)\n}\n\n// ---------------------------------------------------------------------------\n// SSE event → wire chunk\n// ---------------------------------------------------------------------------\n\nconst decodeChunk = Schema.decodeUnknownEffect(WireChunk)\n\n/**\n * Parse one SSE event's `data` payload into a `WireChunk`. Unlike the other\n * providers, Gemini's wire vocabulary is non-discriminated (a single\n * permissive struct, all fields optional) - there's no natural shape for\n * an `_unknown` variant, so JSON parse / schema decode failures flow\n * through as transport errors rather than being silently dropped.\n */\nconst parseJsonUnknown = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown))\n\nconst sseEventToChunk = (ev: SSE.Event) =>\n parseJsonUnknown(ev.data).pipe(\n Effect.mapError((cause) => new JsonParseError({ line: ev.data, cause })),\n Effect.flatMap(decodeChunk),\n )\n\n// ---------------------------------------------------------------------------\n// Service implementation\n// ---------------------------------------------------------------------------\n\nconst httpStatusError = (status: number, body: string): AiError.AiError => {\n const provider = \"gemini\"\n const raw = body\n if (status === 429) return new AiError.RateLimited({ provider, raw })\n if (status === 408 || status === 504) return new AiError.Timeout({ provider, raw })\n if (status === 401) return new AiError.AuthFailed({ provider, subtype: \"auth\", raw })\n if (status === 403) return new AiError.AuthFailed({ provider, subtype: \"permission\", raw })\n if (status === 402) return new AiError.AuthFailed({ provider, subtype: \"billing\", raw })\n if (status === 413) return new AiError.ContextLengthExceeded({ provider, raw })\n if (status >= 500) return new AiError.Unavailable({ provider, status, raw })\n return new AiError.InvalidRequest({ provider, raw })\n}\n\nconst buildNativeStream = (cfg: Config) => {\n const baseUrl = cfg.baseUrl ?? \"https://generativelanguage.googleapis.com/v1beta\"\n return (\n request: GeminiRequest,\n ): Stream.Stream<ProviderEvent, AiError.AiError, HttpClient.HttpClient> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n 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 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/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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,MAAa,gBAAgB;;;;;;;;AAoC7B,MAAM,eAAe,UAA2B,MAAM,SAAS,QAAQ;;;;;;AAsCvE,IAAa,SAAb,cAA4B,QAAQ,QAA+B,CAAC,CAClE,8CACF,CAAC,CAAC,CAAC;AAWH,MAAM,iBAAkD,CAAC,QAAQ,OAAO;AAExE,MAAM,yBAAyB,YAA4D;CACzF,MAAM,aACJ,QAAQ,uBAAuB,YAAY,QAAQ,KAAK,IAAI,iBAAiB,KAAA;CAC/E,MAAM,MAAwB;EAC5B,GAAI,QAAQ,gBAAgB,KAAA,KAAa,EAAE,aAAa,QAAQ,YAAY;EAC5E,GAAI,QAAQ,oBAAoB,KAAA,KAAa,EAAE,iBAAiB,QAAQ,gBAAgB;EACxF,GAAI,QAAQ,SAAS,KAAA,KAAa,EAAE,MAAM,QAAQ,KAAK;EACvD,GAAI,QAAQ,mBAAmB,KAAA,KAAa,EAC1C,gBAAgB,EAAE,gBAAgB,QAAQ,eAAe,EAC3D;EACA,GAAI,eAAe,KAAA,KAAa,EAAE,oBAAoB,WAAW;EACjE,GAAI,QAAQ,gBAAgB,KAAA,KAAa,EAAE,aAAa,QAAQ,YAAY;EAC5E,GAAI,QAAQ,eAAe,KAAA,KAAa;GACtC,kBAAkB;GAClB,oBAAoB,QAAQ,WAAW,OAAO,YAAY,CAAC,WAAW,MAAM,EAC1E,QAAQ,gBACV,CAAC;EACH;CACF;CACA,OAAO,OAAO,KAAK,GAAG,CAAC,CAAC,WAAW,IAAI,OAAO,KAAK,IAAI,OAAO,KAAK,GAAG;AACxE;AAMA,MAAM,cAAc,OAAO,oBAAoB,SAAS;;;;;;;;AASxD,MAAM,mBAAmB,OAAO,oBAAoB,OAAO,eAAe,OAAO,OAAO,CAAC;AAEzF,MAAM,mBAAmB,OACvB,iBAAiB,GAAG,IAAI,CAAC,CAAC,KACxB,OAAO,UAAU,UAAU,IAAI,eAAe;CAAE,MAAM,GAAG;CAAM;AAAM,CAAC,CAAC,GACvE,OAAO,SAAS,SAId,YAAY,IAAI,CAAC,CAAC,KAChB,OAAO,UAAU,UAAU,IAAI,eAAe;CAAE,MAAM,GAAG,KAAK,MAAM,GAAG,GAAI;CAAG;AAAM,CAAC,CAAC,CACxF,CACF,CACF;AAMF,MAAM,kBAAkBA,kBAAsB,QAAQ;AAEtD,MAAM,qBAAqB,QAAgB;CACzC,MAAM,UAAU,IAAI,WAAW;CAC/B,QACE,YAEA,OAAO,OACL,OAAO,IAAI,aAAa;EACtB,MAAM,SAAS,OAAO,WAAW;EACjC,IAAI,kBAAkB,QAAQ,OAAO,GACnC,OAAO,OAAO,IAAI,QAAQ,YAAY;GACpC,UAAU;GACV,YAAY;GACZ,QACE;EACJ,CAAC;EAEH,MAAM,MAAM,GAAG,QAAQ,UAAU,QAAQ,MAAM;EAC/C,MAAM,mBAAmB,sBAAsB,OAAO;EACtD,MAAM,sBAAsB,OAAO,OAAO,MACxC,oBAAoB,gBAAgB,QAAQ,KAAK,CAAC,GAClD;GACE,YAAY,MACV,OAAO,KACL,IAAI,QAAQ,YAAY;IACtB,UAAU;IACV,YAAY,EAAE;IACd,QAAQ,EAAE;GACZ,CAAC,CACH;GACF,YAAY,YAAY,OAAO,QAAQ,OAAO;EAChD,CACF;EACA,MAAM,OAAO,iBACX,QAAQ,SACR,kBACA,cAAc,QAAQ,KAAK,GAC3B,qBACA,QAAQ,UACV;EACA,MAAM,cAAc,kBAAkB,KAAK,GAAG,CAAC,CAAC,KAC9C,kBAAkB,UAAU,kBAAkB,SAAS,MAAM,IAAI,MAAM,CAAC,GACxE,kBAAkB,eAAe,IAAI,GACrC,kBAAkB,OAAO,mBAAmB,CAC9C;EACA,MAAM,WAAW,OAAO,OACrB,QAAQ,WAAW,CAAC,CACpB,KACC,OAAO,UACJ,UACC,IAAI,QAAQ,YAAY;GAAE,UAAU;GAAU,KAAK;EAAM,CAAC,CAC9D,CACF;EACF,IAAI,SAAS,UAAU,KAAK;GAC1B,MAAM,OAAO,OAAO,SAAS,KAAK,KAAK,OAAO,oBAAoB,EAAE,CAAC;GACrE,OAAO,OAAO,KAAK,gBAAgB,SAAS,QAAQ,IAAI,CAAC;EAC3D;EAEA,OAAO,SAAS,OAAO,KACrB,OAAO,UACJ,UAA2B,IAAI,QAAQ,YAAY;GAAE,UAAU;GAAU,KAAK;EAAM,CAAC,CACxF,GACA,IAAI,WACJ,OAAO,WAAW,OAChB,gBAAgB,EAAE,CAAC,CAAC,KAClB,OAAO,UACJ,UACC,IAAI,QAAQ,YAAY;GAAE,UAAU;GAAU,KAAK;EAAM,CAAC,CAC9D,CACF,CACF,CACF;CACF,CAAC,CACH;AACJ;;;;;;;;;;;;;AAcA,MAAM,oBACJ,MACA,aAEA,MAAM,MAAM,IAAI,CAAC,CAAC,KAChB,MAAM,yBAAyB,MAAM,CAAC,CAAC;CACrC,OAAO,MAAgC,CAAC,UAAU,UAAU,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;CAC7E,YAAY,MAAgC,CAC1C,UAAU,eAAe;EAAE,MAAM,EAAE;EAAM,MAAM;CAAQ,CAAC,CAC1D;CAGA,QAAQ,MAAgC,CAAC,UAAU,YAAY,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;CACnF,gBAAgB,MAAgC;EAC9C,MAAM,UAAU,SAAS,EAAE,MAAM,CAAC;EAClC,OAAO,CACL,UAAU,cAAc;GAAE;GAAS,MAAM,EAAE;EAAK,CAAC,GACjD,UAAU,kBAAkB;GAAE;GAAS,OAAO,KAAK,UAAU,EAAE,QAAQ,CAAC,CAAC;EAAE,CAAC,CAC9E;CACF;AACF,CAAC,CACH;AAEF,MAAa,eACX,MAEA,EAAE,KACA,OAAO,eACC,mBACL,KAAK,UAAU;CACd,MAAM,SAAS,YAAY,KAAK,KAAK;CAIrC,MAAM,WAAW,OAAO,MAAM,QAAQ,MAAM,EAAE,SAAS,eAAe;CACtE,MAAM,YAAY,OAAO,YAAY,cAAc,MACjD,OAAO,YAAY,cAAc,SAAS,SAAS,MACrD;CACA,IAAI,gBAAgB;CACpB,MAAM,aAAuC,OAAO,MAAM,SAAS,MACjE,iBAAiB,SAAS;EACxB,MAAM,KAAK,UAAU,cAAc,EAAE,UAAU,EAAE;EACjD,iBAAiB;EACjB,OAAO;CACT,CAAC,CACH;CACA,MAAM,SAAmC,OAAO,WAC5C,CAAC,GAAG,YAAY,UAAU,aAAa,EAAE,MAAM,kBAAkB,OAAO,WAAW,EAAE,CAAC,CAAC,IACvF;CACJ,OAAO,CAAC,OAAO,aAAa,MAAM;AACpC,CACF,CACF;;;;AASF,MAAa,QAAQ,QACnB,OAAO,IAAI,WAAW,aAAa,WAAW;CAC5C,MAAM,gBAA+C,YACnD,kBAAkB,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,OAAO,eAAe,WAAW,YAAY,MAAM,CAAC;CAC3F,MAAM,cAA2C,YAAY,YAAY,aAAa,OAAO,CAAC;CAC9F,OAAO;EACL;EACA;EACA,MAAM,eAAe,UAAU;EAC/B;CACF;AACF,CAAC;;;;;;;;AASH,MAAa,SACX,QACsE;CACtE,MAAM,QAAQ,MAAM,OAAO,QAAQ,KAAK,GAAG,CAAC;CAC5C,MAAM,UAAU,MAAM,OACpB,eACA,OAAO,IAAI,KAAK,GAAG,IAAI,OAA6B;EAClD,aAAa,YAAY,EAAE,WAAW,OAAwB;EAC9D,OAAO,YAAY,EAAE,KAAK,OAAwB;CACpD,EAAE,CACJ;CACA,OAAO,MAAM,MAAM,OAAO,OAAO;AACnC"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { GeminiImageModel } from "./models.mjs";
|
|
2
|
+
import { Context, Effect, Layer, Redacted, Schema } from "effect";
|
|
3
|
+
import { HttpClient } from "effect/unstable/http";
|
|
4
|
+
import * as AiError from "@effect-uai/core/AiError";
|
|
5
|
+
import { ImageBase64Source, ImageBytesSource, ImageSource } from "@effect-uai/core/Image";
|
|
6
|
+
import { CommonImageEditRequest, CommonImageGenerateRequest, ImageGenerator, ImageGeneratorService, ImageResponse } from "@effect-uai/core/ImageGenerator";
|
|
7
|
+
declare namespace GeminiImageGenerator_d_exports {
|
|
8
|
+
export { Config, GeminiImageEditRequest, GeminiImageGenerateRequest, GeminiImageGenerator, GeminiImageGeneratorService, GeminiImageRef, WireBody, buildBody, decodeWire, imageSizeOf, layer, make, toResponse };
|
|
9
|
+
}
|
|
10
|
+
/** Wire knobs `generateContent` accepts alongside the portable request. */
|
|
11
|
+
type GeminiImageKnobs = {
|
|
12
|
+
/**
|
|
13
|
+
* Sent as `imageConfig.imageSize`. Takes precedence over `resolution`,
|
|
14
|
+
* which is then warn-dropped. `"512"` is Flash-only.
|
|
15
|
+
*/
|
|
16
|
+
readonly imageSize?: "512" | "1K" | "2K" | "4K";
|
|
17
|
+
/** Thinking is always on for image models; this only sets how long. */
|
|
18
|
+
readonly thinkingLevel?: "minimal" | "low" | "medium" | "high";
|
|
19
|
+
/** Ground the image in web results. Flash and Pro only. */
|
|
20
|
+
readonly googleSearch?: boolean;
|
|
21
|
+
};
|
|
22
|
+
type GeminiImageGenerateRequest = Omit<CommonImageGenerateRequest, "model"> & {
|
|
23
|
+
readonly model: GeminiImageModel;
|
|
24
|
+
} & GeminiImageKnobs;
|
|
25
|
+
/**
|
|
26
|
+
* Reference images for an edit. Narrowed off `ImageSource`: references
|
|
27
|
+
* ride in the request body as base64 `inlineData`, so a `url` variant has
|
|
28
|
+
* no wire representation short of pre-uploading through the Files API.
|
|
29
|
+
* A compile-time error rather than a 400; fetch it yourself and pass the
|
|
30
|
+
* bytes.
|
|
31
|
+
*/
|
|
32
|
+
type GeminiImageRef = ImageBase64Source | ImageBytesSource;
|
|
33
|
+
type GeminiImageEditRequest = Omit<CommonImageEditRequest, "model" | "images"> & {
|
|
34
|
+
readonly model: GeminiImageModel;
|
|
35
|
+
/** 14 total, split between object / character / style refs per model. */
|
|
36
|
+
readonly images: ReadonlyArray<GeminiImageRef>;
|
|
37
|
+
} & GeminiImageKnobs;
|
|
38
|
+
type GeminiImageGeneratorService = {
|
|
39
|
+
readonly generate: (request: GeminiImageGenerateRequest) => Effect.Effect<ImageResponse, AiError.AiError>;
|
|
40
|
+
readonly edit: (request: GeminiImageEditRequest) => Effect.Effect<ImageResponse, AiError.AiError>;
|
|
41
|
+
/** Both always `Unsupported`: `ImageStreaming` is not registered by {@link layer}. */
|
|
42
|
+
readonly streamGeneration: ImageGeneratorService["streamGeneration"];
|
|
43
|
+
readonly streamEdit: ImageGeneratorService["streamEdit"];
|
|
44
|
+
};
|
|
45
|
+
declare const GeminiImageGenerator_base: Context.ServiceClass<GeminiImageGenerator, "@betalyra/effect-uai/providers/google/GeminiImageGenerator", GeminiImageGeneratorService>;
|
|
46
|
+
/**
|
|
47
|
+
* Provider-typed service tag. Yield this for `imageSize`, `thinkingLevel`
|
|
48
|
+
* and search grounding; yield the generic `ImageGenerator` tag for
|
|
49
|
+
* provider-portable code. Both are registered by {@link layer}.
|
|
50
|
+
*/
|
|
51
|
+
declare class GeminiImageGenerator extends GeminiImageGenerator_base {}
|
|
52
|
+
type Config = {
|
|
53
|
+
readonly apiKey: Redacted.Redacted;
|
|
54
|
+
readonly baseUrl?: string;
|
|
55
|
+
};
|
|
56
|
+
type ImageConfig = {
|
|
57
|
+
readonly aspectRatio?: string;
|
|
58
|
+
readonly imageSize?: string;
|
|
59
|
+
};
|
|
60
|
+
type RequestPart = {
|
|
61
|
+
readonly text: string;
|
|
62
|
+
} | {
|
|
63
|
+
readonly inlineData: {
|
|
64
|
+
readonly mimeType: string;
|
|
65
|
+
readonly data: string;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
type WireBody = {
|
|
69
|
+
readonly contents: ReadonlyArray<{
|
|
70
|
+
readonly role: "user";
|
|
71
|
+
readonly parts: ReadonlyArray<RequestPart>;
|
|
72
|
+
}>;
|
|
73
|
+
readonly generationConfig: {
|
|
74
|
+
readonly responseModalities: ReadonlyArray<"TEXT" | "IMAGE">;
|
|
75
|
+
readonly imageConfig?: ImageConfig;
|
|
76
|
+
readonly thinkingConfig?: {
|
|
77
|
+
readonly thinkingLevel: string;
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
readonly tools?: ReadonlyArray<{
|
|
81
|
+
readonly googleSearch: {};
|
|
82
|
+
}>;
|
|
83
|
+
};
|
|
84
|
+
/** The typed `imageSize` wins over the portable tier, which is then dropped. */
|
|
85
|
+
declare const imageSizeOf: (request: Pick<GeminiImageGenerateRequest, "imageSize" | "resolution">) => Effect.Effect<string | undefined>;
|
|
86
|
+
declare const buildBody: (request: GeminiImageGenerateRequest, images: ReadonlyArray<ImageSource>) => Effect.Effect<WireBody, AiError.AiError>;
|
|
87
|
+
declare const Wire: Schema.Struct<{
|
|
88
|
+
readonly candidates: Schema.optional<Schema.$Array<Schema.Struct<{
|
|
89
|
+
readonly content: Schema.optional<Schema.Struct<{
|
|
90
|
+
readonly parts: Schema.optional<Schema.$Array<Schema.Struct<{
|
|
91
|
+
readonly text: Schema.optional<Schema.String>;
|
|
92
|
+
readonly inlineData: Schema.optional<Schema.Struct<{
|
|
93
|
+
readonly mimeType: Schema.optional<Schema.String>;
|
|
94
|
+
readonly data: Schema.String;
|
|
95
|
+
}>>;
|
|
96
|
+
/** Interim images the model drew while thinking. Not the answer. */
|
|
97
|
+
readonly thought: Schema.optional<Schema.Boolean>;
|
|
98
|
+
}>>>;
|
|
99
|
+
}>>;
|
|
100
|
+
readonly finishReason: Schema.optional<Schema.String>;
|
|
101
|
+
}>>>;
|
|
102
|
+
readonly promptFeedback: Schema.optional<Schema.Struct<{
|
|
103
|
+
readonly blockReason: Schema.optional<Schema.String>;
|
|
104
|
+
}>>;
|
|
105
|
+
readonly usageMetadata: Schema.optional<Schema.Struct<{
|
|
106
|
+
readonly promptTokenCount: Schema.optional<Schema.Number>;
|
|
107
|
+
readonly candidatesTokenCount: Schema.optional<Schema.Number>;
|
|
108
|
+
readonly totalTokenCount: Schema.optional<Schema.Number>;
|
|
109
|
+
}>>;
|
|
110
|
+
}>;
|
|
111
|
+
type Wire = typeof Wire.Type;
|
|
112
|
+
declare const decodeWire: (input: unknown, options?: import("effect/SchemaAST").ParseOptions) => Effect.Effect<{
|
|
113
|
+
readonly candidates?: readonly {
|
|
114
|
+
readonly content?: {
|
|
115
|
+
readonly parts?: readonly {
|
|
116
|
+
readonly text?: string | undefined;
|
|
117
|
+
readonly inlineData?: {
|
|
118
|
+
readonly mimeType?: string | undefined;
|
|
119
|
+
readonly data: string;
|
|
120
|
+
} | undefined;
|
|
121
|
+
readonly thought?: boolean | undefined;
|
|
122
|
+
}[] | undefined;
|
|
123
|
+
} | undefined;
|
|
124
|
+
readonly finishReason?: string | undefined;
|
|
125
|
+
}[] | undefined;
|
|
126
|
+
readonly promptFeedback?: {
|
|
127
|
+
readonly blockReason?: string | undefined;
|
|
128
|
+
} | undefined;
|
|
129
|
+
readonly usageMetadata?: {
|
|
130
|
+
readonly promptTokenCount?: number | undefined;
|
|
131
|
+
readonly candidatesTokenCount?: number | undefined;
|
|
132
|
+
readonly totalTokenCount?: number | undefined;
|
|
133
|
+
} | undefined;
|
|
134
|
+
}, Schema.SchemaError, never>;
|
|
135
|
+
declare const toResponse: (wire: Wire) => Effect.Effect<ImageResponse, AiError.AiError>;
|
|
136
|
+
declare const make: (cfg: Config) => Effect.Effect<GeminiImageGeneratorService, never, HttpClient.HttpClient>;
|
|
137
|
+
/**
|
|
138
|
+
* Layer registering the provider-typed tag and the generic
|
|
139
|
+
* `ImageGenerator`. A `CommonImageGenerateRequest` is structurally a
|
|
140
|
+
* `GeminiImageGenerateRequest` with no vendor knobs set, so the generic
|
|
141
|
+
* registration forwards directly; a URL reference slipping through the
|
|
142
|
+
* generic `edit` fails `InvalidRequest` at encode time.
|
|
143
|
+
*
|
|
144
|
+
* Does NOT register `ImageStreaming`.
|
|
145
|
+
*/
|
|
146
|
+
declare const layer: (cfg: Config) => Layer.Layer<GeminiImageGenerator | ImageGenerator, never, HttpClient.HttpClient>;
|
|
147
|
+
//#endregion
|
|
148
|
+
export { Config, GeminiImageEditRequest, GeminiImageGenerateRequest, GeminiImageGenerator, GeminiImageGeneratorService, GeminiImageRef, WireBody, buildBody, decodeWire, imageSizeOf, layer, make, GeminiImageGenerator_d_exports as t, toResponse };
|
|
149
|
+
//# sourceMappingURL=GeminiImageGenerator.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GeminiImageGenerator.d.mts","names":[],"sources":["../src/GeminiImageGenerator.ts"],"mappings":";;;;;;;;;;KA8BK;;;;;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"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { n as __exportAll } from "./rolldown-runtime-EAsCxrDo.mjs";
|
|
2
|
+
import { httpStatusError, transportFailure } from "./codec.mjs";
|
|
3
|
+
import { Context, Effect, Encoding, Layer, Match, Redacted, Schema, Stream } from "effect";
|
|
4
|
+
import { HttpClient, HttpClientRequest } from "effect/unstable/http";
|
|
5
|
+
import * as AiError from "@effect-uai/core/AiError";
|
|
6
|
+
import { imageBase64 } from "@effect-uai/core/Image";
|
|
7
|
+
import * as Capabilities from "@effect-uai/core/Capabilities";
|
|
8
|
+
import { ImageGenerator } from "@effect-uai/core/ImageGenerator";
|
|
9
|
+
//#region src/GeminiImageGenerator.ts
|
|
10
|
+
var GeminiImageGenerator_exports = /* @__PURE__ */ __exportAll({
|
|
11
|
+
GeminiImageGenerator: () => GeminiImageGenerator,
|
|
12
|
+
buildBody: () => buildBody,
|
|
13
|
+
decodeWire: () => decodeWire,
|
|
14
|
+
imageSizeOf: () => imageSizeOf,
|
|
15
|
+
layer: () => layer,
|
|
16
|
+
make: () => make,
|
|
17
|
+
toResponse: () => toResponse
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* Provider-typed service tag. Yield this for `imageSize`, `thinkingLevel`
|
|
21
|
+
* and search grounding; yield the generic `ImageGenerator` tag for
|
|
22
|
+
* provider-portable code. Both are registered by {@link layer}.
|
|
23
|
+
*/
|
|
24
|
+
var GeminiImageGenerator = class extends Context.Service()("@betalyra/effect-uai/providers/google/GeminiImageGenerator") {};
|
|
25
|
+
const PROVIDER = "gemini";
|
|
26
|
+
const urlRefNotSupported = new AiError.InvalidRequest({
|
|
27
|
+
provider: PROVIDER,
|
|
28
|
+
param: "images",
|
|
29
|
+
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 }`."
|
|
30
|
+
});
|
|
31
|
+
const referencePart = Match.type().pipe(Match.tag("base64", (i) => Effect.succeed({ inlineData: {
|
|
32
|
+
mimeType: i.mimeType,
|
|
33
|
+
data: i.base64
|
|
34
|
+
} })), Match.tag("bytes", (i) => Effect.succeed({ inlineData: {
|
|
35
|
+
mimeType: i.mimeType,
|
|
36
|
+
data: Encoding.encodeBase64(i.bytes)
|
|
37
|
+
} })), Match.tag("url", () => Effect.fail(urlRefNotSupported)), Match.exhaustive);
|
|
38
|
+
/**
|
|
39
|
+
* More than one image per call is bucket 1: `candidateCount` is a generic
|
|
40
|
+
* `generationConfig` field whose behaviour on image models is undocumented,
|
|
41
|
+
* and silently returning one image where several were asked for is a
|
|
42
|
+
* different result, not a smaller one.
|
|
43
|
+
*/
|
|
44
|
+
const rejectMultiple = (n) => n === void 0 || n <= 1 ? Effect.void : Effect.fail(new AiError.Unsupported({
|
|
45
|
+
provider: PROVIDER,
|
|
46
|
+
capability: "n",
|
|
47
|
+
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.`
|
|
48
|
+
}));
|
|
49
|
+
/** The typed `imageSize` wins over the portable tier, which is then dropped. */
|
|
50
|
+
const imageSizeOf = (request) => request.imageSize === void 0 ? Effect.succeed(request.resolution) : Effect.as(Capabilities.warnDroppedWhen(request.resolution, {
|
|
51
|
+
provider: PROVIDER,
|
|
52
|
+
capability: "resolution",
|
|
53
|
+
field: "resolution",
|
|
54
|
+
reason: `\`imageSize: "${request.imageSize}"\` sets the tier directly.`
|
|
55
|
+
}), request.imageSize);
|
|
56
|
+
const buildBody = (request, images) => Effect.gen(function* () {
|
|
57
|
+
yield* rejectMultiple(request.n);
|
|
58
|
+
const imageSize = yield* imageSizeOf(request);
|
|
59
|
+
const refs = yield* Effect.forEach(images, referencePart);
|
|
60
|
+
const imageConfig = {
|
|
61
|
+
...request.aspectRatio !== void 0 && { aspectRatio: request.aspectRatio },
|
|
62
|
+
...imageSize !== void 0 && { imageSize }
|
|
63
|
+
};
|
|
64
|
+
return {
|
|
65
|
+
contents: [{
|
|
66
|
+
role: "user",
|
|
67
|
+
parts: [{ text: request.prompt }, ...refs]
|
|
68
|
+
}],
|
|
69
|
+
generationConfig: {
|
|
70
|
+
responseModalities: ["TEXT", "IMAGE"],
|
|
71
|
+
...Object.keys(imageConfig).length > 0 && { imageConfig },
|
|
72
|
+
...request.thinkingLevel !== void 0 && { thinkingConfig: { thinkingLevel: request.thinkingLevel.toUpperCase() } }
|
|
73
|
+
},
|
|
74
|
+
...request.googleSearch === true && { tools: [{ googleSearch: {} }] }
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
const InlineData = Schema.Struct({
|
|
78
|
+
mimeType: Schema.optional(Schema.String),
|
|
79
|
+
data: Schema.String
|
|
80
|
+
});
|
|
81
|
+
const Part = Schema.Struct({
|
|
82
|
+
text: Schema.optional(Schema.String),
|
|
83
|
+
inlineData: Schema.optional(InlineData),
|
|
84
|
+
/** Interim images the model drew while thinking. Not the answer. */
|
|
85
|
+
thought: Schema.optional(Schema.Boolean)
|
|
86
|
+
});
|
|
87
|
+
const Candidate = Schema.Struct({
|
|
88
|
+
content: Schema.optional(Schema.Struct({ parts: Schema.optional(Schema.Array(Part)) })),
|
|
89
|
+
finishReason: Schema.optional(Schema.String)
|
|
90
|
+
});
|
|
91
|
+
const UsageMetadata = Schema.Struct({
|
|
92
|
+
promptTokenCount: Schema.optional(Schema.Number),
|
|
93
|
+
candidatesTokenCount: Schema.optional(Schema.Number),
|
|
94
|
+
totalTokenCount: Schema.optional(Schema.Number)
|
|
95
|
+
});
|
|
96
|
+
const Wire = Schema.Struct({
|
|
97
|
+
candidates: Schema.optional(Schema.Array(Candidate)),
|
|
98
|
+
promptFeedback: Schema.optional(Schema.Struct({ blockReason: Schema.optional(Schema.String) })),
|
|
99
|
+
usageMetadata: Schema.optional(UsageMetadata)
|
|
100
|
+
});
|
|
101
|
+
const decodeWire = Schema.decodeUnknownEffect(Wire);
|
|
102
|
+
/**
|
|
103
|
+
* Finish reasons that are a content decision rather than a failure. The
|
|
104
|
+
* generic safety codes appear when the prompt trips a filter, the
|
|
105
|
+
* `IMAGE_`-prefixed ones when the drawn image does. `NO_IMAGE` and
|
|
106
|
+
* `IMAGE_OTHER` are not here: they say the model produced nothing, not
|
|
107
|
+
* that it refused.
|
|
108
|
+
*/
|
|
109
|
+
const FILTERED_FINISH_REASONS = /* @__PURE__ */ new Set([
|
|
110
|
+
"SAFETY",
|
|
111
|
+
"RECITATION",
|
|
112
|
+
"BLOCKLIST",
|
|
113
|
+
"PROHIBITED_CONTENT",
|
|
114
|
+
"SPII",
|
|
115
|
+
"IMAGE_SAFETY",
|
|
116
|
+
"IMAGE_PROHIBITED_CONTENT",
|
|
117
|
+
"IMAGE_RECITATION"
|
|
118
|
+
]);
|
|
119
|
+
const usageOf = (usage) => ({
|
|
120
|
+
...usage?.promptTokenCount !== void 0 && { inputTokens: usage.promptTokenCount },
|
|
121
|
+
...usage?.candidatesTokenCount !== void 0 && { outputTokens: usage.candidatesTokenCount },
|
|
122
|
+
...usage?.totalTokenCount !== void 0 && { totalTokens: usage.totalTokenCount }
|
|
123
|
+
});
|
|
124
|
+
/** Every Gemini image carries SynthID; Lite adds a C2PA manifest on top. */
|
|
125
|
+
const generatedImage = (inline) => ({
|
|
126
|
+
image: imageBase64(inline.data, inline.mimeType ?? "image/png"),
|
|
127
|
+
watermark: "synthid"
|
|
128
|
+
});
|
|
129
|
+
const toResponse = (wire) => {
|
|
130
|
+
const blockReason = wire.promptFeedback?.blockReason;
|
|
131
|
+
const finishReason = wire.candidates?.[0]?.finishReason;
|
|
132
|
+
const filtered = blockReason ?? (finishReason !== void 0 && FILTERED_FINISH_REASONS.has(finishReason) ? finishReason : void 0);
|
|
133
|
+
if (filtered !== void 0) return Effect.fail(new AiError.ContentFiltered({
|
|
134
|
+
provider: PROVIDER,
|
|
135
|
+
reason: filtered,
|
|
136
|
+
raw: wire
|
|
137
|
+
}));
|
|
138
|
+
const images = (wire.candidates ?? []).flatMap((candidate) => candidate.content?.parts ?? []).flatMap((part) => part.thought === true || part.inlineData === void 0 ? [] : [generatedImage(part.inlineData)]);
|
|
139
|
+
return images.length === 0 ? Effect.fail(new AiError.GenerationFailed({
|
|
140
|
+
provider: PROVIDER,
|
|
141
|
+
...finishReason !== void 0 && { code: finishReason },
|
|
142
|
+
message: "Gemini returned no image part.",
|
|
143
|
+
raw: wire
|
|
144
|
+
})) : Effect.succeed({
|
|
145
|
+
images,
|
|
146
|
+
usage: usageOf(wire.usageMetadata)
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
const host = (cfg) => cfg.baseUrl ?? "https://generativelanguage.googleapis.com/v1beta";
|
|
150
|
+
const requestImages = (cfg, request, images) => Effect.gen(function* () {
|
|
151
|
+
const client = yield* HttpClient.HttpClient;
|
|
152
|
+
const body = yield* buildBody(request, images);
|
|
153
|
+
const response = yield* client.execute(HttpClientRequest.post(`${host(cfg)}/models/${request.model}:generateContent`).pipe(HttpClientRequest.setHeader("x-goog-api-key", Redacted.value(cfg.apiKey)), HttpClientRequest.bodyJsonUnsafe(body))).pipe(Effect.mapError(transportFailure(PROVIDER)));
|
|
154
|
+
if (response.status >= 400) {
|
|
155
|
+
const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""));
|
|
156
|
+
return yield* httpStatusError(PROVIDER)(response.status, text);
|
|
157
|
+
}
|
|
158
|
+
const json = yield* response.json.pipe(Effect.mapError(transportFailure(PROVIDER)));
|
|
159
|
+
const wire = yield* decodeWire(json).pipe(Effect.mapError(transportFailure(PROVIDER)));
|
|
160
|
+
return yield* toResponse(wire);
|
|
161
|
+
});
|
|
162
|
+
/**
|
|
163
|
+
* `generateContent` has no partial-image wire, so this never succeeds and
|
|
164
|
+
* {@link layer} does not register `ImageStreaming`. Code calling the
|
|
165
|
+
* top-level `streamGeneration` helper against this Layer alone fails to
|
|
166
|
+
* typecheck, which is the intended UX; this branch covers the service
|
|
167
|
+
* being used directly.
|
|
168
|
+
*/
|
|
169
|
+
const streamUnsupported = (capability) => Stream.fail(new AiError.Unsupported({
|
|
170
|
+
provider: PROVIDER,
|
|
171
|
+
capability,
|
|
172
|
+
reason: "Gemini image models have no partial-image wire; the image arrives whole. Use `generate` or `edit` and render when it resolves."
|
|
173
|
+
}));
|
|
174
|
+
const make = (cfg) => Effect.map(HttpClient.HttpClient, (client) => ({
|
|
175
|
+
generate: (request) => requestImages(cfg, request, []).pipe(Effect.provideService(HttpClient.HttpClient, client)),
|
|
176
|
+
edit: (request) => requestImages(cfg, request, request.images).pipe(Effect.provideService(HttpClient.HttpClient, client)),
|
|
177
|
+
streamGeneration: () => streamUnsupported("streamGeneration"),
|
|
178
|
+
streamEdit: () => streamUnsupported("streamEdit")
|
|
179
|
+
}));
|
|
180
|
+
/**
|
|
181
|
+
* Layer registering the provider-typed tag and the generic
|
|
182
|
+
* `ImageGenerator`. A `CommonImageGenerateRequest` is structurally a
|
|
183
|
+
* `GeminiImageGenerateRequest` with no vendor knobs set, so the generic
|
|
184
|
+
* registration forwards directly; a URL reference slipping through the
|
|
185
|
+
* generic `edit` fails `InvalidRequest` at encode time.
|
|
186
|
+
*
|
|
187
|
+
* Does NOT register `ImageStreaming`.
|
|
188
|
+
*/
|
|
189
|
+
const layer = (cfg) => Layer.merge(Layer.effect(GeminiImageGenerator, make(cfg)), Layer.effect(ImageGenerator, Effect.map(make(cfg), (s) => ({
|
|
190
|
+
generate: (request) => s.generate(request),
|
|
191
|
+
edit: (request) => s.edit(request),
|
|
192
|
+
streamGeneration: (_request) => streamUnsupported("streamGeneration"),
|
|
193
|
+
streamEdit: (_request) => streamUnsupported("streamEdit")
|
|
194
|
+
}))));
|
|
195
|
+
//#endregion
|
|
196
|
+
export { GeminiImageGenerator, buildBody, decodeWire, imageSizeOf, layer, make, GeminiImageGenerator_exports as t, toResponse };
|
|
197
|
+
|
|
198
|
+
//# sourceMappingURL=GeminiImageGenerator.mjs.map
|
|
@@ -0,0 +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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LyriaGenerator.d.mts","names":[],"sources":["../src/LyriaGenerator.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"LyriaGenerator.d.mts","names":[],"sources":["../src/LyriaGenerator.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;KAkDY,uBAAuB,KAAK;WAC7B,OAAO;;KAGN;WACD,WACP,SAAS,yBACN,OAAO,OAAO,gBAAgB,QAAQ;WAClC,mBACP,SAAS,yBACN,OAAO,OAAO,YAAY,QAAQ;WAC9B,uBAAuB,GAAG,GACjC,OAAO,OAAO,OAAO,mBAAmB,GAAG,IAC3C,SAAS,qCACN,OAAO,OAAO,kBAAkB,QAAQ,UAAU,GAAG;;;cAG/C,uBAAuB;KAIxB;WACD,QAAQ,SAAS;WACjB;;KAOC;cAEC,sBACX,WAAW,6BACR,OAAO,OAAO,uBAAuB,QAAQ;cAyBrC,iBAAiB,MAAM,0BAA0B;cAsOjD,OAAI,KACV,WACJ,OAAO,OAAO,8BAA8B,WAAW;;;;;;;;;;cAkB7C,QAAK,KACX,WACJ,MAAM,MAAM,iBAAiB,uBAAuB,WAAW"}
|