@warlock.js/ai-google 4.5.0 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,16 @@ All notable changes to `@warlock.js/ai-google` are documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). `@warlock.js/*` packages are released in lockstep — every package shares the same version number, so a version below may list only the changes that affected this package.
6
6
 
7
+ ## 4.6.0
8
+
9
+ ### Added
10
+
11
+ - **`google.image({ name })`** — Imagen (`imagen-*`) image generation for use with `ai.image()`. Per-image-metered; when every candidate is safety-filtered the run surfaces a typed `ContentFilterError`. A non-Imagen model id is rejected at construction.
12
+
13
+ ### Fixed
14
+
15
+ - **PDF + audio input are now explicitly mapped and tested.** The content-part mapper documents and proves that `pdf` / `audio` parts route to Gemini `inlineData` (the `pdf` / `audio` capabilities the adapter advertises are backed by a real mapper, not an accident of the image path), and the remote-URL rejection now names the actual modality instead of always saying "images".
16
+
7
17
  ## 4.3.0 - 2026-06-21
8
18
 
9
19
  ### Added
package/cjs/index.cjs CHANGED
@@ -133,17 +133,26 @@ function toResponseObject(raw) {
133
133
  return { result: raw };
134
134
  }
135
135
  /**
136
- * Map a resolved `ContentPart` to a Gemini `Part`. Images are sent as
137
- * inline base64 (`inlineData`). Gemini's `generateContent` does not
138
- * fetch arbitrary remote URLs (only Files API / GCS URIs via
139
- * `fileData`), so a neutral `{ url }` image surfaces a typed
140
- * `InvalidRequestError` upfront rather than a downstream Gemini fault.
141
- * The agent resolves attachments before this point, so nothing is
142
- * read or fetched here.
136
+ * Map a resolved `ContentPart` to a Gemini `Part`. All binary
137
+ * modalities **image, PDF, and audio** — go to a single
138
+ * `inlineData: { mimeType, data }` block; Gemini's multimodal input is
139
+ * media-agnostic and keys off the IANA `mimeType` (`image/png`,
140
+ * `application/pdf`, `audio/mpeg`, …), so one mapping covers every part
141
+ * type the model's capabilities admit. PDF and audio reach this point
142
+ * only when the model declares the matching capability (`google.model`
143
+ * infers `pdf` / `audio` from the multimodal Gemini families); the
144
+ * agent's modality gate throws upfront otherwise, so capability and
145
+ * behavior stay in lockstep.
146
+ *
147
+ * Gemini's `generateContent` does not fetch arbitrary remote URLs (only
148
+ * Files API / GCS URIs via `fileData`), so a neutral `{ url }` source
149
+ * surfaces a typed `InvalidRequestError` upfront — for any modality —
150
+ * rather than a downstream Gemini fault. The agent resolves attachments
151
+ * before this point, so nothing is read or fetched here.
143
152
  */
