@effect-uai/google 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/Gemini.d.mts +8 -2
  2. package/dist/Gemini.d.mts.map +1 -1
  3. package/dist/Gemini.mjs +1 -1
  4. package/dist/Gemini.mjs.map +1 -1
  5. package/dist/GeminiEmbedding.d.mts +2 -3
  6. package/dist/GeminiEmbedding.d.mts.map +1 -1
  7. package/dist/GeminiEmbedding.mjs +1 -1
  8. package/dist/GeminiEmbedding.mjs.map +1 -1
  9. package/dist/GeminiSynthesizer.d.mts +14 -6
  10. package/dist/GeminiSynthesizer.d.mts.map +1 -1
  11. package/dist/GeminiSynthesizer.mjs +13 -12
  12. package/dist/GeminiSynthesizer.mjs.map +1 -1
  13. package/dist/GeminiSynthesizer.test.d.mts +1 -1
  14. package/dist/GeminiSynthesizer.test.mjs +1 -1
  15. package/dist/GeminiSynthesizer.test.mjs.map +1 -1
  16. package/dist/GeminiTools.d.mts +9 -5
  17. package/dist/GeminiTools.d.mts.map +1 -1
  18. package/dist/GeminiTools.mjs +1 -1
  19. package/dist/GeminiTools.mjs.map +1 -1
  20. package/dist/GoogleDeepResearch.d.mts +58 -7
  21. package/dist/GoogleDeepResearch.d.mts.map +1 -1
  22. package/dist/GoogleDeepResearch.mjs +77 -23
  23. package/dist/GoogleDeepResearch.mjs.map +1 -1
  24. package/dist/GoogleDeepResearch.test.d.mts +1 -0
  25. package/dist/GoogleDeepResearch.test.mjs +66 -0
  26. package/dist/GoogleDeepResearch.test.mjs.map +1 -0
  27. package/dist/LyriaGenerator.d.mts +0 -2
  28. package/dist/LyriaGenerator.d.mts.map +1 -1
  29. package/dist/LyriaGenerator.mjs +9 -6
  30. package/dist/LyriaGenerator.mjs.map +1 -1
  31. package/dist/LyriaGenerator.test.d.mts +1 -1
  32. package/dist/LyriaGenerator.test.mjs +153 -8
  33. package/dist/LyriaGenerator.test.mjs.map +1 -1
  34. package/dist/codec.d.mts +36 -11
  35. package/dist/codec.d.mts.map +1 -1
  36. package/dist/codec.mjs +27 -4
  37. package/dist/codec.mjs.map +1 -1
  38. package/dist/geminiSpeechCodec.d.mts +0 -1
  39. package/dist/geminiSpeechCodec.d.mts.map +1 -1
  40. package/dist/geminiSpeechCodec.mjs.map +1 -1
  41. package/dist/geminiSpeechCodec.test.d.mts +1 -1
  42. package/dist/geminiSpeechCodec.test.mjs +1 -1
  43. package/dist/geminiSpeechCodec.test.mjs.map +1 -1
  44. package/dist/geminiTools.test.d.mts +1 -1
  45. package/dist/geminiTools.test.mjs +44 -19
  46. package/dist/geminiTools.test.mjs.map +1 -1
  47. package/dist/{magic-string.es-CaWgF-3p.mjs → magic-string.es-CUAnjKYC.mjs} +14 -13
  48. package/dist/{magic-string.es-CaWgF-3p.mjs.map → magic-string.es-CUAnjKYC.mjs.map} +1 -1
  49. package/dist/models.d.mts.map +1 -1
  50. package/dist/{dist-CWoAuwS9.mjs → test.DNmyFkvJ-N3QqMRwZ.mjs} +257 -381
  51. package/dist/test.DNmyFkvJ-N3QqMRwZ.mjs.map +1 -0
  52. package/package.json +5 -5
  53. package/src/Gemini.ts +4 -7
  54. package/src/GeminiEmbedding.ts +36 -48
  55. package/src/GeminiSynthesizer.ts +20 -27
  56. package/src/GeminiTools.ts +4 -6
  57. package/src/GoogleDeepResearch.test.ts +80 -0
  58. package/src/GoogleDeepResearch.ts +127 -37
  59. package/src/LyriaGenerator.ts +19 -23
  60. package/src/codec.ts +98 -12
  61. package/src/geminiTools.test.ts +21 -3
  62. package/dist/dist-CWoAuwS9.mjs.map +0 -1
  63. /package/dist/{chunk-uyGKjUfl.mjs → rolldown-runtime-EAsCxrDo.mjs} +0 -0
package/dist/codec.mjs CHANGED
@@ -1,11 +1,13 @@
1
- import { n as __exportAll } from "./chunk-uyGKjUfl.mjs";
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
3
  //#region src/codec.ts
4
4
  var codec_exports = /* @__PURE__ */ __exportAll({
5
+ GroundingMetadata: () => GroundingMetadata,
5
6
  WireChunk: () => WireChunk,
6
7
  accumulatorToTurn: () => accumulatorToTurn,
7
8
  buildRequestBody: () => buildRequestBody,
8
9
  emptyAccumulator: () => emptyAccumulator,
10
+ groundingToAnnotations: () => groundingToAnnotations,
9
11
  hasUrlImageSource: () => hasUrlImageSource,
10
12
  ingestChunk: () => ingestChunk
11
13
  });
@@ -37,8 +39,20 @@ const Content = Schema.Struct({
37
39
  role: Schema.optional(Schema.String),
38
40
  parts: Schema.optional(Schema.Array(Part))
39
41
  });
42
+ const GroundingChunk = Schema.Struct({ web: Schema.optional(Schema.NullOr(Schema.Struct({
43
+ uri: Schema.optional(Schema.NullOr(Schema.String)),
44
+ title: Schema.optional(Schema.NullOr(Schema.String))
45
+ }))) });
46
+ const GroundingMetadata = Schema.Struct({ groundingChunks: Schema.optional(Schema.NullOr(Schema.Array(GroundingChunk))) });
47
+ const sameUrlCitation = (a, b) => a.type === "url_citation" && b.type === "url_citation" && a.url === b.url;
48
+ const groundingToAnnotations = (...metas) => pipe(metas, Array$1.flatMap((meta) => meta?.groundingChunks ?? []), Array$1.filterMap((chunk) => chunk.web?.uri == null ? Result.failVoid : Result.succeed({
49
+ type: "url_citation",
50
+ url: chunk.web.uri,
51
+ title: chunk.web.title ?? chunk.web.uri
52
+ })), Array$1.dedupeWith(sameUrlCitation));
40
53
  const Candidate = Schema.Struct({
41
54
  content: Schema.optional(Content),
55
+ groundingMetadata: Schema.optional(GroundingMetadata),
42
56
  finishReason: Schema.optional(Schema.String),
43
57
  index: Schema.optional(Schema.Number)
44
58
  });
@@ -128,6 +142,7 @@ const geminiField = (item, pick) => pipe(decodeGemini(item.providerData), Result
128
142
  onFailure: () => Option.none()
129
143
  }));
130
144
  const providerIdFor = (item) => geminiField(item, (g) => g.id);
145
+ const providerIdForCallId = (history, call_id) => pipe(history, Array$1.findFirst((item) => isFunctionCallItem(item) && item.call_id === call_id), Option.flatMap(providerIdFor));
131
146
  const signatureFor = (item) => geminiField(item, (g) => g.thoughtSignature);
