@effect-uai/google 0.12.1 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
1
  import { n as __exportAll } from "./rolldown-runtime-EAsCxrDo.mjs";
2
+ import { httpStatusError as httpStatusError$1, transportFailure as transportFailure$1 } from "./codec.mjs";
2
3
  import { Context, Duration, Effect, Encoding, Layer, Match, Redacted, Result, Schema, Stream } from "effect";
3
4
  import { HttpClient, HttpClientRequest } from "effect/unstable/http";
4
5
  import * as AiError from "@effect-uai/core/AiError";
@@ -78,36 +79,8 @@ const Candidate = Schema.Struct({ content: Schema.optional(Content) });
78
79
  const Wire = Schema.Struct({ candidates: Schema.optional(Schema.Array(Candidate)) });
79
80
  const decodeWire = Schema.decodeUnknownEffect(Wire);
80
81
  const baseUrl = (cfg) => cfg.baseUrl ?? "https://generativelanguage.googleapis.com/v1beta";
81
- const transportFailure = (cause) => new AiError.Unavailable({
82
- provider: "lyria",
83
- raw: cause
84
- });
85
- const httpStatusError = (status, body) => Match.value(status).pipe(Match.when(429, () => new AiError.RateLimited({
86
- provider: "lyria",
87
- raw: body
88
- })), Match.whenOr(408, 504, () => new AiError.Timeout({
89
- provider: "lyria",
90
- raw: body
91
- })), Match.when(401, () => new AiError.AuthFailed({
92
- provider: "lyria",
93
- subtype: "auth",
94
- raw: body
95
- })), Match.when(403, () => new AiError.AuthFailed({
96
- provider: "lyria",
97
- subtype: "permission",
98
- raw: body
99
- })), Match.when(402, () => new AiError.AuthFailed({
100
- provider: "lyria",
101
- subtype: "billing",
102
- raw: body
103
- })), Match.when((n) => n >= 500, (n) => new AiError.Unavailable({
104
- provider: "lyria",
105
- status: n,
106
- raw: body
107
- })), Match.orElse(() => new AiError.InvalidRequest({
108
- provider: "lyria",
109
- raw: body
110
- })));
82
+ const transportFailure = transportFailure$1("lyria");
83
+ const httpStatusError = httpStatusError$1("lyria");
111
84
  const decodeBase64ToBytes = (b64) => Result.match(Encoding.decodeBase64(b64), {
112
85
  onSuccess: Effect.succeed,
113
86
  onFailure: (cause) => Effect.fail(new AiError.InvalidRequest({
@@ -1 +1 @@
1
- {"version":3,"file":"LyriaGenerator.mjs","names":[],"sources":["../src/LyriaGenerator.ts"],"sourcesContent":["import {\n Context,\n Duration,\n Effect,\n Encoding,\n Layer,\n Match,\n Redacted,\n Result,\n Schema,\n Stream,\n} from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport type { AudioChunk, AudioFormat } from \"@effect-uai/core/Audio\"\nimport { warnDroppedWhen } from \"@effect-uai/core/Capabilities\"\nimport type {\n CommonGenerateMusicRequest,\n CommonStreamGenerateMusicRequest,\n GenerateResult,\n MusicResult,\n MusicSessionInput,\n MusicStreamEvent,\n} from \"@effect-uai/core/Music\"\nimport { singleVariant } from \"@effect-uai/core/Music\"\nimport { MusicGenerator, type MusicGeneratorService } from \"@effect-uai/core/MusicGenerator\"\nimport type { LyriaModel } from \"./models.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Lyria-typed generate request. Narrows `model` to `LyriaModel`.\n *\n * Lyria 3 sync `generateContent` has no structured wire fields for\n * `lyrics` / `duration` / `seed` / `instrumental` / `bpm` / `scale` /\n * etc. Those fields on the Common request are logged as\n * dropped-bucket-2 hints; the adapter never rewrites or augments the\n * caller's prompt on their behalf. If you want lyrics or vocal\n * suppression, include them in your `prompt` text yourself.\n *\n * Output format: only `audio/mp3` and `audio/wav` are accepted; other\n * containers fail `Unsupported`. The Clip model variant is fixed at\n * `audio/mp3`.\n */\nexport type LyriaGenerateRequest = Omit<CommonGenerateMusicRequest, \"model\"> & {\n readonly model: LyriaModel\n}\n\nexport type LyriaGeneratorService = {\n readonly generate: (\n request: LyriaGenerateRequest,\n ) => Effect.Effect<GenerateResult, AiError.AiError>\n readonly streamGeneration: (\n request: LyriaGenerateRequest,\n ) => Stream.Stream<AudioChunk, AiError.AiError>\n readonly streamGenerationFrom: <E, R>(\n input: Stream.Stream<MusicSessionInput, E, R>,\n request: CommonStreamGenerateMusicRequest,\n ) => Stream.Stream<MusicStreamEvent, AiError.AiError | E, R>\n}\n\nexport class LyriaGenerator extends Context.Service<LyriaGenerator, LyriaGeneratorService>()(\n \"@betalyra/effect-uai/providers/google/LyriaGenerator\",\n) {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n}\n\n// ---------------------------------------------------------------------------\n// Codec: AudioFormat → Lyria mimeType\n// ---------------------------------------------------------------------------\n\nexport type LyriaResponseMimeType = \"audio/mp3\" | \"audio/wav\"\n\nexport const containerToMimeType: (\n container: AudioFormat[\"container\"],\n) => Effect.Effect<LyriaResponseMimeType, AiError.AiError> = Match.type<\n AudioFormat[\"container\"]\n>().pipe(\n Match.when(\"mp3\", () => Effect.succeed<LyriaResponseMimeType>(\"audio/mp3\")),\n Match.when(\"wav\", () => Effect.succeed<LyriaResponseMimeType>(\"audio/wav\")),\n Match.whenOr(\"ogg\", \"opus\", \"flac\", \"aac\", \"webm\", \"raw\", (c) =>\n Effect.fail(\n new AiError.Unsupported({\n provider: \"lyria\",\n capability: \"outputFormat\",\n reason: `Lyria 3 only produces audio/mp3 or audio/wav; ${c} is not available. Use container: \"mp3\" or \"wav\".`,\n }),\n ),\n ),\n Match.exhaustive,\n)\n\n/**\n * Is this a Lyria 3 Clip model variant? Clip is fixed at 30 s MP3, it\n * does not accept a `responseFormat` on `generationConfig` (the live\n * API rejects unknown fields). Pro accepts the documented\n * `responseFormat` knob.\n */\nconst isClipModel = (model: string): boolean => model.includes(\"clip\")\n\nexport const realizedFormat: (mime: LyriaResponseMimeType) => AudioFormat =\n Match.type<LyriaResponseMimeType>().pipe(\n Match.when(\"audio/mp3\", (): AudioFormat => ({\n container: \"mp3\",\n encoding: \"mp3\",\n sampleRate: 44100,\n channels: 2,\n })),\n Match.when(\"audio/wav\", (): AudioFormat => ({\n container: \"wav\",\n encoding: \"pcm_s16le\",\n sampleRate: 44100,\n channels: 2,\n })),\n Match.exhaustive,\n )\n\n// ---------------------------------------------------------------------------\n// Bucket-2 warn-and-drop for Common fields Lyria 3 can't honor\n// ---------------------------------------------------------------------------\n\n/**\n * Warn (don't fail) when the caller passed Common fields Lyria 3 has\n * no wire field for. Bucket-2 per capabilities policy: provider has\n * no structured interpretation; the output is still valid music, just\n * less aligned with the caller's hint. We never embed these into the\n * prompt on the caller's behalf, prompt construction is the\n * developer's job.\n */\nconst warnDroppedHints = (request: LyriaGenerateRequest): Effect.Effect<void> =>\n Effect.all(\n [\n warnDroppedWhen(request.lyrics, {\n provider: \"lyria\",\n capability: \"lyrics\",\n field: \"lyrics\",\n reason:\n \"Lyria 3 sync has no `lyrics` wire field. If you want vocals to follow specific lyrics, embed them in your prompt yourself (e.g. with `[Verse]` / `[Chorus]` tags).\",\n }),\n warnDroppedWhen(\n request.duration !== undefined ? Duration.toMillis(request.duration) : undefined,\n {\n provider: \"lyria\",\n capability: \"duration\",\n field: \"duration\",\n reason:\n \"Lyria 3 clip is fixed at 30 s; pro derives duration from prompt content. There is no wire field for duration. If you want to steer length, mention it in your prompt.\",\n },\n ),\n warnDroppedWhen(request.seed, {\n provider: \"lyria\",\n capability: \"seed\",\n field: \"seed\",\n reason:\n \"Lyria 3 (Gemini surface) does not expose a seed parameter. Output is non-deterministic across identical prompts. Use Lyria 2 (Vertex) for seeded generation.\",\n }),\n ],\n { discard: true },\n )\n\n// ---------------------------------------------------------------------------\n// Wire response schema\n// ---------------------------------------------------------------------------\n\nconst InlineData = Schema.Struct({\n mimeType: Schema.optional(Schema.String),\n data: Schema.String,\n})\n\nconst Part = Schema.Struct({\n text: Schema.optional(Schema.String),\n inlineData: Schema.optional(InlineData),\n})\n\nconst Content = Schema.Struct({\n parts: Schema.optional(Schema.Array(Part)),\n})\n\nconst Candidate = Schema.Struct({\n content: Schema.optional(Content),\n})\n\nconst Wire = Schema.Struct({\n candidates: Schema.optional(Schema.Array(Candidate)),\n})\n\nconst decodeWire = Schema.decodeUnknownEffect(Wire)\n\n// ---------------------------------------------------------------------------\n// HTTP plumbing\n// ---------------------------------------------------------------------------\n\nconst baseUrl = (cfg: Config): string =>\n cfg.baseUrl ?? \"https://generativelanguage.googleapis.com/v1beta\"\n\nconst transportFailure = (cause: unknown): AiError.AiError =>\n new AiError.Unavailable({ provider: \"lyria\", raw: cause })\n\nconst httpStatusError: (status: number, body: string) => AiError.AiError = (status, body) =>\n Match.value(status).pipe(\n Match.when(\n 429,\n (): AiError.AiError => new AiError.RateLimited({ provider: \"lyria\", raw: body }),\n ),\n Match.whenOr(\n 408,\n 504,\n (): AiError.AiError => new AiError.Timeout({ provider: \"lyria\", raw: body }),\n ),\n Match.when(\n 401,\n (): AiError.AiError =>\n new AiError.AuthFailed({ provider: \"lyria\", subtype: \"auth\", raw: body }),\n ),\n Match.when(\n 403,\n (): AiError.AiError =>\n new AiError.AuthFailed({ provider: \"lyria\", subtype: \"permission\", raw: body }),\n ),\n Match.when(\n 402,\n (): AiError.AiError =>\n new AiError.AuthFailed({ provider: \"lyria\", subtype: \"billing\", raw: body }),\n ),\n Match.when(\n (n) => n >= 500,\n (n): AiError.AiError => new AiError.Unavailable({ provider: \"lyria\", status: n, raw: body }),\n ),\n Match.orElse(\n (): AiError.AiError => new AiError.InvalidRequest({ provider: \"lyria\", raw: body }),\n ),\n )\n\nconst decodeBase64ToBytes = (b64: string): Effect.Effect<Uint8Array, AiError.AiError> =>\n Result.match(Encoding.decodeBase64(b64), {\n onSuccess: Effect.succeed,\n onFailure: (cause) =>\n Effect.fail(new AiError.InvalidRequest({ provider: \"lyria\", param: \"audio\", raw: cause })),\n })\n\n/**\n * Find the first inline-data audio part and the concatenated text parts\n * from a Wire response.\n */\nconst extractParts = (\n wire: typeof Wire.Type,\n): { readonly inline?: typeof InlineData.Type; readonly text?: string } => {\n const allParts = (wire.candidates ?? []).flatMap((c) => c.content?.parts ?? [])\n const inline = allParts.find((p) => p.inlineData !== undefined)?.inlineData\n const textParts = allParts.flatMap((p) => (p.text !== undefined ? [p.text] : []))\n const text = textParts.length === 0 ? undefined : textParts.join(\"\\n\")\n return {\n ...(inline !== undefined && { inline }),\n ...(text !== undefined && { text }),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Service implementation\n// ---------------------------------------------------------------------------\n\nconst generateImpl =\n (cfg: Config) =>\n (\n request: LyriaGenerateRequest,\n ): Effect.Effect<GenerateResult, AiError.AiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const requestedContainer = (request.outputFormat?.container ??\n \"mp3\") satisfies AudioFormat[\"container\"]\n const requestedMime = yield* containerToMimeType(requestedContainer)\n // lyria-3-clip-preview has no `responseFormat` wire field and always\n // emits mp3, so there is no format to send and no 400 to surface. Rather\n // than keep a per-model table that rejects `wav`, report the format the\n // model actually produces (mp3) so the result is never mislabeled.\n // lyria-3-pro honours the requested container.\n const mimeType = isClipModel(request.model) ? (\"audio/mp3\" as const) : requestedMime\n yield* warnDroppedHints(request)\n const body = isClipModel(request.model)\n ? { contents: [{ parts: [{ text: request.prompt }] }] }\n : {\n contents: [{ parts: [{ text: request.prompt }] }],\n generationConfig: {\n responseModalities: [\"AUDIO\", \"TEXT\"],\n responseFormat: { audio: { mimeType } },\n },\n }\n const httpRequest = HttpClientRequest.post(\n `${baseUrl(cfg)}/models/${request.model}:generateContent`,\n ).pipe(\n HttpClientRequest.setHeader(\"x-goog-api-key\", Redacted.value(cfg.apiKey)),\n HttpClientRequest.bodyJsonUnsafe(body),\n )\n const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure))\n if (response.status >= 400) {\n const text = yield* response.text.pipe(Effect.orElseSucceed(() => \"\"))\n return yield* httpStatusError(response.status, text)\n }\n const json = yield* response.json.pipe(Effect.mapError(transportFailure))\n const wire = yield* decodeWire(json).pipe(\n Effect.mapError(\n (cause): AiError.AiError =>\n new AiError.GenerationFailed({ provider: \"lyria\", raw: cause }),\n ),\n )\n const { inline, text } = extractParts(wire)\n if (inline === undefined) {\n return yield* new AiError.GenerationFailed({\n provider: \"lyria\",\n raw: {\n message: \"Lyria response had no inlineData audio part.\",\n hint: \"Likely a prompt-filter rejection. Lyria filters references to real artists / copyrighted song lyrics. Rephrase with descriptive style language instead of artist names.\",\n textParts: text,\n response: json,\n },\n })\n }\n const bytes = yield* decodeBase64ToBytes(inline.data)\n const result: MusicResult = {\n audio: { format: realizedFormat(mimeType), bytes },\n provider: \"lyria\",\n ...(text !== undefined && { lyrics: text }),\n watermark: \"synthid\",\n }\n return singleVariant(result)\n })\n\nconst streamGenerationImpl =\n (cfg: Config) =>\n (\n request: LyriaGenerateRequest,\n ): Stream.Stream<AudioChunk, AiError.AiError, HttpClient.HttpClient> =>\n Stream.unwrap(\n Effect.map(generateImpl(cfg)(request), (result) =>\n Stream.succeed<AudioChunk>({ bytes: result.primary.audio.bytes }),\n ),\n )\n\n/**\n * Lyria 3 sync has no bidirectional session at the wire level, that's\n * Lyria RealTime (separate Layer). The provider Layer here also does\n * NOT register `MusicInteractiveSession`, so callers using\n * `MusicGenerator.streamGenerationFrom` against only this Layer get a\n * compile-time error before this runtime fallback ever fires.\n */\nconst streamGenerationFromUnsupported = <E, R>(\n _input: Stream.Stream<MusicSessionInput, E, R>,\n _request: CommonStreamGenerateMusicRequest,\n): Stream.Stream<MusicStreamEvent, AiError.AiError | E, R> => {\n const fail: Stream.Stream<MusicStreamEvent, AiError.AiError | E, R> = Stream.fail(\n new AiError.Unsupported({\n provider: \"lyria\",\n capability: \"streamGenerationFrom\",\n reason:\n \"Lyria 3 sync does not support bidirectional sessions. Use the Lyria RealTime Layer (WebSocket BidiGenerateMusic) for interactive mid-session prompts.\",\n }),\n )\n return fail\n}\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\nexport const make = (\n cfg: Config,\n): Effect.Effect<LyriaGeneratorService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient, (client) => ({\n generate: (request) =>\n generateImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n streamGeneration: (request) =>\n streamGenerationImpl(cfg)(request).pipe(Stream.provideService(HttpClient.HttpClient, client)),\n streamGenerationFrom: streamGenerationFromUnsupported,\n }))\n\n/**\n * Layer that registers both the provider-specific `LyriaGenerator` tag\n * and the generic `MusicGenerator` tag.\n *\n * Does NOT register the `MusicInteractiveSession` capability marker,\n * Lyria 3 sync has no bidirectional session at the wire level. Code\n * calling `MusicGenerator.streamGenerationFrom` will fail to typecheck\n * against this Layer alone, which is the intended UX.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<LyriaGenerator | MusicGenerator, never, HttpClient.HttpClient> =>\n Layer.merge(\n Layer.effect(LyriaGenerator, make(cfg)),\n Layer.effect(\n MusicGenerator,\n Effect.map(make(cfg), (s): MusicGeneratorService => ({\n generate: (req: CommonGenerateMusicRequest) =>\n s.generate({ ...req, model: req.model as LyriaModel }),\n streamGeneration: (req: CommonStreamGenerateMusicRequest) =>\n s.streamGeneration({ ...req, model: req.model as LyriaModel }),\n streamGenerationFrom: s.streamGenerationFrom,\n })),\n ),\n )\n"],"mappings":"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"}
1
+ {"version":3,"file":"LyriaGenerator.mjs","names":["sharedTransportFailure","sharedHttpStatusError"],"sources":["../src/LyriaGenerator.ts"],"sourcesContent":["import {\n Context,\n Duration,\n Effect,\n Encoding,\n Layer,\n Match,\n Redacted,\n Result,\n Schema,\n Stream,\n} from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport type { AudioChunk, AudioFormat } from \"@effect-uai/core/Audio\"\nimport { warnDroppedWhen } from \"@effect-uai/core/Capabilities\"\nimport type {\n CommonGenerateMusicRequest,\n CommonStreamGenerateMusicRequest,\n GenerateResult,\n MusicResult,\n MusicSessionInput,\n MusicStreamEvent,\n} from \"@effect-uai/core/Music\"\nimport { singleVariant } from \"@effect-uai/core/Music\"\nimport { MusicGenerator, type MusicGeneratorService } from \"@effect-uai/core/MusicGenerator\"\nimport {\n httpStatusError as sharedHttpStatusError,\n transportFailure as sharedTransportFailure,\n} from \"./codec.js\"\nimport type { LyriaModel } from \"./models.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Lyria-typed generate request. Narrows `model` to `LyriaModel`.\n *\n * Lyria 3 sync `generateContent` has no structured wire fields for\n * `lyrics` / `duration` / `seed` / `instrumental` / `bpm` / `scale` /\n * etc. Those fields on the Common request are logged as\n * dropped-bucket-2 hints; the adapter never rewrites or augments the\n * caller's prompt on their behalf. If you want lyrics or vocal\n * suppression, include them in your `prompt` text yourself.\n *\n * Output format: only `audio/mp3` and `audio/wav` are accepted; other\n * containers fail `Unsupported`. The Clip model variant is fixed at\n * `audio/mp3`.\n */\nexport type LyriaGenerateRequest = Omit<CommonGenerateMusicRequest, \"model\"> & {\n readonly model: LyriaModel\n}\n\nexport type LyriaGeneratorService = {\n readonly generate: (\n request: LyriaGenerateRequest,\n ) => Effect.Effect<GenerateResult, AiError.AiError>\n readonly streamGeneration: (\n request: LyriaGenerateRequest,\n ) => Stream.Stream<AudioChunk, AiError.AiError>\n readonly streamGenerationFrom: <E, R>(\n input: Stream.Stream<MusicSessionInput, E, R>,\n request: CommonStreamGenerateMusicRequest,\n ) => Stream.Stream<MusicStreamEvent, AiError.AiError | E, R>\n}\n\nexport class LyriaGenerator extends Context.Service<LyriaGenerator, LyriaGeneratorService>()(\n \"@betalyra/effect-uai/providers/google/LyriaGenerator\",\n) {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n}\n\n// ---------------------------------------------------------------------------\n// Codec: AudioFormat → Lyria mimeType\n// ---------------------------------------------------------------------------\n\nexport type LyriaResponseMimeType = \"audio/mp3\" | \"audio/wav\"\n\nexport const containerToMimeType: (\n container: AudioFormat[\"container\"],\n) => Effect.Effect<LyriaResponseMimeType, AiError.AiError> = Match.type<\n AudioFormat[\"container\"]\n>().pipe(\n Match.when(\"mp3\", () => Effect.succeed<LyriaResponseMimeType>(\"audio/mp3\")),\n Match.when(\"wav\", () => Effect.succeed<LyriaResponseMimeType>(\"audio/wav\")),\n Match.whenOr(\"ogg\", \"opus\", \"flac\", \"aac\", \"webm\", \"raw\", (c) =>\n Effect.fail(\n new AiError.Unsupported({\n provider: \"lyria\",\n capability: \"outputFormat\",\n reason: `Lyria 3 only produces audio/mp3 or audio/wav; ${c} is not available. Use container: \"mp3\" or \"wav\".`,\n }),\n ),\n ),\n Match.exhaustive,\n)\n\n/**\n * Is this a Lyria 3 Clip model variant? Clip is fixed at 30 s MP3, it\n * does not accept a `responseFormat` on `generationConfig` (the live\n * API rejects unknown fields). Pro accepts the documented\n * `responseFormat` knob.\n */\nconst isClipModel = (model: string): boolean => model.includes(\"clip\")\n\nexport const realizedFormat: (mime: LyriaResponseMimeType) => AudioFormat =\n Match.type<LyriaResponseMimeType>().pipe(\n Match.when(\"audio/mp3\", (): AudioFormat => ({\n container: \"mp3\",\n encoding: \"mp3\",\n sampleRate: 44100,\n channels: 2,\n })),\n Match.when(\"audio/wav\", (): AudioFormat => ({\n container: \"wav\",\n encoding: \"pcm_s16le\",\n sampleRate: 44100,\n channels: 2,\n })),\n Match.exhaustive,\n )\n\n// ---------------------------------------------------------------------------\n// Bucket-2 warn-and-drop for Common fields Lyria 3 can't honor\n// ---------------------------------------------------------------------------\n\n/**\n * Warn (don't fail) when the caller passed Common fields Lyria 3 has\n * no wire field for. Bucket-2 per capabilities policy: provider has\n * no structured interpretation; the output is still valid music, just\n * less aligned with the caller's hint. We never embed these into the\n * prompt on the caller's behalf, prompt construction is the\n * developer's job.\n */\nconst warnDroppedHints = (request: LyriaGenerateRequest): Effect.Effect<void> =>\n Effect.all(\n [\n warnDroppedWhen(request.lyrics, {\n provider: \"lyria\",\n capability: \"lyrics\",\n field: \"lyrics\",\n reason:\n \"Lyria 3 sync has no `lyrics` wire field. If you want vocals to follow specific lyrics, embed them in your prompt yourself (e.g. with `[Verse]` / `[Chorus]` tags).\",\n }),\n warnDroppedWhen(\n request.duration !== undefined ? Duration.toMillis(request.duration) : undefined,\n {\n provider: \"lyria\",\n capability: \"duration\",\n field: \"duration\",\n reason:\n \"Lyria 3 clip is fixed at 30 s; pro derives duration from prompt content. There is no wire field for duration. If you want to steer length, mention it in your prompt.\",\n },\n ),\n warnDroppedWhen(request.seed, {\n provider: \"lyria\",\n capability: \"seed\",\n field: \"seed\",\n reason:\n \"Lyria 3 (Gemini surface) does not expose a seed parameter. Output is non-deterministic across identical prompts. Use Lyria 2 (Vertex) for seeded generation.\",\n }),\n ],\n { discard: true },\n )\n\n// ---------------------------------------------------------------------------\n// Wire response schema\n// ---------------------------------------------------------------------------\n\nconst InlineData = Schema.Struct({\n mimeType: Schema.optional(Schema.String),\n data: Schema.String,\n})\n\nconst Part = Schema.Struct({\n text: Schema.optional(Schema.String),\n inlineData: Schema.optional(InlineData),\n})\n\nconst Content = Schema.Struct({\n parts: Schema.optional(Schema.Array(Part)),\n})\n\nconst Candidate = Schema.Struct({\n content: Schema.optional(Content),\n})\n\nconst Wire = Schema.Struct({\n candidates: Schema.optional(Schema.Array(Candidate)),\n})\n\nconst decodeWire = Schema.decodeUnknownEffect(Wire)\n\n// ---------------------------------------------------------------------------\n// HTTP plumbing\n// ---------------------------------------------------------------------------\n\nconst baseUrl = (cfg: Config): string =>\n cfg.baseUrl ?? \"https://generativelanguage.googleapis.com/v1beta\"\n\nconst transportFailure = sharedTransportFailure(\"lyria\")\n\nconst httpStatusError = sharedHttpStatusError(\"lyria\")\n\nconst decodeBase64ToBytes = (b64: string): Effect.Effect<Uint8Array, AiError.AiError> =>\n Result.match(Encoding.decodeBase64(b64), {\n onSuccess: Effect.succeed,\n onFailure: (cause) =>\n Effect.fail(new AiError.InvalidRequest({ provider: \"lyria\", param: \"audio\", raw: cause })),\n })\n\n/**\n * Find the first inline-data audio part and the concatenated text parts\n * from a Wire response.\n */\nconst extractParts = (\n wire: typeof Wire.Type,\n): { readonly inline?: typeof InlineData.Type; readonly text?: string } => {\n const allParts = (wire.candidates ?? []).flatMap((c) => c.content?.parts ?? [])\n const inline = allParts.find((p) => p.inlineData !== undefined)?.inlineData\n const textParts = allParts.flatMap((p) => (p.text !== undefined ? [p.text] : []))\n const text = textParts.length === 0 ? undefined : textParts.join(\"\\n\")\n return {\n ...(inline !== undefined && { inline }),\n ...(text !== undefined && { text }),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Service implementation\n// ---------------------------------------------------------------------------\n\nconst generateImpl =\n (cfg: Config) =>\n (\n request: LyriaGenerateRequest,\n ): Effect.Effect<GenerateResult, AiError.AiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const requestedContainer = (request.outputFormat?.container ??\n \"mp3\") satisfies AudioFormat[\"container\"]\n const requestedMime = yield* containerToMimeType(requestedContainer)\n // lyria-3-clip-preview has no `responseFormat` wire field and always\n // emits mp3, so there is no format to send and no 400 to surface. Rather\n // than keep a per-model table that rejects `wav`, report the format the\n // model actually produces (mp3) so the result is never mislabeled.\n // lyria-3-pro honours the requested container.\n const mimeType = isClipModel(request.model) ? (\"audio/mp3\" as const) : requestedMime\n yield* warnDroppedHints(request)\n const body = isClipModel(request.model)\n ? { contents: [{ parts: [{ text: request.prompt }] }] }\n : {\n contents: [{ parts: [{ text: request.prompt }] }],\n generationConfig: {\n responseModalities: [\"AUDIO\", \"TEXT\"],\n responseFormat: { audio: { mimeType } },\n },\n }\n const httpRequest = HttpClientRequest.post(\n `${baseUrl(cfg)}/models/${request.model}:generateContent`,\n ).pipe(\n HttpClientRequest.setHeader(\"x-goog-api-key\", Redacted.value(cfg.apiKey)),\n HttpClientRequest.bodyJsonUnsafe(body),\n )\n const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure))\n if (response.status >= 400) {\n const text = yield* response.text.pipe(Effect.orElseSucceed(() => \"\"))\n return yield* httpStatusError(response.status, text)\n }\n const json = yield* response.json.pipe(Effect.mapError(transportFailure))\n const wire = yield* decodeWire(json).pipe(\n Effect.mapError(\n (cause): AiError.AiError =>\n new AiError.GenerationFailed({ provider: \"lyria\", raw: cause }),\n ),\n )\n const { inline, text } = extractParts(wire)\n if (inline === undefined) {\n return yield* new AiError.GenerationFailed({\n provider: \"lyria\",\n raw: {\n message: \"Lyria response had no inlineData audio part.\",\n hint: \"Likely a prompt-filter rejection. Lyria filters references to real artists / copyrighted song lyrics. Rephrase with descriptive style language instead of artist names.\",\n textParts: text,\n response: json,\n },\n })\n }\n const bytes = yield* decodeBase64ToBytes(inline.data)\n const result: MusicResult = {\n audio: { format: realizedFormat(mimeType), bytes },\n provider: \"lyria\",\n ...(text !== undefined && { lyrics: text }),\n watermark: \"synthid\",\n }\n return singleVariant(result)\n })\n\nconst streamGenerationImpl =\n (cfg: Config) =>\n (\n request: LyriaGenerateRequest,\n ): Stream.Stream<AudioChunk, AiError.AiError, HttpClient.HttpClient> =>\n Stream.unwrap(\n Effect.map(generateImpl(cfg)(request), (result) =>\n Stream.succeed<AudioChunk>({ bytes: result.primary.audio.bytes }),\n ),\n )\n\n/**\n * Lyria 3 sync has no bidirectional session at the wire level, that's\n * Lyria RealTime (separate Layer). The provider Layer here also does\n * NOT register `MusicInteractiveSession`, so callers using\n * `MusicGenerator.streamGenerationFrom` against only this Layer get a\n * compile-time error before this runtime fallback ever fires.\n */\nconst streamGenerationFromUnsupported = <E, R>(\n _input: Stream.Stream<MusicSessionInput, E, R>,\n _request: CommonStreamGenerateMusicRequest,\n): Stream.Stream<MusicStreamEvent, AiError.AiError | E, R> => {\n const fail: Stream.Stream<MusicStreamEvent, AiError.AiError | E, R> = Stream.fail(\n new AiError.Unsupported({\n provider: \"lyria\",\n capability: \"streamGenerationFrom\",\n reason:\n \"Lyria 3 sync does not support bidirectional sessions. Use the Lyria RealTime Layer (WebSocket BidiGenerateMusic) for interactive mid-session prompts.\",\n }),\n )\n return fail\n}\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\nexport const make = (\n cfg: Config,\n): Effect.Effect<LyriaGeneratorService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient, (client) => ({\n generate: (request) =>\n generateImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n streamGeneration: (request) =>\n streamGenerationImpl(cfg)(request).pipe(Stream.provideService(HttpClient.HttpClient, client)),\n streamGenerationFrom: streamGenerationFromUnsupported,\n }))\n\n/**\n * Layer that registers both the provider-specific `LyriaGenerator` tag\n * and the generic `MusicGenerator` tag.\n *\n * Does NOT register the `MusicInteractiveSession` capability marker,\n * Lyria 3 sync has no bidirectional session at the wire level. Code\n * calling `MusicGenerator.streamGenerationFrom` will fail to typecheck\n * against this Layer alone, which is the intended UX.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<LyriaGenerator | MusicGenerator, never, HttpClient.HttpClient> =>\n Layer.merge(\n Layer.effect(LyriaGenerator, make(cfg)),\n Layer.effect(\n MusicGenerator,\n Effect.map(make(cfg), (s): MusicGeneratorService => ({\n generate: (req: CommonGenerateMusicRequest) =>\n s.generate({ ...req, model: req.model as LyriaModel }),\n streamGeneration: (req: CommonStreamGenerateMusicRequest) =>\n s.streamGeneration({ ...req, model: req.model as LyriaModel }),\n streamGenerationFrom: s.streamGenerationFrom,\n })),\n ),\n )\n"],"mappings":"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"}
package/dist/codec.d.mts CHANGED
@@ -1,10 +1,12 @@
1
1
  import { Option, Schema } from "effect";
2
+ import * as AiError from "@effect-uai/core/AiError";
2
3
  import { CommonRequest } from "@effect-uai/core/LanguageModel";
3
4
  import { ToolDescriptor } from "@effect-uai/core/Tool";
4
5
  import { Turn } from "@effect-uai/core/Turn";
6
+ import { ImageSource } from "@effect-uai/core/Image";
5
7
  import { Annotation, HistoryItem } from "@effect-uai/core/Items";
6
8
  declare namespace codec_d_exports {
7
- export { AccumulatedFunctionCall, Accumulator, ChunkPart, ChunkResult, GenerationConfig, GroundingMetadata, RequestBody, RequestTool, ThinkingConfig, WireChunk, accumulatorToTurn, buildRequestBody, emptyAccumulator, groundingToAnnotations, hasUrlImageSource, ingestChunk };
9
+ export { AccumulatedFunctionCall, Accumulator, ChunkPart, ChunkResult, GenerationConfig, GroundingMetadata, ImageConfig, RequestBody, RequestTool, ThinkingConfig, WireChunk, accumulatorToTurn, buildRequestBody, emptyAccumulator, groundingToAnnotations, hasUrlImageSource, httpStatusError, ingestChunk, transportFailure };
8
10
  }
9
11
  declare const GroundingMetadata: Schema.Struct<{
10
12
  readonly groundingChunks: Schema.optional<Schema.NullOr<Schema.$Array<Schema.Struct<{
@@ -35,6 +37,12 @@ declare const WireChunk: Schema.Struct<{
35
37
  readonly args: Schema.optional<Schema.Unknown>;
36
38
  }>;
37
39
  readonly thoughtSignature: Schema.optional<Schema.String>;
40
+ }>, Schema.Struct<{
41
+ readonly inlineData: Schema.Struct<{
42
+ readonly mimeType: Schema.optional<Schema.String>;
43
+ readonly data: Schema.String;
44
+ }>;
45
+ readonly thought: Schema.optional<Schema.Boolean>;
38
46
  }>]>>>;
39
47
  }>>;
40
48
  readonly groundingMetadata: Schema.optional<Schema.Struct<{
@@ -116,11 +124,18 @@ type RequestToolConfig = {
116
124
  type ThinkingConfig = {
117
125
  readonly thinkingBudget: number;
118
126
  };
127
+ type ImageConfig = {
128
+ readonly aspectRatio?: string;
129
+ readonly imageSize?: string;
130
+ };
119
131
  type GenerationConfig = {
120
132
  readonly temperature?: number;
121
133
  readonly maxOutputTokens?: number;
122
134
  readonly topP?: number;
123
135
  readonly thinkingConfig?: ThinkingConfig;
136
+ /** Image models need `["TEXT", "IMAGE"]` or they answer in prose only. */
137
+ readonly responseModalities?: ReadonlyArray<"TEXT" | "IMAGE">;
138
+ readonly imageConfig?: ImageConfig;
124
139
  /** Set together with `responseJsonSchema` to constrain output to JSON. */
125
140
  readonly responseMimeType?: string;
126
141
  /** JSON Schema constraint on the output. */
@@ -133,6 +148,10 @@ type RequestBody = {
133
148
  readonly tools?: ReadonlyArray<RequestTool>;
134
149
  readonly toolConfig?: RequestToolConfig;
135
150
  };
151
+ /** `provider` names the surface that failed, so errors read `gemini` or `lyria`. */
152
+ declare const transportFailure: (provider: string) => (cause: unknown) => AiError.AiError;
153
+ /** 413 is the inline-payload cap, which is a context limit on every surface. */
154
+ declare const httpStatusError: (provider: string) => (status: number, body: string) => AiError.AiError;
136
155
  /**
137
156
  * URL-form images need Gemini's Files API (upload, then `fileData`); we
138
157
  * don't pre-upload, so such a part would otherwise vanish from the request.
@@ -155,6 +174,8 @@ type AccumulatedFunctionCall = {
155
174
  type Accumulator = {
156
175
  readonly text: string;
157
176
  readonly reasoning: string;
177
+ /** Images the model drew, in emission order. Image models only. */
178
+ readonly images: ReadonlyArray<ImageSource>;
158
179
  readonly functionCalls: ReadonlyArray<AccumulatedFunctionCall>;
159
180
  /** Grounding citations, accumulated (de-duped by url) across chunks. */
160
181
  readonly annotations: ReadonlyArray<Annotation>;
@@ -183,6 +204,9 @@ type ChunkPart = {
183
204
  } | {
184
205
  readonly kind: "reasoning";
185
206
  readonly text: string;
207
+ } | {
208
+ readonly kind: "image";
209
+ readonly source: ImageSource;
186
210
  } | {
187
211
  readonly kind: "function_call";
188
212
  readonly id: Option.Option<string>;
@@ -198,5 +222,5 @@ type ChunkResult = {
198
222
  declare const ingestChunk: (acc: Accumulator, chunk: WireChunk) => ChunkResult;
199
223
  declare const accumulatorToTurn: (acc: Accumulator) => Turn;
200
224
  //#endregion
201
- export { AccumulatedFunctionCall, Accumulator, ChunkPart, ChunkResult, GenerationConfig, GroundingMetadata, RequestBody, RequestTool, ThinkingConfig, WireChunk, accumulatorToTurn, buildRequestBody, emptyAccumulator, groundingToAnnotations, hasUrlImageSource, ingestChunk, codec_d_exports as t };
225
+ export { AccumulatedFunctionCall, Accumulator, ChunkPart, ChunkResult, GenerationConfig, GroundingMetadata, ImageConfig, RequestBody, RequestTool, ThinkingConfig, WireChunk, accumulatorToTurn, buildRequestBody, emptyAccumulator, groundingToAnnotations, hasUrlImageSource, httpStatusError, ingestChunk, codec_d_exports as t, transportFailure };
202
226
  //# sourceMappingURL=codec.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"codec.d.mts","names":[],"sources":["../src/codec.ts"],"mappings":";;;;;;;;cAoEa,mBAAiB,OAAA;;;;;;;;KAGlB,2BAA2B,kBAAkB;cAU5C,4BAAsB,OACvB,cAAc,0CACvB,cAAc;cA+BJ,WAAS,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAIV,mBAAmB,UAAU;KAMpC;WACU;;WACA;aAAuB;aAA2B;;;WAElD;aACE;aACA;aACA;;WAEF;;WAGA;aACE;aACA;aACA,UAAU;;;KAItB;WACM;WACA,OAAO,cAAc;;KAG3B;WACM,OAAO;aAAyB;;;;;;;;;;KAU/B;WAEG,sBAAsB;aACpB;aACA;aACA,YAAY;;;WAGd;;WACA;;WACA;;KAEV;WACM;aACE;aACA,uBAAuB;;;KAIxB;WACD;;KAGC;WACD;WACA;WACA;WACA,iBAAiB;;WAEjB;;WAEA,qBAAqB;;KAGpB;WACD,UAAU,cAAc;WACxB,oBAAoB;WACpB,mBAAmB;WACnB,QAAQ,cAAc;WACtB,aAAa;;;;;;;;cA8QX,oBAAiB,SAAa,cAAc;cAO5C,mBAAgB,SAClB,cAAc,cAAY,kBACjB,OAAO,OAAO,mBAAiB,QAC1C,cAAc,iBAAe,sBACf,cAAc,cAAY,aACnC,gCACX;KAoCS;;WAED;WACA;;WAEA,YAAY,OAAO;;WAEnB,WAAW,OAAO;;WAElB;;KAGC;WACD;WACA;WACA,eAAe,cAAc;;WAE7B,aAAa,cAAc;WAC3B,cAAc,OAAO;WACrB;aACE;aACA;aACA;aACA;eAAkC;;aAClC;eAAmC;;;;cAInC,kBAAkB;;;;;;KAcnB;WACG;WAAuB;;WACvB;WAA4B;;WAE5B;WACA,IAAI,OAAO;WACX,WAAW,OAAO;WAClB;WACA;;KAGH;WACD,aAAa;WACb,OAAO,cAAc;WACrB;;cAuGE,cAAW,KAAS,aAAW,OAAS,cAAY;cA0DpD,oBAAiB,KAAS,gBAAc"}
1
+ {"version":3,"file":"codec.d.mts","names":[],"sources":["../src/codec.ts"],"mappings":";;;;;;;;;;cAsFa,mBAAiB,OAAA;;;;;;;;KAGlB,2BAA2B,kBAAkB;cAU5C,4BAAsB,OACvB,cAAc,0CACvB,cAAc;cA+BJ,WAAS,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAIV,mBAAmB,UAAU;KAMpC;WACU;;WACA;aAAuB;aAA2B;;;WAElD;aACE;aACA;aACA;;WAEF;;WAGA;aACE;aACA;aACA,UAAU;;;KAItB;WACM;WACA,OAAO,cAAc;;KAG3B;WACM,OAAO;aAAyB;;;;;;;;;;KAU/B;WAEG,sBAAsB;aACpB;aACA;aACA,YAAY;;;WAGd;;WACA;;WACA;;KAEV;WACM;aACE;aACA,uBAAuB;;;KAIxB;WACD;;KAGC;WACD;WACA;;KAGC;WACD;WACA;WACA;WACA,iBAAiB;;WAEjB,qBAAqB;WACrB,cAAc;;WAEd;;WAEA,qBAAqB;;KAGpB;WACD,UAAU,cAAc;WACxB,oBAAoB;WACpB,mBAAmB;WACnB,QAAQ,cAAc;WACtB,aAAa;;;cAiRX,mBAAgB,sBACV,mBACC,QAAQ;;cAIf,kBAAe,sBACT,gBACF,iBAAiB,QAAQ;;;;;;;cAkC7B,oBAAiB,SAAa,cAAc;cAO5C,mBAAgB,SAClB,cAAc,cAAY,kBACjB,OAAO,OAAO,mBAAiB,QAC1C,cAAc,iBAAe,sBACf,cAAc,cAAY,aACnC,gCACX;KAoCS;;WAED;WACA;;WAEA,YAAY,OAAO;;WAEnB,WAAW,OAAO;;WAElB;;KAGC;WACD;WACA;;WAEA,QAAQ,cAAc;WACtB,eAAe,cAAc;;WAE7B,aAAa,cAAc;WAC3B,cAAc,OAAO;WACrB;aACE;aACA;aACA;aACA;eAAkC;;aAClC;eAAmC;;;;cAInC,kBAAkB;;;;;;KAenB;WACG;WAAuB;;WACvB;WAA4B;;WAC5B;WAAwB,QAAQ;;WAEhC;WACA,IAAI,OAAO;WACX,WAAW,OAAO;WAClB;WACA;;KAGH;WACD,aAAa;WACb,OAAO,cAAc;WACrB;;cA6HE,cAAW,KAAS,aAAW,OAAS,cAAY;cA8DpD,oBAAiB,KAAS,gBAAc"}
package/dist/codec.mjs CHANGED
@@ -1,5 +1,7 @@
1
1
  import { n as __exportAll } from "./rolldown-runtime-EAsCxrDo.mjs";
2
2
  import { Array as Array$1, Encoding, Match, Option, Result, Schema, pipe } from "effect";
3
+ import * as AiError from "@effect-uai/core/AiError";
4
+ import { imageBase64 } from "@effect-uai/core/Image";
3
5
  //#region src/codec.ts
4
6
  var codec_exports = /* @__PURE__ */ __exportAll({
5
7
  GroundingMetadata: () => GroundingMetadata,
@@ -9,7 +11,9 @@ var codec_exports = /* @__PURE__ */ __exportAll({
9
11
  emptyAccumulator: () => emptyAccumulator,
10
12
  groundingToAnnotations: () => groundingToAnnotations,
11
13
  hasUrlImageSource: () => hasUrlImageSource,
12
- ingestChunk: () => ingestChunk
14
+ httpStatusError: () => httpStatusError,
15
+ ingestChunk: () => ingestChunk,
16
+ transportFailure: () => transportFailure
13
17
  });
14
18
  const TextPart = Schema.Struct({
15
19
  text: Schema.String,
@@ -34,7 +38,24 @@ const FunctionCallPart = Schema.Struct({
34
38
  }),
35
39
  thoughtSignature: Schema.optional(Schema.String)
36
40
  });
37
- const Part = Schema.Union([TextPart, FunctionCallPart]);
41
+ /**
42
+ * An image the model drew, as a part of its own turn. Only image models
43
+ * emit these, and only when the request asked for the IMAGE modality.
44
+ * `thought: true` marks an interim image drawn while planning, which is
45
+ * not the answer and is dropped the same way thought text is.
46
+ */
47
+ const InlineDataPart = Schema.Struct({
48
+ inlineData: Schema.Struct({
49
+ mimeType: Schema.optional(Schema.String),
50
+ data: Schema.String
51
+ }),
52
+ thought: Schema.optional(Schema.Boolean)
53
+ });
54
+ const Part = Schema.Union([
55
+ TextPart,
56
+ FunctionCallPart,
57
+ InlineDataPart
58
+ ]);
38
59
  const Content = Schema.Struct({
39
60
  role: Schema.optional(Schema.String),
40
61
  parts: Schema.optional(Schema.Array(Part))
@@ -71,6 +92,7 @@ const blockText = Match.type().pipe(Match.discriminatorsExhaustive("type")({
71
92
  input_text: (b) => b.text,
72
93
  input_image: () => "",
73
94
  output_text: (b) => b.text,
95
+ output_image: () => "",
74
96
  refusal: (b) => b.text
75
97
  }));
76
98
  const messageText = (message) => message.content.map(blockText).join("");
@@ -91,6 +113,7 @@ const blockToParts = Match.type().pipe(Match.discriminatorsExhaustive("type")({
91
113
  input_text: (b) => b.text.length === 0 ? [] : [{ text: b.text }],
92
114
  input_image: (b) => imageSourceToParts(b.source),
93
115
  output_text: (b) => b.text.length === 0 ? [] : [{ text: b.text }],
116
+ output_image: (b) => imageSourceToParts(b.source),
94
117
  refusal: () => []
95
118
  }));
96
119
  const messageToContent = (message) => {
@@ -203,6 +226,41 @@ const toolChoiceToFunctionCallingConfig = (toolChoice) => Match.value(toolChoice
203
226
  mode: "ANY",
204
227
  allowedFunctionNames: [tc.name]
205
228
  })));
229
+ /** `provider` names the surface that failed, so errors read `gemini` or `lyria`. */
230
+ const transportFailure = (provider) => (cause) => new AiError.Unavailable({
231
+ provider,
232
+ raw: cause
233
+ });
234
+ /** 413 is the inline-payload cap, which is a context limit on every surface. */
235
+ const httpStatusError = (provider) => (status, body) => Match.value(status).pipe(Match.when(429, () => new AiError.RateLimited({
236
+ provider,
237
+ raw: body
238
+ })), Match.whenOr(408, 504, () => new AiError.Timeout({
239
+ provider,
240
+ raw: body
241
+ })), Match.when(401, () => new AiError.AuthFailed({
242
+ provider,
243
+ subtype: "auth",
244
+ raw: body
245
+ })), Match.when(403, () => new AiError.AuthFailed({
246
+ provider,
247
+ subtype: "permission",
248
+ raw: body
249
+ })), Match.when(402, () => new AiError.AuthFailed({
250
+ provider,
251
+ subtype: "billing",
252
+ raw: body
253
+ })), Match.when(413, () => new AiError.ContextLengthExceeded({
254
+ provider,
255
+ raw: body
256
+ })), Match.when((n) => n >= 500, (n) => new AiError.Unavailable({
257
+ provider,
258
+ status: n,
259
+ raw: body
260
+ })), Match.orElse(() => new AiError.InvalidRequest({
261
+ provider,
262
+ raw: body
263
+ })));
206
264
  /**
207
265
  * URL-form images need Gemini's Files API (upload, then `fileData`); we
208
266
  * don't pre-upload, so such a part would otherwise vanish from the request.
@@ -233,16 +291,21 @@ const finishReasonToStop = (reason) => Option.match(reason, {
233
291
  const emptyAccumulator = {
234
292
  text: "",
235
293
  reasoning: "",
294
+ images: [],
236
295
  functionCalls: [],
237
296
  annotations: [],
238
297
  finishReason: Option.none(),
239
298
  usage: {}
240
299
  };
241
- const isTextPart = (p) => "text" in p;
242
300
  const textToChunkParts = (p) => p.text.length === 0 ? [] : [{
243
301
  kind: p.thought === true ? "reasoning" : "text",
244
302
  text: p.text
245
303
  }];
304
+ /** Interim images drawn while planning are dropped, as thought text is. */
305
+ const inlineDataToChunkParts = (p) => p.thought === true ? [] : [{
306
+ kind: "image",
307
+ source: imageBase64(p.inlineData.data, p.inlineData.mimeType ?? "image/png")
308
+ }];
246
309
  const functionCallToChunkParts = (p) => [{
247
310
  kind: "function_call",
248
311
  id: Option.fromNullishOr(p.functionCall.id),
@@ -250,9 +313,11 @@ const functionCallToChunkParts = (p) => [{
250
313
  name: p.functionCall.name,
251
314
  args: p.functionCall.args ?? {}
252
315
  }];
253
- const partToChunkParts = (p) => isTextPart(p) ? textToChunkParts(p) : functionCallToChunkParts(p);
316
+ /** Parts are distinguished by which key they carry, not by a discriminator. */
317
+ const partToChunkParts = Match.type().pipe(Match.when({ text: Match.string }, textToChunkParts), Match.when({ inlineData: Match.defined }, inlineDataToChunkParts), Match.when({ functionCall: Match.defined }, functionCallToChunkParts), Match.exhaustive);
254
318
  const chunkParts = (chunk) => pipe(chunk.candidates?.[0]?.content?.parts ?? [], Array$1.flatMap(partToChunkParts));
255
319
  const sumStrings = (parts, kind) => pipe(parts, Array$1.filterMap((p) => p.kind === kind ? Result.succeed(p.text) : Result.failVoid)).join("");
320
+ const collectImages = (parts) => pipe(parts, Array$1.filterMap((p) => p.kind === "image" ? Result.succeed(p.source) : Result.failVoid));
256
321
  const collectFunctionCalls = (parts) => pipe(parts, Array$1.filterMap((p) => p.kind === "function_call" ? Result.succeed(p) : Result.failVoid));
257
322
  const mergeUsage = (prev, next) => next === void 0 ? prev : {
258
323
  ...prev,
@@ -289,6 +354,7 @@ const ingestChunk = (acc, chunk) => {
289
354
  accumulator: {
290
355
  text: acc.text + sumStrings(parts, "text"),
291
356
  reasoning: acc.reasoning + sumStrings(parts, "reasoning"),
357
+ images: Array$1.appendAll(acc.images, collectImages(parts)),
292
358
  functionCalls: appendFunctionCalls(acc.functionCalls, collectFunctionCalls(parts)),
293
359
  annotations: mergeAnnotations(acc.annotations, groundingToAnnotations(chunk.candidates?.[0]?.groundingMetadata)),
294
360
  finishReason: Option.orElse(finishReason, () => acc.finishReason),
@@ -300,15 +366,25 @@ const reasoningItems = (acc) => acc.reasoning.length > 0 ? [{
300
366
  type: "reasoning",
301
367
  summary: acc.reasoning
302
368
  }] : [];
303
- const assistantMessageItems = (acc) => acc.text.length === 0 ? [] : [{
304
- type: "message",
305
- role: "assistant",
306
- content: [{
369
+ /**
370
+ * Image models interleave text and image parts, and often answer with an
371
+ * image and no prose at all, so the message exists when either is present.
372
+ */
373
+ const assistantMessageItems = (acc) => {
374
+ const content = [...acc.text.length === 0 ? [] : [{
307
375
  type: "output_text",
308
376
  text: acc.text,
309
377
  ...acc.annotations.length > 0 && { annotations: acc.annotations }
310
- }]
311
- }];
378
+ }], ...acc.images.map((source) => ({
379
+ type: "output_image",
380
+ source
381
+ }))];
382
+ return content.length === 0 ? [] : [{
383
+ type: "message",
384
+ role: "assistant",
385
+ content
386
+ }];
387
+ };
312
388
  const functionCallItems = (acc) => pipe(acc.functionCalls, Array$1.map((c) => {
313
389
  const gemini = {
314
390
  ...Option.isSome(c.providerId) && { id: c.providerId.value },
@@ -332,6 +408,6 @@ const accumulatorToTurn = (acc) => ({
332
408
  ]
333
409
  });
334
410
  //#endregion
335
- export { GroundingMetadata, WireChunk, accumulatorToTurn, buildRequestBody, emptyAccumulator, groundingToAnnotations, hasUrlImageSource, ingestChunk, codec_exports as t };
411
+ export { GroundingMetadata, WireChunk, accumulatorToTurn, buildRequestBody, emptyAccumulator, groundingToAnnotations, hasUrlImageSource, httpStatusError, ingestChunk, codec_exports as t, transportFailure };
336
412
 
337
413
  //# sourceMappingURL=codec.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"codec.mjs","names":["Arr"],"sources":["../src/codec.ts"],"sourcesContent":["import { Array as Arr, Encoding, Match, Option, Result, Schema, pipe } from \"effect\"\nimport type {\n ContentBlock,\n ToolCall,\n InputImage,\n HistoryItem,\n Message,\n Annotation,\n} from \"@effect-uai/core/Items\"\nimport type { ToolDescriptor } from \"@effect-uai/core/Tool\"\nimport type { Turn } from \"@effect-uai/core/Turn\"\nimport type { CommonRequest } from \"@effect-uai/core/LanguageModel\"\n\n// ---------------------------------------------------------------------------\n// Wire schemas - the subset of Gemini's streamGenerateContent payload we\n// consume. Reference: https://ai.google.dev/api/generate-content\n// ---------------------------------------------------------------------------\n\nconst TextPart = Schema.Struct({\n text: Schema.String,\n /**\n * Gemini's flag for chain-of-thought parts. When `true`, the part is\n * reasoning trace, not the model's user-facing answer. Maps onto\n * `reasoning_delta { kind: \"trace\" }` in the canonical view.\n */\n thought: Schema.optional(Schema.Boolean),\n})\n\n/**\n * Gemini function-call part. Args are delivered as a whole JSON *object*\n * in a single chunk (Gemini does not stream tool-call args). On Gemini 3\n * the response also carries an `id` per call, which we echo back on the\n * corresponding `functionResponse`.\n */\nconst FunctionCallPart = Schema.Struct({\n functionCall: Schema.Struct({\n id: Schema.optional(Schema.String),\n name: Schema.String,\n args: Schema.optional(Schema.Unknown),\n }),\n // Gemini 3 thinking models attach an opaque signature to each function-call\n // part that must be replayed on the next request, or the call is rejected.\n thoughtSignature: Schema.optional(Schema.String),\n})\n\nconst Part = Schema.Union([TextPart, FunctionCallPart])\ntype WireFunctionCallPart = typeof FunctionCallPart.Type\n\nconst Content = Schema.Struct({\n role: Schema.optional(Schema.String),\n parts: Schema.optional(Schema.Array(Part)),\n})\n\n// Google grounding metadata. On a grounded response the candidate carries\n// `groundingChunks[].web.{uri,title}` - the structured form of the inline `[n]`\n// / trailing `**Sources:**` list the model renders into the answer text.\n// Decoded defensively (every field optional) as it is absent on ungrounded\n// turns. Shared with the Interactions deep-research surface via `codec.ts`.\nconst GroundingChunk = Schema.Struct({\n web: Schema.optional(\n Schema.NullOr(\n Schema.Struct({\n uri: Schema.optional(Schema.NullOr(Schema.String)),\n title: Schema.optional(Schema.NullOr(Schema.String)),\n }),\n ),\n ),\n})\nexport const GroundingMetadata = Schema.Struct({\n groundingChunks: Schema.optional(Schema.NullOr(Schema.Array(GroundingChunk))),\n})\nexport type GroundingMetadata = typeof GroundingMetadata.Type\n\n// Two citations are the same source when they point at the same url.\nconst sameUrlCitation = (a: Annotation, b: Annotation): boolean =>\n a.type === \"url_citation\" && b.type === \"url_citation\" && a.url === b.url\n\n// Grounding chunks → `url_citation` annotations, de-duped by url across every\n// metadata block passed. `web.uri` is a `grounding-api-redirect` URL and\n// `web.title` the bare domain; both are kept as-is (the redirect resolves to\n// the real page).\nexport const groundingToAnnotations = (\n ...metas: ReadonlyArray<GroundingMetadata | null | undefined>\n): ReadonlyArray<Annotation> =>\n pipe(\n metas,\n Arr.flatMap((meta) => meta?.groundingChunks ?? []),\n Arr.filterMap((chunk) =>\n chunk.web?.uri == null\n ? Result.failVoid\n : Result.succeed<Annotation>({\n type: \"url_citation\",\n url: chunk.web.uri,\n title: chunk.web.title ?? chunk.web.uri,\n }),\n ),\n Arr.dedupeWith(sameUrlCitation),\n )\n\nconst Candidate = Schema.Struct({\n content: Schema.optional(Content),\n groundingMetadata: Schema.optional(GroundingMetadata),\n finishReason: Schema.optional(Schema.String),\n index: Schema.optional(Schema.Number),\n})\n\nconst UsageMetadata = Schema.Struct({\n promptTokenCount: Schema.optional(Schema.Number),\n candidatesTokenCount: Schema.optional(Schema.Number),\n totalTokenCount: Schema.optional(Schema.Number),\n cachedContentTokenCount: Schema.optional(Schema.Number),\n thoughtsTokenCount: Schema.optional(Schema.Number),\n})\n\nexport const WireChunk = Schema.Struct({\n candidates: Schema.optional(Schema.Array(Candidate)),\n usageMetadata: Schema.optional(UsageMetadata),\n})\nexport type WireChunk = typeof WireChunk.Type\n\n// ---------------------------------------------------------------------------\n// History → request body\n// ---------------------------------------------------------------------------\n\ntype RequestPart =\n | { readonly text: string }\n | { readonly inlineData: { readonly mimeType: string; readonly data: string } }\n | {\n readonly functionCall: {\n readonly id?: string\n readonly name: string\n readonly args: unknown\n }\n readonly thoughtSignature?: string\n }\n | {\n readonly functionResponse: {\n readonly id?: string\n readonly name: string\n readonly response: Record<string, unknown>\n }\n }\n\ntype RequestContent = {\n readonly role: \"user\" | \"model\"\n readonly parts: ReadonlyArray<RequestPart>\n}\n\ntype RequestSystemInstruction = {\n readonly parts: ReadonlyArray<{ readonly text: string }>\n}\n\n/**\n * Gemini's tool declaration. A `tools[]` entry is either a function-declaration\n * block (translated from `ToolDescriptor`s) or one of the native hosted-tool\n * entries. The native entries are empty objects keyed camelCase per the REST\n * v1beta `Tool` object, and sit as siblings of the declarations in the same\n * `tools` array.\n */\nexport type RequestTool =\n | {\n readonly functionDeclarations: ReadonlyArray<{\n readonly name: string\n readonly description: string\n readonly parameters: Record<string, unknown>\n }>\n }\n | { readonly googleSearch: {} }\n | { readonly urlContext: {} }\n | { readonly codeExecution: {} }\n\ntype RequestToolConfig = {\n readonly functionCallingConfig: {\n readonly mode: \"AUTO\" | \"ANY\" | \"NONE\"\n readonly allowedFunctionNames?: ReadonlyArray<string>\n }\n}\n\nexport type ThinkingConfig = {\n readonly thinkingBudget: number\n}\n\nexport type GenerationConfig = {\n readonly temperature?: number\n readonly maxOutputTokens?: number\n readonly topP?: number\n readonly thinkingConfig?: ThinkingConfig\n /** Set together with `responseJsonSchema` to constrain output to JSON. */\n readonly responseMimeType?: string\n /** JSON Schema constraint on the output. */\n readonly responseJsonSchema?: Record<string, unknown>\n}\n\nexport type RequestBody = {\n readonly contents: ReadonlyArray<RequestContent>\n readonly systemInstruction?: RequestSystemInstruction\n readonly generationConfig?: GenerationConfig\n readonly tools?: ReadonlyArray<RequestTool>\n readonly toolConfig?: RequestToolConfig\n}\n\nconst blockText = Match.type<ContentBlock>().pipe(\n Match.discriminatorsExhaustive(\"type\")({\n input_text: (b) => b.text,\n input_image: () => \"\",\n output_text: (b) => b.text,\n refusal: (b) => b.text,\n }),\n)\n\nconst messageText = (message: Message): string => message.content.map(blockText).join(\"\")\n\n/**\n * Gemini's `inlineData` form expects a base64 payload. URL-form images\n * would need to go through Gemini's Files API (upload then `fileData`\n * with the returned URI); pre-uploading isn't free, so we skip those for\n * now and document as a follow-up.\n */\nconst imageSourceToParts = Match.type<InputImage[\"source\"]>().pipe(\n Match.tag(\"url\", (): ReadonlyArray<RequestPart> => []),\n Match.tag(\"base64\", (s): ReadonlyArray<RequestPart> => [\n { inlineData: { mimeType: s.mimeType, data: s.base64 } },\n ]),\n Match.tag(\"bytes\", (s): ReadonlyArray<RequestPart> => [\n { inlineData: { mimeType: s.mimeType, data: Encoding.encodeBase64(s.bytes) } },\n ]),\n Match.exhaustive,\n)\n\nconst blockToParts = Match.type<ContentBlock>().pipe(\n Match.discriminatorsExhaustive(\"type\")({\n input_text: (b): ReadonlyArray<RequestPart> => (b.text.length === 0 ? [] : [{ text: b.text }]),\n input_image: (b): ReadonlyArray<RequestPart> => imageSourceToParts(b.source),\n output_text: (b): ReadonlyArray<RequestPart> => (b.text.length === 0 ? [] : [{ text: b.text }]),\n // Refusals are assistant-side content; they don't round-trip into Gemini's\n // request body as parts. Skip.\n refusal: (): ReadonlyArray<RequestPart> => [],\n }),\n)\n\nconst messageToContent = (message: Message): Result.Result<RequestContent, void> => {\n const parts = pipe(message.content, Arr.flatMap(blockToParts))\n if (parts.length === 0) return Result.failVoid\n return Match.value(message.role).pipe(\n Match.when(\"user\", () => Result.succeed({ role: \"user\" as const, parts })),\n Match.when(\"assistant\", () => Result.succeed({ role: \"model\" as const, parts })),\n Match.when(\"system\", () => Result.failVoid),\n Match.exhaustive,\n )\n}\n\nconst systemMessageText = (message: Message): Result.Result<string, void> => {\n if (message.role !== \"system\") return Result.failVoid\n const text = messageText(message)\n return text.length === 0 ? Result.failVoid : Result.succeed(text)\n}\n\nconst allMessages = (history: ReadonlyArray<HistoryItem>): ReadonlyArray<Message> =>\n pipe(\n history,\n Arr.filterMap((item) => (item.type === \"message\" ? Result.succeed(item) : Result.failVoid)),\n )\n\n// ---------------------------------------------------------------------------\n// Function-call round-trip\n//\n// `ToolCall.arguments` and `ToolCallOutput.output` are JSON-encoded\n// strings; Gemini's `functionCall.args` and `functionResponse.response`\n// expect parsed JSON *objects*. Decode via Schema:\n// - object payload → use as-is\n// - scalar / array / malformed → wrap as `{ output: <raw string> }` so the\n// model still sees *some* response without crashing the request.\n// ---------------------------------------------------------------------------\n\nconst JsonObject = Schema.Record(Schema.String, Schema.Unknown)\nconst decodeJsonObject = Schema.decodeResult(Schema.fromJsonString(JsonObject))\n\nconst parsedJsonObject =\n (fallback: (raw: string) => Record<string, unknown>) =>\n (encoded: string): Record<string, unknown> =>\n pipe(\n decodeJsonObject(encoded),\n Result.match({\n onSuccess: (v) => v,\n onFailure: () => fallback(encoded),\n }),\n )\n\nconst parsedArgs = parsedJsonObject(() => ({}))\nconst parsedResponse = parsedJsonObject((raw) => ({ output: raw }))\n\n/**\n * `ToolCallOutput` only carries `call_id`; Gemini's `functionResponse`\n * requires the declared function `name`. Resolve the name by scanning prior\n * `function_call` items in the history for a matching `call_id`. If we\n * cannot resolve, fall back to `call_id` as the name - imperfect but\n * preserves stream shape so the model sees *some* response.\n */\nconst isFunctionCallItem = (item: HistoryItem): item is ToolCall => item.type === \"function_call\"\n\nconst nameForCallId = (\n history: ReadonlyArray<HistoryItem>,\n call_id: string,\n): Option.Option<string> =>\n pipe(\n history,\n Arr.findFirst((item): item is ToolCall => isFunctionCallItem(item) && item.call_id === call_id),\n Option.map((f) => f.name),\n )\n\n/**\n * Extract the Gemini-3 wire id and thought signature we stashed in\n * `providerData` on the way out. Schema-driven so the shape lives in one\n * place; failure or absence → `Option.none()`.\n */\nconst ProviderDataGemini = Schema.Struct({\n gemini: Schema.Struct({\n id: Schema.optional(Schema.String),\n thoughtSignature: Schema.optional(Schema.String),\n }),\n})\nconst decodeGemini = Schema.decodeUnknownResult(ProviderDataGemini)\n\nconst geminiField = (\n item: ToolCall,\n pick: (g: {\n readonly id?: string | undefined\n readonly thoughtSignature?: string | undefined\n }) => string | undefined,\n): Option.Option<string> =>\n pipe(\n decodeGemini(item.providerData),\n Result.match({\n onSuccess: (d) => Option.fromNullishOr(pick(d.gemini)),\n onFailure: () => Option.none<string>(),\n }),\n )\n\nconst providerIdFor = (item: ToolCall): Option.Option<string> => geminiField(item, (g) => g.id)\n\n// The originating call's Gemini id, looked up from history by our `call_id`.\n// Gemini 3 maps a `functionResponse` back to its call by this id, so parallel\n// calls to the same function are mis-paired if we drop it.\nconst providerIdForCallId = (\n history: ReadonlyArray<HistoryItem>,\n call_id: string,\n): Option.Option<string> =>\n pipe(\n history,\n Arr.findFirst((item): item is ToolCall => isFunctionCallItem(item) && item.call_id === call_id),\n Option.flatMap(providerIdFor),\n )\n\nconst signatureFor = (item: ToolCall): Option.Option<string> =>\n geminiField(item, (g) => g.thoughtSignature)\n\nconst itemToContent =\n (history: ReadonlyArray<HistoryItem>) =>\n (item: HistoryItem): Result.Result<RequestContent, void> =>\n Match.value(item).pipe(\n Match.discriminatorsExhaustive(\"type\")({\n message: messageToContent,\n function_call: (f) =>\n Result.succeed({\n role: \"model\" as const,\n parts: [\n {\n ...Option.match(signatureFor(f), {\n onSome: (thoughtSignature) => ({ thoughtSignature }),\n onNone: () => ({}),\n }),\n functionCall: {\n ...Option.match(providerIdFor(f), {\n onSome: (id) => ({ id }),\n onNone: () => ({}),\n }),\n name: f.name,\n args: parsedArgs(f.arguments),\n },\n },\n ],\n }),\n function_call_output: (o) =>\n Result.succeed({\n role: \"user\" as const,\n parts: [\n {\n functionResponse: {\n ...Option.match(providerIdForCallId(history, o.call_id), {\n onSome: (id) => ({ id }),\n onNone: () => ({}),\n }),\n name: Option.getOrElse(nameForCallId(history, o.call_id), () => o.call_id),\n response: parsedResponse(o.output),\n },\n },\n ],\n }),\n reasoning: () => Result.failVoid,\n }),\n )\n\n// ---------------------------------------------------------------------------\n// Tool descriptors → Gemini `functionDeclarations`. Gemini accepts only a\n// strict OpenAPI 3.0 subset for `parameters`; strip JSON-Schema keys it\n// rejects (`$schema`, `$ref`, `additionalProperties`, `oneOf`,\n// `definitions`).\n// ---------------------------------------------------------------------------\n\nconst UNSUPPORTED_SCHEMA_KEYS: ReadonlySet<string> = new Set([\n \"$schema\",\n \"$ref\",\n \"$defs\",\n \"definitions\",\n \"additionalProperties\",\n \"oneOf\",\n])\n\nconst sanitizeSchema = (schema: unknown): unknown => {\n if (Array.isArray(schema)) return schema.map(sanitizeSchema)\n if (schema === null || typeof schema !== \"object\") return schema\n return pipe(\n Object.entries(schema as Record<string, unknown>),\n Arr.filterMap(([k, v]) =>\n UNSUPPORTED_SCHEMA_KEYS.has(k)\n ? Result.failVoid\n : Result.succeed([k, sanitizeSchema(v)] as const),\n ),\n Object.fromEntries,\n )\n}\n\nconst toolDescriptorsToTools = (\n tools: ReadonlyArray<ToolDescriptor>,\n): ReadonlyArray<RequestTool> =>\n tools.length === 0\n ? []\n : [\n {\n functionDeclarations: tools.map((t) => ({\n name: t.name,\n description: t.description,\n parameters: sanitizeSchema(t.inputSchema) as Record<string, unknown>,\n })),\n },\n ]\n\n/**\n * Map our cross-provider `toolChoice` onto Gemini's `functionCallingConfig`.\n * Gemini honours the same three modes plus a function allow-list, so the\n * choice is mapped, not dropped. `undefined` keeps the API default (`AUTO`).\n */\nconst toolChoiceToFunctionCallingConfig = (\n toolChoice: CommonRequest[\"toolChoice\"],\n): RequestToolConfig[\"functionCallingConfig\"] =>\n Match.value(toolChoice ?? \"auto\").pipe(\n Match.when(\"auto\", () => ({ mode: \"AUTO\" as const })),\n Match.when(\"required\", () => ({ mode: \"ANY\" as const })),\n Match.when(\"none\", () => ({ mode: \"NONE\" as const })),\n Match.orElse((tc) => ({ mode: \"ANY\" as const, allowedFunctionNames: [tc.name] })),\n )\n\n/**\n * URL-form images need Gemini's Files API (upload, then `fileData`); we\n * don't pre-upload, so such a part would otherwise vanish from the request.\n * The caller fails the request with `Unsupported` instead of silently\n * dropping the image.\n */\nexport const hasUrlImageSource = (history: ReadonlyArray<HistoryItem>): boolean =>\n history.some(\n (item) =>\n item.type === \"message\" &&\n item.content.some((b) => b.type === \"input_image\" && b.source._tag === \"url\"),\n )\n\nexport const buildRequestBody = (\n history: ReadonlyArray<HistoryItem>,\n generationConfig: Option.Option<GenerationConfig>,\n tools: ReadonlyArray<ToolDescriptor> = [],\n providerToolEntries: ReadonlyArray<RequestTool> = [],\n toolChoice: CommonRequest[\"toolChoice\"] = undefined,\n): RequestBody => {\n const systemTexts = pipe(allMessages(history), Arr.filterMap(systemMessageText))\n const contents = pipe(history, Arr.filterMap(itemToContent(history)))\n const functionTools = toolDescriptorsToTools(tools)\n const requestTools = [...functionTools, ...providerToolEntries]\n return {\n contents,\n ...(systemTexts.length > 0 && {\n systemInstruction: { parts: [{ text: systemTexts.join(\"\\n\") }] },\n }),\n ...Option.match(generationConfig, {\n onNone: () => ({}),\n onSome: (cfg) => ({ generationConfig: cfg }),\n }),\n ...(requestTools.length > 0 && { tools: requestTools }),\n // Only force a function-calling mode when there are function declarations;\n // a grounding-only request must keep the API default.\n ...(functionTools.length > 0 && {\n toolConfig: { functionCallingConfig: toolChoiceToFunctionCallingConfig(toolChoice) },\n }),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Stream-level state - accumulate chunk text + final usage/finish.\n//\n// `Accumulator` is immutable; `ingestChunk` returns a fresh one per chunk.\n// Drive it via `Stream.mapAccum` in the consumer.\n// ---------------------------------------------------------------------------\n\nconst finishReasonToStop = (reason: Option.Option<string>): Turn[\"stop_reason\"] =>\n Option.match(reason, {\n onNone: () => \"stop\" as const,\n onSome: (r) => (r === \"MAX_TOKENS\" ? (\"max_tokens\" as const) : (\"stop\" as const)),\n })\n\nexport type AccumulatedFunctionCall = {\n /** Synthesized id-or-name we surface as `call_id` on the canonical item. */\n readonly callId: string\n readonly name: string\n /** Wire id from Gemini 3, when present - echoed back on `functionResponse`. */\n readonly providerId: Option.Option<string>\n /** Thought signature from Gemini 3, replayed on the next request's part. */\n readonly signature: Option.Option<string>\n /** Args as JSON-encoded string, mirroring `Items.ToolCall.arguments`. */\n readonly arguments: string\n}\n\nexport type Accumulator = {\n readonly text: string\n readonly reasoning: string\n readonly functionCalls: ReadonlyArray<AccumulatedFunctionCall>\n /** Grounding citations, accumulated (de-duped by url) across chunks. */\n readonly annotations: ReadonlyArray<Annotation>\n readonly finishReason: Option.Option<string>\n readonly usage: {\n readonly input_tokens?: number\n readonly output_tokens?: number\n readonly total_tokens?: number\n readonly input_tokens_details?: { readonly cached_tokens?: number }\n readonly output_tokens_details?: { readonly reasoning_tokens?: number }\n }\n}\n\nexport const emptyAccumulator: Accumulator = {\n text: \"\",\n reasoning: \"\",\n functionCalls: [],\n annotations: [],\n finishReason: Option.none(),\n usage: {},\n}\n\n/**\n * One part's worth of streamable output. `text` and `reasoning` are\n * incremental string deltas; `function_call` arrives whole-in-one-chunk\n * (Gemini does not stream tool-call args).\n */\nexport type ChunkPart =\n | { readonly kind: \"text\"; readonly text: string }\n | { readonly kind: \"reasoning\"; readonly text: string }\n | {\n readonly kind: \"function_call\"\n readonly id: Option.Option<string>\n readonly signature: Option.Option<string>\n readonly name: string\n readonly args: unknown\n }\n\nexport type ChunkResult = {\n readonly accumulator: Accumulator\n readonly parts: ReadonlyArray<ChunkPart>\n readonly finished: boolean\n}\n\nconst isTextPart = (p: typeof Part.Type): p is typeof TextPart.Type => \"text\" in p\n\nconst textToChunkParts = (p: typeof TextPart.Type): ReadonlyArray<ChunkPart> =>\n p.text.length === 0 ? [] : [{ kind: p.thought === true ? \"reasoning\" : \"text\", text: p.text }]\n\nconst functionCallToChunkParts = (p: WireFunctionCallPart): ReadonlyArray<ChunkPart> => [\n {\n kind: \"function_call\",\n id: Option.fromNullishOr(p.functionCall.id),\n signature: Option.fromNullishOr(p.thoughtSignature),\n name: p.functionCall.name,\n args: p.functionCall.args ?? {},\n },\n]\n\nconst partToChunkParts = (p: typeof Part.Type): ReadonlyArray<ChunkPart> =>\n isTextPart(p) ? textToChunkParts(p) : functionCallToChunkParts(p)\n\nconst chunkParts = (chunk: WireChunk): ReadonlyArray<ChunkPart> =>\n pipe(chunk.candidates?.[0]?.content?.parts ?? [], Arr.flatMap(partToChunkParts))\n\nconst sumStrings = (parts: ReadonlyArray<ChunkPart>, kind: \"text\" | \"reasoning\"): string =>\n pipe(\n parts,\n Arr.filterMap((p) => (p.kind === kind ? Result.succeed(p.text) : Result.failVoid)),\n ).join(\"\")\n\nconst collectFunctionCalls = (\n parts: ReadonlyArray<ChunkPart>,\n): ReadonlyArray<Extract<ChunkPart, { kind: \"function_call\" }>> =>\n pipe(\n parts,\n Arr.filterMap((p) => (p.kind === \"function_call\" ? Result.succeed(p) : Result.failVoid)),\n )\n\nconst mergeUsage = (\n prev: Accumulator[\"usage\"],\n next: WireChunk[\"usageMetadata\"],\n): Accumulator[\"usage\"] =>\n next === undefined\n ? prev\n : {\n ...prev,\n ...(next.promptTokenCount !== undefined && { input_tokens: next.promptTokenCount }),\n ...(next.candidatesTokenCount !== undefined && {\n output_tokens: next.candidatesTokenCount,\n }),\n ...(next.totalTokenCount !== undefined && { total_tokens: next.totalTokenCount }),\n ...(next.cachedContentTokenCount !== undefined && {\n input_tokens_details: { cached_tokens: next.cachedContentTokenCount },\n }),\n ...(next.thoughtsTokenCount !== undefined && {\n output_tokens_details: { reasoning_tokens: next.thoughtsTokenCount },\n }),\n }\n\n/**\n * Synthesize a stable `call_id` for a function call. Gemini 3 provides one\n * via `functionCall.id`; older models do not, so we fall back to\n * `<name>_<index>` based on prior calls' position in the accumulator.\n */\nconst synthesizeCallId = (\n call: Extract<ChunkPart, { kind: \"function_call\" }>,\n priorCalls: ReadonlyArray<AccumulatedFunctionCall>,\n): string =>\n Option.match(call.id, {\n onSome: (id) => id,\n onNone: () => `${call.name}_${priorCalls.length}`,\n })\n\nconst chunkCallToAccumulated = (\n prior: ReadonlyArray<AccumulatedFunctionCall>,\n call: Extract<ChunkPart, { kind: \"function_call\" }>,\n): AccumulatedFunctionCall => ({\n callId: synthesizeCallId(call, prior),\n name: call.name,\n providerId: call.id,\n signature: call.signature,\n // `functionCallToChunkParts` already replaced null/undefined with `{}`,\n // so `call.args` is always a JSON-encodable value here.\n arguments: JSON.stringify(call.args),\n})\n\nconst appendFunctionCalls = (\n prior: ReadonlyArray<AccumulatedFunctionCall>,\n fromChunk: ReadonlyArray<Extract<ChunkPart, { kind: \"function_call\" }>>,\n): ReadonlyArray<AccumulatedFunctionCall> =>\n fromChunk.reduce<ReadonlyArray<AccumulatedFunctionCall>>(\n (acc, call) => [...acc, chunkCallToAccumulated(acc, call)],\n prior,\n )\n\n// Grounding usually arrives on the final chunk, but merge across chunks and\n// de-dupe by url so a mid-stream metadata block is not lost or double-counted.\nconst mergeAnnotations = (\n prev: ReadonlyArray<Annotation>,\n next: ReadonlyArray<Annotation>,\n): ReadonlyArray<Annotation> =>\n next.length === 0 ? prev : Arr.dedupeWith(Arr.appendAll(prev, next), sameUrlCitation)\n\nexport const ingestChunk = (acc: Accumulator, chunk: WireChunk): ChunkResult => {\n const parts = chunkParts(chunk)\n const finishReason = Option.fromNullishOr(chunk.candidates?.[0]?.finishReason)\n return {\n parts,\n finished: Option.isSome(finishReason),\n accumulator: {\n text: acc.text + sumStrings(parts, \"text\"),\n reasoning: acc.reasoning + sumStrings(parts, \"reasoning\"),\n functionCalls: appendFunctionCalls(acc.functionCalls, collectFunctionCalls(parts)),\n annotations: mergeAnnotations(\n acc.annotations,\n groundingToAnnotations(chunk.candidates?.[0]?.groundingMetadata),\n ),\n finishReason: Option.orElse(finishReason, () => acc.finishReason),\n usage: mergeUsage(acc.usage, chunk.usageMetadata),\n },\n }\n}\n\nconst reasoningItems = (acc: Accumulator): ReadonlyArray<HistoryItem> =>\n acc.reasoning.length > 0 ? [{ type: \"reasoning\", summary: acc.reasoning }] : []\n\nconst assistantMessageItems = (acc: Accumulator): ReadonlyArray<HistoryItem> =>\n acc.text.length === 0\n ? []\n : [\n {\n type: \"message\",\n role: \"assistant\",\n content: [\n {\n type: \"output_text\",\n text: acc.text,\n ...(acc.annotations.length > 0 && { annotations: acc.annotations }),\n },\n ],\n },\n ]\n\nconst functionCallItems = (acc: Accumulator): ReadonlyArray<HistoryItem> =>\n pipe(\n acc.functionCalls,\n Arr.map((c) => {\n const gemini = {\n ...(Option.isSome(c.providerId) && { id: c.providerId.value }),\n ...(Option.isSome(c.signature) && { thoughtSignature: c.signature.value }),\n }\n return {\n type: \"function_call\" as const,\n call_id: c.callId,\n name: c.name,\n arguments: c.arguments,\n ...(Object.keys(gemini).length > 0 && { providerData: { gemini } }),\n }\n }),\n )\n\nexport const accumulatorToTurn = (acc: Accumulator): Turn => ({\n stop_reason:\n acc.functionCalls.length > 0 ? (\"tool_calls\" as const) : finishReasonToStop(acc.finishReason),\n usage: { ...acc.usage },\n items: [...reasoningItems(acc), ...assistantMessageItems(acc), ...functionCallItems(acc)],\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"codec.mjs","names":["Arr"],"sources":["../src/codec.ts"],"sourcesContent":["import { Array as Arr, Encoding, Match, Option, Result, Schema, pipe } from \"effect\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport type { ImageSource } from \"@effect-uai/core/Image\"\nimport { imageBase64 } from \"@effect-uai/core/Image\"\nimport type {\n ContentBlock,\n ToolCall,\n InputImage,\n HistoryItem,\n Message,\n Annotation,\n} from \"@effect-uai/core/Items\"\nimport type { ToolDescriptor } from \"@effect-uai/core/Tool\"\nimport type { Turn } from \"@effect-uai/core/Turn\"\nimport type { CommonRequest } from \"@effect-uai/core/LanguageModel\"\n\n// ---------------------------------------------------------------------------\n// Wire schemas - the subset of Gemini's streamGenerateContent payload we\n// consume. Reference: https://ai.google.dev/api/generate-content\n// ---------------------------------------------------------------------------\n\nconst TextPart = Schema.Struct({\n text: Schema.String,\n /**\n * Gemini's flag for chain-of-thought parts. When `true`, the part is\n * reasoning trace, not the model's user-facing answer. Maps onto\n * `reasoning_delta { kind: \"trace\" }` in the canonical view.\n */\n thought: Schema.optional(Schema.Boolean),\n})\n\n/**\n * Gemini function-call part. Args are delivered as a whole JSON *object*\n * in a single chunk (Gemini does not stream tool-call args). On Gemini 3\n * the response also carries an `id` per call, which we echo back on the\n * corresponding `functionResponse`.\n */\nconst FunctionCallPart = Schema.Struct({\n functionCall: Schema.Struct({\n id: Schema.optional(Schema.String),\n name: Schema.String,\n args: Schema.optional(Schema.Unknown),\n }),\n // Gemini 3 thinking models attach an opaque signature to each function-call\n // part that must be replayed on the next request, or the call is rejected.\n thoughtSignature: Schema.optional(Schema.String),\n})\n\n/**\n * An image the model drew, as a part of its own turn. Only image models\n * emit these, and only when the request asked for the IMAGE modality.\n * `thought: true` marks an interim image drawn while planning, which is\n * not the answer and is dropped the same way thought text is.\n */\nconst InlineDataPart = Schema.Struct({\n inlineData: Schema.Struct({\n mimeType: Schema.optional(Schema.String),\n data: Schema.String,\n }),\n thought: Schema.optional(Schema.Boolean),\n})\n\nconst Part = Schema.Union([TextPart, FunctionCallPart, InlineDataPart])\ntype WireFunctionCallPart = typeof FunctionCallPart.Type\ntype WireInlineDataPart = typeof InlineDataPart.Type\n\nconst Content = Schema.Struct({\n role: Schema.optional(Schema.String),\n parts: Schema.optional(Schema.Array(Part)),\n})\n\n// Google grounding metadata. On a grounded response the candidate carries\n// `groundingChunks[].web.{uri,title}` - the structured form of the inline `[n]`\n// / trailing `**Sources:**` list the model renders into the answer text.\n// Decoded defensively (every field optional) as it is absent on ungrounded\n// turns. Shared with the Interactions deep-research surface via `codec.ts`.\nconst GroundingChunk = Schema.Struct({\n web: Schema.optional(\n Schema.NullOr(\n Schema.Struct({\n uri: Schema.optional(Schema.NullOr(Schema.String)),\n title: Schema.optional(Schema.NullOr(Schema.String)),\n }),\n ),\n ),\n})\nexport const GroundingMetadata = Schema.Struct({\n groundingChunks: Schema.optional(Schema.NullOr(Schema.Array(GroundingChunk))),\n})\nexport type GroundingMetadata = typeof GroundingMetadata.Type\n\n// Two citations are the same source when they point at the same url.\nconst sameUrlCitation = (a: Annotation, b: Annotation): boolean =>\n a.type === \"url_citation\" && b.type === \"url_citation\" && a.url === b.url\n\n// Grounding chunks → `url_citation` annotations, de-duped by url across every\n// metadata block passed. `web.uri` is a `grounding-api-redirect` URL and\n// `web.title` the bare domain; both are kept as-is (the redirect resolves to\n// the real page).\nexport const groundingToAnnotations = (\n ...metas: ReadonlyArray<GroundingMetadata | null | undefined>\n): ReadonlyArray<Annotation> =>\n pipe(\n metas,\n Arr.flatMap((meta) => meta?.groundingChunks ?? []),\n Arr.filterMap((chunk) =>\n chunk.web?.uri == null\n ? Result.failVoid\n : Result.succeed<Annotation>({\n type: \"url_citation\",\n url: chunk.web.uri,\n title: chunk.web.title ?? chunk.web.uri,\n }),\n ),\n Arr.dedupeWith(sameUrlCitation),\n )\n\nconst Candidate = Schema.Struct({\n content: Schema.optional(Content),\n groundingMetadata: Schema.optional(GroundingMetadata),\n finishReason: Schema.optional(Schema.String),\n index: Schema.optional(Schema.Number),\n})\n\nconst UsageMetadata = Schema.Struct({\n promptTokenCount: Schema.optional(Schema.Number),\n candidatesTokenCount: Schema.optional(Schema.Number),\n totalTokenCount: Schema.optional(Schema.Number),\n cachedContentTokenCount: Schema.optional(Schema.Number),\n thoughtsTokenCount: Schema.optional(Schema.Number),\n})\n\nexport const WireChunk = Schema.Struct({\n candidates: Schema.optional(Schema.Array(Candidate)),\n usageMetadata: Schema.optional(UsageMetadata),\n})\nexport type WireChunk = typeof WireChunk.Type\n\n// ---------------------------------------------------------------------------\n// History → request body\n// ---------------------------------------------------------------------------\n\ntype RequestPart =\n | { readonly text: string }\n | { readonly inlineData: { readonly mimeType: string; readonly data: string } }\n | {\n readonly functionCall: {\n readonly id?: string\n readonly name: string\n readonly args: unknown\n }\n readonly thoughtSignature?: string\n }\n | {\n readonly functionResponse: {\n readonly id?: string\n readonly name: string\n readonly response: Record<string, unknown>\n }\n }\n\ntype RequestContent = {\n readonly role: \"user\" | \"model\"\n readonly parts: ReadonlyArray<RequestPart>\n}\n\ntype RequestSystemInstruction = {\n readonly parts: ReadonlyArray<{ readonly text: string }>\n}\n\n/**\n * Gemini's tool declaration. A `tools[]` entry is either a function-declaration\n * block (translated from `ToolDescriptor`s) or one of the native hosted-tool\n * entries. The native entries are empty objects keyed camelCase per the REST\n * v1beta `Tool` object, and sit as siblings of the declarations in the same\n * `tools` array.\n */\nexport type RequestTool =\n | {\n readonly functionDeclarations: ReadonlyArray<{\n readonly name: string\n readonly description: string\n readonly parameters: Record<string, unknown>\n }>\n }\n | { readonly googleSearch: {} }\n | { readonly urlContext: {} }\n | { readonly codeExecution: {} }\n\ntype RequestToolConfig = {\n readonly functionCallingConfig: {\n readonly mode: \"AUTO\" | \"ANY\" | \"NONE\"\n readonly allowedFunctionNames?: ReadonlyArray<string>\n }\n}\n\nexport type ThinkingConfig = {\n readonly thinkingBudget: number\n}\n\nexport type ImageConfig = {\n readonly aspectRatio?: string\n readonly imageSize?: string\n}\n\nexport type GenerationConfig = {\n readonly temperature?: number\n readonly maxOutputTokens?: number\n readonly topP?: number\n readonly thinkingConfig?: ThinkingConfig\n /** Image models need `[\"TEXT\", \"IMAGE\"]` or they answer in prose only. */\n readonly responseModalities?: ReadonlyArray<\"TEXT\" | \"IMAGE\">\n readonly imageConfig?: ImageConfig\n /** Set together with `responseJsonSchema` to constrain output to JSON. */\n readonly responseMimeType?: string\n /** JSON Schema constraint on the output. */\n readonly responseJsonSchema?: Record<string, unknown>\n}\n\nexport type RequestBody = {\n readonly contents: ReadonlyArray<RequestContent>\n readonly systemInstruction?: RequestSystemInstruction\n readonly generationConfig?: GenerationConfig\n readonly tools?: ReadonlyArray<RequestTool>\n readonly toolConfig?: RequestToolConfig\n}\n\nconst blockText = Match.type<ContentBlock>().pipe(\n Match.discriminatorsExhaustive(\"type\")({\n input_text: (b) => b.text,\n input_image: () => \"\",\n output_text: (b) => b.text,\n output_image: () => \"\",\n refusal: (b) => b.text,\n }),\n)\n\nconst messageText = (message: Message): string => message.content.map(blockText).join(\"\")\n\n/**\n * Gemini's `inlineData` form expects a base64 payload. URL-form images\n * would need to go through Gemini's Files API (upload then `fileData`\n * with the returned URI); pre-uploading isn't free, so we skip those for\n * now and document as a follow-up.\n */\nconst imageSourceToParts = Match.type<InputImage[\"source\"]>().pipe(\n Match.tag(\"url\", (): ReadonlyArray<RequestPart> => []),\n Match.tag(\"base64\", (s): ReadonlyArray<RequestPart> => [\n { inlineData: { mimeType: s.mimeType, data: s.base64 } },\n ]),\n Match.tag(\"bytes\", (s): ReadonlyArray<RequestPart> => [\n { inlineData: { mimeType: s.mimeType, data: Encoding.encodeBase64(s.bytes) } },\n ]),\n Match.exhaustive,\n)\n\nconst blockToParts = Match.type<ContentBlock>().pipe(\n Match.discriminatorsExhaustive(\"type\")({\n input_text: (b): ReadonlyArray<RequestPart> => (b.text.length === 0 ? [] : [{ text: b.text }]),\n input_image: (b): ReadonlyArray<RequestPart> => imageSourceToParts(b.source),\n output_text: (b): ReadonlyArray<RequestPart> => (b.text.length === 0 ? [] : [{ text: b.text }]),\n // An image the model drew goes back as a model-role `inlineData` part,\n // which is how a multi-turn edit keeps its subject.\n output_image: (b): ReadonlyArray<RequestPart> => imageSourceToParts(b.source),\n // Refusals are assistant-side content; they don't round-trip into Gemini's\n // request body as parts. Skip.\n refusal: (): ReadonlyArray<RequestPart> => [],\n }),\n)\n\nconst messageToContent = (message: Message): Result.Result<RequestContent, void> => {\n const parts = pipe(message.content, Arr.flatMap(blockToParts))\n if (parts.length === 0) return Result.failVoid\n return Match.value(message.role).pipe(\n Match.when(\"user\", () => Result.succeed({ role: \"user\" as const, parts })),\n Match.when(\"assistant\", () => Result.succeed({ role: \"model\" as const, parts })),\n Match.when(\"system\", () => Result.failVoid),\n Match.exhaustive,\n )\n}\n\nconst systemMessageText = (message: Message): Result.Result<string, void> => {\n if (message.role !== \"system\") return Result.failVoid\n const text = messageText(message)\n return text.length === 0 ? Result.failVoid : Result.succeed(text)\n}\n\nconst allMessages = (history: ReadonlyArray<HistoryItem>): ReadonlyArray<Message> =>\n pipe(\n history,\n Arr.filterMap((item) => (item.type === \"message\" ? Result.succeed(item) : Result.failVoid)),\n )\n\n// ---------------------------------------------------------------------------\n// Function-call round-trip\n//\n// `ToolCall.arguments` and `ToolCallOutput.output` are JSON-encoded\n// strings; Gemini's `functionCall.args` and `functionResponse.response`\n// expect parsed JSON *objects*. Decode via Schema:\n// - object payload → use as-is\n// - scalar / array / malformed → wrap as `{ output: <raw string> }` so the\n// model still sees *some* response without crashing the request.\n// ---------------------------------------------------------------------------\n\nconst JsonObject = Schema.Record(Schema.String, Schema.Unknown)\nconst decodeJsonObject = Schema.decodeResult(Schema.fromJsonString(JsonObject))\n\nconst parsedJsonObject =\n (fallback: (raw: string) => Record<string, unknown>) =>\n (encoded: string): Record<string, unknown> =>\n pipe(\n decodeJsonObject(encoded),\n Result.match({\n onSuccess: (v) => v,\n onFailure: () => fallback(encoded),\n }),\n )\n\nconst parsedArgs = parsedJsonObject(() => ({}))\nconst parsedResponse = parsedJsonObject((raw) => ({ output: raw }))\n\n/**\n * `ToolCallOutput` only carries `call_id`; Gemini's `functionResponse`\n * requires the declared function `name`. Resolve the name by scanning prior\n * `function_call` items in the history for a matching `call_id`. If we\n * cannot resolve, fall back to `call_id` as the name - imperfect but\n * preserves stream shape so the model sees *some* response.\n */\nconst isFunctionCallItem = (item: HistoryItem): item is ToolCall => item.type === \"function_call\"\n\nconst nameForCallId = (\n history: ReadonlyArray<HistoryItem>,\n call_id: string,\n): Option.Option<string> =>\n pipe(\n history,\n Arr.findFirst((item): item is ToolCall => isFunctionCallItem(item) && item.call_id === call_id),\n Option.map((f) => f.name),\n )\n\n/**\n * Extract the Gemini-3 wire id and thought signature we stashed in\n * `providerData` on the way out. Schema-driven so the shape lives in one\n * place; failure or absence → `Option.none()`.\n */\nconst ProviderDataGemini = Schema.Struct({\n gemini: Schema.Struct({\n id: Schema.optional(Schema.String),\n thoughtSignature: Schema.optional(Schema.String),\n }),\n})\nconst decodeGemini = Schema.decodeUnknownResult(ProviderDataGemini)\n\nconst geminiField = (\n item: ToolCall,\n pick: (g: {\n readonly id?: string | undefined\n readonly thoughtSignature?: string | undefined\n }) => string | undefined,\n): Option.Option<string> =>\n pipe(\n decodeGemini(item.providerData),\n Result.match({\n onSuccess: (d) => Option.fromNullishOr(pick(d.gemini)),\n onFailure: () => Option.none<string>(),\n }),\n )\n\nconst providerIdFor = (item: ToolCall): Option.Option<string> => geminiField(item, (g) => g.id)\n\n// The originating call's Gemini id, looked up from history by our `call_id`.\n// Gemini 3 maps a `functionResponse` back to its call by this id, so parallel\n// calls to the same function are mis-paired if we drop it.\nconst providerIdForCallId = (\n history: ReadonlyArray<HistoryItem>,\n call_id: string,\n): Option.Option<string> =>\n pipe(\n history,\n Arr.findFirst((item): item is ToolCall => isFunctionCallItem(item) && item.call_id === call_id),\n Option.flatMap(providerIdFor),\n )\n\nconst signatureFor = (item: ToolCall): Option.Option<string> =>\n geminiField(item, (g) => g.thoughtSignature)\n\nconst itemToContent =\n (history: ReadonlyArray<HistoryItem>) =>\n (item: HistoryItem): Result.Result<RequestContent, void> =>\n Match.value(item).pipe(\n Match.discriminatorsExhaustive(\"type\")({\n message: messageToContent,\n function_call: (f) =>\n Result.succeed({\n role: \"model\" as const,\n parts: [\n {\n ...Option.match(signatureFor(f), {\n onSome: (thoughtSignature) => ({ thoughtSignature }),\n onNone: () => ({}),\n }),\n functionCall: {\n ...Option.match(providerIdFor(f), {\n onSome: (id) => ({ id }),\n onNone: () => ({}),\n }),\n name: f.name,\n args: parsedArgs(f.arguments),\n },\n },\n ],\n }),\n function_call_output: (o) =>\n Result.succeed({\n role: \"user\" as const,\n parts: [\n {\n functionResponse: {\n ...Option.match(providerIdForCallId(history, o.call_id), {\n onSome: (id) => ({ id }),\n onNone: () => ({}),\n }),\n name: Option.getOrElse(nameForCallId(history, o.call_id), () => o.call_id),\n response: parsedResponse(o.output),\n },\n },\n ],\n }),\n reasoning: () => Result.failVoid,\n }),\n )\n\n// ---------------------------------------------------------------------------\n// Tool descriptors → Gemini `functionDeclarations`. Gemini accepts only a\n// strict OpenAPI 3.0 subset for `parameters`; strip JSON-Schema keys it\n// rejects (`$schema`, `$ref`, `additionalProperties`, `oneOf`,\n// `definitions`).\n// ---------------------------------------------------------------------------\n\nconst UNSUPPORTED_SCHEMA_KEYS: ReadonlySet<string> = new Set([\n \"$schema\",\n \"$ref\",\n \"$defs\",\n \"definitions\",\n \"additionalProperties\",\n \"oneOf\",\n])\n\nconst sanitizeSchema = (schema: unknown): unknown => {\n if (Array.isArray(schema)) return schema.map(sanitizeSchema)\n if (schema === null || typeof schema !== \"object\") return schema\n return pipe(\n Object.entries(schema as Record<string, unknown>),\n Arr.filterMap(([k, v]) =>\n UNSUPPORTED_SCHEMA_KEYS.has(k)\n ? Result.failVoid\n : Result.succeed([k, sanitizeSchema(v)] as const),\n ),\n Object.fromEntries,\n )\n}\n\nconst toolDescriptorsToTools = (\n tools: ReadonlyArray<ToolDescriptor>,\n): ReadonlyArray<RequestTool> =>\n tools.length === 0\n ? []\n : [\n {\n functionDeclarations: tools.map((t) => ({\n name: t.name,\n description: t.description,\n parameters: sanitizeSchema(t.inputSchema) as Record<string, unknown>,\n })),\n },\n ]\n\n/**\n * Map our cross-provider `toolChoice` onto Gemini's `functionCallingConfig`.\n * Gemini honours the same three modes plus a function allow-list, so the\n * choice is mapped, not dropped. `undefined` keeps the API default (`AUTO`).\n */\nconst toolChoiceToFunctionCallingConfig = (\n toolChoice: CommonRequest[\"toolChoice\"],\n): RequestToolConfig[\"functionCallingConfig\"] =>\n Match.value(toolChoice ?? \"auto\").pipe(\n Match.when(\"auto\", () => ({ mode: \"AUTO\" as const })),\n Match.when(\"required\", () => ({ mode: \"ANY\" as const })),\n Match.when(\"none\", () => ({ mode: \"NONE\" as const })),\n Match.orElse((tc) => ({ mode: \"ANY\" as const, allowedFunctionNames: [tc.name] })),\n )\n\n// ---------------------------------------------------------------------------\n// HTTP failure mapping, shared by every Google surface\n// ---------------------------------------------------------------------------\n\n/** `provider` names the surface that failed, so errors read `gemini` or `lyria`. */\nexport const transportFailure =\n (provider: string) =>\n (cause: unknown): AiError.AiError =>\n new AiError.Unavailable({ provider, raw: cause })\n\n/** 413 is the inline-payload cap, which is a context limit on every surface. */\nexport const httpStatusError =\n (provider: string) =>\n (status: number, body: string): AiError.AiError =>\n Match.value(status).pipe(\n Match.when(429, (): AiError.AiError => new AiError.RateLimited({ provider, raw: body })),\n Match.whenOr(408, 504, (): AiError.AiError => new AiError.Timeout({ provider, raw: body })),\n Match.when(\n 401,\n (): AiError.AiError => new AiError.AuthFailed({ provider, subtype: \"auth\", raw: body }),\n ),\n Match.when(\n 403,\n (): AiError.AiError =>\n new AiError.AuthFailed({ provider, subtype: \"permission\", raw: body }),\n ),\n Match.when(\n 402,\n (): AiError.AiError => new AiError.AuthFailed({ provider, subtype: \"billing\", raw: body }),\n ),\n Match.when(\n 413,\n (): AiError.AiError => new AiError.ContextLengthExceeded({ provider, raw: body }),\n ),\n Match.when(\n (n) => n >= 500,\n (n): AiError.AiError => new AiError.Unavailable({ provider, status: n, raw: body }),\n ),\n Match.orElse((): AiError.AiError => new AiError.InvalidRequest({ provider, raw: body })),\n )\n\n/**\n * URL-form images need Gemini's Files API (upload, then `fileData`); we\n * don't pre-upload, so such a part would otherwise vanish from the request.\n * The caller fails the request with `Unsupported` instead of silently\n * dropping the image.\n */\nexport const hasUrlImageSource = (history: ReadonlyArray<HistoryItem>): boolean =>\n history.some(\n (item) =>\n item.type === \"message\" &&\n item.content.some((b) => b.type === \"input_image\" && b.source._tag === \"url\"),\n )\n\nexport const buildRequestBody = (\n history: ReadonlyArray<HistoryItem>,\n generationConfig: Option.Option<GenerationConfig>,\n tools: ReadonlyArray<ToolDescriptor> = [],\n providerToolEntries: ReadonlyArray<RequestTool> = [],\n toolChoice: CommonRequest[\"toolChoice\"] = undefined,\n): RequestBody => {\n const systemTexts = pipe(allMessages(history), Arr.filterMap(systemMessageText))\n const contents = pipe(history, Arr.filterMap(itemToContent(history)))\n const functionTools = toolDescriptorsToTools(tools)\n const requestTools = [...functionTools, ...providerToolEntries]\n return {\n contents,\n ...(systemTexts.length > 0 && {\n systemInstruction: { parts: [{ text: systemTexts.join(\"\\n\") }] },\n }),\n ...Option.match(generationConfig, {\n onNone: () => ({}),\n onSome: (cfg) => ({ generationConfig: cfg }),\n }),\n ...(requestTools.length > 0 && { tools: requestTools }),\n // Only force a function-calling mode when there are function declarations;\n // a grounding-only request must keep the API default.\n ...(functionTools.length > 0 && {\n toolConfig: { functionCallingConfig: toolChoiceToFunctionCallingConfig(toolChoice) },\n }),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Stream-level state - accumulate chunk text + final usage/finish.\n//\n// `Accumulator` is immutable; `ingestChunk` returns a fresh one per chunk.\n// Drive it via `Stream.mapAccum` in the consumer.\n// ---------------------------------------------------------------------------\n\nconst finishReasonToStop = (reason: Option.Option<string>): Turn[\"stop_reason\"] =>\n Option.match(reason, {\n onNone: () => \"stop\" as const,\n onSome: (r) => (r === \"MAX_TOKENS\" ? (\"max_tokens\" as const) : (\"stop\" as const)),\n })\n\nexport type AccumulatedFunctionCall = {\n /** Synthesized id-or-name we surface as `call_id` on the canonical item. */\n readonly callId: string\n readonly name: string\n /** Wire id from Gemini 3, when present - echoed back on `functionResponse`. */\n readonly providerId: Option.Option<string>\n /** Thought signature from Gemini 3, replayed on the next request's part. */\n readonly signature: Option.Option<string>\n /** Args as JSON-encoded string, mirroring `Items.ToolCall.arguments`. */\n readonly arguments: string\n}\n\nexport type Accumulator = {\n readonly text: string\n readonly reasoning: string\n /** Images the model drew, in emission order. Image models only. */\n readonly images: ReadonlyArray<ImageSource>\n readonly functionCalls: ReadonlyArray<AccumulatedFunctionCall>\n /** Grounding citations, accumulated (de-duped by url) across chunks. */\n readonly annotations: ReadonlyArray<Annotation>\n readonly finishReason: Option.Option<string>\n readonly usage: {\n readonly input_tokens?: number\n readonly output_tokens?: number\n readonly total_tokens?: number\n readonly input_tokens_details?: { readonly cached_tokens?: number }\n readonly output_tokens_details?: { readonly reasoning_tokens?: number }\n }\n}\n\nexport const emptyAccumulator: Accumulator = {\n text: \"\",\n reasoning: \"\",\n images: [],\n functionCalls: [],\n annotations: [],\n finishReason: Option.none(),\n usage: {},\n}\n\n/**\n * One part's worth of streamable output. `text` and `reasoning` are\n * incremental string deltas; `function_call` arrives whole-in-one-chunk\n * (Gemini does not stream tool-call args).\n */\nexport type ChunkPart =\n | { readonly kind: \"text\"; readonly text: string }\n | { readonly kind: \"reasoning\"; readonly text: string }\n | { readonly kind: \"image\"; readonly source: ImageSource }\n | {\n readonly kind: \"function_call\"\n readonly id: Option.Option<string>\n readonly signature: Option.Option<string>\n readonly name: string\n readonly args: unknown\n }\n\nexport type ChunkResult = {\n readonly accumulator: Accumulator\n readonly parts: ReadonlyArray<ChunkPart>\n readonly finished: boolean\n}\n\nconst textToChunkParts = (p: typeof TextPart.Type): ReadonlyArray<ChunkPart> =>\n p.text.length === 0 ? [] : [{ kind: p.thought === true ? \"reasoning\" : \"text\", text: p.text }]\n\n/** Interim images drawn while planning are dropped, as thought text is. */\nconst inlineDataToChunkParts = (p: WireInlineDataPart): ReadonlyArray<ChunkPart> =>\n p.thought === true\n ? []\n : [\n {\n kind: \"image\",\n source: imageBase64(p.inlineData.data, p.inlineData.mimeType ?? \"image/png\"),\n },\n ]\n\nconst functionCallToChunkParts = (p: WireFunctionCallPart): ReadonlyArray<ChunkPart> => [\n {\n kind: \"function_call\",\n id: Option.fromNullishOr(p.functionCall.id),\n signature: Option.fromNullishOr(p.thoughtSignature),\n name: p.functionCall.name,\n args: p.functionCall.args ?? {},\n },\n]\n\n/** Parts are distinguished by which key they carry, not by a discriminator. */\nconst partToChunkParts: (p: typeof Part.Type) => ReadonlyArray<ChunkPart> = Match.type<\n typeof Part.Type\n>().pipe(\n Match.when({ text: Match.string }, textToChunkParts),\n Match.when({ inlineData: Match.defined }, inlineDataToChunkParts),\n Match.when({ functionCall: Match.defined }, functionCallToChunkParts),\n Match.exhaustive,\n)\n\nconst chunkParts = (chunk: WireChunk): ReadonlyArray<ChunkPart> =>\n pipe(chunk.candidates?.[0]?.content?.parts ?? [], Arr.flatMap(partToChunkParts))\n\nconst sumStrings = (parts: ReadonlyArray<ChunkPart>, kind: \"text\" | \"reasoning\"): string =>\n pipe(\n parts,\n Arr.filterMap((p) => (p.kind === kind ? Result.succeed(p.text) : Result.failVoid)),\n ).join(\"\")\n\nconst collectImages = (parts: ReadonlyArray<ChunkPart>): ReadonlyArray<ImageSource> =>\n pipe(\n parts,\n Arr.filterMap((p) => (p.kind === \"image\" ? Result.succeed(p.source) : Result.failVoid)),\n )\n\nconst collectFunctionCalls = (\n parts: ReadonlyArray<ChunkPart>,\n): ReadonlyArray<Extract<ChunkPart, { kind: \"function_call\" }>> =>\n pipe(\n parts,\n Arr.filterMap((p) => (p.kind === \"function_call\" ? Result.succeed(p) : Result.failVoid)),\n )\n\nconst mergeUsage = (\n prev: Accumulator[\"usage\"],\n next: WireChunk[\"usageMetadata\"],\n): Accumulator[\"usage\"] =>\n next === undefined\n ? prev\n : {\n ...prev,\n ...(next.promptTokenCount !== undefined && { input_tokens: next.promptTokenCount }),\n ...(next.candidatesTokenCount !== undefined && {\n output_tokens: next.candidatesTokenCount,\n }),\n ...(next.totalTokenCount !== undefined && { total_tokens: next.totalTokenCount }),\n ...(next.cachedContentTokenCount !== undefined && {\n input_tokens_details: { cached_tokens: next.cachedContentTokenCount },\n }),\n ...(next.thoughtsTokenCount !== undefined && {\n output_tokens_details: { reasoning_tokens: next.thoughtsTokenCount },\n }),\n }\n\n/**\n * Synthesize a stable `call_id` for a function call. Gemini 3 provides one\n * via `functionCall.id`; older models do not, so we fall back to\n * `<name>_<index>` based on prior calls' position in the accumulator.\n */\nconst synthesizeCallId = (\n call: Extract<ChunkPart, { kind: \"function_call\" }>,\n priorCalls: ReadonlyArray<AccumulatedFunctionCall>,\n): string =>\n Option.match(call.id, {\n onSome: (id) => id,\n onNone: () => `${call.name}_${priorCalls.length}`,\n })\n\nconst chunkCallToAccumulated = (\n prior: ReadonlyArray<AccumulatedFunctionCall>,\n call: Extract<ChunkPart, { kind: \"function_call\" }>,\n): AccumulatedFunctionCall => ({\n callId: synthesizeCallId(call, prior),\n name: call.name,\n providerId: call.id,\n signature: call.signature,\n // `functionCallToChunkParts` already replaced null/undefined with `{}`,\n // so `call.args` is always a JSON-encodable value here.\n arguments: JSON.stringify(call.args),\n})\n\nconst appendFunctionCalls = (\n prior: ReadonlyArray<AccumulatedFunctionCall>,\n fromChunk: ReadonlyArray<Extract<ChunkPart, { kind: \"function_call\" }>>,\n): ReadonlyArray<AccumulatedFunctionCall> =>\n fromChunk.reduce<ReadonlyArray<AccumulatedFunctionCall>>(\n (acc, call) => [...acc, chunkCallToAccumulated(acc, call)],\n prior,\n )\n\n// Grounding usually arrives on the final chunk, but merge across chunks and\n// de-dupe by url so a mid-stream metadata block is not lost or double-counted.\nconst mergeAnnotations = (\n prev: ReadonlyArray<Annotation>,\n next: ReadonlyArray<Annotation>,\n): ReadonlyArray<Annotation> =>\n next.length === 0 ? prev : Arr.dedupeWith(Arr.appendAll(prev, next), sameUrlCitation)\n\nexport const ingestChunk = (acc: Accumulator, chunk: WireChunk): ChunkResult => {\n const parts = chunkParts(chunk)\n const finishReason = Option.fromNullishOr(chunk.candidates?.[0]?.finishReason)\n return {\n parts,\n finished: Option.isSome(finishReason),\n accumulator: {\n text: acc.text + sumStrings(parts, \"text\"),\n reasoning: acc.reasoning + sumStrings(parts, \"reasoning\"),\n images: Arr.appendAll(acc.images, collectImages(parts)),\n functionCalls: appendFunctionCalls(acc.functionCalls, collectFunctionCalls(parts)),\n annotations: mergeAnnotations(\n acc.annotations,\n groundingToAnnotations(chunk.candidates?.[0]?.groundingMetadata),\n ),\n finishReason: Option.orElse(finishReason, () => acc.finishReason),\n usage: mergeUsage(acc.usage, chunk.usageMetadata),\n },\n }\n}\n\nconst reasoningItems = (acc: Accumulator): ReadonlyArray<HistoryItem> =>\n acc.reasoning.length > 0 ? [{ type: \"reasoning\", summary: acc.reasoning }] : []\n\n/**\n * Image models interleave text and image parts, and often answer with an\n * image and no prose at all, so the message exists when either is present.\n */\nconst assistantMessageItems = (acc: Accumulator): ReadonlyArray<HistoryItem> => {\n const content: ReadonlyArray<ContentBlock> = [\n ...(acc.text.length === 0\n ? []\n : [\n {\n type: \"output_text\" as const,\n text: acc.text,\n ...(acc.annotations.length > 0 && { annotations: acc.annotations }),\n },\n ]),\n ...acc.images.map((source) => ({ type: \"output_image\" as const, source })),\n ]\n return content.length === 0 ? [] : [{ type: \"message\", role: \"assistant\", content }]\n}\n\nconst functionCallItems = (acc: Accumulator): ReadonlyArray<HistoryItem> =>\n pipe(\n acc.functionCalls,\n Arr.map((c) => {\n const gemini = {\n ...(Option.isSome(c.providerId) && { id: c.providerId.value }),\n ...(Option.isSome(c.signature) && { thoughtSignature: c.signature.value }),\n }\n return {\n type: \"function_call\" as const,\n call_id: c.callId,\n name: c.name,\n arguments: c.arguments,\n ...(Object.keys(gemini).length > 0 && { providerData: { gemini } }),\n }\n }),\n )\n\nexport const accumulatorToTurn = (acc: Accumulator): Turn => ({\n stop_reason:\n acc.functionCalls.length > 0 ? (\"tool_calls\" as const) : finishReasonToStop(acc.finishReason),\n usage: { ...acc.usage },\n items: [...reasoningItems(acc), ...assistantMessageItems(acc), ...functionCallItems(acc)],\n})\n"],"mappings":"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"}