144
153
  function toGooglePart(part) {
145
154
  if (part.type === "text") return { text: part.text };
146
- if ("url" in part.source) throw new _warlock_js_ai.InvalidRequestError("Gemini generateContent does not fetch remote-URL images; supply base64 image bytes instead.");
155
+ if ("url" in part.source) throw new _warlock_js_ai.InvalidRequestError(`Gemini generateContent cannot fetch remote-URL ${part.type} media; supply base64 bytes instead.`);
147
156
  return { inlineData: {
148
157
  mimeType: part.source.mediaType,
149
158
  data: part.source.base64
@@ -292,7 +301,7 @@ function buildContext(shape) {
292
301
 
293
302
  //#endregion
294
303
  //#region ../@warlock.js/ai-google/src/embedder.ts
295
- const LOG_MODULE$1 = "ai.google";
304
+ const LOG_MODULE$2 = "ai.google";
296
305
  /**
297
306
  * Token usage is not returned by Gemini's `embedContent`, so every
298
307
  * embedding result reports a zeroed `EmbeddingUsage` (honest absence,
@@ -358,7 +367,7 @@ var GoogleEmbedder = class {
358
367
  * and return the raw vectors in input order.
359
368
  */
360
369
  async request(inputs) {
361
- this.logger.debug(LOG_MODULE$1, "embedder.request", "embedContent", {
370
+ this.logger.debug(LOG_MODULE$2, "embedder.request", "embedContent", {
362
371
  model: this.name,
363
372
  count: inputs.length
364
373
  });
@@ -371,7 +380,7 @@ var GoogleEmbedder = class {
371
380
  });
372
381
  } catch (thrown) {
373
382
  const wrapped = wrapGoogleError(thrown);
374
- this.logger.error(LOG_MODULE$1, "embedder.error", wrapped.message, {
383
+ this.logger.error(LOG_MODULE$2, "embedder.error", wrapped.message, {
375
384
  code: wrapped.code,
376
385
  context: wrapped.context
377
386
  });
@@ -379,7 +388,7 @@ var GoogleEmbedder = class {
379
388
  }
380
389
  const vectors = (response.embeddings ?? []).map((embedding) => embedding.values ?? []);
381
390
  if (this.dimensions === 0 && vectors[0]) this.dimensions = vectors[0].length;
382
- this.logger.debug(LOG_MODULE$1, "embedder.response", "embedContent returned", {
391
+ this.logger.debug(LOG_MODULE$2, "embedder.response", "embedContent returned", {
383
392
  count: vectors.length,
384
393
  dimensions: this.dimensions
385
394
  });
@@ -387,6 +396,135 @@ var GoogleEmbedder = class {
387
396
  }
388
397
  };
389
398
 
399
+ //#endregion
400
+ //#region ../@warlock.js/ai-google/src/known-image-models.ts
401
+ /**
402
+ * Model-id prefixes Google exposes through the **Imagen** image API
403
+ * (`ai.models.generateImages`) — `imagen-3.0-*`, `imagen-4.0-*`, and
404
+ * their fast/ultra variants. All are per-image-metered and return
405
+ * base64 bytes.
406
+ *
407
+ * Gemini's *native* image output (`gemini-2.5-flash-image`) is a
408
+ * different surface (`generateContent` with `responseModalities`) and
409
+ * is intentionally NOT routed here — `google.image()` targets the
410
+ * dedicated Imagen endpoint only.
411
+ *
412
+ * Used by {@link isGoogleImageModel} for the construction-time guard so
413
+ * `google.image({ name: "gemini-2.5-flash" })` fails fast with a
414
+ * curated error rather than a downstream 400.
415
+ */
416
+ const GOOGLE_IMAGE_MODEL_PREFIXES = ["imagen-"];
417
+ /**
418
+ * True when `name` is a recognized Google Imagen model. A prefix match
419
+ * so dated/variant ids (`imagen-4.0-ultra-generate-001`) are covered
420
+ * without an exact-list maintenance burden.
421
+ *
422
+ * @example
423
+ * isGoogleImageModel("imagen-4.0-generate-001"); // true
424
+ * isGoogleImageModel("gemini-2.5-flash"); // false
425
+ */
426
+ function isGoogleImageModel(name) {
427
+ return GOOGLE_IMAGE_MODEL_PREFIXES.some((prefix) => name.startsWith(prefix));
428
+ }
429
+
430
+ //#endregion
431
+ //#region ../@warlock.js/ai-google/src/image.ts
432
+ const LOG_MODULE$1 = "ai.google";
433
+ /** Map a neutral output container hint to an IANA media type. */
434
+ function mediaTypeFor(format) {
435
+ switch (format) {
436
+ case "png": return "image/png";
437
+ case "jpeg":
438
+ case "jpg": return "image/jpeg";
439
+ case "webp": return "image/webp";
440
+ default: return;
441
+ }
442
+ }
443
+ /**
444
+ * Google Imagen-backed implementation of `ImageModelContract`, via
445
+ * `ai.models.generateImages`. Imagen is per-image-metered and returns
446
+ * base64 image bytes (no hosted URL, no token usage).
447
+ *
448
+ * **Capability guard.** The constructor rejects a non-Imagen model id
449
+ * up front — `google.image({ name: "gemini-2.5-flash" })` throws a
450
+ * typed `InvalidRequestError` instead of a downstream 400 (Gemini's
451
+ * native image output is a different API and not routed here).
452
+ *
453
+ * **Safety filtering.** When Imagen filters every candidate for safety
454
+ * (`raiFilteredReason`), this surfaces a typed `ContentFilterError`
455
+ * carrying the reason, rather than returning an empty success.
456
+ *
457
+ * @example
458
+ * const model = new GoogleImageModel(ai, { name: "imagen-4.0-generate-001" }, "google");
459
+ * const { images } = await model.generate("a watercolor lighthouse at dawn");
460
+ */
461
+ var GoogleImageModel = class {
462
+ constructor(ai, config, provider = "google") {
463
+ this.logger = _warlock_js_logger.log;
464
+ if (!isGoogleImageModel(config.name)) throw new _warlock_js_ai.InvalidRequestError(`"${config.name}" is not a known Google Imagen model. Use an \`imagen-*\` model with google.image({ name }).`);
465
+ this.ai = ai;
466
+ this.name = config.name;
467
+ this.provider = provider;
468
+ this.pricing = config.pricing;
469
+ }
470
+ async generate(prompt, options) {
471
+ const config = {};
472
+ if (options?.count !== void 0) config.numberOfImages = options.count;
473
+ if (options?.aspectRatio !== void 0) config.aspectRatio = options.aspectRatio;
474
+ if (options?.negativePrompt !== void 0) config.negativePrompt = options.negativePrompt;
475
+ if (options?.signal !== void 0) config.abortSignal = options.signal;
476
+ const outputMimeType = mediaTypeFor(options?.format);
477
+ if (outputMimeType !== void 0) config.outputMimeType = outputMimeType;
478
+ if (typeof options?.imageSize === "string") config.imageSize = options.imageSize;
479
+ if (typeof options?.personGeneration === "string") config.personGeneration = options.personGeneration;
480
+ this.logger.debug(LOG_MODULE$1, "image.request", "models.generateImages", {
481
+ model: this.name,
482
+ count: options?.count ?? 1
483
+ });
484
+ let response;
485
+ try {
486
+ response = await this.ai.models.generateImages({
487
+ model: this.name,
488
+ prompt,
489
+ config
490
+ });
491
+ } catch (thrown) {
492
+ const wrapped = wrapGoogleError(thrown);
493
+ this.logger.error(LOG_MODULE$1, "image.error", wrapped.message, {
494
+ code: wrapped.code,
495
+ context: wrapped.context
496
+ });
497
+ throw wrapped;
498
+ }
499
+ const generated = response.generatedImages ?? [];
500
+ const images = [];
501
+ for (const candidate of generated) {
502
+ const bytes = candidate.image?.imageBytes;
503
+ if (!bytes) continue;
504
+ images.push({
505
+ type: "base64",
506
+ base64: bytes,
507
+ mediaType: candidate.image?.mimeType ?? outputMimeType ?? "image/png",
508
+ ...candidate.enhancedPrompt ? { revisedPrompt: candidate.enhancedPrompt } : {}
509
+ });
510
+ }
511
+ if (images.length === 0) {
512
+ const filtered = generated.find((candidate) => candidate.raiFilteredReason);
513
+ if (filtered?.raiFilteredReason) throw new _warlock_js_ai.ContentFilterError(`Imagen filtered all candidates: ${filtered.raiFilteredReason}`, { reason: filtered.raiFilteredReason });
514
+ throw new _warlock_js_ai.ProviderError("Imagen returned no images.");
515
+ }
516
+ this.logger.debug(LOG_MODULE$1, "image.response", "models.generateImages succeeded", { images: images.length });
517
+ return {
518
+ images,
519
+ usage: {
520
+ input: 0,
521
+ output: 0,
522
+ total: 0
523
+ }
524
+ };
525
+ }
526
+ };
527
+
390
528
  //#endregion
391
529
  //#region ../@warlock.js/ai-google/src/known-vision-models.ts
392
530
  /**
@@ -805,8 +943,33 @@ var GoogleSDK = class {
805
943
  embedder(config) {
806
944
  return new GoogleEmbedder(this.ai, config, this.provider);
807
945
  }
946
+ /**
947
+ * Build a `GoogleImageModel` (Imagen) bound to this SDK's client for
948
+ * use with `ai.image({ model, prompt })`. Accepts the `imagen-*`
949
+ * family; a non-Imagen model id is rejected at construction.
950
+ *
951
+ * Pricing resolution mirrors `model()`: per-model `config.pricing`
952
+ * wins, otherwise the SDK-level registry entry keyed by `config.name`,
953
+ * otherwise `undefined`. Imagen is per-image-metered, so the registry
954
+ * entry typically carries `{ perImage }`.
955
+ *
956
+ * @example
957
+ * const model = google.image({ name: "imagen-4.0-generate-001" });
958
+ * const { data } = await ai.image({ model, prompt: "a watercolor lighthouse" });
959
+ */
960
+ image(config) {
961
+ const resolvedPricing = config.pricing ?? this.pricing?.[config.name];
962
+ const resolvedConfig = resolvedPricing === config.pricing ? config : {
963
+ ...config,
964
+ pricing: resolvedPricing
965
+ };
966
+ return new GoogleImageModel(this.ai, resolvedConfig, this.provider);
967
+ }
808
968
  };
809
969
 
810
970
  //#endregion
971
+ exports.GOOGLE_IMAGE_MODEL_PREFIXES = GOOGLE_IMAGE_MODEL_PREFIXES;
972
+ exports.GoogleImageModel = GoogleImageModel;
811
973
  exports.GoogleSDK = GoogleSDK;
974
+ exports.isGoogleImageModel = isGoogleImageModel;
812
975
  //# sourceMappingURL=index.cjs.map
package/cjs/index.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["InvalidRequestError","AIError","ProviderTimeoutError","ProviderAuthError","ProviderRateLimitError","ContextLengthExceededError","InvalidRequestError","ProviderError","ApiError","LOG_MODULE","log","log","GoogleGenAI"],"sources":["../../../../../../@warlock.js/ai-google/src/utils/map-finish-reason.ts","../../../../../../@warlock.js/ai-google/src/utils/to-google-contents.ts","../../../../../../@warlock.js/ai-google/src/utils/to-google-tools.ts","../../../../../../@warlock.js/ai-google/src/utils/wrap-google-error.ts","../../../../../../@warlock.js/ai-google/src/embedder.ts","../../../../../../@warlock.js/ai-google/src/known-vision-models.ts","../../../../../../@warlock.js/ai-google/src/model.ts","../../../../../../@warlock.js/ai-google/src/sdk.ts"],"sourcesContent":["import type { FinishReason } from \"@warlock.js/ai\";\n\nconst finishReasonMap: Record<string, FinishReason> = {\n STOP: \"stop\",\n MAX_TOKENS: \"length\",\n};\n\n/**\n * Map Gemini's `FinishReason` enum value to the normalized\n * `FinishReason` union.\n *\n * `STOP` is the natural terminal. `MAX_TOKENS` maps to `length`.\n * Everything else — `SAFETY`, `RECITATION`, `BLOCKLIST`,\n * `PROHIBITED_CONTENT`, `SPII`, `MALFORMED_FUNCTION_CALL`,\n * `UNEXPECTED_TOOL_CALL`, `LANGUAGE`, `OTHER`,\n * `FINISH_REASON_UNSPECIFIED`, `null`, or any future value — falls\n * through to `\"error\"`.\n *\n * Note: Gemini reports `STOP` even when the turn ended in a function\n * call (it has no `tool_use` reason). `GoogleModel` overrides the\n * mapped reason to `\"tool_calls\"` when the response carries function\n * calls — this map intentionally stays purely about the raw signal.\n *\n * @example\n * mapFinishReason(\"STOP\"); // \"stop\"\n * mapFinishReason(\"MAX_TOKENS\"); // \"length\"\n * mapFinishReason(\"SAFETY\"); // \"error\"\n * mapFinishReason(undefined); // \"error\"\n */\nexport function mapFinishReason(raw: string | null | undefined): FinishReason {\n return finishReasonMap[raw ?? \"\"] ?? \"error\";\n}\n","import { InvalidRequestError, safeJsonParse, type ContentPart, type Message } from \"@warlock.js/ai\";\nimport type { Content, Part } from \"@google/genai\";\n\n/**\n * Result of splitting a vendor-neutral `Message[]` for Gemini's\n * `generateContent`: the system prompt is hoisted to a separate\n * `systemInstruction` string (Gemini has no `\"system\"` role — content\n * roles must be `\"user\"` or `\"model\"`), and the remaining turns map to\n * `Content[]`.\n */\nexport type GoogleContents = {\n systemInstruction: string | undefined;\n contents: Content[];\n};\n\n/**\n * Convert vendor-neutral `Message[]` into Gemini's request shape.\n *\n * Gemini specifics this function absorbs:\n *\n * 1. **No `system` role.** System messages concatenate into the\n * separate `systemInstruction` config field.\n * 2. **Role names differ.** Neutral `assistant` → Gemini `\"model\"`;\n * `user` stays `\"user\"`.\n * 3. **Tool results are `user` turns.** A neutral `tool` message\n * becomes a `\"user\"` content with a single `functionResponse` part.\n * 4. **Tool calls are `functionCall` parts.** An assistant message\n * with `toolCalls` becomes a `\"model\"` content: an optional leading\n * `text` part followed by one `functionCall` part per call.\n *\n * @example\n * const { systemInstruction, contents } = toGoogleContents([\n * { role: \"system\", content: \"Be concise.\" },\n * { role: \"user\", content: \"Hi\" },\n * ]);\n */\nexport function toGoogleContents(messages: Message[]): GoogleContents {\n const systemParts: string[] = [];\n const contents: Content[] = [];\n\n for (const message of messages) {\n if (message.role === \"system\") {\n systemParts.push(stringifyContent(message.content));\n\n continue;\n }\n\n if (message.role === \"tool\") {\n contents.push({\n role: \"user\",\n parts: [\n {\n // Gemini matches a `functionResponse` to its `functionCall`\n // by `name` (the Developer API has no call ids). `name` is\n // the neutral `toolCallId`, which `GoogleModel` set to the\n // function name. The wire `id` is intentionally omitted —\n // an empty/synthetic id is rejected as an invalid argument.\n functionResponse: {\n name: message.toolCallId ?? \"\",\n response: toResponseObject(stringifyContent(message.content)),\n },\n },\n ],\n });\n\n continue;\n }\n\n if (message.role === \"assistant\" && message.toolCalls && message.toolCalls.length > 0) {\n const parts: Part[] = [];\n const text = stringifyContent(message.content);\n\n if (text) {\n parts.push({ text });\n }\n\n for (const toolCall of message.toolCalls) {\n // Replay the opaque `thoughtSignature` Gemini attached to this\n // function call on the original turn. Thinking models reject\n // the follow-up request with a 400 if the signature is missing\n // from the echoed `functionCall` part. Captured by\n // `GoogleModel.partToToolCall` into `providerMetadata`.\n const thoughtSignature = toolCall.providerMetadata?.thoughtSignature;\n\n parts.push({\n ...(typeof thoughtSignature === \"string\" ? { thoughtSignature } : {}),\n // `id` omitted deliberately — Gemini Developer API function\n // calls have no ids; echoing an empty/synthetic one is\n // rejected as an invalid argument. Matched by `name`.\n functionCall: {\n name: toolCall.name,\n args: (toolCall.input ?? {}) as Record<string, unknown>,\n },\n });\n }\n\n contents.push({ role: \"model\", parts });\n\n continue;\n }\n\n if (message.role === \"user\" && Array.isArray(message.content)) {\n contents.push({ role: \"user\", parts: message.content.map(toGooglePart) });\n\n continue;\n }\n\n contents.push({\n role: message.role === \"assistant\" ? \"model\" : \"user\",\n parts: [{ text: stringifyContent(message.content) }],\n });\n }\n\n return {\n systemInstruction: systemParts.length > 0 ? systemParts.join(\"\\n\\n\") : undefined,\n contents,\n };\n}\n\n/**\n * Multipart content is only meaningful on user messages — for any\n * other role collapse a `ContentPart[]` to concatenated text. Plain\n * strings pass through unchanged.\n */\nfunction stringifyContent(content: string | ContentPart[]): string {\n if (typeof content === \"string\") {\n return content;\n }\n\n return content\n .filter((part): part is { type: \"text\"; text: string } => part.type === \"text\")\n .map((part) => part.text)\n .join(\"\");\n}\n\n/**\n * Gemini's `functionResponse.response` must be a JSON object. Tool\n * results arrive as a string (usually stringified JSON) — parse it\n * when it is a JSON object, otherwise wrap the raw string under a\n * `result` key so the model always receives a well-formed object.\n */\nfunction toResponseObject(raw: string): Record<string, unknown> {\n const parsed = safeJsonParse<unknown>(raw, undefined);\n\n if (parsed !== null && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n return parsed as Record<string, unknown>;\n }\n\n return { result: raw };\n}\n\n/**\n * Map a resolved `ContentPart` to a Gemini `Part`. Images are sent as\n * inline base64 (`inlineData`). Gemini's `generateContent` does not\n * fetch arbitrary remote URLs (only Files API / GCS URIs via\n * `fileData`), so a neutral `{ url }` image surfaces a typed\n * `InvalidRequestError` upfront rather than a downstream Gemini fault.\n * The agent resolves attachments before this point, so nothing is\n * read or fetched here.\n */\nfunction toGooglePart(part: ContentPart): Part {\n if (part.type === \"text\") {\n return { text: part.text };\n }\n\n if (\"url\" in part.source) {\n throw new InvalidRequestError(\n \"Gemini generateContent does not fetch remote-URL images; supply base64 image bytes instead.\",\n );\n }\n\n return {\n inlineData: { mimeType: part.source.mediaType, data: part.source.base64 },\n };\n}\n","import { extractJsonSchema, type ToolConfig } from \"@warlock.js/ai\";\nimport type { Tool } from \"@google/genai\";\n\n/**\n * Convert vendor-neutral `ToolConfig[]` into Gemini's `tools` array —\n * a single `Tool` carrying one `functionDeclarations` entry per tool.\n *\n * The input schema is forwarded via `parametersJsonSchema` (raw JSON\n * Schema, mutually exclusive with Gemini's typed `parameters`).\n * Non-object extractions degrade to a parameterless object so\n * registration never fails.\n *\n * Returns `undefined` when there are no tools so the caller can omit\n * `config.tools` entirely.\n *\n * @example\n * const tools = toGoogleTools([weatherTool]);\n * await ai.models.generateContent({ model, contents, config: { tools } });\n */\nexport function toGoogleTools(\n tools: ToolConfig<unknown, unknown>[] | undefined,\n): Tool[] | undefined {\n if (!tools || tools.length === 0) {\n return undefined;\n }\n\n return [\n {\n functionDeclarations: tools.map((tool) => ({\n name: tool.name,\n description: tool.description,\n parametersJsonSchema: toJsonSchema(tool.input),\n })),\n },\n ];\n}\n\n/**\n * Resolve a tool's input schema to a JSON-Schema object. Gemini wants\n * an object root for function parameters; anything else (or a failed\n * extraction) degrades to a parameterless object.\n */\nfunction toJsonSchema(input: ToolConfig<unknown, unknown>[\"input\"]): Record<string, unknown> {\n const schema = extractJsonSchema(input);\n\n if (schema && schema.type === \"object\") {\n return schema;\n }\n\n return { type: \"object\" };\n}\n","import {\n AIError,\n ContextLengthExceededError,\n InvalidRequestError,\n ProviderAuthError,\n ProviderError,\n ProviderRateLimitError,\n ProviderTimeoutError,\n} from \"@warlock.js/ai\";\nimport { ApiError } from \"@google/genai\";\n\n/**\n * Raw-error fields the wrapper reads off a Gemini SDK error.\n * `@google/genai`'s `ApiError` exposes `status` (HTTP code) +\n * `message`; transport aborts surface as `AbortError` / `ETIMEDOUT`.\n * We duck-type so proxied / re-thrown errors still classify.\n */\ntype GoogleErrorShape = {\n status?: number;\n message?: string;\n name?: string;\n code?: string;\n};\n\n/**\n * Wrap any thrown value caught inside the Gemini adapter into the\n * appropriate `@warlock.js/ai` `AIError` subclass.\n *\n * **Dispatch strategy.** Gemini has no machine error `code`; the\n * signals are the HTTP `status` and the canonical status phrase Google\n * embeds in `message` (`PERMISSION_DENIED`, `RESOURCE_EXHAUSTED`,\n * `INVALID_ARGUMENT`, …). Dispatch keys on `status`, using the message\n * phrase as the tie-breaker for the two 400 sub-cases\n * (context-length vs generic) and for status-less auth/quota errors.\n *\n * `AIError` instances pass through unchanged so `catch/throw wrap(e)`\n * pipelines never double-wrap.\n *\n * @example\n * try {\n * return await this.ai.models.generateContent(...);\n * } catch (thrown) {\n * throw wrapGoogleError(thrown);\n * }\n */\nexport function wrapGoogleError(thrown: unknown): AIError {\n if (thrown instanceof AIError) {\n return thrown;\n }\n\n const shape = toShape(thrown);\n const context = buildContext(shape);\n const message = shape.message ?? (thrown instanceof Error ? thrown.message : String(thrown));\n\n if (isTimeout(shape)) {\n return new ProviderTimeoutError(message, { cause: thrown, context });\n }\n\n if (\n shape.status === 401 ||\n shape.status === 403 ||\n /permission_denied|api key not valid|unauthenticated/i.test(message)\n ) {\n return new ProviderAuthError(message, { cause: thrown, context });\n }\n\n if (shape.status === 429 || /resource_exhausted|quota/i.test(message)) {\n return new ProviderRateLimitError(message, { cause: thrown, context });\n }\n\n if (shape.status === 400) {\n if (/token count|context length|exceeds the maximum|input is too long/i.test(message)) {\n return new ContextLengthExceededError(message, { cause: thrown, context });\n }\n\n return new InvalidRequestError(message, { cause: thrown, context });\n }\n\n if (shape.status === 404 || isClientStatus(shape.status)) {\n return new InvalidRequestError(message, { cause: thrown, context });\n }\n\n return new ProviderError(message, { cause: thrown, context });\n}\n\n/**\n * Read the raw error shape. The Gemini SDK's `ApiError` carries a\n * numeric `status`; flattened/proxied errors may carry it (or `code`)\n * loosely.\n */\nfunction toShape(thrown: unknown): GoogleErrorShape {\n if (thrown instanceof ApiError) {\n return { status: thrown.status, message: thrown.message, name: thrown.name };\n }\n\n if (typeof thrown === \"object\" && thrown !== null) {\n const raw = thrown as Record<string, unknown>;\n\n return {\n status: typeof raw.status === \"number\" ? raw.status : undefined,\n message: typeof raw.message === \"string\" ? raw.message : undefined,\n name: typeof raw.name === \"string\" ? raw.name : undefined,\n code: typeof raw.code === \"string\" ? raw.code : undefined,\n };\n }\n\n return {};\n}\n\n/**\n * Decide whether the error is a timeout. Gemini maps gateway timeouts\n * to HTTP 504 (`DEADLINE_EXCEEDED`); transport aborts surface as\n * `AbortError` / `ETIMEDOUT` / `ECONNABORTED`.\n */\nfunction isTimeout(shape: GoogleErrorShape): boolean {\n if (shape.status === 504) {\n return true;\n }\n\n if (shape.name === \"AbortError\" || /deadline_exceeded/i.test(shape.message ?? \"\")) {\n return true;\n }\n\n return shape.code === \"ETIMEDOUT\" || shape.code === \"ECONNABORTED\";\n}\n\n/** True for HTTP 4xx — a client-side request problem, not a server fault. */\nfunction isClientStatus(status: number | undefined): boolean {\n return typeof status === \"number\" && status >= 400 && status < 500;\n}\n\n/** Attach the diagnostic fields to `error.context`. */\nfunction buildContext(shape: GoogleErrorShape): Record<string, unknown> {\n const context: Record<string, unknown> = {};\n\n if (shape.status !== undefined) {\n context.status = shape.status;\n }\n\n if (shape.name) {\n context.code = shape.name;\n }\n\n return context;\n}\n","import {\n type EmbeddingBatchResult,\n type EmbeddingResult,\n type EmbeddingUsage,\n type EmbedderContract,\n} from \"@warlock.js/ai\";\nimport { log, type Logger } from \"@warlock.js/logger\";\nimport type { EmbedContentResponse, GoogleGenAI } from \"@google/genai\";\nimport type { GoogleEmbedderConfig } from \"./config.type\";\nimport { wrapGoogleError } from \"./utils\";\n\nconst LOG_MODULE = \"ai.google\";\n\n/**\n * Token usage is not returned by Gemini's `embedContent`, so every\n * embedding result reports a zeroed `EmbeddingUsage` (honest absence,\n * not a fabricated estimate).\n */\nconst NO_USAGE: EmbeddingUsage = { promptTokens: 0, totalTokens: 0 };\n\n/**\n * Google Gemini-backed implementation of `EmbedderContract`\n * (`gemini-embedding-001`, `text-embedding-004`, …) via\n * `models.embedContent`.\n *\n * **Role.** Converts text into floating-point vectors. Standalone\n * primitive — unrelated to generateContent / tools / the agent loop.\n *\n * **Batch is native.** Gemini's `embedContent` accepts an array of\n * inputs and returns embeddings in the same order, so `embedMany` is\n * a single request (unlike the Bedrock/Titan adapter, which has to\n * loop).\n *\n * **No usage.** Gemini's embed endpoint returns no token counts;\n * `usage` is always `{ promptTokens: 0, totalTokens: 0 }`.\n *\n * **Dimensions.** When no `dimensions` override is given,\n * `this.dimensions` starts at `0` and is populated from the first\n * response's vector length, then cached. Passing `dimensions`\n * forwards Gemini's `outputDimensionality` truncation hint and sets\n * the initial value immediately.\n *\n * @example\n * const embedder = new GoogleEmbedder(ai, { name: \"gemini-embedding-001\" });\n * const { vector } = await embedder.embed(\"Hello world\");\n * const { vectors } = await embedder.embedMany([\"doc 1\", \"doc 2\"]);\n */\nexport class GoogleEmbedder implements EmbedderContract {\n public readonly name: string;\n public readonly provider: string;\n public dimensions: number;\n\n private readonly ai: GoogleGenAI;\n private readonly configuredDimensions: number | undefined;\n private readonly logger: Logger = log;\n\n public constructor(\n ai: GoogleGenAI,\n config: GoogleEmbedderConfig,\n provider: string = \"google\",\n ) {\n this.ai = ai;\n this.name = config.name;\n this.provider = provider;\n this.configuredDimensions = config.dimensions;\n this.dimensions = config.dimensions ?? 0;\n }\n\n public async embed(input: string): Promise<EmbeddingResult> {\n const vectors = await this.request([input]);\n\n return { vector: vectors[0], dimensions: this.dimensions, usage: NO_USAGE };\n }\n\n public async embedMany(inputs: string[]): Promise<EmbeddingBatchResult> {\n const vectors = await this.request(inputs);\n\n return { vectors, dimensions: this.dimensions, usage: NO_USAGE };\n }\n\n /**\n * Shared transport: one `embedContent` call for the whole batch,\n * wrap provider errors, cache `dimensions` from the first vector,\n * and return the raw vectors in input order.\n */\n private async request(inputs: string[]): Promise<number[][]> {\n this.logger.debug(LOG_MODULE, \"embedder.request\", \"embedContent\", {\n model: this.name,\n count: inputs.length,\n });\n\n let response: EmbedContentResponse;\n\n try {\n response = await this.ai.models.embedContent({\n model: this.name,\n contents: inputs,\n ...(this.configuredDimensions !== undefined\n ? { config: { outputDimensionality: this.configuredDimensions } }\n : {}),\n });\n } catch (thrown) {\n const wrapped = wrapGoogleError(thrown);\n\n this.logger.error(LOG_MODULE, \"embedder.error\", wrapped.message, {\n code: wrapped.code,\n context: wrapped.context,\n });\n\n throw wrapped;\n }\n\n const vectors = (response.embeddings ?? []).map((embedding) => embedding.values ?? []);\n\n if (this.dimensions === 0 && vectors[0]) {\n this.dimensions = vectors[0].length;\n }\n\n this.logger.debug(LOG_MODULE, \"embedder.response\", \"embedContent returned\", {\n count: vectors.length,\n dimensions: this.dimensions,\n });\n\n return vectors;\n }\n}\n","/**\n * Substrings identifying Gemini model ids whose family accepts image\n * input (vision).\n *\n * Every Gemini 1.5, 2.x, and 2.5 model is natively multimodal, as is\n * the legacy `gemini-pro-vision`. Only the original text-only\n * `gemini-pro` / `gemini-1.0-pro` is excluded. A substring match\n * tolerates the date/preview suffixes Google appends\n * (`gemini-2.5-flash-preview-05-20`). Override per-model via\n * `google.model({ name, vision: true | false })`.\n */\nconst VISION_CAPABLE_SUBSTRINGS = [\n \"gemini-1.5\",\n \"gemini-2\",\n \"gemini-exp\",\n \"gemini-pro-vision\",\n \"gemini-flash\",\n];\n\n/**\n * Infer whether a Gemini model id supports vision based on the known\n * multimodal-family substrings. Unknown ids default to `false` so\n * passing an image attachment to an unsupported model surfaces a\n * clear, agent-side capability error instead of an opaque Gemini 400.\n *\n * @example\n * inferVisionCapability(\"gemini-2.5-flash\"); // → true\n * inferVisionCapability(\"gemini-1.5-pro-002\"); // → true\n * inferVisionCapability(\"gemini-1.0-pro\"); // → false\n * inferVisionCapability(\"text-embedding-004\"); // → false\n */\nexport function inferVisionCapability(modelId: string): boolean {\n const normalized = modelId.toLowerCase();\n\n return VISION_CAPABLE_SUBSTRINGS.some((fragment) => normalized.includes(fragment));\n}\n","import {\n type Message,\n type ModelCallOptions,\n type ModelCapabilities,\n type ModelContract,\n type ModelPricing,\n type ModelResponse,\n type ModelStreamChunk,\n type ModelToolCallRequest,\n type ReasoningEffort,\n type Usage,\n} from \"@warlock.js/ai\";\nimport { log, type Logger } from \"@warlock.js/logger\";\nimport type {\n GenerateContentConfig,\n GenerateContentResponse,\n GoogleGenAI,\n Part,\n} from \"@google/genai\";\nimport type { GoogleModelConfig } from \"./config.type\";\nimport { inferVisionCapability } from \"./known-vision-models\";\nimport { mapFinishReason, toGoogleContents, toGoogleTools, wrapGoogleError } from \"./utils\";\n\nconst LOG_MODULE = \"ai.google\";\n\n/**\n * Bucketed `thinkingBudget` (token caps) for the neutral\n * `reasoning.effort` levels when the caller gives no explicit\n * `reasoning.maxTokens`. Gemini 2.5 accepts a positive budget as a cap\n * on the thinking phase; these mirror the spread the OpenAI\n * `reasoning_effort` low/medium/high tiers imply.\n */\nconst EFFORT_THINKING_BUDGET: Record<ReasoningEffort, number> = {\n low: 1024,\n medium: 8192,\n high: 24576,\n};\n\n/**\n * Google Gemini-backed implementation of `ModelContract`.\n *\n * **Role.** The provider-facing bridge between the vendor-neutral\n * `@warlock.js/ai` agent runtime and the `@google/genai` SDK\n * (`models.generateContent` / `generateContentStream`).\n *\n * **Responsibility.**\n * - Owns: a long-lived `GoogleGenAI` client + frozen `ModelConfig`\n * (name, temperature, maxTokens) used as per-call defaults.\n * - Owns: translating vendor-neutral `Message[]` / `ToolConfig[]` into\n * Gemini shapes (systemInstruction hoisting, `model` role,\n * `functionCall` / `functionResponse` parts, inline image bytes) on\n * the way out, and Gemini's candidate/parts response (text, function\n * calls, finish reason, token usage) back into neutral shapes on the\n * way in.\n * - Does NOT own: dispatching tools, looping, history, retries — those\n * are agent concerns. The model is a per-call protocol adapter.\n *\n * Modeled as a class (see §4.2 of code-style.md — \"long-lived state\n * across calls\"): the `GoogleGenAI` client is reused for the SDK's\n * lifetime.\n *\n * @example\n * import { GoogleGenAI } from \"@google/genai\";\n * const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });\n * const model = new GoogleModel(ai, { name: \"gemini-2.5-flash\" });\n *\n * const myAgent = agent({ model, tools: [searchTool] });\n * const result = await myAgent.execute(\"Summarize today's news.\");\n */\nexport class GoogleModel implements ModelContract {\n public readonly name: string;\n public readonly provider: string;\n public readonly capabilities: ModelCapabilities;\n public readonly pricing?: ModelPricing;\n\n private readonly ai: GoogleGenAI;\n private readonly config: GoogleModelConfig;\n private readonly logger: Logger = log;\n\n public constructor(ai: GoogleGenAI, config: GoogleModelConfig, provider: string = \"google\") {\n this.ai = ai;\n this.config = config;\n this.name = config.name;\n this.provider = provider;\n this.pricing = config.pricing;\n const multimodal = config.vision ?? inferVisionCapability(config.name);\n\n this.capabilities = {\n structuredOutput: config.structuredOutput ?? true,\n vision: multimodal,\n // Every Gemini 2.5 model thinks; older families harmlessly ignore\n // an empty thinking budget. Defaulting `true` lets the agent\n // forward reasoning options; an explicit `false` opts a model out.\n reasoning: config.reasoning ?? true,\n // Gemini reports cache-read hits (`cachedContentTokenCount`) on\n // every call via implicit caching, and accepts explicit context\n // caching. Read-side accounting is always honored.\n promptCaching: true,\n // The multimodal Gemini families that accept images also accept\n // audio and PDF/document parts. Mirror the vision inference unless\n // explicitly overridden.\n audio: config.audio ?? multimodal,\n pdf: config.pdf ?? multimodal,\n };\n }\n\n /**\n * Single-shot completion. Sends the full message list to\n * `generateContent`, waits for the terminal response, and reshapes\n * it into a vendor-neutral `ModelResponse`. Per-call `options`\n * override the instance defaults for this call only.\n */\n public async complete(messages: Message[], options?: ModelCallOptions): Promise<ModelResponse> {\n this.logger.debug(LOG_MODULE, \"request\", \"Starting generateContent call\", {\n model: this.name,\n messageCount: messages.length,\n streaming: false,\n toolCount: options?.tools?.length ?? 0,\n });\n\n const { systemInstruction, contents } = toGoogleContents(messages);\n\n let response: GenerateContentResponse;\n\n try {\n response = await this.ai.models.generateContent({\n model: this.name,\n contents,\n config: this.buildConfig(systemInstruction, options),\n });\n } catch (thrown) {\n throw this.logAndWrap(thrown);\n }\n\n const toolCalls = this.extractToolCalls(response);\n const finishReason = toolCalls\n ? \"tool_calls\"\n : mapFinishReason(response.candidates?.[0]?.finishReason);\n const usage = this.extractUsage(response);\n\n this.logger.debug(LOG_MODULE, \"response\", \"generateContent call succeeded\", {\n finishReason,\n usage,\n });\n\n return {\n content: response.text ?? \"\",\n finishReason,\n usage,\n toolCalls,\n };\n }\n\n /**\n * Incremental streaming completion via `generateContentStream`.\n * Yields neutral `ModelStreamChunk`s — `delta` for text, `tool-call`\n * per function call (Gemini emits a fully-formed call, not partial\n * JSON), and a terminal `done` with the final finish reason + usage.\n */\n public async *stream(\n messages: Message[],\n options?: ModelCallOptions,\n ): AsyncIterable<ModelStreamChunk> {\n this.logger.debug(LOG_MODULE, \"request\", \"Starting generateContentStream call\", {\n model: this.name,\n messageCount: messages.length,\n streaming: true,\n toolCount: options?.tools?.length ?? 0,\n });\n\n const { systemInstruction, contents } = toGoogleContents(messages);\n\n let iterable: AsyncGenerator<GenerateContentResponse>;\n\n try {\n iterable = await this.ai.models.generateContentStream({\n model: this.name,\n contents,\n config: this.buildConfig(systemInstruction, options),\n });\n } catch (thrown) {\n throw this.logAndWrap(thrown);\n }\n\n let rawFinishReason: string | undefined;\n let sawToolCall = false;\n const usage: Usage = { input: 0, output: 0, total: 0 };\n\n try {\n for await (const chunk of iterable) {\n const text = chunk.text;\n\n if (text) {\n yield { type: \"delta\", content: text };\n }\n\n for (const part of chunk.candidates?.[0]?.content?.parts ?? []) {\n const toolCall = this.partToToolCall(part);\n\n if (!toolCall) {\n continue;\n }\n\n sawToolCall = true;\n\n yield {\n type: \"tool-call\",\n id: toolCall.id,\n name: toolCall.name,\n input: toolCall.input,\n ...(toolCall.providerMetadata\n ? { providerMetadata: toolCall.providerMetadata }\n : {}),\n };\n }\n\n const candidateFinish = chunk.candidates?.[0]?.finishReason;\n\n if (candidateFinish) {\n rawFinishReason = candidateFinish;\n }\n\n if (chunk.usageMetadata) {\n this.applyUsage(usage, chunk.usageMetadata);\n }\n }\n } catch (thrown) {\n throw this.logAndWrap(thrown);\n }\n\n const finishReason = sawToolCall ? \"tool_calls\" : mapFinishReason(rawFinishReason);\n\n this.logger.debug(LOG_MODULE, \"response\", \"generateContentStream call succeeded\", {\n finishReason,\n usage,\n });\n\n yield { type: \"done\", finishReason, usage };\n }\n\n /**\n * Assemble the `GenerateContentConfig` shared by `complete()` and\n * `stream()`: inference params, hoisted system instruction,\n * cancellation signal, and conditional tools + native structured\n * output.\n */\n private buildConfig(\n systemInstruction: string | undefined,\n options: ModelCallOptions | undefined,\n ): GenerateContentConfig {\n const temperature = options?.temperature ?? this.config.temperature;\n const maxOutputTokens = options?.maxTokens ?? this.config.maxTokens;\n\n return {\n ...(systemInstruction ? { systemInstruction } : {}),\n ...(temperature !== undefined ? { temperature } : {}),\n ...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),\n ...(options?.signal ? { abortSignal: options.signal } : {}),\n ...this.buildTools(options?.tools),\n ...this.buildStructuredOutput(options?.responseSchema),\n ...this.buildThinking(options?.reasoning),\n };\n }\n\n /**\n * Translate the neutral `reasoning` option into Gemini's\n * `thinkingConfig`. `reasoning.maxTokens` maps directly to\n * `thinkingBudget` (token cap on the thinking phase); when only\n * `reasoning.effort` is given it is bucketed into a budget. Emitted\n * only when the model is `reasoning`-capable — a `false` capability\n * (config override) drops it so a non-thinking model never receives\n * an unsupported `thinkingConfig`.\n *\n * Gemini's `thinkingBudget` semantics: `0` disables thinking, `-1`\n * lets the model decide automatically. A positive value caps the\n * thinking tokens.\n */\n private buildThinking(\n reasoning: ModelCallOptions[\"reasoning\"],\n ): Pick<GenerateContentConfig, \"thinkingConfig\"> {\n if (!reasoning || !this.capabilities.reasoning) {\n return {};\n }\n\n const thinkingBudget =\n reasoning.maxTokens ?? (reasoning.effort ? EFFORT_THINKING_BUDGET[reasoning.effort] : undefined);\n\n if (thinkingBudget === undefined) {\n return {};\n }\n\n return { thinkingConfig: { thinkingBudget } };\n }\n\n /**\n * Spread-friendly tools fragment. Empty object when no tools were\n * supplied so the caller can unconditionally spread it.\n */\n private buildTools(tools: ModelCallOptions[\"tools\"]): Pick<GenerateContentConfig, \"tools\"> {\n const mapped = toGoogleTools(tools);\n\n return mapped ? { tools: mapped } : {};\n }\n\n /**\n * Translate the neutral `responseSchema` into Gemini's native JSON\n * structured output (`responseMimeType: \"application/json\"` +\n * `responseJsonSchema`, which takes a raw JSON Schema directly).\n * Emitted only when the model is `structuredOutput`-capable and the\n * schema is an object root — otherwise the agent's soft prompt hint\n * + client-side `validate()` carry shape.\n */\n private buildStructuredOutput(\n responseSchema: Record<string, unknown> | undefined,\n ): Pick<GenerateContentConfig, \"responseMimeType\" | \"responseJsonSchema\"> {\n if (!responseSchema || !this.capabilities.structuredOutput) {\n return {};\n }\n\n if (responseSchema.type !== \"object\" || typeof responseSchema.properties !== \"object\") {\n return {};\n }\n\n return {\n responseMimeType: \"application/json\",\n responseJsonSchema: responseSchema,\n };\n }\n\n /**\n * Reshape Gemini's function-call content parts into the neutral\n * `ModelToolCallRequest[]`. Returns `undefined` when the model\n * requested no functions so callers can branch on presence.\n *\n * Reads `candidates[0].content.parts` directly rather than the\n * `response.functionCalls` getter: the getter discards the\n * part-level `thoughtSignature`, and Gemini \"thinking\" models 400\n * the follow-up turn if that signature is not echoed back. See\n * `partToToolCall`.\n */\n private extractToolCalls(\n response: GenerateContentResponse,\n ): ModelToolCallRequest[] | undefined {\n const parts = response.candidates?.[0]?.content?.parts ?? [];\n const toolCalls = parts\n .map((part) => this.partToToolCall(part))\n .filter((call): call is ModelToolCallRequest => call !== undefined);\n\n return toolCalls.length > 0 ? toolCalls : undefined;\n }\n\n /**\n * Map a single Gemini `Part` to a neutral `ModelToolCallRequest`,\n * or `undefined` when the part is not a function call. The part's\n * `thoughtSignature` (opaque, set by thinking models) is carried on\n * `providerMetadata` so `toGoogleContents` can replay it on the\n * assistant turn — Gemini rejects the next request without it.\n */\n private partToToolCall(part: Part): ModelToolCallRequest | undefined {\n if (!part.functionCall) {\n return undefined;\n }\n\n const call = part.functionCall;\n\n return {\n // The Gemini Developer API does not assign function-call ids\n // (only Vertex parallel-calling does). Fall back to the function\n // name so the neutral `toolCallId` is non-empty and the echoed\n // `functionResponse.name` resolves — Gemini matches a result to\n // its call by name. See decisions §49.\n id: call.id ?? call.name ?? \"\",\n name: call.name ?? \"\",\n input: (call.args ?? {}) as Record<string, unknown>,\n ...(part.thoughtSignature\n ? { providerMetadata: { thoughtSignature: part.thoughtSignature } }\n : {}),\n };\n }\n\n /**\n * Normalize Gemini's `usageMetadata` into the neutral `Usage` shape.\n * Cache-read tokens are surfaced as `cachedTokens` only when\n * non-zero. Absent usage collapses to zeros.\n */\n private extractUsage(response: GenerateContentResponse): Usage {\n const usage: Usage = { input: 0, output: 0, total: 0 };\n\n if (response.usageMetadata) {\n this.applyUsage(usage, response.usageMetadata);\n }\n\n return usage;\n }\n\n /**\n * Fold a Gemini `usageMetadata` block into the running neutral\n * `Usage` accumulator. Shared by `complete()` and the streaming\n * loop (where the final chunk carries cumulative totals).\n *\n * Cache-read hits (`cachedContentTokenCount`, implicit or explicit\n * context caching) surface as `cachedTokens`; the thinking-phase\n * tokens of a reasoning model (`thoughtsTokenCount`) surface as\n * `reasoningTokens`. Both are emitted only when reported `> 0` so an\n * absent channel leaves the field undefined.\n */\n private applyUsage(\n usage: Usage,\n raw: NonNullable<GenerateContentResponse[\"usageMetadata\"]>,\n ): void {\n usage.input = raw.promptTokenCount ?? usage.input;\n usage.output = raw.candidatesTokenCount ?? usage.output;\n usage.total = raw.totalTokenCount ?? usage.input + usage.output;\n\n const cached = raw.cachedContentTokenCount;\n\n if (cached && cached > 0) {\n usage.cachedTokens = cached;\n }\n\n const reasoning = raw.thoughtsTokenCount;\n\n if (reasoning && reasoning > 0) {\n usage.reasoningTokens = reasoning;\n }\n }\n\n /**\n * Wrap a thrown provider error into the typed `AIError` hierarchy\n * and emit the standard error log line before it propagates.\n */\n private logAndWrap(thrown: unknown) {\n const wrapped = wrapGoogleError(thrown);\n\n this.logger.error(LOG_MODULE, \"error\", wrapped.message, {\n code: wrapped.code,\n context: wrapped.context,\n });\n\n return wrapped;\n }\n}\n","import { GoogleGenAI } from \"@google/genai\";\nimport type {\n EmbedderContract,\n ModelContract,\n ModelPricing,\n SDKAdapterContract,\n} from \"@warlock.js/ai\";\nimport { approximateTokenCount } from \"@warlock.js/ai\";\nimport type {\n GoogleEmbedderConfig,\n GoogleModelConfig,\n GoogleSDKConfig,\n} from \"./config.type\";\nimport { GoogleEmbedder } from \"./embedder\";\nimport { GoogleModel } from \"./model\";\n\n/**\n * Google Gemini-backed implementation of `SDKAdapterContract`.\n *\n * **Role.** The package entry point for Gemini models via the\n * `@google/genai` SDK. A single `GoogleSDK` holds one live\n * `GoogleGenAI` client, shared by every `ModelContract` /\n * `EmbedderContract` it produces. Construct one SDK per\n * account/project and reuse it everywhere.\n *\n * **Responsibility.**\n * - Owns: a long-lived `GoogleGenAI` client (auth, Vertex vs Gemini\n * API) and its lifetime. Factory for `GoogleModel` /\n * `GoogleEmbedder` instances sharing that client.\n * - Does NOT own: anything per-call — those live in `GoogleModel` /\n * `GoogleEmbedder` and the agent runtime.\n *\n * Modeled as a class (see §4.2 of code-style.md — \"long-lived state\n * across many calls\"), fronted by FP usage like the other adapters.\n *\n * @example\n * const google = new GoogleSDK({ apiKey: process.env.GEMINI_API_KEY! });\n * const model = google.model({ name: \"gemini-2.5-flash\", temperature: 0.7 });\n * const embedder = google.embedder({ name: \"gemini-embedding-001\" });\n */\nexport class GoogleSDK implements SDKAdapterContract {\n private readonly ai: GoogleGenAI;\n private readonly provider: string;\n private readonly pricing?: Record<string, ModelPricing>;\n\n public constructor(config: GoogleSDKConfig) {\n const { provider, pricing, ...clientOptions } = config;\n\n this.ai = new GoogleGenAI(clientOptions);\n this.provider = provider ?? \"google\";\n this.pricing = pricing;\n }\n\n /**\n * Build a `GoogleModel` bound to this SDK's client. Each call\n * returns a fresh instance; all instances share the underlying\n * `GoogleGenAI` client. The SDK's `provider` label is forwarded.\n *\n * Pricing resolution: per-model `config.pricing` wins; otherwise the\n * SDK-level registry entry keyed by `config.name`; otherwise\n * `undefined` (no cost computed).\n */\n public model(config: GoogleModelConfig): ModelContract {\n const resolvedPricing = config.pricing ?? this.pricing?.[config.name];\n const resolvedConfig: GoogleModelConfig =\n resolvedPricing === config.pricing ? config : { ...config, pricing: resolvedPricing };\n\n return new GoogleModel(this.ai, resolvedConfig, this.provider);\n }\n\n /**\n * Rough token-count estimate. Uses the character-heuristic\n * (`approximateTokenCount`) from the core package — Gemini's\n * `countTokens` is a network round-trip; `count()` is intentionally\n * offline. Good for budgeting/quota guards, not billing.\n */\n public async count(text: string, _model?: string): Promise<number> {\n return approximateTokenCount(text);\n }\n\n /**\n * Build a `GoogleEmbedder` bound to this SDK's client.\n *\n * @example\n * const embedder = google.embedder({ name: \"gemini-embedding-001\" });\n * const { vector } = await embedder.embed(\"Hello world\");\n */\n public embedder(config: GoogleEmbedderConfig): EmbedderContract {\n return new GoogleEmbedder(this.ai, config, this.provider);\n }\n}\n"],"mappings":";;;;;;AAEA,MAAM,kBAAgD;CACpD,MAAM;CACN,YAAY;AACd;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,gBAAgB,KAA8C;CAC5E,OAAO,gBAAgB,OAAO,OAAO;AACvC;;;;;;;;;;;;;;;;;;;;;;;;;ACKA,SAAgB,iBAAiB,UAAqC;CACpE,MAAM,cAAwB,CAAC;CAC/B,MAAM,WAAsB,CAAC;CAE7B,KAAK,MAAM,WAAW,UAAU;EAC9B,IAAI,QAAQ,SAAS,UAAU;GAC7B,YAAY,KAAK,iBAAiB,QAAQ,OAAO,CAAC;GAElD;EACF;EAEA,IAAI,QAAQ,SAAS,QAAQ;GAC3B,SAAS,KAAK;IACZ,MAAM;IACN,OAAO,CACL,EAME,kBAAkB;KAChB,MAAM,QAAQ,cAAc;KAC5B,UAAU,iBAAiB,iBAAiB,QAAQ,OAAO,CAAC;IAC9D,EACF,CACF;GACF,CAAC;GAED;EACF;EAEA,IAAI,QAAQ,SAAS,eAAe,QAAQ,aAAa,QAAQ,UAAU,SAAS,GAAG;GACrF,MAAM,QAAgB,CAAC;GACvB,MAAM,OAAO,iBAAiB,QAAQ,OAAO;GAE7C,IAAI,MACF,MAAM,KAAK,EAAE,KAAK,CAAC;GAGrB,KAAK,MAAM,YAAY,QAAQ,WAAW;IAMxC,MAAM,mBAAmB,SAAS,kBAAkB;IAEpD,MAAM,KAAK;KACT,GAAI,OAAO,qBAAqB,WAAW,EAAE,iBAAiB,IAAI,CAAC;KAInE,cAAc;MACZ,MAAM,SAAS;MACf,MAAO,SAAS,SAAS,CAAC;KAC5B;IACF,CAAC;GACH;GAEA,SAAS,KAAK;IAAE,MAAM;IAAS;GAAM,CAAC;GAEtC;EACF;EAEA,IAAI,QAAQ,SAAS,UAAU,MAAM,QAAQ,QAAQ,OAAO,GAAG;GAC7D,SAAS,KAAK;IAAE,MAAM;IAAQ,OAAO,QAAQ,QAAQ,IAAI,YAAY;GAAE,CAAC;GAExE;EACF;EAEA,SAAS,KAAK;GACZ,MAAM,QAAQ,SAAS,cAAc,UAAU;GAC/C,OAAO,CAAC,EAAE,MAAM,iBAAiB,QAAQ,OAAO,EAAE,CAAC;EACrD,CAAC;CACH;CAEA,OAAO;EACL,mBAAmB,YAAY,SAAS,IAAI,YAAY,KAAK,MAAM,IAAI;EACvE;CACF;AACF;;;;;;AAOA,SAAS,iBAAiB,SAAyC;CACjE,IAAI,OAAO,YAAY,UACrB,OAAO;CAGT,OAAO,QACJ,QAAQ,SAAiD,KAAK,SAAS,MAAM,CAAC,CAC9E,KAAK,SAAS,KAAK,IAAI,CAAC,CACxB,KAAK,EAAE;AACZ;;;;;;;AAQA,SAAS,iBAAiB,KAAsC;CAC9D,MAAM,2CAAgC,KAAK,MAAS;CAEpD,IAAI,WAAW,QAAQ,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,MAAM,GACxE,OAAO;CAGT,OAAO,EAAE,QAAQ,IAAI;AACvB;;;;;;;;;;AAWA,SAAS,aAAa,MAAyB;CAC7C,IAAI,KAAK,SAAS,QAChB,OAAO,EAAE,MAAM,KAAK,KAAK;CAG3B,IAAI,SAAS,KAAK,QAChB,MAAM,IAAIA,mCACR,6FACF;CAGF,OAAO,EACL,YAAY;EAAE,UAAU,KAAK,OAAO;EAAW,MAAM,KAAK,OAAO;CAAO,EAC1E;AACF;;;;;;;;;;;;;;;;;;;;AC3JA,SAAgB,cACd,OACoB;CACpB,IAAI,CAAC,SAAS,MAAM,WAAW,GAC7B;CAGF,OAAO,CACL,EACE,sBAAsB,MAAM,KAAK,UAAU;EACzC,MAAM,KAAK;EACX,aAAa,KAAK;EAClB,sBAAsB,aAAa,KAAK,KAAK;CAC/C,EAAE,EACJ,CACF;AACF;;;;;;AAOA,SAAS,aAAa,OAAuE;CAC3F,MAAM,+CAA2B,KAAK;CAEtC,IAAI,UAAU,OAAO,SAAS,UAC5B,OAAO;CAGT,OAAO,EAAE,MAAM,SAAS;AAC1B;;;;;;;;;;;;;;;;;;;;;;;;;ACLA,SAAgB,gBAAgB,QAA0B;CACxD,IAAI,kBAAkBC,wBACpB,OAAO;CAGT,MAAM,QAAQ,QAAQ,MAAM;CAC5B,MAAM,UAAU,aAAa,KAAK;CAClC,MAAM,UAAU,MAAM,YAAY,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM;CAE1F,IAAI,UAAU,KAAK,GACjB,OAAO,IAAIC,oCAAqB,SAAS;EAAE,OAAO;EAAQ;CAAQ,CAAC;CAGrE,IACE,MAAM,WAAW,OACjB,MAAM,WAAW,OACjB,uDAAuD,KAAK,OAAO,GAEnE,OAAO,IAAIC,iCAAkB,SAAS;EAAE,OAAO;EAAQ;CAAQ,CAAC;CAGlE,IAAI,MAAM,WAAW,OAAO,4BAA4B,KAAK,OAAO,GAClE,OAAO,IAAIC,sCAAuB,SAAS;EAAE,OAAO;EAAQ;CAAQ,CAAC;CAGvE,IAAI,MAAM,WAAW,KAAK;EACxB,IAAI,oEAAoE,KAAK,OAAO,GAClF,OAAO,IAAIC,0CAA2B,SAAS;GAAE,OAAO;GAAQ;EAAQ,CAAC;EAG3E,OAAO,IAAIC,mCAAoB,SAAS;GAAE,OAAO;GAAQ;EAAQ,CAAC;CACpE;CAEA,IAAI,MAAM,WAAW,OAAO,eAAe,MAAM,MAAM,GACrD,OAAO,IAAIA,mCAAoB,SAAS;EAAE,OAAO;EAAQ;CAAQ,CAAC;CAGpE,OAAO,IAAIC,6BAAc,SAAS;EAAE,OAAO;EAAQ;CAAQ,CAAC;AAC9D;;;;;;AAOA,SAAS,QAAQ,QAAmC;CAClD,IAAI,kBAAkBC,wBACpB,OAAO;EAAE,QAAQ,OAAO;EAAQ,SAAS,OAAO;EAAS,MAAM,OAAO;CAAK;CAG7E,IAAI,OAAO,WAAW,YAAY,WAAW,MAAM;EACjD,MAAM,MAAM;EAEZ,OAAO;GACL,QAAQ,OAAO,IAAI,WAAW,WAAW,IAAI,SAAS;GACtD,SAAS,OAAO,IAAI,YAAY,WAAW,IAAI,UAAU;GACzD,MAAM,OAAO,IAAI,SAAS,WAAW,IAAI,OAAO;GAChD,MAAM,OAAO,IAAI,SAAS,WAAW,IAAI,OAAO;EAClD;CACF;CAEA,OAAO,CAAC;AACV;;;;;;AAOA,SAAS,UAAU,OAAkC;CACnD,IAAI,MAAM,WAAW,KACnB,OAAO;CAGT,IAAI,MAAM,SAAS,gBAAgB,qBAAqB,KAAK,MAAM,WAAW,EAAE,GAC9E,OAAO;CAGT,OAAO,MAAM,SAAS,eAAe,MAAM,SAAS;AACtD;;AAGA,SAAS,eAAe,QAAqC;CAC3D,OAAO,OAAO,WAAW,YAAY,UAAU,OAAO,SAAS;AACjE;;AAGA,SAAS,aAAa,OAAkD;CACtE,MAAM,UAAmC,CAAC;CAE1C,IAAI,MAAM,WAAW,QACnB,QAAQ,SAAS,MAAM;CAGzB,IAAI,MAAM,MACR,QAAQ,OAAO,MAAM;CAGvB,OAAO;AACT;;;;ACrIA,MAAMC,eAAa;;;;;;AAOnB,MAAM,WAA2B;CAAE,cAAc;CAAG,aAAa;AAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BnE,IAAa,iBAAb,MAAwD;CAStD,AAAO,YACL,IACA,QACA,WAAmB,UACnB;gBANgCC;EAOhC,KAAK,KAAK;EACV,KAAK,OAAO,OAAO;EACnB,KAAK,WAAW;EAChB,KAAK,uBAAuB,OAAO;EACnC,KAAK,aAAa,OAAO,cAAc;CACzC;CAEA,MAAa,MAAM,OAAyC;EAG1D,OAAO;GAAE,SAAQ,MAFK,KAAK,QAAQ,CAAC,KAAK,CAAC,EAElB,CAAC;GAAI,YAAY,KAAK;GAAY,OAAO;EAAS;CAC5E;CAEA,MAAa,UAAU,QAAiD;EAGtE,OAAO;GAAE,eAFa,KAAK,QAAQ,MAAM;GAEvB,YAAY,KAAK;GAAY,OAAO;EAAS;CACjE;;;;;;CAOA,MAAc,QAAQ,QAAuC;EAC3D,KAAK,OAAO,MAAMD,cAAY,oBAAoB,gBAAgB;GAChE,OAAO,KAAK;GACZ,OAAO,OAAO;EAChB,CAAC;EAED,IAAI;EAEJ,IAAI;GACF,WAAW,MAAM,KAAK,GAAG,OAAO,aAAa;IAC3C,OAAO,KAAK;IACZ,UAAU;IACV,GAAI,KAAK,yBAAyB,SAC9B,EAAE,QAAQ,EAAE,sBAAsB,KAAK,qBAAqB,EAAE,IAC9D,CAAC;GACP,CAAC;EACH,SAAS,QAAQ;GACf,MAAM,UAAU,gBAAgB,MAAM;GAEtC,KAAK,OAAO,MAAMA,cAAY,kBAAkB,QAAQ,SAAS;IAC/D,MAAM,QAAQ;IACd,SAAS,QAAQ;GACnB,CAAC;GAED,MAAM;EACR;EAEA,MAAM,WAAW,SAAS,cAAc,CAAC,EAAC,CAAE,KAAK,cAAc,UAAU,UAAU,CAAC,CAAC;EAErF,IAAI,KAAK,eAAe,KAAK,QAAQ,IACnC,KAAK,aAAa,QAAQ,EAAE,CAAC;EAG/B,KAAK,OAAO,MAAMA,cAAY,qBAAqB,yBAAyB;GAC1E,OAAO,QAAQ;GACf,YAAY,KAAK;EACnB,CAAC;EAED,OAAO;CACT;AACF;;;;;;;;;;;;;;;AClHA,MAAM,4BAA4B;CAChC;CACA;CACA;CACA;CACA;AACF;;;;;;;;;;;;;AAcA,SAAgB,sBAAsB,SAA0B;CAC9D,MAAM,aAAa,QAAQ,YAAY;CAEvC,OAAO,0BAA0B,MAAM,aAAa,WAAW,SAAS,QAAQ,CAAC;AACnF;;;;ACZA,MAAM,aAAa;;;;;;;;AASnB,MAAM,yBAA0D;CAC9D,KAAK;CACL,QAAQ;CACR,MAAM;AACR;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,IAAa,cAAb,MAAkD;CAUhD,AAAO,YAAY,IAAiB,QAA2B,WAAmB,UAAU;gBAF1DE;EAGhC,KAAK,KAAK;EACV,KAAK,SAAS;EACd,KAAK,OAAO,OAAO;EACnB,KAAK,WAAW;EAChB,KAAK,UAAU,OAAO;EACtB,MAAM,aAAa,OAAO,UAAU,sBAAsB,OAAO,IAAI;EAErE,KAAK,eAAe;GAClB,kBAAkB,OAAO,oBAAoB;GAC7C,QAAQ;GAIR,WAAW,OAAO,aAAa;GAI/B,eAAe;GAIf,OAAO,OAAO,SAAS;GACvB,KAAK,OAAO,OAAO;EACrB;CACF;;;;;;;CAQA,MAAa,SAAS,UAAqB,SAAoD;EAC7F,KAAK,OAAO,MAAM,YAAY,WAAW,iCAAiC;GACxE,OAAO,KAAK;GACZ,cAAc,SAAS;GACvB,WAAW;GACX,WAAW,SAAS,OAAO,UAAU;EACvC,CAAC;EAED,MAAM,EAAE,mBAAmB,aAAa,iBAAiB,QAAQ;EAEjE,IAAI;EAEJ,IAAI;GACF,WAAW,MAAM,KAAK,GAAG,OAAO,gBAAgB;IAC9C,OAAO,KAAK;IACZ;IACA,QAAQ,KAAK,YAAY,mBAAmB,OAAO;GACrD,CAAC;EACH,SAAS,QAAQ;GACf,MAAM,KAAK,WAAW,MAAM;EAC9B;EAEA,MAAM,YAAY,KAAK,iBAAiB,QAAQ;EAChD,MAAM,eAAe,YACjB,eACA,gBAAgB,SAAS,aAAa,EAAE,EAAE,YAAY;EAC1D,MAAM,QAAQ,KAAK,aAAa,QAAQ;EAExC,KAAK,OAAO,MAAM,YAAY,YAAY,kCAAkC;GAC1E;GACA;EACF,CAAC;EAED,OAAO;GACL,SAAS,SAAS,QAAQ;GAC1B;GACA;GACA;EACF;CACF;;;;;;;CAQA,OAAc,OACZ,UACA,SACiC;EACjC,KAAK,OAAO,MAAM,YAAY,WAAW,uCAAuC;GAC9E,OAAO,KAAK;GACZ,cAAc,SAAS;GACvB,WAAW;GACX,WAAW,SAAS,OAAO,UAAU;EACvC,CAAC;EAED,MAAM,EAAE,mBAAmB,aAAa,iBAAiB,QAAQ;EAEjE,IAAI;EAEJ,IAAI;GACF,WAAW,MAAM,KAAK,GAAG,OAAO,sBAAsB;IACpD,OAAO,KAAK;IACZ;IACA,QAAQ,KAAK,YAAY,mBAAmB,OAAO;GACrD,CAAC;EACH,SAAS,QAAQ;GACf,MAAM,KAAK,WAAW,MAAM;EAC9B;EAEA,IAAI;EACJ,IAAI,cAAc;EAClB,MAAM,QAAe;GAAE,OAAO;GAAG,QAAQ;GAAG,OAAO;EAAE;EAErD,IAAI;GACF,WAAW,MAAM,SAAS,UAAU;IAClC,MAAM,OAAO,MAAM;IAEnB,IAAI,MACF,MAAM;KAAE,MAAM;KAAS,SAAS;IAAK;IAGvC,KAAK,MAAM,QAAQ,MAAM,aAAa,EAAE,EAAE,SAAS,SAAS,CAAC,GAAG;KAC9D,MAAM,WAAW,KAAK,eAAe,IAAI;KAEzC,IAAI,CAAC,UACH;KAGF,cAAc;KAEd,MAAM;MACJ,MAAM;MACN,IAAI,SAAS;MACb,MAAM,SAAS;MACf,OAAO,SAAS;MAChB,GAAI,SAAS,mBACT,EAAE,kBAAkB,SAAS,iBAAiB,IAC9C,CAAC;KACP;IACF;IAEA,MAAM,kBAAkB,MAAM,aAAa,EAAE,EAAE;IAE/C,IAAI,iBACF,kBAAkB;IAGpB,IAAI,MAAM,eACR,KAAK,WAAW,OAAO,MAAM,aAAa;GAE9C;EACF,SAAS,QAAQ;GACf,MAAM,KAAK,WAAW,MAAM;EAC9B;EAEA,MAAM,eAAe,cAAc,eAAe,gBAAgB,eAAe;EAEjF,KAAK,OAAO,MAAM,YAAY,YAAY,wCAAwC;GAChF;GACA;EACF,CAAC;EAED,MAAM;GAAE,MAAM;GAAQ;GAAc;EAAM;CAC5C;;;;;;;CAQA,AAAQ,YACN,mBACA,SACuB;EACvB,MAAM,cAAc,SAAS,eAAe,KAAK,OAAO;EACxD,MAAM,kBAAkB,SAAS,aAAa,KAAK,OAAO;EAE1D,OAAO;GACL,GAAI,oBAAoB,EAAE,kBAAkB,IAAI,CAAC;GACjD,GAAI,gBAAgB,SAAY,EAAE,YAAY,IAAI,CAAC;GACnD,GAAI,oBAAoB,SAAY,EAAE,gBAAgB,IAAI,CAAC;GAC3D,GAAI,SAAS,SAAS,EAAE,aAAa,QAAQ,OAAO,IAAI,CAAC;GACzD,GAAG,KAAK,WAAW,SAAS,KAAK;GACjC,GAAG,KAAK,sBAAsB,SAAS,cAAc;GACrD,GAAG,KAAK,cAAc,SAAS,SAAS;EAC1C;CACF;;;;;;;;;;;;;;CAeA,AAAQ,cACN,WAC+C;EAC/C,IAAI,CAAC,aAAa,CAAC,KAAK,aAAa,WACnC,OAAO,CAAC;EAGV,MAAM,iBACJ,UAAU,cAAc,UAAU,SAAS,uBAAuB,UAAU,UAAU;EAExF,IAAI,mBAAmB,QACrB,OAAO,CAAC;EAGV,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE;CAC9C;;;;;CAMA,AAAQ,WAAW,OAAwE;EACzF,MAAM,SAAS,cAAc,KAAK;EAElC,OAAO,SAAS,EAAE,OAAO,OAAO,IAAI,CAAC;CACvC;;;;;;;;;CAUA,AAAQ,sBACN,gBACwE;EACxE,IAAI,CAAC,kBAAkB,CAAC,KAAK,aAAa,kBACxC,OAAO,CAAC;EAGV,IAAI,eAAe,SAAS,YAAY,OAAO,eAAe,eAAe,UAC3E,OAAO,CAAC;EAGV,OAAO;GACL,kBAAkB;GAClB,oBAAoB;EACtB;CACF;;;;;;;;;;;;CAaA,AAAQ,iBACN,UACoC;EAEpC,MAAM,aADQ,SAAS,aAAa,EAAE,EAAE,SAAS,SAAS,CAAC,EACpC,CACpB,KAAK,SAAS,KAAK,eAAe,IAAI,CAAC,CAAC,CACxC,QAAQ,SAAuC,SAAS,MAAS;EAEpE,OAAO,UAAU,SAAS,IAAI,YAAY;CAC5C;;;;;;;;CASA,AAAQ,eAAe,MAA8C;EACnE,IAAI,CAAC,KAAK,cACR;EAGF,MAAM,OAAO,KAAK;EAElB,OAAO;GAML,IAAI,KAAK,MAAM,KAAK,QAAQ;GAC5B,MAAM,KAAK,QAAQ;GACnB,OAAQ,KAAK,QAAQ,CAAC;GACtB,GAAI,KAAK,mBACL,EAAE,kBAAkB,EAAE,kBAAkB,KAAK,iBAAiB,EAAE,IAChE,CAAC;EACP;CACF;;;;;;CAOA,AAAQ,aAAa,UAA0C;EAC7D,MAAM,QAAe;GAAE,OAAO;GAAG,QAAQ;GAAG,OAAO;EAAE;EAErD,IAAI,SAAS,eACX,KAAK,WAAW,OAAO,SAAS,aAAa;EAG/C,OAAO;CACT;;;;;;;;;;;;CAaA,AAAQ,WACN,OACA,KACM;EACN,MAAM,QAAQ,IAAI,oBAAoB,MAAM;EAC5C,MAAM,SAAS,IAAI,wBAAwB,MAAM;EACjD,MAAM,QAAQ,IAAI,mBAAmB,MAAM,QAAQ,MAAM;EAEzD,MAAM,SAAS,IAAI;EAEnB,IAAI,UAAU,SAAS,GACrB,MAAM,eAAe;EAGvB,MAAM,YAAY,IAAI;EAEtB,IAAI,aAAa,YAAY,GAC3B,MAAM,kBAAkB;CAE5B;;;;;CAMA,AAAQ,WAAW,QAAiB;EAClC,MAAM,UAAU,gBAAgB,MAAM;EAEtC,KAAK,OAAO,MAAM,YAAY,SAAS,QAAQ,SAAS;GACtD,MAAM,QAAQ;GACd,SAAS,QAAQ;EACnB,CAAC;EAED,OAAO;CACT;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACjZA,IAAa,YAAb,MAAqD;CAKnD,AAAO,YAAY,QAAyB;EAC1C,MAAM,EAAE,UAAU,SAAS,GAAG,kBAAkB;EAEhD,KAAK,KAAK,IAAIC,0BAAY,aAAa;EACvC,KAAK,WAAW,YAAY;EAC5B,KAAK,UAAU;CACjB;;;;;;;;;;CAWA,AAAO,MAAM,QAA0C;EACrD,MAAM,kBAAkB,OAAO,WAAW,KAAK,UAAU,OAAO;EAChE,MAAM,iBACJ,oBAAoB,OAAO,UAAU,SAAS;GAAE,GAAG;GAAQ,SAAS;EAAgB;EAEtF,OAAO,IAAI,YAAY,KAAK,IAAI,gBAAgB,KAAK,QAAQ;CAC/D;;;;;;;CAQA,MAAa,MAAM,MAAc,QAAkC;EACjE,iDAA6B,IAAI;CACnC;;;;;;;;CASA,AAAO,SAAS,QAAgD;EAC9D,OAAO,IAAI,eAAe,KAAK,IAAI,QAAQ,KAAK,QAAQ;CAC1D;AACF"}
1
+ {"version":3,"file":"index.cjs","names":["InvalidRequestError","AIError","ProviderTimeoutError","ProviderAuthError","ProviderRateLimitError","ContextLengthExceededError","InvalidRequestError","ProviderError","ApiError","LOG_MODULE","log","LOG_MODULE","log","InvalidRequestError","ContentFilterError","ProviderError","log","GoogleGenAI"],"sources":["../../../../../../@warlock.js/ai-google/src/utils/map-finish-reason.ts","../../../../../../@warlock.js/ai-google/src/utils/to-google-contents.ts","../../../../../../@warlock.js/ai-google/src/utils/to-google-tools.ts","../../../../../../@warlock.js/ai-google/src/utils/wrap-google-error.ts","../../../../../../@warlock.js/ai-google/src/embedder.ts","../../../../../../@warlock.js/ai-google/src/known-image-models.ts","../../../../../../@warlock.js/ai-google/src/image.ts","../../../../../../@warlock.js/ai-google/src/known-vision-models.ts","../../../../../../@warlock.js/ai-google/src/model.ts","../../../../../../@warlock.js/ai-google/src/sdk.ts"],"sourcesContent":["import type { FinishReason } from \"@warlock.js/ai\";\n\nconst finishReasonMap: Record<string, FinishReason> = {\n STOP: \"stop\",\n MAX_TOKENS: \"length\",\n};\n\n/**\n * Map Gemini's `FinishReason` enum value to the normalized\n * `FinishReason` union.\n *\n * `STOP` is the natural terminal. `MAX_TOKENS` maps to `length`.\n * Everything else — `SAFETY`, `RECITATION`, `BLOCKLIST`,\n * `PROHIBITED_CONTENT`, `SPII`, `MALFORMED_FUNCTION_CALL`,\n * `UNEXPECTED_TOOL_CALL`, `LANGUAGE`, `OTHER`,\n * `FINISH_REASON_UNSPECIFIED`, `null`, or any future value — falls\n * through to `\"error\"`.\n *\n * Note: Gemini reports `STOP` even when the turn ended in a function\n * call (it has no `tool_use` reason). `GoogleModel` overrides the\n * mapped reason to `\"tool_calls\"` when the response carries function\n * calls — this map intentionally stays purely about the raw signal.\n *\n * @example\n * mapFinishReason(\"STOP\"); // \"stop\"\n * mapFinishReason(\"MAX_TOKENS\"); // \"length\"\n * mapFinishReason(\"SAFETY\"); // \"error\"\n * mapFinishReason(undefined); // \"error\"\n */\nexport function mapFinishReason(raw: string | null | undefined): FinishReason {\n return finishReasonMap[raw ?? \"\"] ?? \"error\";\n}\n","import { InvalidRequestError, safeJsonParse, type ContentPart, type Message } from \"@warlock.js/ai\";\nimport type { Content, Part } from \"@google/genai\";\n\n/**\n * Result of splitting a vendor-neutral `Message[]` for Gemini's\n * `generateContent`: the system prompt is hoisted to a separate\n * `systemInstruction` string (Gemini has no `\"system\"` role — content\n * roles must be `\"user\"` or `\"model\"`), and the remaining turns map to\n * `Content[]`.\n */\nexport type GoogleContents = {\n systemInstruction: string | undefined;\n contents: Content[];\n};\n\n/**\n * Convert vendor-neutral `Message[]` into Gemini's request shape.\n *\n * Gemini specifics this function absorbs:\n *\n * 1. **No `system` role.** System messages concatenate into the\n * separate `systemInstruction` config field.\n * 2. **Role names differ.** Neutral `assistant` → Gemini `\"model\"`;\n * `user` stays `\"user\"`.\n * 3. **Tool results are `user` turns.** A neutral `tool` message\n * becomes a `\"user\"` content with a single `functionResponse` part.\n * 4. **Tool calls are `functionCall` parts.** An assistant message\n * with `toolCalls` becomes a `\"model\"` content: an optional leading\n * `text` part followed by one `functionCall` part per call.\n *\n * @example\n * const { systemInstruction, contents } = toGoogleContents([\n * { role: \"system\", content: \"Be concise.\" },\n * { role: \"user\", content: \"Hi\" },\n * ]);\n */\nexport function toGoogleContents(messages: Message[]): GoogleContents {\n const systemParts: string[] = [];\n const contents: Content[] = [];\n\n for (const message of messages) {\n if (message.role === \"system\") {\n systemParts.push(stringifyContent(message.content));\n\n continue;\n }\n\n if (message.role === \"tool\") {\n contents.push({\n role: \"user\",\n parts: [\n {\n // Gemini matches a `functionResponse` to its `functionCall`\n // by `name` (the Developer API has no call ids). `name` is\n // the neutral `toolCallId`, which `GoogleModel` set to the\n // function name. The wire `id` is intentionally omitted —\n // an empty/synthetic id is rejected as an invalid argument.\n functionResponse: {\n name: message.toolCallId ?? \"\",\n response: toResponseObject(stringifyContent(message.content)),\n },\n },\n ],\n });\n\n continue;\n }\n\n if (message.role === \"assistant\" && message.toolCalls && message.toolCalls.length > 0) {\n const parts: Part[] = [];\n const text = stringifyContent(message.content);\n\n if (text) {\n parts.push({ text });\n }\n\n for (const toolCall of message.toolCalls) {\n // Replay the opaque `thoughtSignature` Gemini attached to this\n // function call on the original turn. Thinking models reject\n // the follow-up request with a 400 if the signature is missing\n // from the echoed `functionCall` part. Captured by\n // `GoogleModel.partToToolCall` into `providerMetadata`.\n const thoughtSignature = toolCall.providerMetadata?.thoughtSignature;\n\n parts.push({\n ...(typeof thoughtSignature === \"string\" ? { thoughtSignature } : {}),\n // `id` omitted deliberately — Gemini Developer API function\n // calls have no ids; echoing an empty/synthetic one is\n // rejected as an invalid argument. Matched by `name`.\n functionCall: {\n name: toolCall.name,\n args: (toolCall.input ?? {}) as Record<string, unknown>,\n },\n });\n }\n\n contents.push({ role: \"model\", parts });\n\n continue;\n }\n\n if (message.role === \"user\" && Array.isArray(message.content)) {\n contents.push({ role: \"user\", parts: message.content.map(toGooglePart) });\n\n continue;\n }\n\n contents.push({\n role: message.role === \"assistant\" ? \"model\" : \"user\",\n parts: [{ text: stringifyContent(message.content) }],\n });\n }\n\n return {\n systemInstruction: systemParts.length > 0 ? systemParts.join(\"\\n\\n\") : undefined,\n contents,\n };\n}\n\n/**\n * Multipart content is only meaningful on user messages — for any\n * other role collapse a `ContentPart[]` to concatenated text. Plain\n * strings pass through unchanged.\n */\nfunction stringifyContent(content: string | ContentPart[]): string {\n if (typeof content === \"string\") {\n return content;\n }\n\n return content\n .filter((part): part is { type: \"text\"; text: string } => part.type === \"text\")\n .map((part) => part.text)\n .join(\"\");\n}\n\n/**\n * Gemini's `functionResponse.response` must be a JSON object. Tool\n * results arrive as a string (usually stringified JSON) — parse it\n * when it is a JSON object, otherwise wrap the raw string under a\n * `result` key so the model always receives a well-formed object.\n */\nfunction toResponseObject(raw: string): Record<string, unknown> {\n const parsed = safeJsonParse<unknown>(raw, undefined);\n\n if (parsed !== null && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n return parsed as Record<string, unknown>;\n }\n\n return { result: raw };\n}\n\n/**\n * Map a resolved `ContentPart` to a Gemini `Part`. All binary\n * modalities — **image, PDF, and audio** — go to a single\n * `inlineData: { mimeType, data }` block; Gemini's multimodal input is\n * media-agnostic and keys off the IANA `mimeType` (`image/png`,\n * `application/pdf`, `audio/mpeg`, …), so one mapping covers every part\n * type the model's capabilities admit. PDF and audio reach this point\n * only when the model declares the matching capability (`google.model`\n * infers `pdf` / `audio` from the multimodal Gemini families); the\n * agent's modality gate throws upfront otherwise, so capability and\n * behavior stay in lockstep.\n *\n * Gemini's `generateContent` does not fetch arbitrary remote URLs (only\n * Files API / GCS URIs via `fileData`), so a neutral `{ url }` source\n * surfaces a typed `InvalidRequestError` upfront — for any modality —\n * rather than a downstream Gemini fault. The agent resolves attachments\n * before this point, so nothing is read or fetched here.\n */\nfunction toGooglePart(part: ContentPart): Part {\n if (part.type === \"text\") {\n return { text: part.text };\n }\n\n if (\"url\" in part.source) {\n throw new InvalidRequestError(\n `Gemini generateContent cannot fetch remote-URL ${part.type} media; supply base64 bytes instead.`,\n );\n }\n\n return {\n inlineData: { mimeType: part.source.mediaType, data: part.source.base64 },\n };\n}\n","import { extractJsonSchema, type ToolConfig } from \"@warlock.js/ai\";\nimport type { Tool } from \"@google/genai\";\n\n/**\n * Convert vendor-neutral `ToolConfig[]` into Gemini's `tools` array —\n * a single `Tool` carrying one `functionDeclarations` entry per tool.\n *\n * The input schema is forwarded via `parametersJsonSchema` (raw JSON\n * Schema, mutually exclusive with Gemini's typed `parameters`).\n * Non-object extractions degrade to a parameterless object so\n * registration never fails.\n *\n * Returns `undefined` when there are no tools so the caller can omit\n * `config.tools` entirely.\n *\n * @example\n * const tools = toGoogleTools([weatherTool]);\n * await ai.models.generateContent({ model, contents, config: { tools } });\n */\nexport function toGoogleTools(\n tools: ToolConfig<unknown, unknown>[] | undefined,\n): Tool[] | undefined {\n if (!tools || tools.length === 0) {\n return undefined;\n }\n\n return [\n {\n functionDeclarations: tools.map((tool) => ({\n name: tool.name,\n description: tool.description,\n parametersJsonSchema: toJsonSchema(tool.input),\n })),\n },\n ];\n}\n\n/**\n * Resolve a tool's input schema to a JSON-Schema object. Gemini wants\n * an object root for function parameters; anything else (or a failed\n * extraction) degrades to a parameterless object.\n */\nfunction toJsonSchema(input: ToolConfig<unknown, unknown>[\"input\"]): Record<string, unknown> {\n const schema = extractJsonSchema(input);\n\n if (schema && schema.type === \"object\") {\n return schema;\n }\n\n return { type: \"object\" };\n}\n","import {\n AIError,\n ContextLengthExceededError,\n InvalidRequestError,\n ProviderAuthError,\n ProviderError,\n ProviderRateLimitError,\n ProviderTimeoutError,\n} from \"@warlock.js/ai\";\nimport { ApiError } from \"@google/genai\";\n\n/**\n * Raw-error fields the wrapper reads off a Gemini SDK error.\n * `@google/genai`'s `ApiError` exposes `status` (HTTP code) +\n * `message`; transport aborts surface as `AbortError` / `ETIMEDOUT`.\n * We duck-type so proxied / re-thrown errors still classify.\n */\ntype GoogleErrorShape = {\n status?: number;\n message?: string;\n name?: string;\n code?: string;\n};\n\n/**\n * Wrap any thrown value caught inside the Gemini adapter into the\n * appropriate `@warlock.js/ai` `AIError` subclass.\n *\n * **Dispatch strategy.** Gemini has no machine error `code`; the\n * signals are the HTTP `status` and the canonical status phrase Google\n * embeds in `message` (`PERMISSION_DENIED`, `RESOURCE_EXHAUSTED`,\n * `INVALID_ARGUMENT`, …). Dispatch keys on `status`, using the message\n * phrase as the tie-breaker for the two 400 sub-cases\n * (context-length vs generic) and for status-less auth/quota errors.\n *\n * `AIError` instances pass through unchanged so `catch/throw wrap(e)`\n * pipelines never double-wrap.\n *\n * @example\n * try {\n * return await this.ai.models.generateContent(...);\n * } catch (thrown) {\n * throw wrapGoogleError(thrown);\n * }\n */\nexport function wrapGoogleError(thrown: unknown): AIError {\n if (thrown instanceof AIError) {\n return thrown;\n }\n\n const shape = toShape(thrown);\n const context = buildContext(shape);\n const message = shape.message ?? (thrown instanceof Error ? thrown.message : String(thrown));\n\n if (isTimeout(shape)) {\n return new ProviderTimeoutError(message, { cause: thrown, context });\n }\n\n if (\n shape.status === 401 ||\n shape.status === 403 ||\n /permission_denied|api key not valid|unauthenticated/i.test(message)\n ) {\n return new ProviderAuthError(message, { cause: thrown, context });\n }\n\n if (shape.status === 429 || /resource_exhausted|quota/i.test(message)) {\n return new ProviderRateLimitError(message, { cause: thrown, context });\n }\n\n if (shape.status === 400) {\n if (/token count|context length|exceeds the maximum|input is too long/i.test(message)) {\n return new ContextLengthExceededError(message, { cause: thrown, context });\n }\n\n return new InvalidRequestError(message, { cause: thrown, context });\n }\n\n if (shape.status === 404 || isClientStatus(shape.status)) {\n return new InvalidRequestError(message, { cause: thrown, context });\n }\n\n return new ProviderError(message, { cause: thrown, context });\n}\n\n/**\n * Read the raw error shape. The Gemini SDK's `ApiError` carries a\n * numeric `status`; flattened/proxied errors may carry it (or `code`)\n * loosely.\n */\nfunction toShape(thrown: unknown): GoogleErrorShape {\n if (thrown instanceof ApiError) {\n return { status: thrown.status, message: thrown.message, name: thrown.name };\n }\n\n if (typeof thrown === \"object\" && thrown !== null) {\n const raw = thrown as Record<string, unknown>;\n\n return {\n status: typeof raw.status === \"number\" ? raw.status : undefined,\n message: typeof raw.message === \"string\" ? raw.message : undefined,\n name: typeof raw.name === \"string\" ? raw.name : undefined,\n code: typeof raw.code === \"string\" ? raw.code : undefined,\n };\n }\n\n return {};\n}\n\n/**\n * Decide whether the error is a timeout. Gemini maps gateway timeouts\n * to HTTP 504 (`DEADLINE_EXCEEDED`); transport aborts surface as\n * `AbortError` / `ETIMEDOUT` / `ECONNABORTED`.\n */\nfunction isTimeout(shape: GoogleErrorShape): boolean {\n if (shape.status === 504) {\n return true;\n }\n\n if (shape.name === \"AbortError\" || /deadline_exceeded/i.test(shape.message ?? \"\")) {\n return true;\n }\n\n return shape.code === \"ETIMEDOUT\" || shape.code === \"ECONNABORTED\";\n}\n\n/** True for HTTP 4xx — a client-side request problem, not a server fault. */\nfunction isClientStatus(status: number | undefined): boolean {\n return typeof status === \"number\" && status >= 400 && status < 500;\n}\n\n/** Attach the diagnostic fields to `error.context`. */\nfunction buildContext(shape: GoogleErrorShape): Record<string, unknown> {\n const context: Record<string, unknown> = {};\n\n if (shape.status !== undefined) {\n context.status = shape.status;\n }\n\n if (shape.name) {\n context.code = shape.name;\n }\n\n return context;\n}\n","import {\n type EmbeddingBatchResult,\n type EmbeddingResult,\n type EmbeddingUsage,\n type EmbedderContract,\n} from \"@warlock.js/ai\";\nimport { log, type Logger } from \"@warlock.js/logger\";\nimport type { EmbedContentResponse, GoogleGenAI } from \"@google/genai\";\nimport type { GoogleEmbedderConfig } from \"./config.type\";\nimport { wrapGoogleError } from \"./utils\";\n\nconst LOG_MODULE = \"ai.google\";\n\n/**\n * Token usage is not returned by Gemini's `embedContent`, so every\n * embedding result reports a zeroed `EmbeddingUsage` (honest absence,\n * not a fabricated estimate).\n */\nconst NO_USAGE: EmbeddingUsage = { promptTokens: 0, totalTokens: 0 };\n\n/**\n * Google Gemini-backed implementation of `EmbedderContract`\n * (`gemini-embedding-001`, `text-embedding-004`, …) via\n * `models.embedContent`.\n *\n * **Role.** Converts text into floating-point vectors. Standalone\n * primitive — unrelated to generateContent / tools / the agent loop.\n *\n * **Batch is native.** Gemini's `embedContent` accepts an array of\n * inputs and returns embeddings in the same order, so `embedMany` is\n * a single request (unlike the Bedrock/Titan adapter, which has to\n * loop).\n *\n * **No usage.** Gemini's embed endpoint returns no token counts;\n * `usage` is always `{ promptTokens: 0, totalTokens: 0 }`.\n *\n * **Dimensions.** When no `dimensions` override is given,\n * `this.dimensions` starts at `0` and is populated from the first\n * response's vector length, then cached. Passing `dimensions`\n * forwards Gemini's `outputDimensionality` truncation hint and sets\n * the initial value immediately.\n *\n * @example\n * const embedder = new GoogleEmbedder(ai, { name: \"gemini-embedding-001\" });\n * const { vector } = await embedder.embed(\"Hello world\");\n * const { vectors } = await embedder.embedMany([\"doc 1\", \"doc 2\"]);\n */\nexport class GoogleEmbedder implements EmbedderContract {\n public readonly name: string;\n public readonly provider: string;\n public dimensions: number;\n\n private readonly ai: GoogleGenAI;\n private readonly configuredDimensions: number | undefined;\n private readonly logger: Logger = log;\n\n public constructor(\n ai: GoogleGenAI,\n config: GoogleEmbedderConfig,\n provider: string = \"google\",\n ) {\n this.ai = ai;\n this.name = config.name;\n this.provider = provider;\n this.configuredDimensions = config.dimensions;\n this.dimensions = config.dimensions ?? 0;\n }\n\n public async embed(input: string): Promise<EmbeddingResult> {\n const vectors = await this.request([input]);\n\n return { vector: vectors[0], dimensions: this.dimensions, usage: NO_USAGE };\n }\n\n public async embedMany(inputs: string[]): Promise<EmbeddingBatchResult> {\n const vectors = await this.request(inputs);\n\n return { vectors, dimensions: this.dimensions, usage: NO_USAGE };\n }\n\n /**\n * Shared transport: one `embedContent` call for the whole batch,\n * wrap provider errors, cache `dimensions` from the first vector,\n * and return the raw vectors in input order.\n */\n private async request(inputs: string[]): Promise<number[][]> {\n this.logger.debug(LOG_MODULE, \"embedder.request\", \"embedContent\", {\n model: this.name,\n count: inputs.length,\n });\n\n let response: EmbedContentResponse;\n\n try {\n response = await this.ai.models.embedContent({\n model: this.name,\n contents: inputs,\n ...(this.configuredDimensions !== undefined\n ? { config: { outputDimensionality: this.configuredDimensions } }\n : {}),\n });\n } catch (thrown) {\n const wrapped = wrapGoogleError(thrown);\n\n this.logger.error(LOG_MODULE, \"embedder.error\", wrapped.message, {\n code: wrapped.code,\n context: wrapped.context,\n });\n\n throw wrapped;\n }\n\n const vectors = (response.embeddings ?? []).map((embedding) => embedding.values ?? []);\n\n if (this.dimensions === 0 && vectors[0]) {\n this.dimensions = vectors[0].length;\n }\n\n this.logger.debug(LOG_MODULE, \"embedder.response\", \"embedContent returned\", {\n count: vectors.length,\n dimensions: this.dimensions,\n });\n\n return vectors;\n }\n}\n","/**\n * Model-id prefixes Google exposes through the **Imagen** image API\n * (`ai.models.generateImages`) — `imagen-3.0-*`, `imagen-4.0-*`, and\n * their fast/ultra variants. All are per-image-metered and return\n * base64 bytes.\n *\n * Gemini's *native* image output (`gemini-2.5-flash-image`) is a\n * different surface (`generateContent` with `responseModalities`) and\n * is intentionally NOT routed here — `google.image()` targets the\n * dedicated Imagen endpoint only.\n *\n * Used by {@link isGoogleImageModel} for the construction-time guard so\n * `google.image({ name: \"gemini-2.5-flash\" })` fails fast with a\n * curated error rather than a downstream 400.\n */\nexport const GOOGLE_IMAGE_MODEL_PREFIXES = [\"imagen-\"] as const;\n\n/**\n * True when `name` is a recognized Google Imagen model. A prefix match\n * so dated/variant ids (`imagen-4.0-ultra-generate-001`) are covered\n * without an exact-list maintenance burden.\n *\n * @example\n * isGoogleImageModel(\"imagen-4.0-generate-001\"); // true\n * isGoogleImageModel(\"gemini-2.5-flash\"); // false\n */\nexport function isGoogleImageModel(name: string): boolean {\n return GOOGLE_IMAGE_MODEL_PREFIXES.some((prefix) => name.startsWith(prefix));\n}\n","import {\n ContentFilterError,\n InvalidRequestError,\n ProviderError,\n type GeneratedImage,\n type ImageGenerationOptions,\n type ImageGenerationResponse,\n type ImageModelContract,\n type ImageModelPricing,\n} from \"@warlock.js/ai\";\nimport { log, type Logger } from \"@warlock.js/logger\";\nimport type { GenerateImagesConfig, GoogleGenAI } from \"@google/genai\";\nimport type { GoogleImageConfig } from \"./config.type\";\nimport { isGoogleImageModel } from \"./known-image-models\";\nimport { wrapGoogleError } from \"./utils\";\n\nconst LOG_MODULE = \"ai.google\";\n\n/** Map a neutral output container hint to an IANA media type. */\nfunction mediaTypeFor(format: string | undefined): string | undefined {\n switch (format) {\n case \"png\":\n return \"image/png\";\n case \"jpeg\":\n case \"jpg\":\n return \"image/jpeg\";\n case \"webp\":\n return \"image/webp\";\n default:\n return undefined;\n }\n}\n\n/**\n * Google Imagen-backed implementation of `ImageModelContract`, via\n * `ai.models.generateImages`. Imagen is per-image-metered and returns\n * base64 image bytes (no hosted URL, no token usage).\n *\n * **Capability guard.** The constructor rejects a non-Imagen model id\n * up front — `google.image({ name: \"gemini-2.5-flash\" })` throws a\n * typed `InvalidRequestError` instead of a downstream 400 (Gemini's\n * native image output is a different API and not routed here).\n *\n * **Safety filtering.** When Imagen filters every candidate for safety\n * (`raiFilteredReason`), this surfaces a typed `ContentFilterError`\n * carrying the reason, rather than returning an empty success.\n *\n * @example\n * const model = new GoogleImageModel(ai, { name: \"imagen-4.0-generate-001\" }, \"google\");\n * const { images } = await model.generate(\"a watercolor lighthouse at dawn\");\n */\nexport class GoogleImageModel implements ImageModelContract {\n public readonly name: string;\n public readonly provider: string;\n public readonly pricing?: ImageModelPricing;\n\n private readonly ai: GoogleGenAI;\n private readonly logger: Logger = log;\n\n public constructor(ai: GoogleGenAI, config: GoogleImageConfig, provider: string = \"google\") {\n if (!isGoogleImageModel(config.name)) {\n throw new InvalidRequestError(\n `\"${config.name}\" is not a known Google Imagen model. ` +\n \"Use an `imagen-*` model with google.image({ name }).\",\n );\n }\n\n this.ai = ai;\n this.name = config.name;\n this.provider = provider;\n this.pricing = config.pricing;\n }\n\n public async generate(\n prompt: string,\n options?: ImageGenerationOptions,\n ): Promise<ImageGenerationResponse> {\n const config: GenerateImagesConfig = {};\n\n if (options?.count !== undefined) config.numberOfImages = options.count;\n if (options?.aspectRatio !== undefined) config.aspectRatio = options.aspectRatio;\n if (options?.negativePrompt !== undefined) config.negativePrompt = options.negativePrompt;\n if (options?.signal !== undefined) config.abortSignal = options.signal;\n\n const outputMimeType = mediaTypeFor(options?.format);\n if (outputMimeType !== undefined) config.outputMimeType = outputMimeType;\n\n // Imagen sizing is `imageSize` (\"1K\"/\"2K\") — a distinct concept from\n // OpenAI's WxH `size`, so we honor only an explicit passthrough.\n if (typeof options?.imageSize === \"string\") config.imageSize = options.imageSize;\n if (typeof options?.personGeneration === \"string\") {\n config.personGeneration = options.personGeneration as GenerateImagesConfig[\"personGeneration\"];\n }\n\n this.logger.debug(LOG_MODULE, \"image.request\", \"models.generateImages\", {\n model: this.name,\n count: options?.count ?? 1,\n });\n\n let response: Awaited<ReturnType<GoogleGenAI[\"models\"][\"generateImages\"]>>;\n\n try {\n response = await this.ai.models.generateImages({ model: this.name, prompt, config });\n } catch (thrown) {\n const wrapped = wrapGoogleError(thrown);\n\n this.logger.error(LOG_MODULE, \"image.error\", wrapped.message, {\n code: wrapped.code,\n context: wrapped.context,\n });\n\n throw wrapped;\n }\n\n const generated = response.generatedImages ?? [];\n const images: GeneratedImage[] = [];\n\n for (const candidate of generated) {\n const bytes = candidate.image?.imageBytes;\n if (!bytes) continue;\n\n images.push({\n type: \"base64\",\n base64: bytes,\n mediaType: candidate.image?.mimeType ?? outputMimeType ?? \"image/png\",\n ...(candidate.enhancedPrompt ? { revisedPrompt: candidate.enhancedPrompt } : {}),\n });\n }\n\n if (images.length === 0) {\n const filtered = generated.find((candidate) => candidate.raiFilteredReason);\n\n if (filtered?.raiFilteredReason) {\n throw new ContentFilterError(\n `Imagen filtered all candidates: ${filtered.raiFilteredReason}`,\n { reason: filtered.raiFilteredReason },\n );\n }\n\n throw new ProviderError(\"Imagen returned no images.\");\n }\n\n this.logger.debug(LOG_MODULE, \"image.response\", \"models.generateImages succeeded\", {\n images: images.length,\n });\n\n // Imagen returns no token usage — honest zero (priced per image).\n return { images, usage: { input: 0, output: 0, total: 0 } };\n }\n}\n","/**\n * Substrings identifying Gemini model ids whose family accepts image\n * input (vision).\n *\n * Every Gemini 1.5, 2.x, and 2.5 model is natively multimodal, as is\n * the legacy `gemini-pro-vision`. Only the original text-only\n * `gemini-pro` / `gemini-1.0-pro` is excluded. A substring match\n * tolerates the date/preview suffixes Google appends\n * (`gemini-2.5-flash-preview-05-20`). Override per-model via\n * `google.model({ name, vision: true | false })`.\n */\nconst VISION_CAPABLE_SUBSTRINGS = [\n \"gemini-1.5\",\n \"gemini-2\",\n \"gemini-exp\",\n \"gemini-pro-vision\",\n \"gemini-flash\",\n];\n\n/**\n * Infer whether a Gemini model id supports vision based on the known\n * multimodal-family substrings. Unknown ids default to `false` so\n * passing an image attachment to an unsupported model surfaces a\n * clear, agent-side capability error instead of an opaque Gemini 400.\n *\n * @example\n * inferVisionCapability(\"gemini-2.5-flash\"); // → true\n * inferVisionCapability(\"gemini-1.5-pro-002\"); // → true\n * inferVisionCapability(\"gemini-1.0-pro\"); // → false\n * inferVisionCapability(\"text-embedding-004\"); // → false\n */\nexport function inferVisionCapability(modelId: string): boolean {\n const normalized = modelId.toLowerCase();\n\n return VISION_CAPABLE_SUBSTRINGS.some((fragment) => normalized.includes(fragment));\n}\n","import {\n type Message,\n type ModelCallOptions,\n type ModelCapabilities,\n type ModelContract,\n type ModelPricing,\n type ModelResponse,\n type ModelStreamChunk,\n type ModelToolCallRequest,\n type ReasoningEffort,\n type Usage,\n} from \"@warlock.js/ai\";\nimport { log, type Logger } from \"@warlock.js/logger\";\nimport type {\n GenerateContentConfig,\n GenerateContentResponse,\n GoogleGenAI,\n Part,\n} from \"@google/genai\";\nimport type { GoogleModelConfig } from \"./config.type\";\nimport { inferVisionCapability } from \"./known-vision-models\";\nimport { mapFinishReason, toGoogleContents, toGoogleTools, wrapGoogleError } from \"./utils\";\n\nconst LOG_MODULE = \"ai.google\";\n\n/**\n * Bucketed `thinkingBudget` (token caps) for the neutral\n * `reasoning.effort` levels when the caller gives no explicit\n * `reasoning.maxTokens`. Gemini 2.5 accepts a positive budget as a cap\n * on the thinking phase; these mirror the spread the OpenAI\n * `reasoning_effort` low/medium/high tiers imply.\n */\nconst EFFORT_THINKING_BUDGET: Record<ReasoningEffort, number> = {\n low: 1024,\n medium: 8192,\n high: 24576,\n};\n\n/**\n * Google Gemini-backed implementation of `ModelContract`.\n *\n * **Role.** The provider-facing bridge between the vendor-neutral\n * `@warlock.js/ai` agent runtime and the `@google/genai` SDK\n * (`models.generateContent` / `generateContentStream`).\n *\n * **Responsibility.**\n * - Owns: a long-lived `GoogleGenAI` client + frozen `ModelConfig`\n * (name, temperature, maxTokens) used as per-call defaults.\n * - Owns: translating vendor-neutral `Message[]` / `ToolConfig[]` into\n * Gemini shapes (systemInstruction hoisting, `model` role,\n * `functionCall` / `functionResponse` parts, inline image bytes) on\n * the way out, and Gemini's candidate/parts response (text, function\n * calls, finish reason, token usage) back into neutral shapes on the\n * way in.\n * - Does NOT own: dispatching tools, looping, history, retries — those\n * are agent concerns. The model is a per-call protocol adapter.\n *\n * Modeled as a class (see §4.2 of code-style.md — \"long-lived state\n * across calls\"): the `GoogleGenAI` client is reused for the SDK's\n * lifetime.\n *\n * @example\n * import { GoogleGenAI } from \"@google/genai\";\n * const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });\n * const model = new GoogleModel(ai, { name: \"gemini-2.5-flash\" });\n *\n * const myAgent = agent({ model, tools: [searchTool] });\n * const result = await myAgent.execute(\"Summarize today's news.\");\n */\nexport class GoogleModel implements ModelContract {\n public readonly name: string;\n public readonly provider: string;\n public readonly capabilities: ModelCapabilities;\n public readonly pricing?: ModelPricing;\n\n private readonly ai: GoogleGenAI;\n private readonly config: GoogleModelConfig;\n private readonly logger: Logger = log;\n\n public constructor(ai: GoogleGenAI, config: GoogleModelConfig, provider: string = \"google\") {\n this.ai = ai;\n this.config = config;\n this.name = config.name;\n this.provider = provider;\n this.pricing = config.pricing;\n const multimodal = config.vision ?? inferVisionCapability(config.name);\n\n this.capabilities = {\n structuredOutput: config.structuredOutput ?? true,\n vision: multimodal,\n // Every Gemini 2.5 model thinks; older families harmlessly ignore\n // an empty thinking budget. Defaulting `true` lets the agent\n // forward reasoning options; an explicit `false` opts a model out.\n reasoning: config.reasoning ?? true,\n // Gemini reports cache-read hits (`cachedContentTokenCount`) on\n // every call via implicit caching, and accepts explicit context\n // caching. Read-side accounting is always honored.\n promptCaching: true,\n // The multimodal Gemini families that accept images also accept\n // audio and PDF/document parts. Mirror the vision inference unless\n // explicitly overridden.\n audio: config.audio ?? multimodal,\n pdf: config.pdf ?? multimodal,\n };\n }\n\n /**\n * Single-shot completion. Sends the full message list to\n * `generateContent`, waits for the terminal response, and reshapes\n * it into a vendor-neutral `ModelResponse`. Per-call `options`\n * override the instance defaults for this call only.\n */\n public async complete(messages: Message[], options?: ModelCallOptions): Promise<ModelResponse> {\n this.logger.debug(LOG_MODULE, \"request\", \"Starting generateContent call\", {\n model: this.name,\n messageCount: messages.length,\n streaming: false,\n toolCount: options?.tools?.length ?? 0,\n });\n\n const { systemInstruction, contents } = toGoogleContents(messages);\n\n let response: GenerateContentResponse;\n\n try {\n response = await this.ai.models.generateContent({\n model: this.name,\n contents,\n config: this.buildConfig(systemInstruction, options),\n });\n } catch (thrown) {\n throw this.logAndWrap(thrown);\n }\n\n const toolCalls = this.extractToolCalls(response);\n const finishReason = toolCalls\n ? \"tool_calls\"\n : mapFinishReason(response.candidates?.[0]?.finishReason);\n const usage = this.extractUsage(response);\n\n this.logger.debug(LOG_MODULE, \"response\", \"generateContent call succeeded\", {\n finishReason,\n usage,\n });\n\n return {\n content: response.text ?? \"\",\n finishReason,\n usage,\n toolCalls,\n };\n }\n\n /**\n * Incremental streaming completion via `generateContentStream`.\n * Yields neutral `ModelStreamChunk`s — `delta` for text, `tool-call`\n * per function call (Gemini emits a fully-formed call, not partial\n * JSON), and a terminal `done` with the final finish reason + usage.\n */\n public async *stream(\n messages: Message[],\n options?: ModelCallOptions,\n ): AsyncIterable<ModelStreamChunk> {\n this.logger.debug(LOG_MODULE, \"request\", \"Starting generateContentStream call\", {\n model: this.name,\n messageCount: messages.length,\n streaming: true,\n toolCount: options?.tools?.length ?? 0,\n });\n\n const { systemInstruction, contents } = toGoogleContents(messages);\n\n let iterable: AsyncGenerator<GenerateContentResponse>;\n\n try {\n iterable = await this.ai.models.generateContentStream({\n model: this.name,\n contents,\n config: this.buildConfig(systemInstruction, options),\n });\n } catch (thrown) {\n throw this.logAndWrap(thrown);\n }\n\n let rawFinishReason: string | undefined;\n let sawToolCall = false;\n const usage: Usage = { input: 0, output: 0, total: 0 };\n\n try {\n for await (const chunk of iterable) {\n const text = chunk.text;\n\n if (text) {\n yield { type: \"delta\", content: text };\n }\n\n for (const part of chunk.candidates?.[0]?.content?.parts ?? []) {\n const toolCall = this.partToToolCall(part);\n\n if (!toolCall) {\n continue;\n }\n\n sawToolCall = true;\n\n yield {\n type: \"tool-call\",\n id: toolCall.id,\n name: toolCall.name,\n input: toolCall.input,\n ...(toolCall.providerMetadata\n ? { providerMetadata: toolCall.providerMetadata }\n : {}),\n };\n }\n\n const candidateFinish = chunk.candidates?.[0]?.finishReason;\n\n if (candidateFinish) {\n rawFinishReason = candidateFinish;\n }\n\n if (chunk.usageMetadata) {\n this.applyUsage(usage, chunk.usageMetadata);\n }\n }\n } catch (thrown) {\n throw this.logAndWrap(thrown);\n }\n\n const finishReason = sawToolCall ? \"tool_calls\" : mapFinishReason(rawFinishReason);\n\n this.logger.debug(LOG_MODULE, \"response\", \"generateContentStream call succeeded\", {\n finishReason,\n usage,\n });\n\n yield { type: \"done\", finishReason, usage };\n }\n\n /**\n * Assemble the `GenerateContentConfig` shared by `complete()` and\n * `stream()`: inference params, hoisted system instruction,\n * cancellation signal, and conditional tools + native structured\n * output.\n */\n private buildConfig(\n systemInstruction: string | undefined,\n options: ModelCallOptions | undefined,\n ): GenerateContentConfig {\n const temperature = options?.temperature ?? this.config.temperature;\n const maxOutputTokens = options?.maxTokens ?? this.config.maxTokens;\n\n return {\n ...(systemInstruction ? { systemInstruction } : {}),\n ...(temperature !== undefined ? { temperature } : {}),\n ...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),\n ...(options?.signal ? { abortSignal: options.signal } : {}),\n ...this.buildTools(options?.tools),\n ...this.buildStructuredOutput(options?.responseSchema),\n ...this.buildThinking(options?.reasoning),\n };\n }\n\n /**\n * Translate the neutral `reasoning` option into Gemini's\n * `thinkingConfig`. `reasoning.maxTokens` maps directly to\n * `thinkingBudget` (token cap on the thinking phase); when only\n * `reasoning.effort` is given it is bucketed into a budget. Emitted\n * only when the model is `reasoning`-capable — a `false` capability\n * (config override) drops it so a non-thinking model never receives\n * an unsupported `thinkingConfig`.\n *\n * Gemini's `thinkingBudget` semantics: `0` disables thinking, `-1`\n * lets the model decide automatically. A positive value caps the\n * thinking tokens.\n */\n private buildThinking(\n reasoning: ModelCallOptions[\"reasoning\"],\n ): Pick<GenerateContentConfig, \"thinkingConfig\"> {\n if (!reasoning || !this.capabilities.reasoning) {\n return {};\n }\n\n const thinkingBudget =\n reasoning.maxTokens ?? (reasoning.effort ? EFFORT_THINKING_BUDGET[reasoning.effort] : undefined);\n\n if (thinkingBudget === undefined) {\n return {};\n }\n\n return { thinkingConfig: { thinkingBudget } };\n }\n\n /**\n * Spread-friendly tools fragment. Empty object when no tools were\n * supplied so the caller can unconditionally spread it.\n */\n private buildTools(tools: ModelCallOptions[\"tools\"]): Pick<GenerateContentConfig, \"tools\"> {\n const mapped = toGoogleTools(tools);\n\n return mapped ? { tools: mapped } : {};\n }\n\n /**\n * Translate the neutral `responseSchema` into Gemini's native JSON\n * structured output (`responseMimeType: \"application/json\"` +\n * `responseJsonSchema`, which takes a raw JSON Schema directly).\n * Emitted only when the model is `structuredOutput`-capable and the\n * schema is an object root — otherwise the agent's soft prompt hint\n * + client-side `validate()` carry shape.\n */\n private buildStructuredOutput(\n responseSchema: Record<string, unknown> | undefined,\n ): Pick<GenerateContentConfig, \"responseMimeType\" | \"responseJsonSchema\"> {\n if (!responseSchema || !this.capabilities.structuredOutput) {\n return {};\n }\n\n if (responseSchema.type !== \"object\" || typeof responseSchema.properties !== \"object\") {\n return {};\n }\n\n return {\n responseMimeType: \"application/json\",\n responseJsonSchema: responseSchema,\n };\n }\n\n /**\n * Reshape Gemini's function-call content parts into the neutral\n * `ModelToolCallRequest[]`. Returns `undefined` when the model\n * requested no functions so callers can branch on presence.\n *\n * Reads `candidates[0].content.parts` directly rather than the\n * `response.functionCalls` getter: the getter discards the\n * part-level `thoughtSignature`, and Gemini \"thinking\" models 400\n * the follow-up turn if that signature is not echoed back. See\n * `partToToolCall`.\n */\n private extractToolCalls(\n response: GenerateContentResponse,\n ): ModelToolCallRequest[] | undefined {\n const parts = response.candidates?.[0]?.content?.parts ?? [];\n const toolCalls = parts\n .map((part) => this.partToToolCall(part))\n .filter((call): call is ModelToolCallRequest => call !== undefined);\n\n return toolCalls.length > 0 ? toolCalls : undefined;\n }\n\n /**\n * Map a single Gemini `Part` to a neutral `ModelToolCallRequest`,\n * or `undefined` when the part is not a function call. The part's\n * `thoughtSignature` (opaque, set by thinking models) is carried on\n * `providerMetadata` so `toGoogleContents` can replay it on the\n * assistant turn — Gemini rejects the next request without it.\n */\n private partToToolCall(part: Part): ModelToolCallRequest | undefined {\n if (!part.functionCall) {\n return undefined;\n }\n\n const call = part.functionCall;\n\n return {\n // The Gemini Developer API does not assign function-call ids\n // (only Vertex parallel-calling does). Fall back to the function\n // name so the neutral `toolCallId` is non-empty and the echoed\n // `functionResponse.name` resolves — Gemini matches a result to\n // its call by name. See decisions §49.\n id: call.id ?? call.name ?? \"\",\n name: call.name ?? \"\",\n input: (call.args ?? {}) as Record<string, unknown>,\n ...(part.thoughtSignature\n ? { providerMetadata: { thoughtSignature: part.thoughtSignature } }\n : {}),\n };\n }\n\n /**\n * Normalize Gemini's `usageMetadata` into the neutral `Usage` shape.\n * Cache-read tokens are surfaced as `cachedTokens` only when\n * non-zero. Absent usage collapses to zeros.\n */\n private extractUsage(response: GenerateContentResponse): Usage {\n const usage: Usage = { input: 0, output: 0, total: 0 };\n\n if (response.usageMetadata) {\n this.applyUsage(usage, response.usageMetadata);\n }\n\n return usage;\n }\n\n /**\n * Fold a Gemini `usageMetadata` block into the running neutral\n * `Usage` accumulator. Shared by `complete()` and the streaming\n * loop (where the final chunk carries cumulative totals).\n *\n * Cache-read hits (`cachedContentTokenCount`, implicit or explicit\n * context caching) surface as `cachedTokens`; the thinking-phase\n * tokens of a reasoning model (`thoughtsTokenCount`) surface as\n * `reasoningTokens`. Both are emitted only when reported `> 0` so an\n * absent channel leaves the field undefined.\n */\n private applyUsage(\n usage: Usage,\n raw: NonNullable<GenerateContentResponse[\"usageMetadata\"]>,\n ): void {\n usage.input = raw.promptTokenCount ?? usage.input;\n usage.output = raw.candidatesTokenCount ?? usage.output;\n usage.total = raw.totalTokenCount ?? usage.input + usage.output;\n\n const cached = raw.cachedContentTokenCount;\n\n if (cached && cached > 0) {\n usage.cachedTokens = cached;\n }\n\n const reasoning = raw.thoughtsTokenCount;\n\n if (reasoning && reasoning > 0) {\n usage.reasoningTokens = reasoning;\n }\n }\n\n /**\n * Wrap a thrown provider error into the typed `AIError` hierarchy\n * and emit the standard error log line before it propagates.\n */\n private logAndWrap(thrown: unknown) {\n const wrapped = wrapGoogleError(thrown);\n\n this.logger.error(LOG_MODULE, \"error\", wrapped.message, {\n code: wrapped.code,\n context: wrapped.context,\n });\n\n return wrapped;\n }\n}\n","import { GoogleGenAI } from \"@google/genai\";\nimport type {\n EmbedderContract,\n ImageModelContract,\n ModelContract,\n ModelPricing,\n SDKAdapterContract,\n} from \"@warlock.js/ai\";\nimport { approximateTokenCount } from \"@warlock.js/ai\";\nimport type {\n GoogleEmbedderConfig,\n GoogleImageConfig,\n GoogleModelConfig,\n GoogleSDKConfig,\n} from \"./config.type\";\nimport { GoogleEmbedder } from \"./embedder\";\nimport { GoogleImageModel } from \"./image\";\nimport { GoogleModel } from \"./model\";\n\n/**\n * Google Gemini-backed implementation of `SDKAdapterContract`.\n *\n * **Role.** The package entry point for Gemini models via the\n * `@google/genai` SDK. A single `GoogleSDK` holds one live\n * `GoogleGenAI` client, shared by every `ModelContract` /\n * `EmbedderContract` it produces. Construct one SDK per\n * account/project and reuse it everywhere.\n *\n * **Responsibility.**\n * - Owns: a long-lived `GoogleGenAI` client (auth, Vertex vs Gemini\n * API) and its lifetime. Factory for `GoogleModel` /\n * `GoogleEmbedder` instances sharing that client.\n * - Does NOT own: anything per-call — those live in `GoogleModel` /\n * `GoogleEmbedder` and the agent runtime.\n *\n * Modeled as a class (see §4.2 of code-style.md — \"long-lived state\n * across many calls\"), fronted by FP usage like the other adapters.\n *\n * @example\n * const google = new GoogleSDK({ apiKey: process.env.GEMINI_API_KEY! });\n * const model = google.model({ name: \"gemini-2.5-flash\", temperature: 0.7 });\n * const embedder = google.embedder({ name: \"gemini-embedding-001\" });\n */\nexport class GoogleSDK implements SDKAdapterContract {\n private readonly ai: GoogleGenAI;\n private readonly provider: string;\n private readonly pricing?: Record<string, ModelPricing>;\n\n public constructor(config: GoogleSDKConfig) {\n const { provider, pricing, ...clientOptions } = config;\n\n this.ai = new GoogleGenAI(clientOptions);\n this.provider = provider ?? \"google\";\n this.pricing = pricing;\n }\n\n /**\n * Build a `GoogleModel` bound to this SDK's client. Each call\n * returns a fresh instance; all instances share the underlying\n * `GoogleGenAI` client. The SDK's `provider` label is forwarded.\n *\n * Pricing resolution: per-model `config.pricing` wins; otherwise the\n * SDK-level registry entry keyed by `config.name`; otherwise\n * `undefined` (no cost computed).\n */\n public model(config: GoogleModelConfig): ModelContract {\n const resolvedPricing = config.pricing ?? this.pricing?.[config.name];\n const resolvedConfig: GoogleModelConfig =\n resolvedPricing === config.pricing ? config : { ...config, pricing: resolvedPricing };\n\n return new GoogleModel(this.ai, resolvedConfig, this.provider);\n }\n\n /**\n * Rough token-count estimate. Uses the character-heuristic\n * (`approximateTokenCount`) from the core package — Gemini's\n * `countTokens` is a network round-trip; `count()` is intentionally\n * offline. Good for budgeting/quota guards, not billing.\n */\n public async count(text: string, _model?: string): Promise<number> {\n return approximateTokenCount(text);\n }\n\n /**\n * Build a `GoogleEmbedder` bound to this SDK's client.\n *\n * @example\n * const embedder = google.embedder({ name: \"gemini-embedding-001\" });\n * const { vector } = await embedder.embed(\"Hello world\");\n */\n public embedder(config: GoogleEmbedderConfig): EmbedderContract {\n return new GoogleEmbedder(this.ai, config, this.provider);\n }\n\n /**\n * Build a `GoogleImageModel` (Imagen) bound to this SDK's client for\n * use with `ai.image({ model, prompt })`. Accepts the `imagen-*`\n * family; a non-Imagen model id is rejected at construction.\n *\n * Pricing resolution mirrors `model()`: per-model `config.pricing`\n * wins, otherwise the SDK-level registry entry keyed by `config.name`,\n * otherwise `undefined`. Imagen is per-image-metered, so the registry\n * entry typically carries `{ perImage }`.\n *\n * @example\n * const model = google.image({ name: \"imagen-4.0-generate-001\" });\n * const { data } = await ai.image({ model, prompt: \"a watercolor lighthouse\" });\n */\n public image(config: GoogleImageConfig): ImageModelContract {\n const resolvedPricing = config.pricing ?? this.pricing?.[config.name];\n const resolvedConfig: GoogleImageConfig =\n resolvedPricing === config.pricing ? config : { ...config, pricing: resolvedPricing };\n\n return new GoogleImageModel(this.ai, resolvedConfig, this.provider);\n }\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { GoogleGenAIOptions } from "@google/genai";
2
- import { EmbedderConfig, ModelConfig, ModelPricing } from "@warlock.js/ai";
2
+ import { EmbedderConfig, ImageModelConfig, ModelConfig, ModelPricing } from "@warlock.js/ai";
3
3
 
4
4
  //#region ../@warlock.js/ai-google/src/config.type.d.ts
5
5
  /**
@@ -102,6 +102,16 @@ type GoogleModelConfig = ModelConfig & {
102
102
  * google.embedder({ name: "gemini-embedding-001", dimensions: 768 });
103
103
  */
104
104
  type GoogleEmbedderConfig = EmbedderConfig;
105
+ /**
106
+ * Per-model configuration for `GoogleSDK.image()`. Mirrors the neutral
107
+ * {@link ImageModelConfig} — `name` is an `imagen-*` model id and
108
+ * `pricing` is the optional per-model `perImage` USD override.
109
+ *
110
+ * @example
111
+ * google.image({ name: "imagen-4.0-generate-001" });
112
+ * google.image({ name: "imagen-4.0-generate-001", pricing: { perImage: 0.04 } });
113
+ */
114
+ type GoogleImageConfig = ImageModelConfig;
105
115
  //#endregion
106
- export { GoogleEmbedderConfig, GoogleModelConfig, GoogleSDKConfig };
116
+ export { GoogleEmbedderConfig, GoogleImageConfig, GoogleModelConfig, GoogleSDKConfig };
107
117
  //# sourceMappingURL=config.type.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.type.d.mts","names":[],"sources":["../../../../../../@warlock.js/ai-google/src/config.type.ts"],"mappings":";;;;;;AAiCA;;;;;;;;;;;;;;AAOuC;AAWvC;;;;;;;;;;;AAwCK;AAaL;KAvEY,eAAA,GAAkB,kBAAA;EAC5B,QAAA;EAsE+C;AAAA;;;;EAhE/C,OAAA,GAAU,MAAA,SAAe,YAAA;AAAA;;;;;;;;;KAWf,iBAAA,GAAoB,WAAW;;;;;;;EAOzC,MAAA;;;;;;;;;EASA,gBAAA;;;;;;;;;;;EAWA,SAAA;;;;;;EAMA,KAAA;;;;;;;EAOA,GAAA;AAAA;;;;;;;;;;;KAaU,oBAAA,GAAuB,cAAc"}
1
+ {"version":3,"file":"config.type.d.mts","names":[],"sources":["../../../../../../@warlock.js/ai-google/src/config.type.ts"],"mappings":";;;;;;AAsCA;;;;;;;;;;;;;;AAOuC;AAWvC;;;;;;;;;;;AAwCK;AAaL;KAvEY,eAAA,GAAkB,kBAAA;EAC5B,QAAA;EAsE+C;AAAA;AAWjD;;;EA3EE,OAAA,GAAU,MAAA,SAAe,YAAA;AAAA;;;;;;;;;KAWf,iBAAA,GAAoB,WAAW;;;;;;;EAOzC,MAAA;;;;;;;;;EASA,gBAAA;;;;;;;;;;;EAWA,SAAA;;;;;;EAMA,KAAA;;;;;;;EAOA,GAAA;AAAA;;;;;;;;;;;KAaU,oBAAA,GAAuB,cAAc;;;;;;;;;;KAWrC,iBAAA,GAAoB,gBAAgB"}
@@ -0,0 +1,35 @@
1
+ import { GoogleImageConfig } from "./config.type.mjs";
2
+ import { GoogleGenAI } from "@google/genai";
3
+ import { ImageGenerationOptions, ImageGenerationResponse, ImageModelContract, ImageModelPricing } from "@warlock.js/ai";
4
+
5
+ //#region ../@warlock.js/ai-google/src/image.d.ts
6
+ /**
7
+ * Google Imagen-backed implementation of `ImageModelContract`, via
8
+ * `ai.models.generateImages`. Imagen is per-image-metered and returns
9
+ * base64 image bytes (no hosted URL, no token usage).
10
+ *
11
+ * **Capability guard.** The constructor rejects a non-Imagen model id
12
+ * up front — `google.image({ name: "gemini-2.5-flash" })` throws a
13
+ * typed `InvalidRequestError` instead of a downstream 400 (Gemini's
14
+ * native image output is a different API and not routed here).
15
+ *
16
+ * **Safety filtering.** When Imagen filters every candidate for safety
17
+ * (`raiFilteredReason`), this surfaces a typed `ContentFilterError`
18
+ * carrying the reason, rather than returning an empty success.
19
+ *
20
+ * @example
21
+ * const model = new GoogleImageModel(ai, { name: "imagen-4.0-generate-001" }, "google");
22
+ * const { images } = await model.generate("a watercolor lighthouse at dawn");
23
+ */
24
+ declare class GoogleImageModel implements ImageModelContract {
25
+ readonly name: string;
26
+ readonly provider: string;
27
+ readonly pricing?: ImageModelPricing;
28
+ private readonly ai;
29
+ private readonly logger;
30
+ constructor(ai: GoogleGenAI, config: GoogleImageConfig, provider?: string);
31
+ generate(prompt: string, options?: ImageGenerationOptions): Promise<ImageGenerationResponse>;
32
+ }
33
+ //#endregion
34
+ export { GoogleImageModel };
35
+ //# sourceMappingURL=image.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image.d.mts","names":[],"sources":["../../../../../../@warlock.js/ai-google/src/image.ts"],"mappings":";;;;;;;AAmDA;;;;;;;;;;;;;;;;cAAa,gBAAA,YAA4B,kBAAA;EAAA,SACvB,IAAA;EAAA,SACA,QAAA;EAAA,SACA,OAAA,GAAU,iBAAA;EAAA,iBAET,EAAA;EAAA,iBACA,MAAA;cAEE,EAAA,EAAI,WAAA,EAAa,MAAA,EAAQ,iBAAA,EAAmB,QAAA;EAclD,QAAA,CACX,MAAA,UACA,OAAA,GAAU,sBAAA,GACT,OAAA,CAAQ,uBAAA;AAAA"}
package/esm/image.mjs ADDED
@@ -0,0 +1,106 @@
1
+ import { wrapGoogleError } from "./utils/wrap-google-error.mjs";
2
+ import "./utils/index.mjs";
3
+ import { isGoogleImageModel } from "./known-image-models.mjs";
4
+ import { ContentFilterError, InvalidRequestError, ProviderError } from "@warlock.js/ai";
5
+ import { log } from "@warlock.js/logger";
6
+
7
+ //#region ../@warlock.js/ai-google/src/image.ts
8
+ const LOG_MODULE = "ai.google";
9
+ /** Map a neutral output container hint to an IANA media type. */
10
+ function mediaTypeFor(format) {
11
+ switch (format) {
12
+ case "png": return "image/png";
13
+ case "jpeg":
14
+ case "jpg": return "image/jpeg";
15
+ case "webp": return "image/webp";
16
+ default: return;
17
+ }
18
+ }
19
+ /**
20
+ * Google Imagen-backed implementation of `ImageModelContract`, via
21
+ * `ai.models.generateImages`. Imagen is per-image-metered and returns
22
+ * base64 image bytes (no hosted URL, no token usage).
23
+ *
24
+ * **Capability guard.** The constructor rejects a non-Imagen model id
25
+ * up front — `google.image({ name: "gemini-2.5-flash" })` throws a
26
+ * typed `InvalidRequestError` instead of a downstream 400 (Gemini's
27
+ * native image output is a different API and not routed here).
28
+ *
29
+ * **Safety filtering.** When Imagen filters every candidate for safety
30
+ * (`raiFilteredReason`), this surfaces a typed `ContentFilterError`
31
+ * carrying the reason, rather than returning an empty success.
32
+ *
33
+ * @example
34
+ * const model = new GoogleImageModel(ai, { name: "imagen-4.0-generate-001" }, "google");
35
+ * const { images } = await model.generate("a watercolor lighthouse at dawn");
36
+ */
37
+ var GoogleImageModel = class {
38
+ constructor(ai, config, provider = "google") {
39
+ this.logger = log;
40
+ if (!isGoogleImageModel(config.name)) throw new InvalidRequestError(`"${config.name}" is not a known Google Imagen model. Use an \`imagen-*\` model with google.image({ name }).`);
41
+ this.ai = ai;
42
+ this.name = config.name;
43
+ this.provider = provider;
44
+ this.pricing = config.pricing;
45
+ }
46
+ async generate(prompt, options) {
47
+ const config = {};
48
+ if (options?.count !== void 0) config.numberOfImages = options.count;
49
+ if (options?.aspectRatio !== void 0) config.aspectRatio = options.aspectRatio;
50
+ if (options?.negativePrompt !== void 0) config.negativePrompt = options.negativePrompt;
51
+ if (options?.signal !== void 0) config.abortSignal = options.signal;
52
+ const outputMimeType = mediaTypeFor(options?.format);
53
+ if (outputMimeType !== void 0) config.outputMimeType = outputMimeType;
54
+ if (typeof options?.imageSize === "string") config.imageSize = options.imageSize;
55
+ if (typeof options?.personGeneration === "string") config.personGeneration = options.personGeneration;
56
+ this.logger.debug(LOG_MODULE, "image.request", "models.generateImages", {
57
+ model: this.name,
58
+ count: options?.count ?? 1
59
+ });
60
+ let response;
61
+ try {
62
+ response = await this.ai.models.generateImages({
63
+ model: this.name,
64
+ prompt,
65
+ config
66
+ });
67
+ } catch (thrown) {
68
+ const wrapped = wrapGoogleError(thrown);
69
+ this.logger.error(LOG_MODULE, "image.error", wrapped.message, {
70
+ code: wrapped.code,
71
+ context: wrapped.context
72
+ });
73
+ throw wrapped;
74
+ }
75
+ const generated = response.generatedImages ?? [];
76
+ const images = [];
77
+ for (const candidate of generated) {
78
+ const bytes = candidate.image?.imageBytes;
79
+ if (!bytes) continue;
80
+ images.push({
81
+ type: "base64",
82
+ base64: bytes,
83
+ mediaType: candidate.image?.mimeType ?? outputMimeType ?? "image/png",
84
+ ...candidate.enhancedPrompt ? { revisedPrompt: candidate.enhancedPrompt } : {}
85
+ });
86
+ }
87
+ if (images.length === 0) {
88
+ const filtered = generated.find((candidate) => candidate.raiFilteredReason);
89
+ if (filtered?.raiFilteredReason) throw new ContentFilterError(`Imagen filtered all candidates: ${filtered.raiFilteredReason}`, { reason: filtered.raiFilteredReason });
90
+ throw new ProviderError("Imagen returned no images.");
91
+ }
92
+ this.logger.debug(LOG_MODULE, "image.response", "models.generateImages succeeded", { images: images.length });
93
+ return {
94
+ images,
95
+ usage: {
96
+ input: 0,
97
+ output: 0,
98
+ total: 0
99
+ }
100
+ };
101
+ }
102
+ };
103
+
104
+ //#endregion
105
+ export { GoogleImageModel };
106
+ //# sourceMappingURL=image.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image.mjs","names":[],"sources":["../../../../../../@warlock.js/ai-google/src/image.ts"],"sourcesContent":["import {\n ContentFilterError,\n InvalidRequestError,\n ProviderError,\n type GeneratedImage,\n type ImageGenerationOptions,\n type ImageGenerationResponse,\n type ImageModelContract,\n type ImageModelPricing,\n} from \"@warlock.js/ai\";\nimport { log, type Logger } from \"@warlock.js/logger\";\nimport type { GenerateImagesConfig, GoogleGenAI } from \"@google/genai\";\nimport type { GoogleImageConfig } from \"./config.type\";\nimport { isGoogleImageModel } from \"./known-image-models\";\nimport { wrapGoogleError } from \"./utils\";\n\nconst LOG_MODULE = \"ai.google\";\n\n/** Map a neutral output container hint to an IANA media type. */\nfunction mediaTypeFor(format: string | undefined): string | undefined {\n switch (format) {\n case \"png\":\n return \"image/png\";\n case \"jpeg\":\n case \"jpg\":\n return \"image/jpeg\";\n case \"webp\":\n return \"image/webp\";\n default:\n return undefined;\n }\n}\n\n/**\n * Google Imagen-backed implementation of `ImageModelContract`, via\n * `ai.models.generateImages`. Imagen is per-image-metered and returns\n * base64 image bytes (no hosted URL, no token usage).\n *\n * **Capability guard.** The constructor rejects a non-Imagen model id\n * up front — `google.image({ name: \"gemini-2.5-flash\" })` throws a\n * typed `InvalidRequestError` instead of a downstream 400 (Gemini's\n * native image output is a different API and not routed here).\n *\n * **Safety filtering.** When Imagen filters every candidate for safety\n * (`raiFilteredReason`), this surfaces a typed `ContentFilterError`\n * carrying the reason, rather than returning an empty success.\n *\n * @example\n * const model = new GoogleImageModel(ai, { name: \"imagen-4.0-generate-001\" }, \"google\");\n * const { images } = await model.generate(\"a watercolor lighthouse at dawn\");\n */\nexport class GoogleImageModel implements ImageModelContract {\n public readonly name: string;\n public readonly provider: string;\n public readonly pricing?: ImageModelPricing;\n\n private readonly ai: GoogleGenAI;\n private readonly logger: Logger = log;\n\n public constructor(ai: GoogleGenAI, config: GoogleImageConfig, provider: string = \"google\") {\n if (!isGoogleImageModel(config.name)) {\n throw new InvalidRequestError(\n `\"${config.name}\" is not a known Google Imagen model. ` +\n \"Use an `imagen-*` model with google.image({ name }).\",\n );\n }\n\n this.ai = ai;\n this.name = config.name;\n this.provider = provider;\n this.pricing = config.pricing;\n }\n\n public async generate(\n prompt: string,\n options?: ImageGenerationOptions,\n ): Promise<ImageGenerationResponse> {\n const config: GenerateImagesConfig = {};\n\n if (options?.count !== undefined) config.numberOfImages = options.count;\n if (options?.aspectRatio !== undefined) config.aspectRatio = options.aspectRatio;\n if (options?.negativePrompt !== undefined) config.negativePrompt = options.negativePrompt;\n if (options?.signal !== undefined) config.abortSignal = options.signal;\n\n const outputMimeType = mediaTypeFor(options?.format);\n if (outputMimeType !== undefined) config.outputMimeType = outputMimeType;\n\n // Imagen sizing is `imageSize` (\"1K\"/\"2K\") — a distinct concept from\n // OpenAI's WxH `size`, so we honor only an explicit passthrough.\n if (typeof options?.imageSize === \"string\") config.imageSize = options.imageSize;\n if (typeof options?.personGeneration === \"string\") {\n config.personGeneration = options.personGeneration as GenerateImagesConfig[\"personGeneration\"];\n }\n\n this.logger.debug(LOG_MODULE, \"image.request\", \"models.generateImages\", {\n model: this.name,\n count: options?.count ?? 1,\n });\n\n let response: Awaited<ReturnType<GoogleGenAI[\"models\"][\"generateImages\"]>>;\n\n try {\n response = await this.ai.models.generateImages({ model: this.name, prompt, config });\n } catch (thrown) {\n const wrapped = wrapGoogleError(thrown);\n\n this.logger.error(LOG_MODULE, \"image.error\", wrapped.message, {\n code: wrapped.code,\n context: wrapped.context,\n });\n\n throw wrapped;\n }\n\n const generated = response.generatedImages ?? [];\n const images: GeneratedImage[] = [];\n\n for (const candidate of generated) {\n const bytes = candidate.image?.imageBytes;\n if (!bytes) continue;\n\n images.push({\n type: \"base64\",\n base64: bytes,\n mediaType: candidate.image?.mimeType ?? outputMimeType ?? \"image/png\",\n ...(candidate.enhancedPrompt ? { revisedPrompt: candidate.enhancedPrompt } : {}),\n });\n }\n\n if (images.length === 0) {\n const filtered = generated.find((candidate) => candidate.raiFilteredReason);\n\n if (filtered?.raiFilteredReason) {\n throw new ContentFilterError(\n `Imagen filtered all candidates: ${filtered.raiFilteredReason}`,\n { reason: filtered.raiFilteredReason },\n );\n }\n\n throw new ProviderError(\"Imagen returned no images.\");\n }\n\n this.logger.debug(LOG_MODULE, \"image.response\", \"models.generateImages succeeded\", {\n images: images.length,\n });\n\n // Imagen returns no token usage — honest zero (priced per image).\n return { images, usage: { input: 0, output: 0, total: 0 } };\n }\n}\n"],"mappings":";;;;;;;AAgBA,MAAM,aAAa;;AAGnB,SAAS,aAAa,QAAgD;CACpE,QAAQ,QAAR;EACE,KAAK,OACH,OAAO;EACT,KAAK;EACL,KAAK,OACH,OAAO;EACT,KAAK,QACH,OAAO;EACT,SACE;CACJ;AACF;;;;;;;;;;;;;;;;;;;AAoBA,IAAa,mBAAb,MAA4D;CAQ1D,AAAO,YAAY,IAAiB,QAA2B,WAAmB,UAAU;gBAF1D;EAGhC,IAAI,CAAC,mBAAmB,OAAO,IAAI,GACjC,MAAM,IAAI,oBACR,IAAI,OAAO,KAAK,6FAElB;EAGF,KAAK,KAAK;EACV,KAAK,OAAO,OAAO;EACnB,KAAK,WAAW;EAChB,KAAK,UAAU,OAAO;CACxB;CAEA,MAAa,SACX,QACA,SACkC;EAClC,MAAM,SAA+B,CAAC;EAEtC,IAAI,SAAS,UAAU,QAAW,OAAO,iBAAiB,QAAQ;EAClE,IAAI,SAAS,gBAAgB,QAAW,OAAO,cAAc,QAAQ;EACrE,IAAI,SAAS,mBAAmB,QAAW,OAAO,iBAAiB,QAAQ;EAC3E,IAAI,SAAS,WAAW,QAAW,OAAO,cAAc,QAAQ;EAEhE,MAAM,iBAAiB,aAAa,SAAS,MAAM;EACnD,IAAI,mBAAmB,QAAW,OAAO,iBAAiB;EAI1D,IAAI,OAAO,SAAS,cAAc,UAAU,OAAO,YAAY,QAAQ;EACvE,IAAI,OAAO,SAAS,qBAAqB,UACvC,OAAO,mBAAmB,QAAQ;EAGpC,KAAK,OAAO,MAAM,YAAY,iBAAiB,yBAAyB;GACtE,OAAO,KAAK;GACZ,OAAO,SAAS,SAAS;EAC3B,CAAC;EAED,IAAI;EAEJ,IAAI;GACF,WAAW,MAAM,KAAK,GAAG,OAAO,eAAe;IAAE,OAAO,KAAK;IAAM;IAAQ;GAAO,CAAC;EACrF,SAAS,QAAQ;GACf,MAAM,UAAU,gBAAgB,MAAM;GAEtC,KAAK,OAAO,MAAM,YAAY,eAAe,QAAQ,SAAS;IAC5D,MAAM,QAAQ;IACd,SAAS,QAAQ;GACnB,CAAC;GAED,MAAM;EACR;EAEA,MAAM,YAAY,SAAS,mBAAmB,CAAC;EAC/C,MAAM,SAA2B,CAAC;EAElC,KAAK,MAAM,aAAa,WAAW;GACjC,MAAM,QAAQ,UAAU,OAAO;GAC/B,IAAI,CAAC,OAAO;GAEZ,OAAO,KAAK;IACV,MAAM;IACN,QAAQ;IACR,WAAW,UAAU,OAAO,YAAY,kBAAkB;IAC1D,GAAI,UAAU,iBAAiB,EAAE,eAAe,UAAU,eAAe,IAAI,CAAC;GAChF,CAAC;EACH;EAEA,IAAI,OAAO,WAAW,GAAG;GACvB,MAAM,WAAW,UAAU,MAAM,cAAc,UAAU,iBAAiB;GAE1E,IAAI,UAAU,mBACZ,MAAM,IAAI,mBACR,mCAAmC,SAAS,qBAC5C,EAAE,QAAQ,SAAS,kBAAkB,CACvC;GAGF,MAAM,IAAI,cAAc,4BAA4B;EACtD;EAEA,KAAK,OAAO,MAAM,YAAY,kBAAkB,mCAAmC,EACjF,QAAQ,OAAO,OACjB,CAAC;EAGD,OAAO;GAAE;GAAQ,OAAO;IAAE,OAAO;IAAG,QAAQ;IAAG,OAAO;GAAE;EAAE;CAC5D;AACF"}
package/esm/index.d.mts CHANGED
@@ -1,3 +1,5 @@
1
- import { GoogleEmbedderConfig, GoogleModelConfig, GoogleSDKConfig } from "./config.type.mjs";
1
+ import { GoogleEmbedderConfig, GoogleImageConfig, GoogleModelConfig, GoogleSDKConfig } from "./config.type.mjs";
2
2
  import { GoogleSDK } from "./sdk.mjs";
3
- export { type GoogleEmbedderConfig, type GoogleModelConfig, GoogleSDK, type GoogleSDKConfig };
3
+ import { GoogleImageModel } from "./image.mjs";
4
+ import { GOOGLE_IMAGE_MODEL_PREFIXES, isGoogleImageModel } from "./known-image-models.mjs";
5
+ export { GOOGLE_IMAGE_MODEL_PREFIXES, type GoogleEmbedderConfig, type GoogleImageConfig, GoogleImageModel, type GoogleModelConfig, GoogleSDK, type GoogleSDKConfig, isGoogleImageModel };
package/esm/index.mjs CHANGED
@@ -1,3 +1,5 @@
1
+ import { GOOGLE_IMAGE_MODEL_PREFIXES, isGoogleImageModel } from "./known-image-models.mjs";
2
+ import { GoogleImageModel } from "./image.mjs";
1
3
  import { GoogleSDK } from "./sdk.mjs";
2
4
 
3
- export { GoogleSDK };
5
+ export { GOOGLE_IMAGE_MODEL_PREFIXES, GoogleImageModel, GoogleSDK, isGoogleImageModel };
@@ -0,0 +1,30 @@
1
+ //#region ../@warlock.js/ai-google/src/known-image-models.d.ts
2
+ /**
3
+ * Model-id prefixes Google exposes through the **Imagen** image API
4
+ * (`ai.models.generateImages`) — `imagen-3.0-*`, `imagen-4.0-*`, and
5
+ * their fast/ultra variants. All are per-image-metered and return
6
+ * base64 bytes.
7
+ *
8
+ * Gemini's *native* image output (`gemini-2.5-flash-image`) is a
9
+ * different surface (`generateContent` with `responseModalities`) and
10
+ * is intentionally NOT routed here — `google.image()` targets the
11
+ * dedicated Imagen endpoint only.
12
+ *
13
+ * Used by {@link isGoogleImageModel} for the construction-time guard so
14
+ * `google.image({ name: "gemini-2.5-flash" })` fails fast with a
15
+ * curated error rather than a downstream 400.
16
+ */
17
+ declare const GOOGLE_IMAGE_MODEL_PREFIXES: readonly ["imagen-"];
18
+ /**
19
+ * True when `name` is a recognized Google Imagen model. A prefix match
20
+ * so dated/variant ids (`imagen-4.0-ultra-generate-001`) are covered
21
+ * without an exact-list maintenance burden.
22
+ *
23
+ * @example
24
+ * isGoogleImageModel("imagen-4.0-generate-001"); // true
25
+ * isGoogleImageModel("gemini-2.5-flash"); // false
26
+ */
27
+ declare function isGoogleImageModel(name: string): boolean;
28
+ //#endregion
29
+ export { GOOGLE_IMAGE_MODEL_PREFIXES, isGoogleImageModel };
30
+ //# sourceMappingURL=known-image-models.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"known-image-models.d.mts","names":[],"sources":["../../../../../../@warlock.js/ai-google/src/known-image-models.ts"],"mappings":";;AAeA;;;;AAA+D;AAW/D;;;;AAA+C;;;;;cAXlC,2BAAA;;;;;;;;;;iBAWG,kBAAA,CAAmB,IAAY"}
@@ -0,0 +1,33 @@
1
+ //#region ../@warlock.js/ai-google/src/known-image-models.ts
2
+ /**
3
+ * Model-id prefixes Google exposes through the **Imagen** image API
4
+ * (`ai.models.generateImages`) — `imagen-3.0-*`, `imagen-4.0-*`, and
5
+ * their fast/ultra variants. All are per-image-metered and return
6
+ * base64 bytes.
7
+ *
8
+ * Gemini's *native* image output (`gemini-2.5-flash-image`) is a
9
+ * different surface (`generateContent` with `responseModalities`) and
10
+ * is intentionally NOT routed here — `google.image()` targets the
11
+ * dedicated Imagen endpoint only.
12
+ *
13
+ * Used by {@link isGoogleImageModel} for the construction-time guard so
14
+ * `google.image({ name: "gemini-2.5-flash" })` fails fast with a
15
+ * curated error rather than a downstream 400.
16
+ */
17
+ const GOOGLE_IMAGE_MODEL_PREFIXES = ["imagen-"];
18
+ /**
19
+ * True when `name` is a recognized Google Imagen model. A prefix match
20
+ * so dated/variant ids (`imagen-4.0-ultra-generate-001`) are covered
21
+ * without an exact-list maintenance burden.
22
+ *
23
+ * @example
24
+ * isGoogleImageModel("imagen-4.0-generate-001"); // true
25
+ * isGoogleImageModel("gemini-2.5-flash"); // false
26
+ */
27
+ function isGoogleImageModel(name) {
28
+ return GOOGLE_IMAGE_MODEL_PREFIXES.some((prefix) => name.startsWith(prefix));
29
+ }
30
+
31
+ //#endregion
32
+ export { GOOGLE_IMAGE_MODEL_PREFIXES, isGoogleImageModel };
33
+ //# sourceMappingURL=known-image-models.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"known-image-models.mjs","names":[],"sources":["../../../../../../@warlock.js/ai-google/src/known-image-models.ts"],"sourcesContent":["/**\n * Model-id prefixes Google exposes through the **Imagen** image API\n * (`ai.models.generateImages`) — `imagen-3.0-*`, `imagen-4.0-*`, and\n * their fast/ultra variants. All are per-image-metered and return\n * base64 bytes.\n *\n * Gemini's *native* image output (`gemini-2.5-flash-image`) is a\n * different surface (`generateContent` with `responseModalities`) and\n * is intentionally NOT routed here — `google.image()` targets the\n * dedicated Imagen endpoint only.\n *\n * Used by {@link isGoogleImageModel} for the construction-time guard so\n * `google.image({ name: \"gemini-2.5-flash\" })` fails fast with a\n * curated error rather than a downstream 400.\n */\nexport const GOOGLE_IMAGE_MODEL_PREFIXES = [\"imagen-\"] as const;\n\n/**\n * True when `name` is a recognized Google Imagen model. A prefix match\n * so dated/variant ids (`imagen-4.0-ultra-generate-001`) are covered\n * without an exact-list maintenance burden.\n *\n * @example\n * isGoogleImageModel(\"imagen-4.0-generate-001\"); // true\n * isGoogleImageModel(\"gemini-2.5-flash\"); // false\n */\nexport function isGoogleImageModel(name: string): boolean {\n return GOOGLE_IMAGE_MODEL_PREFIXES.some((prefix) => name.startsWith(prefix));\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAeA,MAAa,8BAA8B,CAAC,SAAS;;;;;;;;;;AAWrD,SAAgB,mBAAmB,MAAuB;CACxD,OAAO,4BAA4B,MAAM,WAAW,KAAK,WAAW,MAAM,CAAC;AAC7E"}
package/esm/sdk.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { GoogleEmbedderConfig, GoogleModelConfig, GoogleSDKConfig } from "./config.type.mjs";
2
- import { EmbedderContract, ModelContract, SDKAdapterContract } from "@warlock.js/ai";
1
+ import { GoogleEmbedderConfig, GoogleImageConfig, GoogleModelConfig, GoogleSDKConfig } from "./config.type.mjs";
2
+ import { EmbedderContract, ImageModelContract, ModelContract, SDKAdapterContract } from "@warlock.js/ai";
3
3
 
4
4
  //#region ../@warlock.js/ai-google/src/sdk.d.ts
5
5
  /**
@@ -56,6 +56,21 @@ declare class GoogleSDK implements SDKAdapterContract {
56
56
  * const { vector } = await embedder.embed("Hello world");
57
57
  */
58
58
  embedder(config: GoogleEmbedderConfig): EmbedderContract;
59
+ /**
60
+ * Build a `GoogleImageModel` (Imagen) bound to this SDK's client for
61
+ * use with `ai.image({ model, prompt })`. Accepts the `imagen-*`
62
+ * family; a non-Imagen model id is rejected at construction.
63
+ *
64
+ * Pricing resolution mirrors `model()`: per-model `config.pricing`
65
+ * wins, otherwise the SDK-level registry entry keyed by `config.name`,
66
+ * otherwise `undefined`. Imagen is per-image-metered, so the registry
67
+ * entry typically carries `{ perImage }`.
68
+ *
69
+ * @example
70
+ * const model = google.image({ name: "imagen-4.0-generate-001" });
71
+ * const { data } = await ai.image({ model, prompt: "a watercolor lighthouse" });
72
+ */
73
+ image(config: GoogleImageConfig): ImageModelContract;
59
74
  }
60
75
  //#endregion
61
76
  export { GoogleSDK };
package/esm/sdk.d.mts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"sdk.d.mts","names":[],"sources":["../../../../../../@warlock.js/ai-google/src/sdk.ts"],"mappings":";;;;;;AAwCA;;;;;;;;;;;;;;;;;;;;;;cAAa,SAAA,YAAqB,kBAAA;EAAA,iBACf,EAAA;EAAA,iBACA,QAAA;EAAA,iBACA,OAAA;cAEE,MAAA,EAAQ,eAAA;EA0CpB;;;;;AAAwD;;;;EAzBxD,KAAA,CAAM,MAAA,EAAQ,iBAAA,GAAoB,aAAA;;;;;;;EAc5B,KAAA,CAAM,IAAA,UAAc,MAAA,YAAkB,OAAA;;;;;;;;EAW5C,QAAA,CAAS,MAAA,EAAQ,oBAAA,GAAuB,gBAAA;AAAA"}
1
+ {"version":3,"file":"sdk.d.mts","names":[],"sources":["../../../../../../@warlock.js/ai-google/src/sdk.ts"],"mappings":";;;;;;AA2CA;;;;;;;;;;;;;;;;;;;;;;cAAa,SAAA,YAAqB,kBAAA;EAAA,iBACf,EAAA;EAAA,iBACA,QAAA;EAAA,iBACA,OAAA;cAEE,MAAA,EAAQ,eAAA;EA+BM;;;;;;;;;EAd1B,KAAA,CAAM,MAAA,EAAQ,iBAAA,GAAoB,aAAA;EA2CkB;AAAA;;;;;EA7B9C,KAAA,CAAM,IAAA,UAAc,MAAA,YAAkB,OAAA;;;;;;;;EAW5C,QAAA,CAAS,MAAA,EAAQ,oBAAA,GAAuB,gBAAA;;;;;;;;;;;;;;;EAkBxC,KAAA,CAAM,MAAA,EAAQ,iBAAA,GAAoB,kBAAA;AAAA"}
package/esm/sdk.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import { GoogleEmbedder } from "./embedder.mjs";
2
+ import { GoogleImageModel } from "./image.mjs";
2
3
  import { GoogleModel } from "./model.mjs";
3
4
  import { GoogleGenAI } from "@google/genai";
4
5
  import { approximateTokenCount } from "@warlock.js/ai";
@@ -71,6 +72,28 @@ var GoogleSDK = class {
71
72
  embedder(config) {
72
73
  return new GoogleEmbedder(this.ai, config, this.provider);
73
74
  }
75
+ /**
76
+ * Build a `GoogleImageModel` (Imagen) bound to this SDK's client for
77
+ * use with `ai.image({ model, prompt })`. Accepts the `imagen-*`
78
+ * family; a non-Imagen model id is rejected at construction.
79
+ *
80
+ * Pricing resolution mirrors `model()`: per-model `config.pricing`
81
+ * wins, otherwise the SDK-level registry entry keyed by `config.name`,
82
+ * otherwise `undefined`. Imagen is per-image-metered, so the registry
83
+ * entry typically carries `{ perImage }`.
84
+ *
85
+ * @example
86
+ * const model = google.image({ name: "imagen-4.0-generate-001" });
87
+ * const { data } = await ai.image({ model, prompt: "a watercolor lighthouse" });
88
+ */
89
+ image(config) {
90
+ const resolvedPricing = config.pricing ?? this.pricing?.[config.name];
91
+ const resolvedConfig = resolvedPricing === config.pricing ? config : {
92
+ ...config,
93
+ pricing: resolvedPricing
94
+ };
95
+ return new GoogleImageModel(this.ai, resolvedConfig, this.provider);
96
+ }
74
97
  };
75
98
 
76
99
  //#endregion
package/esm/sdk.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"sdk.mjs","names":[],"sources":["../../../../../../@warlock.js/ai-google/src/sdk.ts"],"sourcesContent":["import { GoogleGenAI } from \"@google/genai\";\nimport type {\n EmbedderContract,\n ModelContract,\n ModelPricing,\n SDKAdapterContract,\n} from \"@warlock.js/ai\";\nimport { approximateTokenCount } from \"@warlock.js/ai\";\nimport type {\n GoogleEmbedderConfig,\n GoogleModelConfig,\n GoogleSDKConfig,\n} from \"./config.type\";\nimport { GoogleEmbedder } from \"./embedder\";\nimport { GoogleModel } from \"./model\";\n\n/**\n * Google Gemini-backed implementation of `SDKAdapterContract`.\n *\n * **Role.** The package entry point for Gemini models via the\n * `@google/genai` SDK. A single `GoogleSDK` holds one live\n * `GoogleGenAI` client, shared by every `ModelContract` /\n * `EmbedderContract` it produces. Construct one SDK per\n * account/project and reuse it everywhere.\n *\n * **Responsibility.**\n * - Owns: a long-lived `GoogleGenAI` client (auth, Vertex vs Gemini\n * API) and its lifetime. Factory for `GoogleModel` /\n * `GoogleEmbedder` instances sharing that client.\n * - Does NOT own: anything per-call — those live in `GoogleModel` /\n * `GoogleEmbedder` and the agent runtime.\n *\n * Modeled as a class (see §4.2 of code-style.md — \"long-lived state\n * across many calls\"), fronted by FP usage like the other adapters.\n *\n * @example\n * const google = new GoogleSDK({ apiKey: process.env.GEMINI_API_KEY! });\n * const model = google.model({ name: \"gemini-2.5-flash\", temperature: 0.7 });\n * const embedder = google.embedder({ name: \"gemini-embedding-001\" });\n */\nexport class GoogleSDK implements SDKAdapterContract {\n private readonly ai: GoogleGenAI;\n private readonly provider: string;\n private readonly pricing?: Record<string, ModelPricing>;\n\n public constructor(config: GoogleSDKConfig) {\n const { provider, pricing, ...clientOptions } = config;\n\n this.ai = new GoogleGenAI(clientOptions);\n this.provider = provider ?? \"google\";\n this.pricing = pricing;\n }\n\n /**\n * Build a `GoogleModel` bound to this SDK's client. Each call\n * returns a fresh instance; all instances share the underlying\n * `GoogleGenAI` client. The SDK's `provider` label is forwarded.\n *\n * Pricing resolution: per-model `config.pricing` wins; otherwise the\n * SDK-level registry entry keyed by `config.name`; otherwise\n * `undefined` (no cost computed).\n */\n public model(config: GoogleModelConfig): ModelContract {\n const resolvedPricing = config.pricing ?? this.pricing?.[config.name];\n const resolvedConfig: GoogleModelConfig =\n resolvedPricing === config.pricing ? config : { ...config, pricing: resolvedPricing };\n\n return new GoogleModel(this.ai, resolvedConfig, this.provider);\n }\n\n /**\n * Rough token-count estimate. Uses the character-heuristic\n * (`approximateTokenCount`) from the core package — Gemini's\n * `countTokens` is a network round-trip; `count()` is intentionally\n * offline. Good for budgeting/quota guards, not billing.\n */\n public async count(text: string, _model?: string): Promise<number> {\n return approximateTokenCount(text);\n }\n\n /**\n * Build a `GoogleEmbedder` bound to this SDK's client.\n *\n * @example\n * const embedder = google.embedder({ name: \"gemini-embedding-001\" });\n * const { vector } = await embedder.embed(\"Hello world\");\n */\n public embedder(config: GoogleEmbedderConfig): EmbedderContract {\n return new GoogleEmbedder(this.ai, config, this.provider);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,IAAa,YAAb,MAAqD;CAKnD,AAAO,YAAY,QAAyB;EAC1C,MAAM,EAAE,UAAU,SAAS,GAAG,kBAAkB;EAEhD,KAAK,KAAK,IAAI,YAAY,aAAa;EACvC,KAAK,WAAW,YAAY;EAC5B,KAAK,UAAU;CACjB;;;;;;;;;;CAWA,AAAO,MAAM,QAA0C;EACrD,MAAM,kBAAkB,OAAO,WAAW,KAAK,UAAU,OAAO;EAChE,MAAM,iBACJ,oBAAoB,OAAO,UAAU,SAAS;GAAE,GAAG;GAAQ,SAAS;EAAgB;EAEtF,OAAO,IAAI,YAAY,KAAK,IAAI,gBAAgB,KAAK,QAAQ;CAC/D;;;;;;;CAQA,MAAa,MAAM,MAAc,QAAkC;EACjE,OAAO,sBAAsB,IAAI;CACnC;;;;;;;;CASA,AAAO,SAAS,QAAgD;EAC9D,OAAO,IAAI,eAAe,KAAK,IAAI,QAAQ,KAAK,QAAQ;CAC1D;AACF"}
1
+ {"version":3,"file":"sdk.mjs","names":[],"sources":["../../../../../../@warlock.js/ai-google/src/sdk.ts"],"sourcesContent":["import { GoogleGenAI } from \"@google/genai\";\nimport type {\n EmbedderContract,\n ImageModelContract,\n ModelContract,\n ModelPricing,\n SDKAdapterContract,\n} from \"@warlock.js/ai\";\nimport { approximateTokenCount } from \"@warlock.js/ai\";\nimport type {\n GoogleEmbedderConfig,\n GoogleImageConfig,\n GoogleModelConfig,\n GoogleSDKConfig,\n} from \"./config.type\";\nimport { GoogleEmbedder } from \"./embedder\";\nimport { GoogleImageModel } from \"./image\";\nimport { GoogleModel } from \"./model\";\n\n/**\n * Google Gemini-backed implementation of `SDKAdapterContract`.\n *\n * **Role.** The package entry point for Gemini models via the\n * `@google/genai` SDK. A single `GoogleSDK` holds one live\n * `GoogleGenAI` client, shared by every `ModelContract` /\n * `EmbedderContract` it produces. Construct one SDK per\n * account/project and reuse it everywhere.\n *\n * **Responsibility.**\n * - Owns: a long-lived `GoogleGenAI` client (auth, Vertex vs Gemini\n * API) and its lifetime. Factory for `GoogleModel` /\n * `GoogleEmbedder` instances sharing that client.\n * - Does NOT own: anything per-call — those live in `GoogleModel` /\n * `GoogleEmbedder` and the agent runtime.\n *\n * Modeled as a class (see §4.2 of code-style.md — \"long-lived state\n * across many calls\"), fronted by FP usage like the other adapters.\n *\n * @example\n * const google = new GoogleSDK({ apiKey: process.env.GEMINI_API_KEY! });\n * const model = google.model({ name: \"gemini-2.5-flash\", temperature: 0.7 });\n * const embedder = google.embedder({ name: \"gemini-embedding-001\" });\n */\nexport class GoogleSDK implements SDKAdapterContract {\n private readonly ai: GoogleGenAI;\n private readonly provider: string;\n private readonly pricing?: Record<string, ModelPricing>;\n\n public constructor(config: GoogleSDKConfig) {\n const { provider, pricing, ...clientOptions } = config;\n\n this.ai = new GoogleGenAI(clientOptions);\n this.provider = provider ?? \"google\";\n this.pricing = pricing;\n }\n\n /**\n * Build a `GoogleModel` bound to this SDK's client. Each call\n * returns a fresh instance; all instances share the underlying\n * `GoogleGenAI` client. The SDK's `provider` label is forwarded.\n *\n * Pricing resolution: per-model `config.pricing` wins; otherwise the\n * SDK-level registry entry keyed by `config.name`; otherwise\n * `undefined` (no cost computed).\n */\n public model(config: GoogleModelConfig): ModelContract {\n const resolvedPricing = config.pricing ?? this.pricing?.[config.name];\n const resolvedConfig: GoogleModelConfig =\n resolvedPricing === config.pricing ? config : { ...config, pricing: resolvedPricing };\n\n return new GoogleModel(this.ai, resolvedConfig, this.provider);\n }\n\n /**\n * Rough token-count estimate. Uses the character-heuristic\n * (`approximateTokenCount`) from the core package — Gemini's\n * `countTokens` is a network round-trip; `count()` is intentionally\n * offline. Good for budgeting/quota guards, not billing.\n */\n public async count(text: string, _model?: string): Promise<number> {\n return approximateTokenCount(text);\n }\n\n /**\n * Build a `GoogleEmbedder` bound to this SDK's client.\n *\n * @example\n * const embedder = google.embedder({ name: \"gemini-embedding-001\" });\n * const { vector } = await embedder.embed(\"Hello world\");\n */\n public embedder(config: GoogleEmbedderConfig): EmbedderContract {\n return new GoogleEmbedder(this.ai, config, this.provider);\n }\n\n /**\n * Build a `GoogleImageModel` (Imagen) bound to this SDK's client for\n * use with `ai.image({ model, prompt })`. Accepts the `imagen-*`\n * family; a non-Imagen model id is rejected at construction.\n *\n * Pricing resolution mirrors `model()`: per-model `config.pricing`\n * wins, otherwise the SDK-level registry entry keyed by `config.name`,\n * otherwise `undefined`. Imagen is per-image-metered, so the registry\n * entry typically carries `{ perImage }`.\n *\n * @example\n * const model = google.image({ name: \"imagen-4.0-generate-001\" });\n * const { data } = await ai.image({ model, prompt: \"a watercolor lighthouse\" });\n */\n public image(config: GoogleImageConfig): ImageModelContract {\n const resolvedPricing = config.pricing ?? this.pricing?.[config.name];\n const resolvedConfig: GoogleImageConfig =\n resolvedPricing === config.pricing ? config : { ...config, pricing: resolvedPricing };\n\n return new GoogleImageModel(this.ai, resolvedConfig, this.provider);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CA,IAAa,YAAb,MAAqD;CAKnD,AAAO,YAAY,QAAyB;EAC1C,MAAM,EAAE,UAAU,SAAS,GAAG,kBAAkB;EAEhD,KAAK,KAAK,IAAI,YAAY,aAAa;EACvC,KAAK,WAAW,YAAY;EAC5B,KAAK,UAAU;CACjB;;;;;;;;;;CAWA,AAAO,MAAM,QAA0C;EACrD,MAAM,kBAAkB,OAAO,WAAW,KAAK,UAAU,OAAO;EAChE,MAAM,iBACJ,oBAAoB,OAAO,UAAU,SAAS;GAAE,GAAG;GAAQ,SAAS;EAAgB;EAEtF,OAAO,IAAI,YAAY,KAAK,IAAI,gBAAgB,KAAK,QAAQ;CAC/D;;;;;;;CAQA,MAAa,MAAM,MAAc,QAAkC;EACjE,OAAO,sBAAsB,IAAI;CACnC;;;;;;;;CASA,AAAO,SAAS,QAAgD;EAC9D,OAAO,IAAI,eAAe,KAAK,IAAI,QAAQ,KAAK,QAAQ;CAC1D;;;;;;;;;;;;;;;CAgBA,AAAO,MAAM,QAA+C;EAC1D,MAAM,kBAAkB,OAAO,WAAW,KAAK,UAAU,OAAO;EAChE,MAAM,iBACJ,oBAAoB,OAAO,UAAU,SAAS;GAAE,GAAG;GAAQ,SAAS;EAAgB;EAEtF,OAAO,IAAI,iBAAiB,KAAK,IAAI,gBAAgB,KAAK,QAAQ;CACpE;AACF"}
@@ -98,17 +98,26 @@ function toResponseObject(raw) {
98
98
  return { result: raw };
99
99
  }
100
100
  /**
101
- * Map a resolved `ContentPart` to a Gemini `Part`. Images are sent as
102
- * inline base64 (`inlineData`). Gemini's `generateContent` does not
103
- * fetch arbitrary remote URLs (only Files API / GCS URIs via
104
- * `fileData`), so a neutral `{ url }` image surfaces a typed
105
- * `InvalidRequestError` upfront rather than a downstream Gemini fault.
106
- * The agent resolves attachments before this point, so nothing is
107
- * read or fetched here.
101
+ * Map a resolved `ContentPart` to a Gemini `Part`. All binary
102
+ * modalities **image, PDF, and audio** — go to a single
103
+ * `inlineData: { mimeType, data }` block; Gemini's multimodal input is
104
+ * media-agnostic and keys off the IANA `mimeType` (`image/png`,
105
+ * `application/pdf`, `audio/mpeg`, …), so one mapping covers every part
106
+ * type the model's capabilities admit. PDF and audio reach this point
107
+ * only when the model declares the matching capability (`google.model`
108
+ * infers `pdf` / `audio` from the multimodal Gemini families); the
109
+ * agent's modality gate throws upfront otherwise, so capability and
110
+ * behavior stay in lockstep.
111
+ *
112
+ * Gemini's `generateContent` does not fetch arbitrary remote URLs (only
113
+ * Files API / GCS URIs via `fileData`), so a neutral `{ url }` source
114
+ * surfaces a typed `InvalidRequestError` upfront — for any modality —
115
+ * rather than a downstream Gemini fault. The agent resolves attachments
116
+ * before this point, so nothing is read or fetched here.
108
117
  */
109
118
  function toGooglePart(part) {
110
119
  if (part.type === "text") return { text: part.text };
111
- if ("url" in part.source) throw new InvalidRequestError("Gemini generateContent does not fetch remote-URL images; supply base64 image bytes instead.");
120
+ if ("url" in part.source) throw new InvalidRequestError(`Gemini generateContent cannot fetch remote-URL ${part.type} media; supply base64 bytes instead.`);
112
121
  return { inlineData: {
113
122
  mimeType: part.source.mediaType,
114
123
  data: part.source.base64
@@ -1 +1 @@
1
- {"version":3,"file":"to-google-contents.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai-google/src/utils/to-google-contents.ts"],"sourcesContent":["import { InvalidRequestError, safeJsonParse, type ContentPart, type Message } from \"@warlock.js/ai\";\nimport type { Content, Part } from \"@google/genai\";\n\n/**\n * Result of splitting a vendor-neutral `Message[]` for Gemini's\n * `generateContent`: the system prompt is hoisted to a separate\n * `systemInstruction` string (Gemini has no `\"system\"` role — content\n * roles must be `\"user\"` or `\"model\"`), and the remaining turns map to\n * `Content[]`.\n */\nexport type GoogleContents = {\n systemInstruction: string | undefined;\n contents: Content[];\n};\n\n/**\n * Convert vendor-neutral `Message[]` into Gemini's request shape.\n *\n * Gemini specifics this function absorbs:\n *\n * 1. **No `system` role.** System messages concatenate into the\n * separate `systemInstruction` config field.\n * 2. **Role names differ.** Neutral `assistant` → Gemini `\"model\"`;\n * `user` stays `\"user\"`.\n * 3. **Tool results are `user` turns.** A neutral `tool` message\n * becomes a `\"user\"` content with a single `functionResponse` part.\n * 4. **Tool calls are `functionCall` parts.** An assistant message\n * with `toolCalls` becomes a `\"model\"` content: an optional leading\n * `text` part followed by one `functionCall` part per call.\n *\n * @example\n * const { systemInstruction, contents } = toGoogleContents([\n * { role: \"system\", content: \"Be concise.\" },\n * { role: \"user\", content: \"Hi\" },\n * ]);\n */\nexport function toGoogleContents(messages: Message[]): GoogleContents {\n const systemParts: string[] = [];\n const contents: Content[] = [];\n\n for (const message of messages) {\n if (message.role === \"system\") {\n systemParts.push(stringifyContent(message.content));\n\n continue;\n }\n\n if (message.role === \"tool\") {\n contents.push({\n role: \"user\",\n parts: [\n {\n // Gemini matches a `functionResponse` to its `functionCall`\n // by `name` (the Developer API has no call ids). `name` is\n // the neutral `toolCallId`, which `GoogleModel` set to the\n // function name. The wire `id` is intentionally omitted —\n // an empty/synthetic id is rejected as an invalid argument.\n functionResponse: {\n name: message.toolCallId ?? \"\",\n response: toResponseObject(stringifyContent(message.content)),\n },\n },\n ],\n });\n\n continue;\n }\n\n if (message.role === \"assistant\" && message.toolCalls && message.toolCalls.length > 0) {\n const parts: Part[] = [];\n const text = stringifyContent(message.content);\n\n if (text) {\n parts.push({ text });\n }\n\n for (const toolCall of message.toolCalls) {\n // Replay the opaque `thoughtSignature` Gemini attached to this\n // function call on the original turn. Thinking models reject\n // the follow-up request with a 400 if the signature is missing\n // from the echoed `functionCall` part. Captured by\n // `GoogleModel.partToToolCall` into `providerMetadata`.\n const thoughtSignature = toolCall.providerMetadata?.thoughtSignature;\n\n parts.push({\n ...(typeof thoughtSignature === \"string\" ? { thoughtSignature } : {}),\n // `id` omitted deliberately — Gemini Developer API function\n // calls have no ids; echoing an empty/synthetic one is\n // rejected as an invalid argument. Matched by `name`.\n functionCall: {\n name: toolCall.name,\n args: (toolCall.input ?? {}) as Record<string, unknown>,\n },\n });\n }\n\n contents.push({ role: \"model\", parts });\n\n continue;\n }\n\n if (message.role === \"user\" && Array.isArray(message.content)) {\n contents.push({ role: \"user\", parts: message.content.map(toGooglePart) });\n\n continue;\n }\n\n contents.push({\n role: message.role === \"assistant\" ? \"model\" : \"user\",\n parts: [{ text: stringifyContent(message.content) }],\n });\n }\n\n return {\n systemInstruction: systemParts.length > 0 ? systemParts.join(\"\\n\\n\") : undefined,\n contents,\n };\n}\n\n/**\n * Multipart content is only meaningful on user messages — for any\n * other role collapse a `ContentPart[]` to concatenated text. Plain\n * strings pass through unchanged.\n */\nfunction stringifyContent(content: string | ContentPart[]): string {\n if (typeof content === \"string\") {\n return content;\n }\n\n return content\n .filter((part): part is { type: \"text\"; text: string } => part.type === \"text\")\n .map((part) => part.text)\n .join(\"\");\n}\n\n/**\n * Gemini's `functionResponse.response` must be a JSON object. Tool\n * results arrive as a string (usually stringified JSON) — parse it\n * when it is a JSON object, otherwise wrap the raw string under a\n * `result` key so the model always receives a well-formed object.\n */\nfunction toResponseObject(raw: string): Record<string, unknown> {\n const parsed = safeJsonParse<unknown>(raw, undefined);\n\n if (parsed !== null && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n return parsed as Record<string, unknown>;\n }\n\n return { result: raw };\n}\n\n/**\n * Map a resolved `ContentPart` to a Gemini `Part`. Images are sent as\n * inline base64 (`inlineData`). Gemini's `generateContent` does not\n * fetch arbitrary remote URLs (only Files API / GCS URIs via\n * `fileData`), so a neutral `{ url }` image surfaces a typed\n * `InvalidRequestError` upfront rather than a downstream Gemini fault.\n * The agent resolves attachments before this point, so nothing is\n * read or fetched here.\n */\nfunction toGooglePart(part: ContentPart): Part {\n if (part.type === \"text\") {\n return { text: part.text };\n }\n\n if (\"url\" in part.source) {\n throw new InvalidRequestError(\n \"Gemini generateContent does not fetch remote-URL images; supply base64 image bytes instead.\",\n );\n }\n\n return {\n inlineData: { mimeType: part.source.mediaType, data: part.source.base64 },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAoCA,SAAgB,iBAAiB,UAAqC;CACpE,MAAM,cAAwB,CAAC;CAC/B,MAAM,WAAsB,CAAC;CAE7B,KAAK,MAAM,WAAW,UAAU;EAC9B,IAAI,QAAQ,SAAS,UAAU;GAC7B,YAAY,KAAK,iBAAiB,QAAQ,OAAO,CAAC;GAElD;EACF;EAEA,IAAI,QAAQ,SAAS,QAAQ;GAC3B,SAAS,KAAK;IACZ,MAAM;IACN,OAAO,CACL,EAME,kBAAkB;KAChB,MAAM,QAAQ,cAAc;KAC5B,UAAU,iBAAiB,iBAAiB,QAAQ,OAAO,CAAC;IAC9D,EACF,CACF;GACF,CAAC;GAED;EACF;EAEA,IAAI,QAAQ,SAAS,eAAe,QAAQ,aAAa,QAAQ,UAAU,SAAS,GAAG;GACrF,MAAM,QAAgB,CAAC;GACvB,MAAM,OAAO,iBAAiB,QAAQ,OAAO;GAE7C,IAAI,MACF,MAAM,KAAK,EAAE,KAAK,CAAC;GAGrB,KAAK,MAAM,YAAY,QAAQ,WAAW;IAMxC,MAAM,mBAAmB,SAAS,kBAAkB;IAEpD,MAAM,KAAK;KACT,GAAI,OAAO,qBAAqB,WAAW,EAAE,iBAAiB,IAAI,CAAC;KAInE,cAAc;MACZ,MAAM,SAAS;MACf,MAAO,SAAS,SAAS,CAAC;KAC5B;IACF,CAAC;GACH;GAEA,SAAS,KAAK;IAAE,MAAM;IAAS;GAAM,CAAC;GAEtC;EACF;EAEA,IAAI,QAAQ,SAAS,UAAU,MAAM,QAAQ,QAAQ,OAAO,GAAG;GAC7D,SAAS,KAAK;IAAE,MAAM;IAAQ,OAAO,QAAQ,QAAQ,IAAI,YAAY;GAAE,CAAC;GAExE;EACF;EAEA,SAAS,KAAK;GACZ,MAAM,QAAQ,SAAS,cAAc,UAAU;GAC/C,OAAO,CAAC,EAAE,MAAM,iBAAiB,QAAQ,OAAO,EAAE,CAAC;EACrD,CAAC;CACH;CAEA,OAAO;EACL,mBAAmB,YAAY,SAAS,IAAI,YAAY,KAAK,MAAM,IAAI;EACvE;CACF;AACF;;;;;;AAOA,SAAS,iBAAiB,SAAyC;CACjE,IAAI,OAAO,YAAY,UACrB,OAAO;CAGT,OAAO,QACJ,QAAQ,SAAiD,KAAK,SAAS,MAAM,CAAC,CAC9E,KAAK,SAAS,KAAK,IAAI,CAAC,CACxB,KAAK,EAAE;AACZ;;;;;;;AAQA,SAAS,iBAAiB,KAAsC;CAC9D,MAAM,SAAS,cAAuB,KAAK,MAAS;CAEpD,IAAI,WAAW,QAAQ,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,MAAM,GACxE,OAAO;CAGT,OAAO,EAAE,QAAQ,IAAI;AACvB;;;;;;;;;;AAWA,SAAS,aAAa,MAAyB;CAC7C,IAAI,KAAK,SAAS,QAChB,OAAO,EAAE,MAAM,KAAK,KAAK;CAG3B,IAAI,SAAS,KAAK,QAChB,MAAM,IAAI,oBACR,6FACF;CAGF,OAAO,EACL,YAAY;EAAE,UAAU,KAAK,OAAO;EAAW,MAAM,KAAK,OAAO;CAAO,EAC1E;AACF"}
1
+ {"version":3,"file":"to-google-contents.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai-google/src/utils/to-google-contents.ts"],"sourcesContent":["import { InvalidRequestError, safeJsonParse, type ContentPart, type Message } from \"@warlock.js/ai\";\nimport type { Content, Part } from \"@google/genai\";\n\n/**\n * Result of splitting a vendor-neutral `Message[]` for Gemini's\n * `generateContent`: the system prompt is hoisted to a separate\n * `systemInstruction` string (Gemini has no `\"system\"` role — content\n * roles must be `\"user\"` or `\"model\"`), and the remaining turns map to\n * `Content[]`.\n */\nexport type GoogleContents = {\n systemInstruction: string | undefined;\n contents: Content[];\n};\n\n/**\n * Convert vendor-neutral `Message[]` into Gemini's request shape.\n *\n * Gemini specifics this function absorbs:\n *\n * 1. **No `system` role.** System messages concatenate into the\n * separate `systemInstruction` config field.\n * 2. **Role names differ.** Neutral `assistant` → Gemini `\"model\"`;\n * `user` stays `\"user\"`.\n * 3. **Tool results are `user` turns.** A neutral `tool` message\n * becomes a `\"user\"` content with a single `functionResponse` part.\n * 4. **Tool calls are `functionCall` parts.** An assistant message\n * with `toolCalls` becomes a `\"model\"` content: an optional leading\n * `text` part followed by one `functionCall` part per call.\n *\n * @example\n * const { systemInstruction, contents } = toGoogleContents([\n * { role: \"system\", content: \"Be concise.\" },\n * { role: \"user\", content: \"Hi\" },\n * ]);\n */\nexport function toGoogleContents(messages: Message[]): GoogleContents {\n const systemParts: string[] = [];\n const contents: Content[] = [];\n\n for (const message of messages) {\n if (message.role === \"system\") {\n systemParts.push(stringifyContent(message.content));\n\n continue;\n }\n\n if (message.role === \"tool\") {\n contents.push({\n role: \"user\",\n parts: [\n {\n // Gemini matches a `functionResponse` to its `functionCall`\n // by `name` (the Developer API has no call ids). `name` is\n // the neutral `toolCallId`, which `GoogleModel` set to the\n // function name. The wire `id` is intentionally omitted —\n // an empty/synthetic id is rejected as an invalid argument.\n functionResponse: {\n name: message.toolCallId ?? \"\",\n response: toResponseObject(stringifyContent(message.content)),\n },\n },\n ],\n });\n\n continue;\n }\n\n if (message.role === \"assistant\" && message.toolCalls && message.toolCalls.length > 0) {\n const parts: Part[] = [];\n const text = stringifyContent(message.content);\n\n if (text) {\n parts.push({ text });\n }\n\n for (const toolCall of message.toolCalls) {\n // Replay the opaque `thoughtSignature` Gemini attached to this\n // function call on the original turn. Thinking models reject\n // the follow-up request with a 400 if the signature is missing\n // from the echoed `functionCall` part. Captured by\n // `GoogleModel.partToToolCall` into `providerMetadata`.\n const thoughtSignature = toolCall.providerMetadata?.thoughtSignature;\n\n parts.push({\n ...(typeof thoughtSignature === \"string\" ? { thoughtSignature } : {}),\n // `id` omitted deliberately — Gemini Developer API function\n // calls have no ids; echoing an empty/synthetic one is\n // rejected as an invalid argument. Matched by `name`.\n functionCall: {\n name: toolCall.name,\n args: (toolCall.input ?? {}) as Record<string, unknown>,\n },\n });\n }\n\n contents.push({ role: \"model\", parts });\n\n continue;\n }\n\n if (message.role === \"user\" && Array.isArray(message.content)) {\n contents.push({ role: \"user\", parts: message.content.map(toGooglePart) });\n\n continue;\n }\n\n contents.push({\n role: message.role === \"assistant\" ? \"model\" : \"user\",\n parts: [{ text: stringifyContent(message.content) }],\n });\n }\n\n return {\n systemInstruction: systemParts.length > 0 ? systemParts.join(\"\\n\\n\") : undefined,\n contents,\n };\n}\n\n/**\n * Multipart content is only meaningful on user messages — for any\n * other role collapse a `ContentPart[]` to concatenated text. Plain\n * strings pass through unchanged.\n */\nfunction stringifyContent(content: string | ContentPart[]): string {\n if (typeof content === \"string\") {\n return content;\n }\n\n return content\n .filter((part): part is { type: \"text\"; text: string } => part.type === \"text\")\n .map((part) => part.text)\n .join(\"\");\n}\n\n/**\n * Gemini's `functionResponse.response` must be a JSON object. Tool\n * results arrive as a string (usually stringified JSON) — parse it\n * when it is a JSON object, otherwise wrap the raw string under a\n * `result` key so the model always receives a well-formed object.\n */\nfunction toResponseObject(raw: string): Record<string, unknown> {\n const parsed = safeJsonParse<unknown>(raw, undefined);\n\n if (parsed !== null && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n return parsed as Record<string, unknown>;\n }\n\n return { result: raw };\n}\n\n/**\n * Map a resolved `ContentPart` to a Gemini `Part`. All binary\n * modalities — **image, PDF, and audio** — go to a single\n * `inlineData: { mimeType, data }` block; Gemini's multimodal input is\n * media-agnostic and keys off the IANA `mimeType` (`image/png`,\n * `application/pdf`, `audio/mpeg`, …), so one mapping covers every part\n * type the model's capabilities admit. PDF and audio reach this point\n * only when the model declares the matching capability (`google.model`\n * infers `pdf` / `audio` from the multimodal Gemini families); the\n * agent's modality gate throws upfront otherwise, so capability and\n * behavior stay in lockstep.\n *\n * Gemini's `generateContent` does not fetch arbitrary remote URLs (only\n * Files API / GCS URIs via `fileData`), so a neutral `{ url }` source\n * surfaces a typed `InvalidRequestError` upfront — for any modality —\n * rather than a downstream Gemini fault. The agent resolves attachments\n * before this point, so nothing is read or fetched here.\n */\nfunction toGooglePart(part: ContentPart): Part {\n if (part.type === \"text\") {\n return { text: part.text };\n }\n\n if (\"url\" in part.source) {\n throw new InvalidRequestError(\n `Gemini generateContent cannot fetch remote-URL ${part.type} media; supply base64 bytes instead.`,\n );\n }\n\n return {\n inlineData: { mimeType: part.source.mediaType, data: part.source.base64 },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAoCA,SAAgB,iBAAiB,UAAqC;CACpE,MAAM,cAAwB,CAAC;CAC/B,MAAM,WAAsB,CAAC;CAE7B,KAAK,MAAM,WAAW,UAAU;EAC9B,IAAI,QAAQ,SAAS,UAAU;GAC7B,YAAY,KAAK,iBAAiB,QAAQ,OAAO,CAAC;GAElD;EACF;EAEA,IAAI,QAAQ,SAAS,QAAQ;GAC3B,SAAS,KAAK;IACZ,MAAM;IACN,OAAO,CACL,EAME,kBAAkB;KAChB,MAAM,QAAQ,cAAc;KAC5B,UAAU,iBAAiB,iBAAiB,QAAQ,OAAO,CAAC;IAC9D,EACF,CACF;GACF,CAAC;GAED;EACF;EAEA,IAAI,QAAQ,SAAS,eAAe,QAAQ,aAAa,QAAQ,UAAU,SAAS,GAAG;GACrF,MAAM,QAAgB,CAAC;GACvB,MAAM,OAAO,iBAAiB,QAAQ,OAAO;GAE7C,IAAI,MACF,MAAM,KAAK,EAAE,KAAK,CAAC;GAGrB,KAAK,MAAM,YAAY,QAAQ,WAAW;IAMxC,MAAM,mBAAmB,SAAS,kBAAkB;IAEpD,MAAM,KAAK;KACT,GAAI,OAAO,qBAAqB,WAAW,EAAE,iBAAiB,IAAI,CAAC;KAInE,cAAc;MACZ,MAAM,SAAS;MACf,MAAO,SAAS,SAAS,CAAC;KAC5B;IACF,CAAC;GACH;GAEA,SAAS,KAAK;IAAE,MAAM;IAAS;GAAM,CAAC;GAEtC;EACF;EAEA,IAAI,QAAQ,SAAS,UAAU,MAAM,QAAQ,QAAQ,OAAO,GAAG;GAC7D,SAAS,KAAK;IAAE,MAAM;IAAQ,OAAO,QAAQ,QAAQ,IAAI,YAAY;GAAE,CAAC;GAExE;EACF;EAEA,SAAS,KAAK;GACZ,MAAM,QAAQ,SAAS,cAAc,UAAU;GAC/C,OAAO,CAAC,EAAE,MAAM,iBAAiB,QAAQ,OAAO,EAAE,CAAC;EACrD,CAAC;CACH;CAEA,OAAO;EACL,mBAAmB,YAAY,SAAS,IAAI,YAAY,KAAK,MAAM,IAAI;EACvE;CACF;AACF;;;;;;AAOA,SAAS,iBAAiB,SAAyC;CACjE,IAAI,OAAO,YAAY,UACrB,OAAO;CAGT,OAAO,QACJ,QAAQ,SAAiD,KAAK,SAAS,MAAM,CAAC,CAC9E,KAAK,SAAS,KAAK,IAAI,CAAC,CACxB,KAAK,EAAE;AACZ;;;;;;;AAQA,SAAS,iBAAiB,KAAsC;CAC9D,MAAM,SAAS,cAAuB,KAAK,MAAS;CAEpD,IAAI,WAAW,QAAQ,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,MAAM,GACxE,OAAO;CAGT,OAAO,EAAE,QAAQ,IAAI;AACvB;;;;;;;;;;;;;;;;;;;AAoBA,SAAS,aAAa,MAAyB;CAC7C,IAAI,KAAK,SAAS,QAChB,OAAO,EAAE,MAAM,KAAK,KAAK;CAG3B,IAAI,SAAS,KAAK,QAChB,MAAM,IAAI,oBACR,kDAAkD,KAAK,KAAK,qCAC9D;CAGF,OAAO,EACL,YAAY;EAAE,UAAU,KAAK,OAAO;EAAW,MAAM,KAAK,OAAO;CAAO,EAC1E;AACF"}
package/llms-full.txt CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  ---
10
10
  name: setup-google
11
- description: 'Wire @warlock.js/ai-google — new GoogleSDK({apiKey} | {vertexai, project, location}) for Gemini API + Vertex AI. generateContent / embedContent + thoughtSignature round-trip for thinking models, batched embeddings. .model({name, vision?, reasoning?, audio?, pdf?}) with cost-truth capabilities, extended thinking via options.reasoning → thinkingConfig.thinkingBudget, usage reasoningTokens (thoughtsTokenCount) / cachedTokens (cachedContentTokenCount). Triggers: `GoogleSDK`, `google.model`, `google.embedder`, `thoughtSignature`, `responseJsonSchema`, `vertexai`, `reasoning`, `thinkingConfig`, `thinkingBudget`, `thoughtsTokenCount`, `reasoningTokens`, `cachedTokens`, `promptCaching`, `cacheControl`; "use gemini", "wire Vertex AI", "gemini embeddings", "gemini thinking tool calls", "gemini 2.5 thinking budget", "gemini cached content cost"; import `import { GoogleSDK } from "@warlock.js/ai-google"`. Skip: agent loop `@warlock.js/ai/run-ai-agent/SKILL.md`; provider picking `@warlock.js/ai/pick-ai-provider/SKILL.md`; embedder usage `@warlock.js/ai/embed-text/SKILL.md`; siblings `@warlock.js/ai-openai`, `@warlock.js/ai-anthropic`, `@warlock.js/ai-bedrock`, `@warlock.js/ai-ollama`; raw `@google/genai`, `@google-cloud/vertexai`, Vercel `@ai-sdk/google`.'
11
+ description: 'Wire @warlock.js/ai-google — new GoogleSDK({apiKey} | {vertexai, project, location}) for Gemini API + Vertex AI. generateContent / embedContent + thoughtSignature round-trip for thinking models, batched embeddings. .model({name, vision?, reasoning?, audio?, pdf?}) with cost-truth capabilities (PDF + audio input map to Gemini inlineData), .image({name, pricing?}) for Imagen (imagen-*) image generation via ai.image, extended thinking via options.reasoning → thinkingConfig.thinkingBudget, usage reasoningTokens (thoughtsTokenCount) / cachedTokens (cachedContentTokenCount). Triggers: `GoogleSDK`, `google.model`, `google.embedder`, `google.image`, `imagen`, `generateImages`, `ai.image`, `inlineData`, `pdf input`, `audio input`, `thoughtSignature`, `responseJsonSchema`, `vertexai`, `reasoning`, `thinkingConfig`, `thinkingBudget`, `thoughtsTokenCount`, `reasoningTokens`, `cachedTokens`, `promptCaching`, `cacheControl`; "use gemini", "wire Vertex AI", "gemini embeddings", "gemini thinking tool calls", "gemini 2.5 thinking budget", "gemini cached content cost", "generate images with imagen", "send a pdf / audio to gemini"; import `import { GoogleSDK } from "@warlock.js/ai-google"`. Skip: the ai.image verb surface — `@warlock.js/ai/generate-images/SKILL.md`; agent loop `@warlock.js/ai/run-ai-agent/SKILL.md`; provider picking `@warlock.js/ai/pick-ai-provider/SKILL.md`; embedder usage `@warlock.js/ai/embed-text/SKILL.md`; siblings `@warlock.js/ai-openai`, `@warlock.js/ai-anthropic`, `@warlock.js/ai-bedrock`, `@warlock.js/ai-ollama`; raw `@google/genai`, `@google-cloud/vertexai`, Vercel `@ai-sdk/google`.'
12
12
  ---
13
13
 
14
14
  # `@warlock.js/ai-google`
@@ -75,11 +75,37 @@ Gemini content roles must be `"user"` or `"model"` — there is no `"system"` ro
75
75
 
76
76
  Object-root `responseSchema` + `structuredOutput`-capable → `config.responseMimeType = "application/json"` + `config.responseJsonSchema = <schema>` (Gemini takes a **raw JSON Schema** directly, not its typed `Schema`).
77
77
 
78
- ## Multipart messages (vision)
78
+ ## Multipart messages (image / PDF / audio input)
79
+
80
+ Gemini's multimodal input is **media-agnostic** — every binary modality maps to one `inlineData` block keyed by IANA mime type:
79
81
 
80
82
  - `{ type: "text" }` → `{ text }`
81
83
  - `{ type: "image", source: { base64, mediaType } }` → `{ inlineData: { mimeType, data } }`
82
- - `{ type: "image", source: { url } }` → **throws `InvalidRequestError`**. `generateContent` does not fetch arbitrary remote URLs (only Files API / GCS URIs). Resolve images to base64 first.
84
+ - `{ type: "pdf", source: { base64, mediaType: "application/pdf" } }` → `{ inlineData: { mimeType: "application/pdf", data } }` (gated on `capabilities.pdf`)
85
+ - `{ type: "audio", source: { base64, mediaType: "audio/mpeg" } }` → `{ inlineData: { mimeType: "audio/mpeg", data } }` (gated on `capabilities.audio`)
86
+ - any `{ source: { url } }` → **throws `InvalidRequestError`** naming the modality. `generateContent` does not fetch arbitrary remote URLs (only Files API / GCS URIs). Resolve to base64 first.
87
+
88
+ PDF and audio reach the wire only when the model declares the matching capability (inferred for the multimodal Gemini families above) — so capability ≡ behavior.
89
+
90
+ ## Image generation (Imagen)
91
+
92
+ `google.image({ name })` returns an `ImageModelContract` (Imagen, via `ai.models.generateImages`) for the `ai.image()` verb:
93
+
94
+ ```ts
95
+ const imagen = google.image({ name: "imagen-4.0-generate-001", pricing: { perImage: 0.04 } });
96
+
97
+ const { data, error } = await ai.image({
98
+ model: imagen,
99
+ prompt: "a watercolor lighthouse at dawn",
100
+ aspectRatio: "3:4", // Imagen ratio (vs OpenAI's WxH `size`)
101
+ negativePrompt: "text, watermark",
102
+ options: { imageSize: "2K", personGeneration: "allow_adult" }, // Imagen passthroughs
103
+ });
104
+ ```
105
+
106
+ - Imagen is **per-image-metered** (price with `{ perImage }`) and returns base64 bytes — no hosted URL, no token usage.
107
+ - When every candidate is safety-filtered, the run surfaces a typed `ContentFilterError` on `result.error`.
108
+ - A non-Imagen model id (`google.image({ name: "gemini-2.5-flash" })`) throws `InvalidRequestError` at construction — Gemini's *native* image output (`gemini-*-image` via `generateContent`) is a separate surface, not routed here. The verb surface lives in [`@warlock.js/ai/generate-images/SKILL.md`](@warlock.js/ai/generate-images/SKILL.md).
83
109
 
84
110
  ## Streaming
85
111
 
package/llms.txt CHANGED
@@ -6,4 +6,4 @@
6
6
 
7
7
  ## Skills
8
8
 
9
- - [setup-google](@warlock.js/ai-google/setup-google/SKILL.md): Wire @warlock.js/ai-google — new GoogleSDK({apiKey} | {vertexai, project, location}) for Gemini API + Vertex AI. generateContent / embedContent + thoughtSignature round-trip for thinking models, batched embeddings. .model({name, vision?, reasoning?, audio?, pdf?}) with cost-truth capabilities, extended thinking via options.reasoning → thinkingConfig.thinkingBudget, usage reasoningTokens (thoughtsTokenCount) / cachedTokens (cachedContentTokenCount). Triggers: `GoogleSDK`, `google.model`, `google.embedder`, `thoughtSignature`, `responseJsonSchema`, `vertexai`, `reasoning`, `thinkingConfig`, `thinkingBudget`, `thoughtsTokenCount`, `reasoningTokens`, `cachedTokens`, `promptCaching`, `cacheControl`; "use gemini", "wire Vertex AI", "gemini embeddings", "gemini thinking tool calls", "gemini 2.5 thinking budget", "gemini cached content cost"; import `import { GoogleSDK } from "@warlock.js/ai-google"`. Skip: agent loop `@warlock.js/ai/run-ai-agent/SKILL.md`; provider picking `@warlock.js/ai/pick-ai-provider/SKILL.md`; embedder usage `@warlock.js/ai/embed-text/SKILL.md`; siblings `@warlock.js/ai-openai`, `@warlock.js/ai-anthropic`, `@warlock.js/ai-bedrock`, `@warlock.js/ai-ollama`; raw `@google/genai`, `@google-cloud/vertexai`, Vercel `@ai-sdk/google`.
9
+ - [setup-google](@warlock.js/ai-google/setup-google/SKILL.md): Wire @warlock.js/ai-google — new GoogleSDK({apiKey} | {vertexai, project, location}) for Gemini API + Vertex AI. generateContent / embedContent + thoughtSignature round-trip for thinking models, batched embeddings. .model({name, vision?, reasoning?, audio?, pdf?}) with cost-truth capabilities (PDF + audio input map to Gemini inlineData), .image({name, pricing?}) for Imagen (imagen-*) image generation via ai.image, extended thinking via options.reasoning → thinkingConfig.thinkingBudget, usage reasoningTokens (thoughtsTokenCount) / cachedTokens (cachedContentTokenCount). Triggers: `GoogleSDK`, `google.model`, `google.embedder`, `google.image`, `imagen`, `generateImages`, `ai.image`, `inlineData`, `pdf input`, `audio input`, `thoughtSignature`, `responseJsonSchema`, `vertexai`, `reasoning`, `thinkingConfig`, `thinkingBudget`, `thoughtsTokenCount`, `reasoningTokens`, `cachedTokens`, `promptCaching`, `cacheControl`; "use gemini", "wire Vertex AI", "gemini embeddings", "gemini thinking tool calls", "gemini 2.5 thinking budget", "gemini cached content cost", "generate images with imagen", "send a pdf / audio to gemini"; import `import { GoogleSDK } from "@warlock.js/ai-google"`. Skip: the ai.image verb surface — `@warlock.js/ai/generate-images/SKILL.md`; agent loop `@warlock.js/ai/run-ai-agent/SKILL.md`; provider picking `@warlock.js/ai/pick-ai-provider/SKILL.md`; embedder usage `@warlock.js/ai/embed-text/SKILL.md`; siblings `@warlock.js/ai-openai`, `@warlock.js/ai-anthropic`, `@warlock.js/ai-bedrock`, `@warlock.js/ai-ollama`; raw `@google/genai`, `@google-cloud/vertexai`, Vercel `@ai-sdk/google`.
package/package.json CHANGED
@@ -15,12 +15,12 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "@google/genai": "^2.4.0",
18
- "@warlock.js/logger": "4.5.0"
18
+ "@warlock.js/logger": "4.6.0"
19
19
  },
20
20
  "peerDependencies": {
21
- "@warlock.js/ai": "4.5.0"
21
+ "@warlock.js/ai": "4.6.0"
22
22
  },
23
- "version": "4.5.0",
23
+ "version": "4.6.0",
24
24
  "main": "./cjs/index.cjs",
25
25
  "module": "./esm/index.mjs",
26
26
  "types": "./esm/index.d.mts",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: setup-google
3
- description: 'Wire @warlock.js/ai-google — new GoogleSDK({apiKey} | {vertexai, project, location}) for Gemini API + Vertex AI. generateContent / embedContent + thoughtSignature round-trip for thinking models, batched embeddings. .model({name, vision?, reasoning?, audio?, pdf?}) with cost-truth capabilities, extended thinking via options.reasoning → thinkingConfig.thinkingBudget, usage reasoningTokens (thoughtsTokenCount) / cachedTokens (cachedContentTokenCount). Triggers: `GoogleSDK`, `google.model`, `google.embedder`, `thoughtSignature`, `responseJsonSchema`, `vertexai`, `reasoning`, `thinkingConfig`, `thinkingBudget`, `thoughtsTokenCount`, `reasoningTokens`, `cachedTokens`, `promptCaching`, `cacheControl`; "use gemini", "wire Vertex AI", "gemini embeddings", "gemini thinking tool calls", "gemini 2.5 thinking budget", "gemini cached content cost"; import `import { GoogleSDK } from "@warlock.js/ai-google"`. Skip: agent loop `@warlock.js/ai/run-ai-agent/SKILL.md`; provider picking `@warlock.js/ai/pick-ai-provider/SKILL.md`; embedder usage `@warlock.js/ai/embed-text/SKILL.md`; siblings `@warlock.js/ai-openai`, `@warlock.js/ai-anthropic`, `@warlock.js/ai-bedrock`, `@warlock.js/ai-ollama`; raw `@google/genai`, `@google-cloud/vertexai`, Vercel `@ai-sdk/google`.'
3
+ description: 'Wire @warlock.js/ai-google — new GoogleSDK({apiKey} | {vertexai, project, location}) for Gemini API + Vertex AI. generateContent / embedContent + thoughtSignature round-trip for thinking models, batched embeddings. .model({name, vision?, reasoning?, audio?, pdf?}) with cost-truth capabilities (PDF + audio input map to Gemini inlineData), .image({name, pricing?}) for Imagen (imagen-*) image generation via ai.image, extended thinking via options.reasoning → thinkingConfig.thinkingBudget, usage reasoningTokens (thoughtsTokenCount) / cachedTokens (cachedContentTokenCount). Triggers: `GoogleSDK`, `google.model`, `google.embedder`, `google.image`, `imagen`, `generateImages`, `ai.image`, `inlineData`, `pdf input`, `audio input`, `thoughtSignature`, `responseJsonSchema`, `vertexai`, `reasoning`, `thinkingConfig`, `thinkingBudget`, `thoughtsTokenCount`, `reasoningTokens`, `cachedTokens`, `promptCaching`, `cacheControl`; "use gemini", "wire Vertex AI", "gemini embeddings", "gemini thinking tool calls", "gemini 2.5 thinking budget", "gemini cached content cost", "generate images with imagen", "send a pdf / audio to gemini"; import `import { GoogleSDK } from "@warlock.js/ai-google"`. Skip: the ai.image verb surface — `@warlock.js/ai/generate-images/SKILL.md`; agent loop `@warlock.js/ai/run-ai-agent/SKILL.md`; provider picking `@warlock.js/ai/pick-ai-provider/SKILL.md`; embedder usage `@warlock.js/ai/embed-text/SKILL.md`; siblings `@warlock.js/ai-openai`, `@warlock.js/ai-anthropic`, `@warlock.js/ai-bedrock`, `@warlock.js/ai-ollama`; raw `@google/genai`, `@google-cloud/vertexai`, Vercel `@ai-sdk/google`.'
4
4
  ---
5
5
 
6
6
  # `@warlock.js/ai-google`
@@ -67,11 +67,37 @@ Gemini content roles must be `"user"` or `"model"` — there is no `"system"` ro
67
67
 
68
68
  Object-root `responseSchema` + `structuredOutput`-capable → `config.responseMimeType = "application/json"` + `config.responseJsonSchema = <schema>` (Gemini takes a **raw JSON Schema** directly, not its typed `Schema`).
69
69
 
70
- ## Multipart messages (vision)
70
+ ## Multipart messages (image / PDF / audio input)
71
+
72
+ Gemini's multimodal input is **media-agnostic** — every binary modality maps to one `inlineData` block keyed by IANA mime type:
71
73
 
72
74
  - `{ type: "text" }` → `{ text }`
73
75
  - `{ type: "image", source: { base64, mediaType } }` → `{ inlineData: { mimeType, data } }`
74
- - `{ type: "image", source: { url } }` → **throws `InvalidRequestError`**. `generateContent` does not fetch arbitrary remote URLs (only Files API / GCS URIs). Resolve images to base64 first.
76
+ - `{ type: "pdf", source: { base64, mediaType: "application/pdf" } }` → `{ inlineData: { mimeType: "application/pdf", data } }` (gated on `capabilities.pdf`)
77
+ - `{ type: "audio", source: { base64, mediaType: "audio/mpeg" } }` → `{ inlineData: { mimeType: "audio/mpeg", data } }` (gated on `capabilities.audio`)
78
+ - any `{ source: { url } }` → **throws `InvalidRequestError`** naming the modality. `generateContent` does not fetch arbitrary remote URLs (only Files API / GCS URIs). Resolve to base64 first.
79
+
80
+ PDF and audio reach the wire only when the model declares the matching capability (inferred for the multimodal Gemini families above) — so capability ≡ behavior.
81
+
82
+ ## Image generation (Imagen)
83
+
84
+ `google.image({ name })` returns an `ImageModelContract` (Imagen, via `ai.models.generateImages`) for the `ai.image()` verb:
85
+
86
+ ```ts
87
+ const imagen = google.image({ name: "imagen-4.0-generate-001", pricing: { perImage: 0.04 } });
88
+
89
+ const { data, error } = await ai.image({
90
+ model: imagen,
91
+ prompt: "a watercolor lighthouse at dawn",
92
+ aspectRatio: "3:4", // Imagen ratio (vs OpenAI's WxH `size`)
93
+ negativePrompt: "text, watermark",
94
+ options: { imageSize: "2K", personGeneration: "allow_adult" }, // Imagen passthroughs
95
+ });
96
+ ```
97
+
98
+ - Imagen is **per-image-metered** (price with `{ perImage }`) and returns base64 bytes — no hosted URL, no token usage.
99
+ - When every candidate is safety-filtered, the run surfaces a typed `ContentFilterError` on `result.error`.
100
+ - A non-Imagen model id (`google.image({ name: "gemini-2.5-flash" })`) throws `InvalidRequestError` at construction — Gemini's *native* image output (`gemini-*-image` via `generateContent`) is a separate surface, not routed here. The verb surface lives in [`@warlock.js/ai/generate-images/SKILL.md`](@warlock.js/ai/generate-images/SKILL.md).
75
101
 
76
102
  ## Streaming
77
103