132
147
  const itemToContent = (history) => (item) => Match.value(item).pipe(Match.discriminatorsExhaustive("type")({
133
148
  message: messageToContent,
@@ -151,13 +166,17 @@ const itemToContent = (history) => (item) => Match.value(item).pipe(Match.discri
151
166
  function_call_output: (o) => Result.succeed({
152
167
  role: "user",
153
168
  parts: [{ functionResponse: {
169
+ ...Option.match(providerIdForCallId(history, o.call_id), {
170
+ onSome: (id) => ({ id }),
171
+ onNone: () => ({})
172
+ }),
154
173
  name: Option.getOrElse(nameForCallId(history, o.call_id), () => o.call_id),
155
174
  response: parsedResponse(o.output)
156
175
  } }]
157
176
  }),
158
177
  reasoning: () => Result.failVoid
159
178
  }));
160
- const UNSUPPORTED_SCHEMA_KEYS = new Set([
179
+ const UNSUPPORTED_SCHEMA_KEYS = /* @__PURE__ */ new Set([
161
180
  "$schema",
162
181
  "$ref",
163
182
  "$defs",
@@ -215,6 +234,7 @@ const emptyAccumulator = {
215
234
  text: "",
216
235
  reasoning: "",
217
236
  functionCalls: [],
237
+ annotations: [],
218
238
  finishReason: Option.none(),
219
239
  usage: {}
220
240
  };
@@ -259,6 +279,7 @@ const chunkCallToAccumulated = (prior, call) => ({
259
279
  arguments: JSON.stringify(call.args)
260
280
  });
261
281
  const appendFunctionCalls = (prior, fromChunk) => fromChunk.reduce((acc, call) => [...acc, chunkCallToAccumulated(acc, call)], prior);
282
+ const mergeAnnotations = (prev, next) => next.length === 0 ? prev : Array$1.dedupeWith(Array$1.appendAll(prev, next), sameUrlCitation);
262
283
  const ingestChunk = (acc, chunk) => {
263
284
  const parts = chunkParts(chunk);
264
285
  const finishReason = Option.fromNullishOr(chunk.candidates?.[0]?.finishReason);
@@ -269,6 +290,7 @@ const ingestChunk = (acc, chunk) => {
269
290
  text: acc.text + sumStrings(parts, "text"),
270
291
  reasoning: acc.reasoning + sumStrings(parts, "reasoning"),
271
292
  functionCalls: appendFunctionCalls(acc.functionCalls, collectFunctionCalls(parts)),
293
+ annotations: mergeAnnotations(acc.annotations, groundingToAnnotations(chunk.candidates?.[0]?.groundingMetadata)),
272
294
  finishReason: Option.orElse(finishReason, () => acc.finishReason),
273
295
  usage: mergeUsage(acc.usage, chunk.usageMetadata)
274
296
  }
@@ -283,7 +305,8 @@ const assistantMessageItems = (acc) => acc.text.length === 0 ? [] : [{
283
305
  role: "assistant",
284
306
  content: [{
285
307
  type: "output_text",
286
- text: acc.text
308
+ text: acc.text,
309
+ ...acc.annotations.length > 0 && { annotations: acc.annotations }
287
310
  }]
288
311
  }];
289
312
  const functionCallItems = (acc) => pipe(acc.functionCalls, Array$1.map((c) => {
@@ -309,6 +332,6 @@ const accumulatorToTurn = (acc) => ({
309
332
  ]
310
333
  });
311
334
  //#endregion
312
- export { WireChunk, accumulatorToTurn, buildRequestBody, emptyAccumulator, hasUrlImageSource, ingestChunk, codec_exports as t };
335
+ export { GroundingMetadata, WireChunk, accumulatorToTurn, buildRequestBody, emptyAccumulator, groundingToAnnotations, hasUrlImageSource, ingestChunk, codec_exports as t };
313
336
 
314
337
  //# 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} 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\nconst Candidate = Schema.Struct({\n content: Schema.optional(Content),\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 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(\n \"base64\",\n (s): ReadonlyArray<RequestPart> => [{ inlineData: { mimeType: s.mimeType, data: s.base64 } }],\n ),\n Match.tag(\n \"bytes\",\n (s): ReadonlyArray<RequestPart> => [\n { inlineData: { mimeType: s.mimeType, data: Encoding.encodeBase64(s.bytes) } },\n ],\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)\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 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 = (tools: ReadonlyArray<ToolDescriptor>): 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 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 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\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 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: [{ type: \"output_text\", text: acc.text }],\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 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,7 +1,6 @@
1
1
  import { Effect } from "effect";
2
2
  import * as AiError from "@effect-uai/core/AiError";
3
3
  import { AudioSource } from "@effect-uai/core/Audio";
4
-
5
4
  //#region src/geminiSpeechCodec.d.ts
6
5
  /** Wire shape for an inline-audio part on `generateContent`. */
7
6
  type InlineAudioData = {
@@ -1 +1 @@
1
- {"version":3,"file":"geminiSpeechCodec.d.mts","names":[],"sources":["../src/geminiSpeechCodec.ts"],"mappings":";;;;;;KAiBY,eAAA;EAAA,SACD,QAAA;EAAA,SACA,IAAA;AAAA;;;AAWX;;;;;;cAAa,uBAAA,GACX,KAAA,EAAO,WAAA,KACJ,MAAA,CAAO,MAAA,CAAO,eAAA,EAAiB,OAAA,CAAQ,OAAA;;cAe/B,iBAAA,GAAqB,GAAA,aAAc,MAAA,CAAO,MAAA,CAAO,UAAA,EAAY,OAAA,CAAQ,OAAA;;;;;;;cAarE,YAAA,GACX,GAAA,EAAK,UAAA,EACL,UAAA,UACA,QAAA,UACA,aAAA,cACC,UAAA;;;AAlBH;;cAiDa,eAAA,GAAkB,MAAA,UAAgB,IAAA,aAAiB,OAAA,CAAQ,OAAA;AAAA,cAuC3D,gBAAA,GAAoB,KAAA,cAAiB,OAAA,CAAQ,OAAA"}
1
+ {"version":3,"file":"geminiSpeechCodec.d.mts","names":[],"sources":["../src/geminiSpeechCodec.ts"],"mappings":";;;;;KAiBY;WACD;WACA;;;;;;;;;;cAWE,0BACX,OAAO,gBACJ,OAAO,OAAO,iBAAiB,QAAQ;;cAe/B,oBAAiB,gBAAkB,OAAO,OAAO,YAAY,QAAQ;;;;;;;cAarE,eAAY,KAClB,YAAU,oBACG,kBACF,2BAEf;;;;;cA+BU,kBAAkB,gBAAgB,iBAAiB,QAAQ;cAuC3D,mBAAgB,mBAAqB,QAAQ"}
@@ -1 +1 @@
1
- {"version":3,"file":"geminiSpeechCodec.mjs","names":[],"sources":["../src/geminiSpeechCodec.ts"],"sourcesContent":["import { Effect, Encoding, Match, Result } from \"effect\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport type { AudioSource } from \"@effect-uai/core/Audio\"\n\n/**\n * Shared codec helpers for Gemini speech endpoints. The TTS and STT\n * variants both ride on `:generateContent`, so the audio-in / audio-out\n * shapes are siblings.\n */\n\nconst urlNotSupported: AiError.AiError = new AiError.InvalidRequest({\n provider: \"gemini\",\n param: \"audio\",\n raw: 'Gemini inline audio does not accept URL sources. Either fetch the URL yourself and pass `{ _tag: \"bytes\", bytes, mimeType }`, or upload via the Files API and pass a `fileData` URI (not yet wired here).',\n})\n\n/** Wire shape for an inline-audio part on `generateContent`. */\nexport type InlineAudioData = {\n readonly mimeType: string\n readonly data: string\n}\n\n/**\n * Encode any `AudioSource` into the `inlineData` shape expected by\n * Gemini's `generateContent`. URL variants are rejected with\n * `InvalidRequest` — Files API upload is a separate flow.\n *\n * Caller is responsible for keeping the total request body under\n * 20 MB. Beyond that, Gemini's Files API is the only path.\n */\nexport const audioSourceToInlineData: (\n audio: AudioSource,\n) => Effect.Effect<InlineAudioData, AiError.AiError> = Match.type<AudioSource>().pipe(\n Match.tag(\"bytes\", (a) =>\n Effect.succeed<InlineAudioData>({\n mimeType: a.mimeType,\n data: Encoding.encodeBase64(a.bytes),\n }),\n ),\n Match.tag(\"base64\", (a) =>\n Effect.succeed<InlineAudioData>({ mimeType: a.mimeType, data: a.base64 }),\n ),\n Match.tag(\"url\", () => Effect.fail(urlNotSupported)),\n Match.exhaustive,\n)\n\n/** Decode a base64-encoded audio payload into bytes. */\nexport const decodeBase64Audio = (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: \"gemini\", param: \"audio\", raw: cause })),\n })\n\n/**\n * Wrap a raw 16-bit signed little-endian PCM buffer in a standard 44-byte\n * RIFF/WAVE header. Gemini TTS returns PCM-only — for playback or for\n * round-tripping into Gemini transcription (which accepts wav, not raw\n * PCM), the bytes need a container.\n */\nexport const wrapPcmAsWav = (\n pcm: Uint8Array,\n sampleRate: number,\n channels: number,\n bitsPerSample = 16,\n): Uint8Array => {\n const dataSize = pcm.length\n const blockAlign = (channels * bitsPerSample) / 8\n const byteRate = sampleRate * blockAlign\n const totalSize = 44 + dataSize\n const buffer = new ArrayBuffer(totalSize)\n const view = new DataView(buffer)\n const writeAscii = (offset: number, s: string): void => {\n for (let i = 0; i < s.length; i++) view.setUint8(offset + i, s.charCodeAt(i))\n }\n writeAscii(0, \"RIFF\")\n view.setUint32(4, totalSize - 8, true)\n writeAscii(8, \"WAVE\")\n writeAscii(12, \"fmt \")\n view.setUint32(16, 16, true)\n view.setUint16(20, 1, true)\n view.setUint16(22, channels, true)\n view.setUint32(24, sampleRate, true)\n view.setUint32(28, byteRate, true)\n view.setUint16(32, blockAlign, true)\n view.setUint16(34, bitsPerSample, true)\n writeAscii(36, \"data\")\n view.setUint32(40, dataSize, true)\n new Uint8Array(buffer, 44, dataSize).set(pcm)\n return new Uint8Array(buffer)\n}\n\n/**\n * HTTP status → typed `AiError`. Shared between synthesizer and\n * transcriber.\n */\nexport const 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: \"gemini\", raw: body }),\n ),\n Match.whenOr(\n 408,\n 504,\n (): AiError.AiError => new AiError.Timeout({ provider: \"gemini\", raw: body }),\n ),\n Match.when(\n 401,\n (): AiError.AiError =>\n new AiError.AuthFailed({ provider: \"gemini\", subtype: \"auth\", raw: body }),\n ),\n Match.when(\n 403,\n (): AiError.AiError =>\n new AiError.AuthFailed({ provider: \"gemini\", subtype: \"permission\", raw: body }),\n ),\n Match.when(\n 402,\n (): AiError.AiError =>\n new AiError.AuthFailed({ provider: \"gemini\", subtype: \"billing\", raw: body }),\n ),\n Match.when(\n 413,\n (): AiError.AiError => new AiError.ContextLengthExceeded({ provider: \"gemini\", raw: body }),\n ),\n Match.when(\n (n) => n >= 500,\n (n): AiError.AiError => new AiError.Unavailable({ provider: \"gemini\", status: n, raw: body }),\n ),\n Match.orElse(\n (): AiError.AiError => new AiError.InvalidRequest({ provider: \"gemini\", raw: body }),\n ),\n )\n\nexport const transportFailure = (cause: unknown): AiError.AiError =>\n new AiError.Unavailable({ provider: \"gemini\", raw: cause })\n"],"mappings":"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"}
1
+ {"version":3,"file":"geminiSpeechCodec.mjs","names":[],"sources":["../src/geminiSpeechCodec.ts"],"sourcesContent":["import { Effect, Encoding, Match, Result } from \"effect\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport type { AudioSource } from \"@effect-uai/core/Audio\"\n\n/**\n * Shared codec helpers for Gemini speech endpoints. The TTS and STT\n * variants both ride on `:generateContent`, so the audio-in / audio-out\n * shapes are siblings.\n */\n\nconst urlNotSupported: AiError.AiError = new AiError.InvalidRequest({\n provider: \"gemini\",\n param: \"audio\",\n raw: 'Gemini inline audio does not accept URL sources. Either fetch the URL yourself and pass `{ _tag: \"bytes\", bytes, mimeType }`, or upload via the Files API and pass a `fileData` URI (not yet wired here).',\n})\n\n/** Wire shape for an inline-audio part on `generateContent`. */\nexport type InlineAudioData = {\n readonly mimeType: string\n readonly data: string\n}\n\n/**\n * Encode any `AudioSource` into the `inlineData` shape expected by\n * Gemini's `generateContent`. URL variants are rejected with\n * `InvalidRequest` — Files API upload is a separate flow.\n *\n * Caller is responsible for keeping the total request body under\n * 20 MB. Beyond that, Gemini's Files API is the only path.\n */\nexport const audioSourceToInlineData: (\n audio: AudioSource,\n) => Effect.Effect<InlineAudioData, AiError.AiError> = Match.type<AudioSource>().pipe(\n Match.tag(\"bytes\", (a) =>\n Effect.succeed<InlineAudioData>({\n mimeType: a.mimeType,\n data: Encoding.encodeBase64(a.bytes),\n }),\n ),\n Match.tag(\"base64\", (a) =>\n Effect.succeed<InlineAudioData>({ mimeType: a.mimeType, data: a.base64 }),\n ),\n Match.tag(\"url\", () => Effect.fail(urlNotSupported)),\n Match.exhaustive,\n)\n\n/** Decode a base64-encoded audio payload into bytes. */\nexport const decodeBase64Audio = (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: \"gemini\", param: \"audio\", raw: cause })),\n })\n\n/**\n * Wrap a raw 16-bit signed little-endian PCM buffer in a standard 44-byte\n * RIFF/WAVE header. Gemini TTS returns PCM-only — for playback or for\n * round-tripping into Gemini transcription (which accepts wav, not raw\n * PCM), the bytes need a container.\n */\nexport const wrapPcmAsWav = (\n pcm: Uint8Array,\n sampleRate: number,\n channels: number,\n bitsPerSample = 16,\n): Uint8Array => {\n const dataSize = pcm.length\n const blockAlign = (channels * bitsPerSample) / 8\n const byteRate = sampleRate * blockAlign\n const totalSize = 44 + dataSize\n const buffer = new ArrayBuffer(totalSize)\n const view = new DataView(buffer)\n const writeAscii = (offset: number, s: string): void => {\n for (let i = 0; i < s.length; i++) view.setUint8(offset + i, s.charCodeAt(i))\n }\n writeAscii(0, \"RIFF\")\n view.setUint32(4, totalSize - 8, true)\n writeAscii(8, \"WAVE\")\n writeAscii(12, \"fmt \")\n view.setUint32(16, 16, true)\n view.setUint16(20, 1, true)\n view.setUint16(22, channels, true)\n view.setUint32(24, sampleRate, true)\n view.setUint32(28, byteRate, true)\n view.setUint16(32, blockAlign, true)\n view.setUint16(34, bitsPerSample, true)\n writeAscii(36, \"data\")\n view.setUint32(40, dataSize, true)\n new Uint8Array(buffer, 44, dataSize).set(pcm)\n return new Uint8Array(buffer)\n}\n\n/**\n * HTTP status → typed `AiError`. Shared between synthesizer and\n * transcriber.\n */\nexport const 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: \"gemini\", raw: body }),\n ),\n Match.whenOr(\n 408,\n 504,\n (): AiError.AiError => new AiError.Timeout({ provider: \"gemini\", raw: body }),\n ),\n Match.when(\n 401,\n (): AiError.AiError =>\n new AiError.AuthFailed({ provider: \"gemini\", subtype: \"auth\", raw: body }),\n ),\n Match.when(\n 403,\n (): AiError.AiError =>\n new AiError.AuthFailed({ provider: \"gemini\", subtype: \"permission\", raw: body }),\n ),\n Match.when(\n 402,\n (): AiError.AiError =>\n new AiError.AuthFailed({ provider: \"gemini\", subtype: \"billing\", raw: body }),\n ),\n Match.when(\n 413,\n (): AiError.AiError => new AiError.ContextLengthExceeded({ provider: \"gemini\", raw: body }),\n ),\n Match.when(\n (n) => n >= 500,\n (n): AiError.AiError => new AiError.Unavailable({ provider: \"gemini\", status: n, raw: body }),\n ),\n Match.orElse(\n (): AiError.AiError => new AiError.InvalidRequest({ provider: \"gemini\", raw: body }),\n ),\n )\n\nexport const transportFailure = (cause: unknown): AiError.AiError =>\n new AiError.Unavailable({ provider: \"gemini\", raw: cause })\n"],"mappings":"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"}
@@ -1 +1 @@
1
- export { };
1
+ export {}
@@ -1,4 +1,4 @@
1
- import { i as it, n as globalExpect, r as describe } from "./dist-CWoAuwS9.mjs";
1
+ import { n as describe, r as it, t as globalExpect } from "./test.DNmyFkvJ-N3QqMRwZ.mjs";
2
2
  import { audioSourceToInlineData, wrapPcmAsWav } from "./geminiSpeechCodec.mjs";
3
3
  import { Effect, Encoding } from "effect";
4
4
  //#region src/geminiSpeechCodec.test.ts
@@ -1 +1 @@
1
- {"version":3,"file":"geminiSpeechCodec.test.mjs","names":[],"sources":["../src/geminiSpeechCodec.test.ts"],"sourcesContent":["import { Effect, Encoding } from \"effect\"\nimport { describe, expect, it } from \"vitest\"\nimport type { AudioSource } from \"@effect-uai/core/Audio\"\nimport { audioSourceToInlineData, wrapPcmAsWav } from \"./geminiSpeechCodec.js\"\n\ndescribe(\"audioSourceToInlineData\", () => {\n it(\"encodes a `bytes` source to base64 with its MIME type\", async () => {\n const original = new Uint8Array([0xde, 0xad, 0xbe, 0xef])\n const source: AudioSource = { _tag: \"bytes\", bytes: original, mimeType: \"audio/wav\" }\n const inline = await Effect.runPromise(audioSourceToInlineData(source))\n expect(inline.mimeType).toBe(\"audio/wav\")\n expect(inline.data).toBe(Encoding.encodeBase64(original))\n })\n\n it(\"passes a `base64` source through unchanged\", async () => {\n const b64 = Encoding.encodeBase64(new Uint8Array([1, 2, 3]))\n const source: AudioSource = { _tag: \"base64\", base64: b64, mimeType: \"audio/mp3\" }\n const inline = await Effect.runPromise(audioSourceToInlineData(source))\n expect(inline.mimeType).toBe(\"audio/mp3\")\n expect(inline.data).toBe(b64)\n })\n\n it(\"fails InvalidRequest for a URL source\", async () => {\n const source: AudioSource = { _tag: \"url\", url: \"https://x/y.mp3\", mimeType: \"audio/mp3\" }\n const exit = await Effect.runPromiseExit(audioSourceToInlineData(source))\n expect(exit._tag).toBe(\"Failure\")\n if (exit._tag === \"Failure\") {\n expect(JSON.stringify(exit.cause)).toContain(\"InvalidRequest\")\n expect(JSON.stringify(exit.cause)).toContain(\"Files API\")\n }\n })\n})\n\ndescribe(\"wrapPcmAsWav\", () => {\n it(\"produces a 44-byte RIFF/WAVE header for 16-bit mono PCM at 24 kHz\", () => {\n const pcm = new Uint8Array([0x00, 0x01, 0x00, 0x02])\n const wav = wrapPcmAsWav(pcm, 24000, 1, 16)\n expect(wav.length).toBe(44 + pcm.length)\n const ascii = (start: number, len: number) =>\n String.fromCharCode(...Array.from(wav.slice(start, start + len)))\n expect(ascii(0, 4)).toBe(\"RIFF\")\n expect(ascii(8, 4)).toBe(\"WAVE\")\n expect(ascii(12, 4)).toBe(\"fmt \")\n expect(ascii(36, 4)).toBe(\"data\")\n // RIFF chunk size = total - 8\n const view = new DataView(wav.buffer)\n expect(view.getUint32(4, true)).toBe(wav.length - 8)\n expect(view.getUint16(20, true)).toBe(1) // PCM\n expect(view.getUint16(22, true)).toBe(1) // mono\n expect(view.getUint32(24, true)).toBe(24000) // sample rate\n expect(view.getUint16(34, true)).toBe(16) // bits per sample\n expect(view.getUint32(40, true)).toBe(pcm.length) // data size\n // Payload preserved\n expect(Array.from(wav.slice(44))).toEqual(Array.from(pcm))\n })\n})\n"],"mappings":";;;;AAKA,SAAS,iCAAiC;AACxC,IAAG,yDAAyD,YAAY;EACtE,MAAM,WAAW,IAAI,WAAW;GAAC;GAAM;GAAM;GAAM;GAAK,CAAC;EACzD,MAAM,SAAsB;GAAE,MAAM;GAAS,OAAO;GAAU,UAAU;GAAa;EACrF,MAAM,SAAS,MAAM,OAAO,WAAW,wBAAwB,OAAO,CAAC;AACvE,eAAO,OAAO,SAAS,CAAC,KAAK,YAAY;AACzC,eAAO,OAAO,KAAK,CAAC,KAAK,SAAS,aAAa,SAAS,CAAC;GACzD;AAEF,IAAG,8CAA8C,YAAY;EAC3D,MAAM,MAAM,SAAS,aAAa,IAAI,WAAW;GAAC;GAAG;GAAG;GAAE,CAAC,CAAC;EAC5D,MAAM,SAAsB;GAAE,MAAM;GAAU,QAAQ;GAAK,UAAU;GAAa;EAClF,MAAM,SAAS,MAAM,OAAO,WAAW,wBAAwB,OAAO,CAAC;AACvE,eAAO,OAAO,SAAS,CAAC,KAAK,YAAY;AACzC,eAAO,OAAO,KAAK,CAAC,KAAK,IAAI;GAC7B;AAEF,IAAG,yCAAyC,YAAY;EAEtD,MAAM,OAAO,MAAM,OAAO,eAAe,wBAAwB;GADnC,MAAM;GAAO,KAAK;GAAmB,UAAU;GACN,CAAC,CAAC;AACzE,eAAO,KAAK,KAAK,CAAC,KAAK,UAAU;AACjC,MAAI,KAAK,SAAS,WAAW;AAC3B,gBAAO,KAAK,UAAU,KAAK,MAAM,CAAC,CAAC,UAAU,iBAAiB;AAC9D,gBAAO,KAAK,UAAU,KAAK,MAAM,CAAC,CAAC,UAAU,YAAY;;GAE3D;EACF;AAEF,SAAS,sBAAsB;AAC7B,IAAG,2EAA2E;EAC5E,MAAM,MAAM,IAAI,WAAW;GAAC;GAAM;GAAM;GAAM;GAAK,CAAC;EACpD,MAAM,MAAM,aAAa,KAAK,MAAO,GAAG,GAAG;AAC3C,eAAO,IAAI,OAAO,CAAC,KAAK,KAAK,IAAI,OAAO;EACxC,MAAM,SAAS,OAAe,QAC5B,OAAO,aAAa,GAAG,MAAM,KAAK,IAAI,MAAM,OAAO,QAAQ,IAAI,CAAC,CAAC;AACnE,eAAO,MAAM,GAAG,EAAE,CAAC,CAAC,KAAK,OAAO;AAChC,eAAO,MAAM,GAAG,EAAE,CAAC,CAAC,KAAK,OAAO;AAChC,eAAO,MAAM,IAAI,EAAE,CAAC,CAAC,KAAK,OAAO;AACjC,eAAO,MAAM,IAAI,EAAE,CAAC,CAAC,KAAK,OAAO;EAEjC,MAAM,OAAO,IAAI,SAAS,IAAI,OAAO;AACrC,eAAO,KAAK,UAAU,GAAG,KAAK,CAAC,CAAC,KAAK,IAAI,SAAS,EAAE;AACpD,eAAO,KAAK,UAAU,IAAI,KAAK,CAAC,CAAC,KAAK,EAAE;AACxC,eAAO,KAAK,UAAU,IAAI,KAAK,CAAC,CAAC,KAAK,EAAE;AACxC,eAAO,KAAK,UAAU,IAAI,KAAK,CAAC,CAAC,KAAK,KAAM;AAC5C,eAAO,KAAK,UAAU,IAAI,KAAK,CAAC,CAAC,KAAK,GAAG;AACzC,eAAO,KAAK,UAAU,IAAI,KAAK,CAAC,CAAC,KAAK,IAAI,OAAO;AAEjD,eAAO,MAAM,KAAK,IAAI,MAAM,GAAG,CAAC,CAAC,CAAC,QAAQ,MAAM,KAAK,IAAI,CAAC;GAC1D;EACF"}
1
+ {"version":3,"file":"geminiSpeechCodec.test.mjs","names":[],"sources":["../src/geminiSpeechCodec.test.ts"],"sourcesContent":["import { Effect, Encoding } from \"effect\"\nimport { describe, expect, it } from \"vitest\"\nimport type { AudioSource } from \"@effect-uai/core/Audio\"\nimport { audioSourceToInlineData, wrapPcmAsWav } from \"./geminiSpeechCodec.js\"\n\ndescribe(\"audioSourceToInlineData\", () => {\n it(\"encodes a `bytes` source to base64 with its MIME type\", async () => {\n const original = new Uint8Array([0xde, 0xad, 0xbe, 0xef])\n const source: AudioSource = { _tag: \"bytes\", bytes: original, mimeType: \"audio/wav\" }\n const inline = await Effect.runPromise(audioSourceToInlineData(source))\n expect(inline.mimeType).toBe(\"audio/wav\")\n expect(inline.data).toBe(Encoding.encodeBase64(original))\n })\n\n it(\"passes a `base64` source through unchanged\", async () => {\n const b64 = Encoding.encodeBase64(new Uint8Array([1, 2, 3]))\n const source: AudioSource = { _tag: \"base64\", base64: b64, mimeType: \"audio/mp3\" }\n const inline = await Effect.runPromise(audioSourceToInlineData(source))\n expect(inline.mimeType).toBe(\"audio/mp3\")\n expect(inline.data).toBe(b64)\n })\n\n it(\"fails InvalidRequest for a URL source\", async () => {\n const source: AudioSource = { _tag: \"url\", url: \"https://x/y.mp3\", mimeType: \"audio/mp3\" }\n const exit = await Effect.runPromiseExit(audioSourceToInlineData(source))\n expect(exit._tag).toBe(\"Failure\")\n if (exit._tag === \"Failure\") {\n expect(JSON.stringify(exit.cause)).toContain(\"InvalidRequest\")\n expect(JSON.stringify(exit.cause)).toContain(\"Files API\")\n }\n })\n})\n\ndescribe(\"wrapPcmAsWav\", () => {\n it(\"produces a 44-byte RIFF/WAVE header for 16-bit mono PCM at 24 kHz\", () => {\n const pcm = new Uint8Array([0x00, 0x01, 0x00, 0x02])\n const wav = wrapPcmAsWav(pcm, 24000, 1, 16)\n expect(wav.length).toBe(44 + pcm.length)\n const ascii = (start: number, len: number) =>\n String.fromCharCode(...Array.from(wav.slice(start, start + len)))\n expect(ascii(0, 4)).toBe(\"RIFF\")\n expect(ascii(8, 4)).toBe(\"WAVE\")\n expect(ascii(12, 4)).toBe(\"fmt \")\n expect(ascii(36, 4)).toBe(\"data\")\n // RIFF chunk size = total - 8\n const view = new DataView(wav.buffer)\n expect(view.getUint32(4, true)).toBe(wav.length - 8)\n expect(view.getUint16(20, true)).toBe(1) // PCM\n expect(view.getUint16(22, true)).toBe(1) // mono\n expect(view.getUint32(24, true)).toBe(24000) // sample rate\n expect(view.getUint16(34, true)).toBe(16) // bits per sample\n expect(view.getUint32(40, true)).toBe(pcm.length) // data size\n // Payload preserved\n expect(Array.from(wav.slice(44))).toEqual(Array.from(pcm))\n })\n})\n"],"mappings":";;;;AAKA,SAAS,iCAAiC;CACxC,GAAG,yDAAyD,YAAY;EACtE,MAAM,WAAW,IAAI,WAAW;GAAC;GAAM;GAAM;GAAM;EAAI,CAAC;EACxD,MAAM,SAAsB;GAAE,MAAM;GAAS,OAAO;GAAU,UAAU;EAAY;EACpF,MAAM,SAAS,MAAM,OAAO,WAAW,wBAAwB,MAAM,CAAC;EACtE,aAAO,OAAO,QAAQ,CAAC,CAAC,KAAK,WAAW;EACxC,aAAO,OAAO,IAAI,CAAC,CAAC,KAAK,SAAS,aAAa,QAAQ,CAAC;CAC1D,CAAC;CAED,GAAG,8CAA8C,YAAY;EAC3D,MAAM,MAAM,SAAS,aAAa,IAAI,WAAW;GAAC;GAAG;GAAG;EAAC,CAAC,CAAC;EAC3D,MAAM,SAAsB;GAAE,MAAM;GAAU,QAAQ;GAAK,UAAU;EAAY;EACjF,MAAM,SAAS,MAAM,OAAO,WAAW,wBAAwB,MAAM,CAAC;EACtE,aAAO,OAAO,QAAQ,CAAC,CAAC,KAAK,WAAW;EACxC,aAAO,OAAO,IAAI,CAAC,CAAC,KAAK,GAAG;CAC9B,CAAC;CAED,GAAG,yCAAyC,YAAY;EAEtD,MAAM,OAAO,MAAM,OAAO,eAAe,wBAAwB;GADnC,MAAM;GAAO,KAAK;GAAmB,UAAU;EACP,CAAC,CAAC;EACxE,aAAO,KAAK,IAAI,CAAC,CAAC,KAAK,SAAS;EAChC,IAAI,KAAK,SAAS,WAAW;GAC3B,aAAO,KAAK,UAAU,KAAK,KAAK,CAAC,CAAC,CAAC,UAAU,gBAAgB;GAC7D,aAAO,KAAK,UAAU,KAAK,KAAK,CAAC,CAAC,CAAC,UAAU,WAAW;EAC1D;CACF,CAAC;AACH,CAAC;AAED,SAAS,sBAAsB;CAC7B,GAAG,2EAA2E;EAC5E,MAAM,MAAM,IAAI,WAAW;GAAC;GAAM;GAAM;GAAM;EAAI,CAAC;EACnD,MAAM,MAAM,aAAa,KAAK,MAAO,GAAG,EAAE;EAC1C,aAAO,IAAI,MAAM,CAAC,CAAC,KAAK,KAAK,IAAI,MAAM;EACvC,MAAM,SAAS,OAAe,QAC5B,OAAO,aAAa,GAAG,MAAM,KAAK,IAAI,MAAM,OAAO,QAAQ,GAAG,CAAC,CAAC;EAClE,aAAO,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM;EAC/B,aAAO,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM;EAC/B,aAAO,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM;EAChC,aAAO,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM;EAEhC,MAAM,OAAO,IAAI,SAAS,IAAI,MAAM;EACpC,aAAO,KAAK,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,SAAS,CAAC;EACnD,aAAO,KAAK,UAAU,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;EACvC,aAAO,KAAK,UAAU,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;EACvC,aAAO,KAAK,UAAU,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,IAAK;EAC3C,aAAO,KAAK,UAAU,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,EAAE;EACxC,aAAO,KAAK,UAAU,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,MAAM;EAEhD,aAAO,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,MAAM,KAAK,GAAG,CAAC;CAC3D,CAAC;AACH,CAAC"}
@@ -1 +1 @@
1
- export { };
1
+ export {}
@@ -1,9 +1,10 @@
1
1
  import { codeExecutionTool, googleSearchTool, renderProviderTools } from "./GeminiTools.mjs";
2
- import { WireChunk, buildRequestBody } from "./codec.mjs";
3
- import { i as it, n as globalExpect, r as describe } from "./dist-CWoAuwS9.mjs";
2
+ import { WireChunk, accumulatorToTurn, buildRequestBody, emptyAccumulator, ingestChunk } from "./codec.mjs";
3
+ import { n as describe, r as it, t as globalExpect } from "./test.DNmyFkvJ-N3QqMRwZ.mjs";
4
4
  import { Effect, Option, Result, Schema } from "effect";
5
5
  import * as Tool from "@effect-uai/core/Tool";
6
6
  import { descriptorsOf, providerToolsOf } from "@effect-uai/core/Tool";
7
+ import * as Turn$1 from "@effect-uai/core/Turn";
7
8
  //#region src/geminiTools.test.ts
8
9
  const localTool = Tool.make({
9
10
  name: "get_weather",
@@ -17,61 +18,85 @@ const build = (toolkit) => {
17
18
  };
18
19
  describe("Gemini provider tools", () => {
19
20
  it("renders google_search to the native googleSearch entry", () => {
20
- globalExpect(build({ google_search: googleSearchTool }).tools).toEqual([{ googleSearch: {} }]);
21
+ const body = build({ google_search: googleSearchTool });
22
+ globalExpect(body.tools).toEqual([{ googleSearch: {} }]);
21
23
  });
22
24
  it("renders a mixed toolkit to both a functionDeclarations entry and the native entry", () => {
23
- globalExpect(build({
25
+ const body = build({
24
26
  get_weather: localTool,
25
27
  code_execution: codeExecutionTool
26
- }).tools).toEqual([{ functionDeclarations: [globalExpect.objectContaining({
28
+ });
29
+ globalExpect(body.tools).toEqual([{ functionDeclarations: [globalExpect.objectContaining({
27
30
  name: "get_weather",
28
31
  description: "Look up the weather."
29
32
  })] }, { codeExecution: {} }]);
30
33
  });
31
34
  it("omits toolConfig for a grounding-only request (no forced function-calling mode)", () => {
32
- globalExpect(build({ google_search: googleSearchTool }).toolConfig).toBeUndefined();
35
+ const body = build({ google_search: googleSearchTool });
36
+ globalExpect(body.toolConfig).toBeUndefined();
33
37
  });
34
38
  it("sets toolConfig when a function declaration is present", () => {
35
- globalExpect(build({
39
+ const body = build({
36
40
  get_weather: localTool,
37
41
  google_search: googleSearchTool
38
- }).toolConfig).toEqual({ functionCallingConfig: { mode: "AUTO" } });
42
+ });
43
+ globalExpect(body.toolConfig).toEqual({ functionCallingConfig: { mode: "AUTO" } });
39
44
  });
40
45
  it("fails on a foreign provider tool", () => {
41
- const result = renderProviderTools(providerToolsOf({ web_search: Tool.provider({
46
+ const foreign = Tool.provider({
42
47
  name: "web_search",
43
48
  description: "Anthropic web search.",
44
49
  inputSchema: Tool.noInput,
45
50
  provider: "anthropic",
46
51
  config: { type: "web_search_20250305" }
47
- }) }));
52
+ });
53
+ const result = renderProviderTools(providerToolsOf({ web_search: foreign }));
48
54
  globalExpect(Result.isFailure(result)).toBe(true);
49
55
  });
50
56
  it("fails on an unrecognized config kind", () => {
51
- const result = renderProviderTools(providerToolsOf({ mystery: Tool.provider({
57
+ const bogus = Tool.provider({
52
58
  name: "mystery",
53
59
  description: "Unknown Gemini tool.",
54
60
  inputSchema: Tool.noInput,
55
61
  provider: "gemini",
56
62
  config: { kind: "telepathy" }
57
- }) }));
63
+ });
64
+ const result = renderProviderTools(providerToolsOf({ mystery: bogus }));
58
65
  globalExpect(Result.isFailure(result)).toBe(true);
59
66
  });
60
- it("decodes a grounded chunk without throwing (groundingMetadata is dropped)", () => {
61
- globalExpect(Schema.decodeUnknownSync(WireChunk)({ candidates: [{
67
+ it("maps groundingMetadata chunks to url_citation annotations on the turn", () => {
68
+ const chunk = Schema.decodeUnknownSync(WireChunk)({ candidates: [{
62
69
  content: {
63
70
  role: "model",
64
71
  parts: [{ text: "Grounded answer." }]
65
72
  },
66
73
  groundingMetadata: {
67
74
  webSearchQueries: ["effect ts"],
68
- groundingChunks: [{ web: {
69
- uri: "https://example.com",
70
- title: "Example"
71
- } }]
75
+ groundingChunks: [
76
+ { web: {
77
+ uri: "https://example.com",
78
+ title: "Example"
79
+ } },
80
+ { web: {
81
+ uri: "https://example.com",
82
+ title: "Example"
83
+ } },
84
+ { web: { uri: "https://effect.website" } }
85
+ ]
72
86
  },
73
87
  finishReason: "STOP"
74
- }] }).candidates?.[0]?.content?.parts?.[0]).toEqual({ text: "Grounded answer." });
88
+ }] });
89
+ globalExpect(chunk.candidates?.[0]?.content?.parts?.[0]).toEqual({ text: "Grounded answer." });
90
+ const turn = accumulatorToTurn(ingestChunk(emptyAccumulator, chunk).accumulator);
91
+ globalExpect(Turn$1.citations(turn)).toEqual([{
92
+ type: "url_citation",
93
+ url: "https://example.com",
94
+ title: "Example"
95
+ }, {
96
+ type: "url_citation",
97
+ url: "https://effect.website",
98
+ title: "https://effect.website"
99
+ }]);
75
100
  });
76
101
  });
77
102
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"geminiTools.test.mjs","names":["expect"],"sources":["../src/geminiTools.test.ts"],"sourcesContent":["import { Effect, Option, Result, Schema } from \"effect\"\nimport * as Tool from \"@effect-uai/core/Tool\"\nimport { descriptorsOf, providerToolsOf } from \"@effect-uai/core/Tool\"\nimport { describe, expect, it } from \"vitest\"\nimport { WireChunk, buildRequestBody } from \"./codec.js\"\nimport { codeExecutionTool, googleSearchTool, renderProviderTools } from \"./GeminiTools.js\"\n\nconst localTool = Tool.make({\n name: \"get_weather\",\n description: \"Look up the weather.\",\n inputSchema: Tool.fromEffectSchema(Schema.Struct({ city: Schema.String })),\n run: ({ city }) => Effect.succeed(`sunny in ${city}`),\n})\n\nconst build = (toolkit: Record<string, Tool.AnyTool>) => {\n const entries = Result.getOrThrow(renderProviderTools(providerToolsOf(toolkit)))\n return buildRequestBody([], Option.none(), descriptorsOf(toolkit), entries)\n}\n\ndescribe(\"Gemini provider tools\", () => {\n it(\"renders google_search to the native googleSearch entry\", () => {\n const body = build({ google_search: googleSearchTool })\n expect(body.tools).toEqual([{ googleSearch: {} }])\n })\n\n it(\"renders a mixed toolkit to both a functionDeclarations entry and the native entry\", () => {\n const body = build({ get_weather: localTool, code_execution: codeExecutionTool })\n expect(body.tools).toEqual([\n {\n functionDeclarations: [\n expect.objectContaining({ name: \"get_weather\", description: \"Look up the weather.\" }),\n ],\n },\n { codeExecution: {} },\n ])\n })\n\n it(\"omits toolConfig for a grounding-only request (no forced function-calling mode)\", () => {\n const body = build({ google_search: googleSearchTool })\n expect(body.toolConfig).toBeUndefined()\n })\n\n it(\"sets toolConfig when a function declaration is present\", () => {\n const body = build({ get_weather: localTool, google_search: googleSearchTool })\n expect(body.toolConfig).toEqual({ functionCallingConfig: { mode: \"AUTO\" } })\n })\n\n it(\"fails on a foreign provider tool\", () => {\n const foreign = Tool.provider({\n name: \"web_search\",\n description: \"Anthropic web search.\",\n inputSchema: Tool.noInput,\n provider: \"anthropic\",\n config: { type: \"web_search_20250305\" },\n })\n const result = renderProviderTools(providerToolsOf({ web_search: foreign }))\n expect(Result.isFailure(result)).toBe(true)\n })\n\n it(\"fails on an unrecognized config kind\", () => {\n const bogus = Tool.provider({\n name: \"mystery\",\n description: \"Unknown Gemini tool.\",\n inputSchema: Tool.noInput,\n provider: \"gemini\",\n config: { kind: \"telepathy\" },\n })\n const result = renderProviderTools(providerToolsOf({ mystery: bogus }))\n expect(Result.isFailure(result)).toBe(true)\n })\n\n it(\"decodes a grounded chunk without throwing (groundingMetadata is dropped)\", () => {\n const grounded = {\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: \"Grounded answer.\" }] },\n groundingMetadata: {\n webSearchQueries: [\"effect ts\"],\n groundingChunks: [{ web: { uri: \"https://example.com\", title: \"Example\" } }],\n },\n finishReason: \"STOP\",\n },\n ],\n }\n const chunk = Schema.decodeUnknownSync(WireChunk)(grounded)\n expect(chunk.candidates?.[0]?.content?.parts?.[0]).toEqual({ text: \"Grounded answer.\" })\n })\n})\n"],"mappings":";;;;;;;AAOA,MAAM,YAAY,KAAK,KAAK;CAC1B,MAAM;CACN,aAAa;CACb,aAAa,KAAK,iBAAiB,OAAO,OAAO,EAAE,MAAM,OAAO,QAAQ,CAAC,CAAC;CAC1E,MAAM,EAAE,WAAW,OAAO,QAAQ,YAAY,OAAO;CACtD,CAAC;AAEF,MAAM,SAAS,YAA0C;CACvD,MAAM,UAAU,OAAO,WAAW,oBAAoB,gBAAgB,QAAQ,CAAC,CAAC;AAChF,QAAO,iBAAiB,EAAE,EAAE,OAAO,MAAM,EAAE,cAAc,QAAQ,EAAE,QAAQ;;AAG7E,SAAS,+BAA+B;AACtC,IAAG,gEAAgE;AAEjE,eADa,MAAM,EAAE,eAAe,kBAAkB,CAC3C,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,cAAc,EAAE,EAAE,CAAC,CAAC;GAClD;AAEF,IAAG,2FAA2F;AAE5F,eADa,MAAM;GAAE,aAAa;GAAW,gBAAgB;GAAmB,CACrE,CAAC,MAAM,CAAC,QAAQ,CACzB,EACE,sBAAsB,CACpBA,aAAO,iBAAiB;GAAE,MAAM;GAAe,aAAa;GAAwB,CAAC,CACtF,EACF,EACD,EAAE,eAAe,EAAE,EAAE,CACtB,CAAC;GACF;AAEF,IAAG,yFAAyF;AAE1F,eADa,MAAM,EAAE,eAAe,kBAAkB,CAC3C,CAAC,WAAW,CAAC,eAAe;GACvC;AAEF,IAAG,gEAAgE;AAEjE,eADa,MAAM;GAAE,aAAa;GAAW,eAAe;GAAkB,CACnE,CAAC,WAAW,CAAC,QAAQ,EAAE,uBAAuB,EAAE,MAAM,QAAQ,EAAE,CAAC;GAC5E;AAEF,IAAG,0CAA0C;EAQ3C,MAAM,SAAS,oBAAoB,gBAAgB,EAAE,YAPrC,KAAK,SAAS;GAC5B,MAAM;GACN,aAAa;GACb,aAAa,KAAK;GAClB,UAAU;GACV,QAAQ,EAAE,MAAM,uBAAuB;GACxC,CACuE,EAAE,CAAC,CAAC;AAC5E,eAAO,OAAO,UAAU,OAAO,CAAC,CAAC,KAAK,KAAK;GAC3C;AAEF,IAAG,8CAA8C;EAQ/C,MAAM,SAAS,oBAAoB,gBAAgB,EAAE,SAPvC,KAAK,SAAS;GAC1B,MAAM;GACN,aAAa;GACb,aAAa,KAAK;GAClB,UAAU;GACV,QAAQ,EAAE,MAAM,aAAa;GAC9B,CACkE,EAAE,CAAC,CAAC;AACvE,eAAO,OAAO,UAAU,OAAO,CAAC,CAAC,KAAK,KAAK;GAC3C;AAEF,IAAG,kFAAkF;AAcnF,eADc,OAAO,kBAAkB,UAAU,CAAC,EAXhD,YAAY,CACV;GACE,SAAS;IAAE,MAAM;IAAS,OAAO,CAAC,EAAE,MAAM,oBAAoB,CAAC;IAAE;GACjE,mBAAmB;IACjB,kBAAkB,CAAC,YAAY;IAC/B,iBAAiB,CAAC,EAAE,KAAK;KAAE,KAAK;KAAuB,OAAO;KAAW,EAAE,CAAC;IAC7E;GACD,cAAc;GACf,CACF,EAEuD,CAC9C,CAAC,aAAa,IAAI,SAAS,QAAQ,GAAG,CAAC,QAAQ,EAAE,MAAM,oBAAoB,CAAC;GACxF;EACF"}
1
+ {"version":3,"file":"geminiTools.test.mjs","names":["expect","Turn"],"sources":["../src/geminiTools.test.ts"],"sourcesContent":["import { Effect, Option, Result, Schema } from \"effect\"\nimport * as Tool from \"@effect-uai/core/Tool\"\nimport { descriptorsOf, providerToolsOf } from \"@effect-uai/core/Tool\"\nimport * as Turn from \"@effect-uai/core/Turn\"\nimport { describe, expect, it } from \"vitest\"\nimport {\n WireChunk,\n accumulatorToTurn,\n buildRequestBody,\n emptyAccumulator,\n ingestChunk,\n} from \"./codec.js\"\nimport { codeExecutionTool, googleSearchTool, renderProviderTools } from \"./GeminiTools.js\"\n\nconst localTool = Tool.make({\n name: \"get_weather\",\n description: \"Look up the weather.\",\n inputSchema: Tool.fromEffectSchema(Schema.Struct({ city: Schema.String })),\n run: ({ city }) => Effect.succeed(`sunny in ${city}`),\n})\n\nconst build = (toolkit: Record<string, Tool.AnyTool>) => {\n const entries = Result.getOrThrow(renderProviderTools(providerToolsOf(toolkit)))\n return buildRequestBody([], Option.none(), descriptorsOf(toolkit), entries)\n}\n\ndescribe(\"Gemini provider tools\", () => {\n it(\"renders google_search to the native googleSearch entry\", () => {\n const body = build({ google_search: googleSearchTool })\n expect(body.tools).toEqual([{ googleSearch: {} }])\n })\n\n it(\"renders a mixed toolkit to both a functionDeclarations entry and the native entry\", () => {\n const body = build({ get_weather: localTool, code_execution: codeExecutionTool })\n expect(body.tools).toEqual([\n {\n functionDeclarations: [\n expect.objectContaining({ name: \"get_weather\", description: \"Look up the weather.\" }),\n ],\n },\n { codeExecution: {} },\n ])\n })\n\n it(\"omits toolConfig for a grounding-only request (no forced function-calling mode)\", () => {\n const body = build({ google_search: googleSearchTool })\n expect(body.toolConfig).toBeUndefined()\n })\n\n it(\"sets toolConfig when a function declaration is present\", () => {\n const body = build({ get_weather: localTool, google_search: googleSearchTool })\n expect(body.toolConfig).toEqual({ functionCallingConfig: { mode: \"AUTO\" } })\n })\n\n it(\"fails on a foreign provider tool\", () => {\n const foreign = Tool.provider({\n name: \"web_search\",\n description: \"Anthropic web search.\",\n inputSchema: Tool.noInput,\n provider: \"anthropic\",\n config: { type: \"web_search_20250305\" },\n })\n const result = renderProviderTools(providerToolsOf({ web_search: foreign }))\n expect(Result.isFailure(result)).toBe(true)\n })\n\n it(\"fails on an unrecognized config kind\", () => {\n const bogus = Tool.provider({\n name: \"mystery\",\n description: \"Unknown Gemini tool.\",\n inputSchema: Tool.noInput,\n provider: \"gemini\",\n config: { kind: \"telepathy\" },\n })\n const result = renderProviderTools(providerToolsOf({ mystery: bogus }))\n expect(Result.isFailure(result)).toBe(true)\n })\n\n it(\"maps groundingMetadata chunks to url_citation annotations on the turn\", () => {\n const grounded = {\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: \"Grounded answer.\" }] },\n groundingMetadata: {\n webSearchQueries: [\"effect ts\"],\n groundingChunks: [\n { web: { uri: \"https://example.com\", title: \"Example\" } },\n // Duplicate uri (de-duped) and a bare-domain title fallback.\n { web: { uri: \"https://example.com\", title: \"Example\" } },\n { web: { uri: \"https://effect.website\" } },\n ],\n },\n finishReason: \"STOP\",\n },\n ],\n }\n const chunk = Schema.decodeUnknownSync(WireChunk)(grounded)\n expect(chunk.candidates?.[0]?.content?.parts?.[0]).toEqual({ text: \"Grounded answer.\" })\n\n const turn = accumulatorToTurn(ingestChunk(emptyAccumulator, chunk).accumulator)\n expect(Turn.citations(turn)).toEqual([\n { type: \"url_citation\", url: \"https://example.com\", title: \"Example\" },\n { type: \"url_citation\", url: \"https://effect.website\", title: \"https://effect.website\" },\n ])\n })\n})\n"],"mappings":"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"}
@@ -2,8 +2,8 @@
2
2
  var comma = ",".charCodeAt(0);
3
3
  var semicolon = ";".charCodeAt(0);
4
4
  var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
5
- var intToChar = new Uint8Array(64);
6
- var charToInt = new Uint8Array(128);
5
+ var intToChar = /* @__PURE__ */ new Uint8Array(64);
6
+ var charToInt = /* @__PURE__ */ new Uint8Array(128);
7
7
  for (let i = 0; i < chars.length; i++) {
8
8
  const c = chars.charCodeAt(i);
9
9
  intToChar[i] = c;
@@ -20,7 +20,7 @@ function encodeInteger(builder, num, relative) {
20
20
  } while (delta > 0);
21
21
  return num;
22
22
  }
23
- var bufLength = 1024 * 16;
23
+ var bufLength = 16384;
24
24
  var td = typeof TextDecoder !== "undefined" ? /* @__PURE__ */ new TextDecoder() : typeof Buffer !== "undefined" ? { decode(buf) {
25
25
  return Buffer.from(buf.buffer, buf.byteOffset, buf.byteLength).toString();
26
26
  } } : { decode(buf) {
@@ -218,7 +218,7 @@ function getBtoa() {
218
218
  throw new Error("Unsupported environment: `window.btoa` or `Buffer` should be supported.");
219
219
  };
220
220
  }
221
- const btoa = /* @__PURE__ */ getBtoa();
221
+ const btoa = /*#__PURE__*/ getBtoa();
222
222
  var SourceMap = class {
223
223
  constructor(properties) {
224
224
  this.version = 3;
@@ -355,16 +355,17 @@ var Mappings = class {
355
355
  loc.line,
356
356
  loc.column
357
357
  ];
358
- if (this.hires === "boundary") if (wordRegex.test(original[originalCharIndex])) {
359
- if (!charInHiresBoundary) {
358
+ if (this.hires === "boundary") {
359
+ if (wordRegex.test(original[originalCharIndex])) {
360
+ if (!charInHiresBoundary) {
361
+ this.rawSegments.push(segment);
362
+ charInHiresBoundary = true;
363
+ }
364
+ } else {
360
365
  this.rawSegments.push(segment);
361
- charInHiresBoundary = true;
366
+ charInHiresBoundary = false;
362
367
  }
363
- } else {
364
- this.rawSegments.push(segment);
365
- charInHiresBoundary = false;
366
- }
367
- else this.rawSegments.push(segment);
368
+ } else this.rawSegments.push(segment);
368
369
  }
369
370
  loc.column += 1;
370
371
  this.generatedCodeColumn += 1;
@@ -1010,4 +1011,4 @@ var MagicString = class MagicString {
1010
1011
  //#endregion
1011
1012
  export { MagicString as default };
1012
1013
 
1013
- //# sourceMappingURL=magic-string.es-CaWgF-3p.mjs.map
1014
+ //# sourceMappingURL=magic-string.es-CUAnjKYC.mjs.map