@effect-uai/google 0.12.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Gemini.d.mts +0 -2
- package/dist/Gemini.d.mts.map +1 -1
- package/dist/Gemini.mjs +1 -1
- package/dist/Gemini.mjs.map +1 -1
- package/dist/GeminiEmbedding.d.mts +2 -3
- package/dist/GeminiEmbedding.d.mts.map +1 -1
- package/dist/GeminiEmbedding.mjs +1 -1
- package/dist/GeminiEmbedding.mjs.map +1 -1
- package/dist/GeminiSynthesizer.d.mts +1 -3
- package/dist/GeminiSynthesizer.d.mts.map +1 -1
- package/dist/GeminiSynthesizer.mjs +13 -12
- package/dist/GeminiSynthesizer.mjs.map +1 -1
- package/dist/GeminiSynthesizer.test.d.mts +1 -1
- package/dist/GeminiSynthesizer.test.mjs +1 -1
- package/dist/GeminiSynthesizer.test.mjs.map +1 -1
- package/dist/GeminiTools.d.mts +0 -2
- package/dist/GeminiTools.d.mts.map +1 -1
- package/dist/GeminiTools.mjs +1 -1
- package/dist/GeminiTools.mjs.map +1 -1
- package/dist/GoogleDeepResearch.d.mts +2 -3
- package/dist/GoogleDeepResearch.d.mts.map +1 -1
- package/dist/GoogleDeepResearch.mjs +8 -5
- package/dist/GoogleDeepResearch.mjs.map +1 -1
- package/dist/GoogleDeepResearch.test.d.mts +1 -1
- package/dist/GoogleDeepResearch.test.mjs +1 -1
- package/dist/GoogleDeepResearch.test.mjs.map +1 -1
- package/dist/LyriaGenerator.d.mts +0 -2
- package/dist/LyriaGenerator.d.mts.map +1 -1
- package/dist/LyriaGenerator.mjs +9 -6
- package/dist/LyriaGenerator.mjs.map +1 -1
- package/dist/LyriaGenerator.test.d.mts +1 -1
- package/dist/LyriaGenerator.test.mjs +153 -8
- package/dist/LyriaGenerator.test.mjs.map +1 -1
- package/dist/codec.d.mts +14 -9
- package/dist/codec.d.mts.map +1 -1
- package/dist/codec.mjs +2 -2
- package/dist/codec.mjs.map +1 -1
- package/dist/geminiSpeechCodec.d.mts +0 -1
- package/dist/geminiSpeechCodec.d.mts.map +1 -1
- package/dist/geminiSpeechCodec.mjs.map +1 -1
- package/dist/geminiSpeechCodec.test.d.mts +1 -1
- package/dist/geminiSpeechCodec.test.mjs +1 -1
- package/dist/geminiSpeechCodec.test.mjs.map +1 -1
- package/dist/geminiTools.test.d.mts +1 -1
- package/dist/geminiTools.test.mjs +17 -11
- package/dist/geminiTools.test.mjs.map +1 -1
- package/dist/{magic-string.es-CaWgF-3p.mjs → magic-string.es-CUAnjKYC.mjs} +14 -13
- package/dist/{magic-string.es-CaWgF-3p.mjs.map → magic-string.es-CUAnjKYC.mjs.map} +1 -1
- package/dist/models.d.mts.map +1 -1
- package/dist/{dist-CWoAuwS9.mjs → test.DNmyFkvJ-N3QqMRwZ.mjs} +257 -381
- package/dist/test.DNmyFkvJ-N3QqMRwZ.mjs.map +1 -0
- package/package.json +5 -5
- package/src/Gemini.ts +4 -7
- package/src/GeminiEmbedding.ts +36 -48
- package/src/GeminiSynthesizer.ts +20 -27
- package/src/GeminiTools.ts +4 -6
- package/src/GoogleDeepResearch.ts +12 -21
- package/src/LyriaGenerator.ts +19 -23
- package/src/codec.ts +9 -11
- package/dist/dist-CWoAuwS9.mjs.map +0 -1
- /package/dist/{chunk-uyGKjUfl.mjs → rolldown-runtime-EAsCxrDo.mjs} +0 -0
package/dist/Gemini.d.mts
CHANGED
|
@@ -6,8 +6,6 @@ import { HttpClient } from "effect/unstable/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";
|
|
9
|
-
|
|
10
|
-
//#region src/Gemini.d.ts
|
|
11
9
|
declare namespace Gemini_d_exports {
|
|
12
10
|
export { Config, Gemini, GeminiRequest, GeminiService, ProviderEvent, codeExecutionTool, googleSearchTool, layer, make, toCanonical, urlContextTool };
|
|
13
11
|
}
|
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":";;;;;;;;;;;;;;;;;cAmCa,eAAa,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KACd,gBAAgB;KAMhB,gBAAgB,KAAK;;;;;WAKtB,OAAO;;;;;;WAMP;;KAGC;;;;;;;;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;;cA0KE,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,4 +1,4 @@
|
|
|
1
|
-
import { n as __exportAll } from "./
|
|
1
|
+
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, ingestChunk } from "./codec.mjs";
|
|
4
4
|
import { Context, Effect, Layer, Match, Option, Redacted, Result, Schema, Stream } from "effect";
|
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(\n make(cfg),\n (s): LanguageModelService => ({\n streamTurn: (request) => s.streamTurn(request as GeminiRequest),\n turn: (request) => s.turn(request as GeminiRequest),\n }),\n ),\n )\n return Layer.merge(typed, generic)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Gemini.mjs","names":[],"sources":["../src/Gemini.ts"],"sourcesContent":["import { Context, Effect, Layer, Option, Redacted, 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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"}
|
|
@@ -4,8 +4,6 @@ import { HttpClient } from "effect/unstable/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";
|
|
7
|
-
|
|
8
|
-
//#region src/GeminiEmbedding.d.ts
|
|
9
7
|
declare namespace GeminiEmbedding_d_exports {
|
|
10
8
|
export { Config, GeminiEmbedManyRequest, GeminiEmbedRequest, GeminiEmbedding, GeminiEmbeddingService, GoogleEmbeddingTask, layer, make };
|
|
11
9
|
}
|
|
@@ -17,7 +15,8 @@ declare namespace GeminiEmbedding_d_exports {
|
|
|
17
15
|
*/
|
|
18
16
|
type GoogleEmbeddingTask = "query" | "document" | "similarity" | "classification" | "clustering" | "qa" | "fact_verification" | "code_query";
|
|
19
17
|
type GeminiEmbedRequest = Omit<CommonEmbedRequest, "model" | "task" | "encoding"> & {
|
|
20
|
-
/** Narrows `CommonEmbedRequest.model` to the typed Google union. */
|
|
18
|
+
/** Narrows `CommonEmbedRequest.model` to the typed Google union. */
|
|
19
|
+
readonly model: GoogleEmbeddingModel;
|
|
21
20
|
/**
|
|
22
21
|
* Widens the cross-provider `"query" | "document"` to the full Google
|
|
23
22
|
* task enum. Optional. Honoured by `gemini-embedding-001`; ignored by
|
|
@@ -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":";;;;;;;;;;;;;;;KAgCY;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,4 +1,4 @@
|
|
|
1
|
-
import { n as __exportAll } from "./
|
|
1
|
+
import { n as __exportAll } from "./rolldown-runtime-EAsCxrDo.mjs";
|
|
2
2
|
import { Context, Effect, Encoding, Layer, Match, Redacted, Schema } from "effect";
|
|
3
3
|
import { HttpClient, HttpClientRequest } from "effect/unstable/http";
|
|
4
4
|
import * as AiError from "@effect-uai/core/AiError";
|
|
@@ -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(\n \"base64\",\n (s): Effect.Effect<WirePart, AiError.AiError> =>\n Effect.succeed({ inlineData: { mimeType: s.mimeType, data: s.base64 } }),\n ),\n Match.tag(\n \"bytes\",\n (s): Effect.Effect<WirePart, AiError.AiError> =>\n Effect.succeed({\n inlineData: { mimeType: s.mimeType, data: Encoding.encodeBase64(s.bytes) },\n }),\n ),\n Match.tag(\n \"url\",\n (): Effect.Effect<WirePart, AiError.AiError> => Effect.fail(urlRejected(param)),\n ),\n Match.exhaustive,\n )\n\nconst contentPartToPart = (\n param: string,\n part: EmbedContentPart,\n): Effect.Effect<WirePart, AiError.AiError> =>\n \"text\" in part ? Effect.succeed({ text: part.text }) : imageSourceToPart(param)(part.image)\n\nconst inputToContent = (input: EmbedInput): Effect.Effect<WireContent, AiError.AiError> => {\n if (typeof input === \"string\") return Effect.succeed({ parts: [{ text: input }] })\n if (\"text\" in input) return Effect.succeed({ parts: [{ text: input.text }] })\n if (\"image\" in input) {\n return imageSourceToPart(\"input.image\")(input.image).pipe(\n Effect.map((part) => ({ parts: [part] })),\n )\n }\n return Effect.forEach(input.content, (p) => contentPartToPart(\"input.content[].image\", p)).pipe(\n Effect.map((parts) => ({ parts })),\n )\n}\n\ntype WireSingleBody = {\n readonly content: WireContent\n readonly taskType?: string\n readonly outputDimensionality?: number\n readonly title?: string\n}\n\nconst wireOptions = (\n request: Pick<GeminiEmbedRequest, \"task\" | \"dimensions\" | \"title\">,\n): Omit<WireSingleBody, \"content\"> => ({\n ...(request.task !== undefined && { taskType: taskToWire[request.task] }),\n ...(request.dimensions !== undefined && { outputDimensionality: request.dimensions }),\n ...(request.title !== undefined && { title: request.title }),\n})\n\nconst buildSingleBody = (\n request: GeminiEmbedRequest,\n): Effect.Effect<WireSingleBody, AiError.AiError> =>\n inputToContent(request.input).pipe(\n Effect.map((content) => ({ content, ...wireOptions(request) })),\n )\n\ntype WireBatchEntry = WireSingleBody & {\n readonly model: string\n}\ntype WireBatchBody = {\n readonly requests: ReadonlyArray<WireBatchEntry>\n}\n\nconst buildBatchBody = (\n request: GeminiEmbedManyRequest,\n): Effect.Effect<WireBatchBody, AiError.AiError> => {\n const modelPath = `models/${request.model}`\n const options = wireOptions(request)\n return Effect.forEach(request.inputs, (input) =>\n inputToContent(input).pipe(\n Effect.map((content): WireBatchEntry => ({ model: modelPath, content, ...options })),\n ),\n ).pipe(Effect.map((requests) => ({ requests })))\n}\n\n// ---------------------------------------------------------------------------\n// Codec - response\n// ---------------------------------------------------------------------------\n\nconst WireEmbedding = Schema.Struct({\n values: Schema.Array(Schema.Number),\n})\n\nconst WireSingleResponse = Schema.Struct({\n embedding: WireEmbedding,\n})\n\nconst WireBatchResponse = Schema.Struct({\n embeddings: Schema.Array(WireEmbedding),\n})\n\nconst valuesToEmbedding = (values: ReadonlyArray<number>): Float32Embedding => ({\n _tag: \"float32\",\n vector: Float32Array.from(values),\n})\n\n// Gemini's embed endpoints don't return token-count metadata. Estimating\n// client-side would be provider-specific cruft; honest `undefined` is better.\nconst emptyUsage: Usage = {}\n\n// ---------------------------------------------------------------------------\n// HTTP errors\n// ---------------------------------------------------------------------------\n\nconst httpStatusError = (status: number, body: string): AiError.AiError => {\n const provider = \"gemini\"\n const raw = body\n if (status === 429) return new AiError.RateLimited({ provider, raw })\n if (status === 408 || status === 504) return new AiError.Timeout({ provider, raw })\n if (status === 401) return new AiError.AuthFailed({ provider, subtype: \"auth\", raw })\n if (status === 403) return new AiError.AuthFailed({ provider, subtype: \"permission\", raw })\n if (status === 402) return new AiError.AuthFailed({ provider, subtype: \"billing\", raw })\n if (status === 413) return new AiError.ContextLengthExceeded({ provider, raw })\n if (status >= 500) return new AiError.Unavailable({ provider, status, raw })\n return new AiError.InvalidRequest({ provider, raw })\n}\n\nconst transportFailure = (cause: unknown): AiError.AiError =>\n new AiError.Unavailable({ provider: \"gemini\", raw: cause })\n\n// ---------------------------------------------------------------------------\n// HTTP plumbing - send a JSON body, error-map status, return parsed JSON.\n// ---------------------------------------------------------------------------\n\nconst postJson = (\n cfg: Config,\n url: string,\n body: unknown,\n): Effect.Effect<unknown, AiError.AiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const httpRequest = HttpClientRequest.post(url).pipe(\n HttpClientRequest.setHeader(\"x-goog-api-key\", Redacted.value(cfg.apiKey)),\n HttpClientRequest.bodyJsonUnsafe(body),\n )\n const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure))\n if (response.status >= 400) {\n const text = yield* response.text.pipe(Effect.orElseSucceed(() => \"\"))\n return yield* httpStatusError(response.status, text)\n }\n return yield* response.json.pipe(Effect.mapError(transportFailure))\n })\n\nconst decodeSingle = (json: unknown) =>\n Schema.decodeUnknownEffect(WireSingleResponse)(json).pipe(Effect.mapError(transportFailure))\n\nconst decodeBatch = (json: unknown) =>\n Schema.decodeUnknownEffect(WireBatchResponse)(json).pipe(Effect.mapError(transportFailure))\n\nconst baseUrl = (cfg: Config): string =>\n cfg.baseUrl ?? \"https://generativelanguage.googleapis.com/v1beta\"\n\n// ---------------------------------------------------------------------------\n// Service implementation\n// ---------------------------------------------------------------------------\n\nconst embedImpl =\n (cfg: Config) =>\n (\n request: GeminiEmbedRequest,\n ): Effect.Effect<EmbedResponse, AiError.AiError, HttpClient.HttpClient> =>\n buildSingleBody(request).pipe(\n Effect.flatMap((body) =>\n postJson(cfg, `${baseUrl(cfg)}/models/${request.model}:embedContent`, body),\n ),\n Effect.flatMap(decodeSingle),\n Effect.map(\n (decoded): EmbedResponse => ({\n embedding: valuesToEmbedding(decoded.embedding.values),\n usage: emptyUsage,\n }),\n ),\n )\n\nconst embedManyImpl =\n (cfg: Config) =>\n (\n request: GeminiEmbedManyRequest,\n ): Effect.Effect<EmbedManyResponse, AiError.AiError, HttpClient.HttpClient> =>\n buildBatchBody(request).pipe(\n Effect.flatMap((body) =>\n postJson(cfg, `${baseUrl(cfg)}/models/${request.model}:batchEmbedContents`, body),\n ),\n Effect.flatMap(decodeBatch),\n Effect.map(\n (decoded): EmbedManyResponse => ({\n embeddings: decoded.embeddings.map((e) => valuesToEmbedding(e.values)),\n usage: emptyUsage,\n }),\n ),\n )\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\n/**\n * Build a `GeminiEmbeddingService` value. For Layer-based setup, prefer\n * `layer`.\n */\nexport const make = (\n cfg: Config,\n): Effect.Effect<GeminiEmbeddingService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient, (client) => ({\n embed: (request) =>\n embedImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n embedMany: (request) =>\n embedManyImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n }))\n\n/**\n * Layer that registers both the provider-specific `GeminiEmbedding` tag\n * and the generic `EmbeddingModel` tag, sharing one underlying\n * implementation.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<GeminiEmbedding | EmbeddingModel, never, HttpClient.HttpClient> => {\n const typed = Layer.effect(GeminiEmbedding, make(cfg))\n const generic = Layer.effect(\n EmbeddingModel,\n Effect.map(\n make(cfg),\n (s): EmbeddingModelService => ({\n // Gemini's 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 )\n return Layer.merge(typed, generic)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"GeminiEmbedding.mjs","names":[],"sources":["../src/GeminiEmbedding.ts"],"sourcesContent":["import { Context, Effect, Encoding, Layer, Match, Redacted, Schema } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport type {\n EmbedContentPart,\n EmbedInput,\n Float32Embedding,\n Usage,\n} from \"@effect-uai/core/Embedding\"\nimport {\n 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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"}
|
|
@@ -4,8 +4,6 @@ import { HttpClient } from "effect/unstable/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";
|
|
7
|
-
|
|
8
|
-
//#region src/GeminiSynthesizer.d.ts
|
|
9
7
|
declare namespace GeminiSynthesizer_d_exports {
|
|
10
8
|
export { Config, GeminiSynthesizeRequest, GeminiSynthesizer, GeminiSynthesizerService, layer, make, realizeOutput };
|
|
11
9
|
}
|
|
@@ -26,7 +24,7 @@ type Config = {
|
|
|
26
24
|
readonly apiKey: Redacted.Redacted;
|
|
27
25
|
readonly baseUrl?: string;
|
|
28
26
|
};
|
|
29
|
-
declare const realizeOutput: (u: "
|
|
27
|
+
declare const realizeOutput: (u: "aac" | "flac" | "mp3" | "ogg" | "opus" | "raw" | "wav" | "webm") => Result.Result<readonly [{
|
|
30
28
|
readonly container: "raw";
|
|
31
29
|
readonly encoding: "pcm_s16le";
|
|
32
30
|
readonly sampleRate: 24000;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GeminiSynthesizer.d.mts","names":[],"sources":["../src/GeminiSynthesizer.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"GeminiSynthesizer.d.mts","names":[],"sources":["../src/GeminiSynthesizer.ts"],"mappings":";;;;;;;;;KAsBY,0BAA0B,KAAK;WAChC,OAAO;WACP,SAAS;;KAGR;WACD,aAAa,GAAG,4BAA4B,OAAO,OAAO,WAAW,QAAQ;WAC7E,kBACP,GAAG,4BACA,OAAO,OAAO,YAAY,QAAQ;WAC9B,qBAAqB;WACrB,oBAAoB;WACpB,0BAA0B;;;cAGxB,0BAA0B;KAK3B;WAAoB,QAAQ,SAAS;WAAmB;;cAUvD,gBAAa,wEAAA,OAAA;WAGP;WAAiB;;;IAC1B,GAAA,eAAU,WAAA;WAKD;WAAiB;;;IAC1B,GAAA,eAAU,WAAA,mBAAA,QAAA;cAiKP,OAAI,KAAS,WAAM,OAAA,OAAA,iCAAA,WAAA;;;;;;;cAoBnB,QAAK,KAAS,WAAM,MAAA,MAAA,oBAAA,0BAAA,WAAA"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as __exportAll } from "./
|
|
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
4
|
import { HttpClient, HttpClientRequest } from "effect/unstable/http";
|
|
@@ -71,19 +71,20 @@ const synthesizeImpl = (cfg) => (request) => Effect.gen(function* () {
|
|
|
71
71
|
return yield* httpStatusError(response.status, text);
|
|
72
72
|
}
|
|
73
73
|
const json = yield* response.json.pipe(Effect.mapError(transportFailure));
|
|
74
|
+
const inline = yield* decodeWire(json).pipe(Effect.map(findInline), Effect.mapError((cause) => new AiError.GenerationFailed({
|
|
75
|
+
provider: "gemini",
|
|
76
|
+
raw: cause
|
|
77
|
+
})), Effect.flatMap((found) => found === void 0 ? Effect.fail(new AiError.GenerationFailed({
|
|
78
|
+
provider: "gemini",
|
|
79
|
+
raw: {
|
|
80
|
+
message: "Gemini TTS response had no inlineData audio part.",
|
|
81
|
+
hint: "Likely a prompt-safety rejection or the model didn't honour responseModalities.",
|
|
82
|
+
response: json
|
|
83
|
+
}
|
|
84
|
+
})) : Effect.succeed(found)));
|
|
74
85
|
return {
|
|
75
86
|
format,
|
|
76
|
-
bytes: wrap(yield* decodeBase64Audio(
|
|
77
|
-
provider: "gemini",
|
|
78
|
-
raw: cause
|
|
79
|
-
})), Effect.flatMap((found) => found === void 0 ? Effect.fail(new AiError.GenerationFailed({
|
|
80
|
-
provider: "gemini",
|
|
81
|
-
raw: {
|
|
82
|
-
message: "Gemini TTS response had no inlineData audio part.",
|
|
83
|
-
hint: "Likely a prompt-safety rejection or the model didn't honour responseModalities.",
|
|
84
|
-
response: json
|
|
85
|
-
}
|
|
86
|
-
})) : Effect.succeed(found)))).data))
|
|
87
|
+
bytes: wrap(yield* decodeBase64Audio(inline.data))
|
|
87
88
|
};
|
|
88
89
|
});
|
|
89
90
|
const unsupportedStreamFrom = () => Stream.fail(new AiError.Unsupported({
|
|
@@ -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(\n HttpClient.HttpClient,\n (client): GeminiSynthesizerService => ({\n synthesize: (r) =>\n synthesizeImpl(cfg)(r).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n streamSynthesis: (r) =>\n Stream.fromEffect(synthesizeImpl(cfg)(r)).pipe(\n Stream.map((b): AudioChunk => ({ bytes: b.bytes })),\n Stream.provideService(HttpClient.HttpClient, client),\n ),\n streamSynthesisFrom: unsupportedStreamFrom,\n synthesizeDialogue: unsupportedDialogue,\n streamSynthesizeDialogue: unsupportedStreamDialogue,\n }),\n )\n\n/**\n * Layer registers both `GeminiSynthesizer` and the generic\n * `SpeechSynthesizer`. Does NOT register `TtsIncrementalText` — Gemini\n * TTS is sync-only at the wire level, so `streamSynthesisFrom` against\n * this Layer alone is a compile-time error.\n */\nexport const layer = (cfg: Config) =>\n Layer.merge(\n Layer.effect(GeminiSynthesizer, make(cfg)),\n Layer.effect(\n SpeechSynthesizer,\n Effect.map(\n make(cfg),\n (s): SpeechSynthesizerService => ({\n synthesize: (req: CommonSynthesizeRequest) =>\n s.synthesize(req as GeminiSynthesizeRequest),\n streamSynthesis: (req: CommonSynthesizeRequest) =>\n s.streamSynthesis(req as GeminiSynthesizeRequest),\n streamSynthesisFrom: s.streamSynthesisFrom,\n synthesizeDialogue: s.synthesizeDialogue,\n streamSynthesizeDialogue: s.streamSynthesizeDialogue,\n }),\n ),\n ),\n )\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"GeminiSynthesizer.mjs","names":[],"sources":["../src/GeminiSynthesizer.ts"],"sourcesContent":["import { Context, Effect, Layer, Match, Redacted, 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 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as describe, r as it, t as globalExpect } from "./test.DNmyFkvJ-N3QqMRwZ.mjs";
|
|
2
2
|
import { realizeOutput } from "./GeminiSynthesizer.mjs";
|
|
3
3
|
import { Result } from "effect";
|
|
4
4
|
//#region src/GeminiSynthesizer.test.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GeminiSynthesizer.test.mjs","names":["GeminiSynthesizer.realizeOutput"],"sources":["../src/GeminiSynthesizer.test.ts"],"sourcesContent":["import { Result } from \"effect\"\nimport { describe, expect, it } from \"vitest\"\nimport * as GeminiSynthesizer from \"./GeminiSynthesizer.js\"\n\ndescribe(\"GeminiSynthesizer.realizeOutput\", () => {\n it(\"maps raw to native PCM 24 kHz mono with an identity wrapper\", () => {\n const result = GeminiSynthesizer.realizeOutput(\"raw\")\n expect(Result.isSuccess(result)).toBe(true)\n const [format, wrap] = Result.getOrThrow(result)\n expect(format).toEqual({\n container: \"raw\",\n encoding: \"pcm_s16le\",\n sampleRate: 24000,\n channels: 1,\n })\n const pcm = new Uint8Array([1, 2, 3, 4])\n expect(wrap(pcm)).toBe(pcm)\n })\n\n it(\"maps wav to a RIFF/WAVE wrapper that prepends a 44-byte header\", () => {\n const [format, wrap] = Result.getOrThrow(GeminiSynthesizer.realizeOutput(\"wav\"))\n expect(format.container).toBe(\"wav\")\n const out = wrap(new Uint8Array([0, 1, 2, 3]))\n expect(out.length).toBe(44 + 4)\n const ascii = (start: number, len: number) =>\n String.fromCharCode(...Array.from(out.slice(start, start + len)))\n expect(ascii(0, 4)).toBe(\"RIFF\")\n expect(ascii(8, 4)).toBe(\"WAVE\")\n expect(ascii(12, 4)).toBe(\"fmt \")\n expect(ascii(36, 4)).toBe(\"data\")\n })\n\n it(\"fails Unsupported for mp3 / opus / aac / flac / ogg / webm\", () => {\n const result = GeminiSynthesizer.realizeOutput(\"mp3\")\n expect(Result.isFailure(result)).toBe(true)\n expect(JSON.stringify(result)).toContain(\"Unsupported\")\n expect(JSON.stringify(result)).toContain(\"outputFormat\")\n })\n})\n"],"mappings":";;;;AAIA,SAAS,yCAAyC;
|
|
1
|
+
{"version":3,"file":"GeminiSynthesizer.test.mjs","names":["GeminiSynthesizer.realizeOutput"],"sources":["../src/GeminiSynthesizer.test.ts"],"sourcesContent":["import { Result } from \"effect\"\nimport { describe, expect, it } from \"vitest\"\nimport * as GeminiSynthesizer from \"./GeminiSynthesizer.js\"\n\ndescribe(\"GeminiSynthesizer.realizeOutput\", () => {\n it(\"maps raw to native PCM 24 kHz mono with an identity wrapper\", () => {\n const result = GeminiSynthesizer.realizeOutput(\"raw\")\n expect(Result.isSuccess(result)).toBe(true)\n const [format, wrap] = Result.getOrThrow(result)\n expect(format).toEqual({\n container: \"raw\",\n encoding: \"pcm_s16le\",\n sampleRate: 24000,\n channels: 1,\n })\n const pcm = new Uint8Array([1, 2, 3, 4])\n expect(wrap(pcm)).toBe(pcm)\n })\n\n it(\"maps wav to a RIFF/WAVE wrapper that prepends a 44-byte header\", () => {\n const [format, wrap] = Result.getOrThrow(GeminiSynthesizer.realizeOutput(\"wav\"))\n expect(format.container).toBe(\"wav\")\n const out = wrap(new Uint8Array([0, 1, 2, 3]))\n expect(out.length).toBe(44 + 4)\n const ascii = (start: number, len: number) =>\n String.fromCharCode(...Array.from(out.slice(start, start + len)))\n expect(ascii(0, 4)).toBe(\"RIFF\")\n expect(ascii(8, 4)).toBe(\"WAVE\")\n expect(ascii(12, 4)).toBe(\"fmt \")\n expect(ascii(36, 4)).toBe(\"data\")\n })\n\n it(\"fails Unsupported for mp3 / opus / aac / flac / ogg / webm\", () => {\n const result = GeminiSynthesizer.realizeOutput(\"mp3\")\n expect(Result.isFailure(result)).toBe(true)\n expect(JSON.stringify(result)).toContain(\"Unsupported\")\n expect(JSON.stringify(result)).toContain(\"outputFormat\")\n })\n})\n"],"mappings":";;;;AAIA,SAAS,yCAAyC;CAChD,GAAG,qEAAqE;EACtE,MAAM,SAASA,cAAgC,KAAK;EACpD,aAAO,OAAO,UAAU,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI;EAC1C,MAAM,CAAC,QAAQ,QAAQ,OAAO,WAAW,MAAM;EAC/C,aAAO,MAAM,CAAC,CAAC,QAAQ;GACrB,WAAW;GACX,UAAU;GACV,YAAY;GACZ,UAAU;EACZ,CAAC;EACD,MAAM,MAAM,IAAI,WAAW;GAAC;GAAG;GAAG;GAAG;EAAC,CAAC;EACvC,aAAO,KAAK,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG;CAC5B,CAAC;CAED,GAAG,wEAAwE;EACzE,MAAM,CAAC,QAAQ,QAAQ,OAAO,WAAWA,cAAgC,KAAK,CAAC;EAC/E,aAAO,OAAO,SAAS,CAAC,CAAC,KAAK,KAAK;EACnC,MAAM,MAAM,KAAK,IAAI,WAAW;GAAC;GAAG;GAAG;GAAG;EAAC,CAAC,CAAC;EAC7C,aAAO,IAAI,MAAM,CAAC,CAAC,KAAK,EAAM;EAC9B,MAAM,SAAS,OAAe,QAC5B,OAAO,aAAa,GAAG,MAAM,KAAK,IAAI,MAAM,OAAO,QAAQ,GAAG,CAAC,CAAC;EAClE,aAAO,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM;EAC/B,aAAO,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM;EAC/B,aAAO,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM;EAChC,aAAO,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM;CAClC,CAAC;CAED,GAAG,oEAAoE;EACrE,MAAM,SAASA,cAAgC,KAAK;EACpD,aAAO,OAAO,UAAU,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI;EAC1C,aAAO,KAAK,UAAU,MAAM,CAAC,CAAC,CAAC,UAAU,aAAa;EACtD,aAAO,KAAK,UAAU,MAAM,CAAC,CAAC,CAAC,UAAU,cAAc;CACzD,CAAC;AACH,CAAC"}
|
package/dist/GeminiTools.d.mts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { RequestTool } from "./codec.mjs";
|
|
2
2
|
import { Result, Schema } from "effect";
|
|
3
3
|
import * as Tool from "@effect-uai/core/Tool";
|
|
4
|
-
|
|
5
|
-
//#region src/GeminiTools.d.ts
|
|
6
4
|
declare namespace GeminiTools_d_exports {
|
|
7
5
|
export { GeminiToolConfig, ToolRenderError, codeExecutionTool, googleSearchTool, renderProviderTools, urlContextTool };
|
|
8
6
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GeminiTools.d.mts","names":[],"sources":["../src/GeminiTools.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"GeminiTools.d.mts","names":[],"sources":["../src/GeminiTools.ts"],"mappings":";;;;;;;cAca,kBAAgB,OAAA,gBAAA,OAAA;;;;;;;KAKjB,0BAA0B,iBAAiB;;cAG1C,kBAAgB,KAAA,8BAAA;WAKX;;;cAIL,gBAAc,KAAA,4BAAA;WAKT;;;cAIL,mBAAiB,KAAA,+BAAA;WAKZ;;;;;;;;KASN;WACD;WACA;;;;;;;cAkCE,sBAAmB,OACvB,cAAc,KAAK,4CACzB,OAAO,OAAO,cAAc,cAAc"}
|
package/dist/GeminiTools.mjs
CHANGED
package/dist/GeminiTools.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GeminiTools.mjs","names":[],"sources":["../src/GeminiTools.ts"],"sourcesContent":["import { Match, Result, Schema } from \"effect\"\nimport * as Tool from \"@effect-uai/core/Tool\"\nimport type { RequestTool } from \"./codec.js\"\n\n// ---------------------------------------------------------------------------\n// Gemini provider-hosted tools. Each is a `Tool.provider` whose `config`\n// carries the discriminant the renderer maps to a native `tools[]` entry\n// (`googleSearch`, `urlContext`, `codeExecution`). The `inputSchema` is never\n// sent — provider tools are partitioned out of the function declarations — so\n// `Tool.noInput` suffices.\n// Reference: https://ai.google.dev/api/caching#Tool\n// ---------------------------------------------------------------------------\n\n/** Discriminated config for a Gemini hosted tool. */\nexport const GeminiToolConfig = Schema.Union([\n Schema.Struct({ kind: Schema.Literal(\"google_search\") }),\n Schema.Struct({ kind: Schema.Literal(\"url_context\") }),\n Schema.Struct({ kind: Schema.Literal(\"code_execution\") }),\n])\nexport type GeminiToolConfig = typeof GeminiToolConfig.Type\n\n/** Google Search grounding, executed by Gemini. */\nexport const googleSearchTool = Tool.provider({\n name: \"google_search\",\n description: \"Ground the answer with Google Search, executed by Gemini.\",\n inputSchema: Tool.noInput,\n provider: \"gemini\",\n config: { kind: \"google_search\" } as const,\n})\n\n/** URL context: Gemini fetches and reads URLs referenced in the prompt. */\nexport const urlContextTool = Tool.provider({\n name: \"url_context\",\n description: \"Let Gemini fetch and read URLs referenced in the prompt.\",\n inputSchema: Tool.noInput,\n provider: \"gemini\",\n config: { kind: \"url_context\" } as const,\n})\n\n/** Code execution: Gemini runs generated Python in a sandbox. */\nexport const codeExecutionTool = Tool.provider({\n name: \"code_execution\",\n description: \"Let Gemini write and run Python in a sandbox.\",\n inputSchema: Tool.noInput,\n provider: \"gemini\",\n config: { kind: \"code_execution\" } as const,\n})\n\n/**\n * A provider tool this adapter cannot render — a foreign `provider` or an\n * unrecognized `config`. Carried as data so `Gemini.ts` can lift it into a\n * typed `AiError.Unsupported`, mirroring how `hasUrlImageSource` surfaces a\n * request-shaping failure.\n */\nexport type ToolRenderError = {\n readonly capability: string\n readonly reason: string\n}\n\nconst decodeConfig = Schema.decodeUnknownResult(GeminiToolConfig)\n\nconst configToWire = Match.type<GeminiToolConfig>().pipe(\n Match.discriminatorsExhaustive(\"kind\")({\n google_search: (): RequestTool => ({ googleSearch: {} }),\n url_context: (): RequestTool => ({ urlContext: {} }),\n code_execution: (): RequestTool => ({ codeExecution: {} }),\n }),\n)\n\nconst renderOne = (\n tool: Tool.ProviderTool<string, any, string, any>,\n): Result.Result<RequestTool, ToolRenderError> =>\n tool.provider !== \"gemini\"\n ? Result.fail({\n capability: `providerTool:${tool.provider}`,\n reason: `tool \"${tool.name}\" is hosted by \"${tool.provider}\", not gemini`,\n })\n : decodeConfig(tool.config).pipe(\n Result.map(configToWire),\n Result.mapError(
|
|
1
|
+
{"version":3,"file":"GeminiTools.mjs","names":[],"sources":["../src/GeminiTools.ts"],"sourcesContent":["import { Match, Result, Schema } from \"effect\"\nimport * as Tool from \"@effect-uai/core/Tool\"\nimport type { RequestTool } from \"./codec.js\"\n\n// ---------------------------------------------------------------------------\n// Gemini provider-hosted tools. Each is a `Tool.provider` whose `config`\n// carries the discriminant the renderer maps to a native `tools[]` entry\n// (`googleSearch`, `urlContext`, `codeExecution`). The `inputSchema` is never\n// sent — provider tools are partitioned out of the function declarations — so\n// `Tool.noInput` suffices.\n// Reference: https://ai.google.dev/api/caching#Tool\n// ---------------------------------------------------------------------------\n\n/** Discriminated config for a Gemini hosted tool. */\nexport const GeminiToolConfig = Schema.Union([\n Schema.Struct({ kind: Schema.Literal(\"google_search\") }),\n Schema.Struct({ kind: Schema.Literal(\"url_context\") }),\n Schema.Struct({ kind: Schema.Literal(\"code_execution\") }),\n])\nexport type GeminiToolConfig = typeof GeminiToolConfig.Type\n\n/** Google Search grounding, executed by Gemini. */\nexport const googleSearchTool = Tool.provider({\n name: \"google_search\",\n description: \"Ground the answer with Google Search, executed by Gemini.\",\n inputSchema: Tool.noInput,\n provider: \"gemini\",\n config: { kind: \"google_search\" } as const,\n})\n\n/** URL context: Gemini fetches and reads URLs referenced in the prompt. */\nexport const urlContextTool = Tool.provider({\n name: \"url_context\",\n description: \"Let Gemini fetch and read URLs referenced in the prompt.\",\n inputSchema: Tool.noInput,\n provider: \"gemini\",\n config: { kind: \"url_context\" } as const,\n})\n\n/** Code execution: Gemini runs generated Python in a sandbox. */\nexport const codeExecutionTool = Tool.provider({\n name: \"code_execution\",\n description: \"Let Gemini write and run Python in a sandbox.\",\n inputSchema: Tool.noInput,\n provider: \"gemini\",\n config: { kind: \"code_execution\" } as const,\n})\n\n/**\n * A provider tool this adapter cannot render — a foreign `provider` or an\n * unrecognized `config`. Carried as data so `Gemini.ts` can lift it into a\n * typed `AiError.Unsupported`, mirroring how `hasUrlImageSource` surfaces a\n * request-shaping failure.\n */\nexport type ToolRenderError = {\n readonly capability: string\n readonly reason: string\n}\n\nconst decodeConfig = Schema.decodeUnknownResult(GeminiToolConfig)\n\nconst configToWire = Match.type<GeminiToolConfig>().pipe(\n Match.discriminatorsExhaustive(\"kind\")({\n google_search: (): RequestTool => ({ googleSearch: {} }),\n url_context: (): RequestTool => ({ urlContext: {} }),\n code_execution: (): RequestTool => ({ codeExecution: {} }),\n }),\n)\n\nconst renderOne = (\n tool: Tool.ProviderTool<string, any, string, any>,\n): Result.Result<RequestTool, ToolRenderError> =>\n tool.provider !== \"gemini\"\n ? Result.fail({\n capability: `providerTool:${tool.provider}`,\n reason: `tool \"${tool.name}\" is hosted by \"${tool.provider}\", not gemini`,\n })\n : decodeConfig(tool.config).pipe(\n Result.map(configToWire),\n Result.mapError((): ToolRenderError => ({\n capability: \"providerTool\",\n reason: `unrecognized Gemini provider tool config on \"${tool.name}\"`,\n })),\n )\n\n/**\n * Render a toolkit's Gemini provider tools to native `tools[]` entries. Fails\n * with a `ToolRenderError` on the first foreign or unrecognized tool rather\n * than dropping it.\n */\nexport const renderProviderTools = (\n tools: ReadonlyArray<Tool.ProviderTool<string, any, string, any>>,\n): Result.Result<ReadonlyArray<RequestTool>, ToolRenderError> => Result.all(tools.map(renderOne))\n"],"mappings":";;;;;;;;;;;;AAcA,MAAa,mBAAmB,OAAO,MAAM;CAC3C,OAAO,OAAO,EAAE,MAAM,OAAO,QAAQ,eAAe,EAAE,CAAC;CACvD,OAAO,OAAO,EAAE,MAAM,OAAO,QAAQ,aAAa,EAAE,CAAC;CACrD,OAAO,OAAO,EAAE,MAAM,OAAO,QAAQ,gBAAgB,EAAE,CAAC;AAC1D,CAAC;;AAID,MAAa,mBAAmB,KAAK,SAAS;CAC5C,MAAM;CACN,aAAa;CACb,aAAa,KAAK;CAClB,UAAU;CACV,QAAQ,EAAE,MAAM,gBAAgB;AAClC,CAAC;;AAGD,MAAa,iBAAiB,KAAK,SAAS;CAC1C,MAAM;CACN,aAAa;CACb,aAAa,KAAK;CAClB,UAAU;CACV,QAAQ,EAAE,MAAM,cAAc;AAChC,CAAC;;AAGD,MAAa,oBAAoB,KAAK,SAAS;CAC7C,MAAM;CACN,aAAa;CACb,aAAa,KAAK;CAClB,UAAU;CACV,QAAQ,EAAE,MAAM,iBAAiB;AACnC,CAAC;AAaD,MAAM,eAAe,OAAO,oBAAoB,gBAAgB;AAEhE,MAAM,eAAe,MAAM,KAAuB,CAAC,CAAC,KAClD,MAAM,yBAAyB,MAAM,CAAC,CAAC;CACrC,sBAAmC,EAAE,cAAc,CAAC,EAAE;CACtD,oBAAiC,EAAE,YAAY,CAAC,EAAE;CAClD,uBAAoC,EAAE,eAAe,CAAC,EAAE;AAC1D,CAAC,CACH;AAEA,MAAM,aACJ,SAEA,KAAK,aAAa,WACd,OAAO,KAAK;CACV,YAAY,gBAAgB,KAAK;CACjC,QAAQ,SAAS,KAAK,KAAK,kBAAkB,KAAK,SAAS;AAC7D,CAAC,IACD,aAAa,KAAK,MAAM,CAAC,CAAC,KACxB,OAAO,IAAI,YAAY,GACvB,OAAO,gBAAiC;CACtC,YAAY;CACZ,QAAQ,gDAAgD,KAAK,KAAK;AACpE,EAAE,CACJ;;;;;;AAON,MAAa,uBACX,UAC+D,OAAO,IAAI,MAAM,IAAI,SAAS,CAAC"}
|
|
@@ -5,8 +5,6 @@ import { DeepResearch, DeepResearchServiceShape } from "@effect-uai/core/DeepRes
|
|
|
5
5
|
import * as Items from "@effect-uai/core/Items";
|
|
6
6
|
import * as Job from "@effect-uai/core/Job";
|
|
7
7
|
import { ResearchRequest, ResearchState } from "@effect-uai/core/Research";
|
|
8
|
-
|
|
9
|
-
//#region src/GoogleDeepResearch.d.ts
|
|
10
8
|
declare namespace GoogleDeepResearch_d_exports {
|
|
11
9
|
export { Config, GeminiResearchData, GoogleDeepResearch, GoogleDeepResearchService, GoogleResearchRequest, WireInteraction, jobStateOf, layer, make, researchDataOf };
|
|
12
10
|
}
|
|
@@ -33,7 +31,8 @@ declare const GoogleDeepResearch_base: Context.ServiceClass<GoogleDeepResearch,
|
|
|
33
31
|
declare class GoogleDeepResearch extends GoogleDeepResearch_base {}
|
|
34
32
|
type Config = {
|
|
35
33
|
readonly apiKey: Redacted.Redacted;
|
|
36
|
-
readonly baseUrl?: string;
|
|
34
|
+
readonly baseUrl?: string;
|
|
35
|
+
/** Poll cadence / overall timeout for the derived poll loops. */
|
|
37
36
|
readonly job?: Job.JobConfig;
|
|
38
37
|
};
|
|
39
38
|
declare const WireInteraction: Schema.Struct<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GoogleDeepResearch.d.mts","names":[],"sources":["../src/GoogleDeepResearch.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"GoogleDeepResearch.d.mts","names":[],"sources":["../src/GoogleDeepResearch.ts"],"mappings":";;;;;;;;;;;;;;KAwCY,wBAAwB,KAAK;WAC9B,QAAQ;;KAGP,4BAA4B,yBAAyB;;;;;;;;;;;;;cAapD,2BAA2B;KAK5B;WACD,QAAQ,SAAS;WACjB;;WAEA,MAAM,IAAI;;cA4BR,iBAAe,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAehB,yBAAyB,gBAAgB;;;;;;;;cAkCxC,oBAAkB,OAAA;;;;;;;;;KAQnB,4BAA4B,mBAAmB;;cAK9C,iBAAc,MAAU,MAAM,gBAAc,OAAO,OAAO;cA4D1D,aAAU,MAAU,oBAAkB;;cAmStC,OAAI,KACV,WACJ,OAAO,OAAO,kCAAkC,WAAW;;;;;;cAUjD,QAAK,KACX,WACJ,MAAM,MAAM,qBAAqB,qBAAqB,WAAW"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as __exportAll } from "./
|
|
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
4
|
import { HttpClient, HttpClientRequest } from "effect/unstable/http";
|
|
@@ -193,7 +193,8 @@ const submitJob = (cfg, request) => Effect.gen(function* () {
|
|
|
193
193
|
const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""));
|
|
194
194
|
return yield* httpStatusError(response.status, text);
|
|
195
195
|
}
|
|
196
|
-
const
|
|
196
|
+
const json = yield* response.json.pipe(Effect.mapError(transportFailure));
|
|
197
|
+
const wire = yield* decodeSubmit(json).pipe(Effect.mapError(transportFailure));
|
|
197
198
|
return Job.jobRef("google", wire.id);
|
|
198
199
|
});
|
|
199
200
|
const pollJob = (cfg, ref) => Effect.gen(function* () {
|
|
@@ -206,7 +207,8 @@ const pollJob = (cfg, ref) => Effect.gen(function* () {
|
|
|
206
207
|
}
|
|
207
208
|
const json = yield* response.json.pipe(Effect.mapError(transportFailure));
|
|
208
209
|
yield* Effect.logDebug("google interactions poll response", { interaction: json });
|
|
209
|
-
|
|
210
|
+
const wire = yield* decodeInteraction(json).pipe(Effect.mapError(transportFailure));
|
|
211
|
+
return jobStateOf(wire);
|
|
210
212
|
});
|
|
211
213
|
const unsupported = (capability) => new AiError.Unsupported({
|
|
212
214
|
provider: "google",
|
|
@@ -307,10 +309,11 @@ const layer = (cfg) => {
|
|
|
307
309
|
const typed = Layer.effect(GoogleDeepResearch, make(cfg));
|
|
308
310
|
const generic = Layer.effect(DeepResearch, Effect.map(HttpClient.HttpClient, (client) => {
|
|
309
311
|
const ops = jobOps(cfg, client);
|
|
310
|
-
|
|
312
|
+
const genericOps = {
|
|
311
313
|
...ops,
|
|
312
314
|
submit: (request) => ops.submit(request)
|
|
313
|
-
}
|
|
315
|
+
};
|
|
316
|
+
return buildService(cfg, client, genericOps, (request) => request);
|
|
314
317
|
}));
|
|
315
318
|
return Layer.merge(typed, generic);
|
|
316
319
|
};
|