@warlock.js/ai-google 5.30.0 → 5.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import { ApiError } from "@google/genai";
2
- import { AIError, ContextLengthExceededError, InvalidRequestError, ProviderAuthError, ProviderError, ProviderRateLimitError, ProviderTimeoutError } from "@warlock.js/ai";
2
+ import { AIError, ContextLengthExceededError, InvalidRequestError, ProviderAuthError, ProviderError, ProviderOverloadedError, ProviderRateLimitError, ProviderTimeoutError } from "@warlock.js/ai";
3
3
 
4
4
  //#region ../../packages/ai-google/src/utils/wrap-google-error.ts
5
5
  /**
@@ -28,35 +28,48 @@ function wrapGoogleError(thrown) {
28
28
  const shape = toShape(thrown);
29
29
  const context = buildContext(shape);
30
30
  const message = shape.message ?? (thrown instanceof Error ? thrown.message : String(thrown));
31
+ const billed = shape.status === 400 || shape.status === 500 ? false : void 0;
31
32
  if (isTimeout(shape)) return new ProviderTimeoutError(message, {
32
33
  cause: thrown,
33
- context
34
+ context,
35
+ billed
34
36
  });
35
37
  if (shape.status === 401 || shape.status === 403 || /permission_denied|api key not valid|unauthenticated/i.test(message)) return new ProviderAuthError(message, {
36
38
  cause: thrown,
37
- context
39
+ context,
40
+ billed
38
41
  });
39
42
  if (shape.status === 429 || /resource_exhausted|quota/i.test(message)) return new ProviderRateLimitError(message, {
40
43
  cause: thrown,
41
- context
44
+ context,
45
+ billed
42
46
  });
43
47
  if (shape.status === 400) {
44
48
  if (/token count|context length|exceeds the maximum|input is too long/i.test(message)) return new ContextLengthExceededError(message, {
45
49
  cause: thrown,
46
- context
50
+ context,
51
+ billed
47
52
  });
48
53
  return new InvalidRequestError(message, {
49
54
  cause: thrown,
50
- context
55
+ context,
56
+ billed
51
57
  });
52
58
  }
53
59
  if (shape.status === 404 || isClientStatus(shape.status)) return new InvalidRequestError(message, {
54
60
  cause: thrown,
55
- context
61
+ context,
62
+ billed
63
+ });
64
+ if (shape.status === 503 || /\bUNAVAILABLE\b/.test(message) || /overloaded/i.test(message)) return new ProviderOverloadedError(message, {
65
+ cause: thrown,
66
+ context,
67
+ billed
56
68
  });
57
69
  return new ProviderError(message, {
58
70
  cause: thrown,
59
- context
71
+ context,
72
+ billed
60
73
  });
61
74
  }
62
75
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"wrap-google-error.mjs","names":[],"sources":["../../../../../../../../packages/ai-google/src/utils/wrap-google-error.ts"],"sourcesContent":["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"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA6CA,SAAgB,gBAAgB,QAA0B;CACxD,IAAI,kBAAkB,SACpB,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,IAAI,qBAAqB,SAAS;EAAE,OAAO;EAAQ;CAAQ,CAAC;CAGrE,IACE,MAAM,WAAW,OACjB,MAAM,WAAW,OACjB,uDAAuD,KAAK,OAAO,GAEnE,OAAO,IAAI,kBAAkB,SAAS;EAAE,OAAO;EAAQ;CAAQ,CAAC;CAGlE,IAAI,MAAM,WAAW,OAAO,4BAA4B,KAAK,OAAO,GAClE,OAAO,IAAI,uBAAuB,SAAS;EAAE,OAAO;EAAQ;CAAQ,CAAC;CAGvE,IAAI,MAAM,WAAW,KAAK;EACxB,IAAI,oEAAoE,KAAK,OAAO,GAClF,OAAO,IAAI,2BAA2B,SAAS;GAAE,OAAO;GAAQ;EAAQ,CAAC;EAG3E,OAAO,IAAI,oBAAoB,SAAS;GAAE,OAAO;GAAQ;EAAQ,CAAC;CACpE;CAEA,IAAI,MAAM,WAAW,OAAO,eAAe,MAAM,MAAM,GACrD,OAAO,IAAI,oBAAoB,SAAS;EAAE,OAAO;EAAQ;CAAQ,CAAC;CAGpE,OAAO,IAAI,cAAc,SAAS;EAAE,OAAO;EAAQ;CAAQ,CAAC;AAC9D;;;;;;AAOA,SAAS,QAAQ,QAAmC;CAClD,IAAI,kBAAkB,UACpB,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"}
1
+ {"version":3,"file":"wrap-google-error.mjs","names":[],"sources":["../../../../../../../../packages/ai-google/src/utils/wrap-google-error.ts"],"sourcesContent":["import {\n AIError,\n ContextLengthExceededError,\n InvalidRequestError,\n ProviderAuthError,\n ProviderError,\n ProviderOverloadedError,\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 // \"If your request fails with a 400 or 500 error, you won't be charged\n // for the tokens used.\" — https://ai.google.dev/gemini-api/docs/billing\n // (fetched 2026-10-17). Every other failure keeps the default.\n const billed = shape.status === 400 || shape.status === 500 ? false : undefined;\n\n if (isTimeout(shape)) {\n return new ProviderTimeoutError(message, { cause: thrown, context, billed });\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, billed });\n }\n\n if (shape.status === 429 || /resource_exhausted|quota/i.test(message)) {\n return new ProviderRateLimitError(message, { cause: thrown, context, billed });\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, billed });\n }\n\n return new InvalidRequestError(message, { cause: thrown, context, billed });\n }\n\n if (shape.status === 404 || isClientStatus(shape.status)) {\n return new InvalidRequestError(message, { cause: thrown, context, billed });\n }\n\n // 503 UNAVAILABLE — \"temporarily overloaded or down\"\n // (https://ai.google.dev/gemini-api/docs/api-errors).\n if (shape.status === 503 || /\\bUNAVAILABLE\\b/.test(message) || /overloaded/i.test(message)) {\n return new ProviderOverloadedError(message, { cause: thrown, context, billed });\n }\n\n return new ProviderError(message, { cause: thrown, context, billed });\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"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA8CA,SAAgB,gBAAgB,QAA0B;CACxD,IAAI,kBAAkB,SACpB,OAAO;CAGT,MAAM,QAAQ,QAAQ,MAAM;CAC5B,MAAM,UAAU,aAAa,KAAK;CAClC,MAAM,UAAU,MAAM,YAAY,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM;CAI1F,MAAM,SAAS,MAAM,WAAW,OAAO,MAAM,WAAW,MAAM,QAAQ;CAEtE,IAAI,UAAU,KAAK,GACjB,OAAO,IAAI,qBAAqB,SAAS;EAAE,OAAO;EAAQ;EAAS;CAAO,CAAC;CAG7E,IACE,MAAM,WAAW,OACjB,MAAM,WAAW,OACjB,uDAAuD,KAAK,OAAO,GAEnE,OAAO,IAAI,kBAAkB,SAAS;EAAE,OAAO;EAAQ;EAAS;CAAO,CAAC;CAG1E,IAAI,MAAM,WAAW,OAAO,4BAA4B,KAAK,OAAO,GAClE,OAAO,IAAI,uBAAuB,SAAS;EAAE,OAAO;EAAQ;EAAS;CAAO,CAAC;CAG/E,IAAI,MAAM,WAAW,KAAK;EACxB,IAAI,oEAAoE,KAAK,OAAO,GAClF,OAAO,IAAI,2BAA2B,SAAS;GAAE,OAAO;GAAQ;GAAS;EAAO,CAAC;EAGnF,OAAO,IAAI,oBAAoB,SAAS;GAAE,OAAO;GAAQ;GAAS;EAAO,CAAC;CAC5E;CAEA,IAAI,MAAM,WAAW,OAAO,eAAe,MAAM,MAAM,GACrD,OAAO,IAAI,oBAAoB,SAAS;EAAE,OAAO;EAAQ;EAAS;CAAO,CAAC;CAK5E,IAAI,MAAM,WAAW,OAAO,kBAAkB,KAAK,OAAO,KAAK,cAAc,KAAK,OAAO,GACvF,OAAO,IAAI,wBAAwB,SAAS;EAAE,OAAO;EAAQ;EAAS;CAAO,CAAC;CAGhF,OAAO,IAAI,cAAc,SAAS;EAAE,OAAO;EAAQ;EAAS;CAAO,CAAC;AACtE;;;;;;AAOA,SAAS,QAAQ,QAAmC;CAClD,IAAI,kBAAkB,UACpB,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"}
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 (PDF + audio input map to Gemini inlineData), .image({name, pricing?}) for image generation via ai.image — the id picks the transport: gemini-* → generateContent + responseModalities IMAGE (usage passed through; generateImages is deprecated by Google), everything else → generateImages (Imagen, per-image, zero usage) — extended thinking via options.reasoning → thinkingConfig.thinkingBudget, usage reasoningTokens (thoughtsTokenCount) / cachedTokens (cachedContentTokenCount). Triggers: `GoogleSDK`, `google.model`, `google.embedder`, `google.image`, `imagen`, `generateImages`, `gemini image`, `nano banana`, `responseModalities`, `imageConfig`, `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", "generate an image with gemini", "gemini image model 404 not supported for predict", "send a pdf / audio to gemini"; import `import { GoogleSDK } from "@warlock.js/ai-google"`. Skip: the ai.image verb surface — the `generate-images` topic of the `warlock-js-ai` skill; agent loop the `run-ai-agent` topic of the `warlock-js-ai` skill; provider picking the `pick-ai-provider` topic of the `warlock-js-ai` skill; embedder usage the `embed-text` topic of the `warlock-js-ai` skill; 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 image generation via ai.image — the id picks the transport: gemini-* → generateContent + responseModalities IMAGE (usage passed through; generateImages is deprecated by Google), everything else → generateImages (Imagen, per-image, zero usage) — extended thinking via options.reasoning → thinkingConfig.thinkingBudget, usage reasoningTokens (thoughtsTokenCount) / cachedTokens (cachedContentTokenCount). Triggers: `providerOptions`, `GoogleCallOptions`, `countTokens`, `GoogleSDK`, `google.model`, `google.embedder`, `google.image`, `imagen`, `generateImages`, `gemini image`, `nano banana`, `responseModalities`, `imageConfig`, `ai.image`, `inlineData`, `pdf input`, `audio input`, `thoughtSignature`, `responseJsonSchema`, `ai.providerTool`, `googleSearch`, `includeThoughts`, `streamChunks`, `vertexai`, `reasoning`, `thinkingConfig`, `thinkingBudget`, `thoughtsTokenCount`, `reasoningTokens`, `cachedTokens`, `inputIncludesCache`, `GOOGLE_MODEL_PRICING`, `googlePricingFor`, `ProviderOverloadedError`, `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", "generate an image with gemini", "gemini image model 404 not supported for predict", "send a pdf / audio to gemini"; import `import { GoogleSDK } from "@warlock.js/ai-google"`. Skip: the ai.image verb surface — the `generate-images` topic of the `warlock-js-ai` skill; agent loop the `run-ai-agent` topic of the `warlock-js-ai` skill; provider picking the `pick-ai-provider` topic of the `warlock-js-ai` skill; embedder usage the `embed-text` topic of the `warlock-js-ai` skill; 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`
@@ -37,21 +37,21 @@ const vertex = new GoogleSDK({
37
37
  ## Producing a model
38
38
 
39
39
  ```ts
40
- google.model({ name: "gemini-2.5-flash" })
41
- google.model({ name: "gemini-2.5-pro", temperature: 0.2 })
42
- google.model({ name: "gemini-1.5-flash", maxTokens: 2048 })
40
+ google.model({ name: "gemini-2.5-flash" });
41
+ google.model({ name: "gemini-2.5-pro", temperature: 0.2 });
42
+ google.model({ name: "gemini-1.5-flash", maxTokens: 2048 });
43
43
  ```
44
44
 
45
45
  ## Capabilities — what's auto-set
46
46
 
47
- | Flag | Default |
48
- | --- | --- |
49
- | `structuredOutput` | `true` (via `responseMimeType` + `responseJsonSchema`) |
50
- | `vision` | Inferred from model id substring. `true` when the id contains `gemini-1.5`, `gemini-2` (covers 2.x / 2.5), `gemini-exp`, `gemini-flash`, or `gemini-pro-vision`; `false` for bare `gemini-pro` / `gemini-1.0-pro` and any unknown id. Case-insensitive, tolerates date/preview/vendor-prefix suffixes (`models/gemini-1.5-flash-001`). |
51
- | `reasoning` | `true` — every Gemini 2.5 model thinks; older families harmlessly ignore an empty budget. Set `reasoning: false` to stop forwarding `thinkingConfig` to a model that rejects it. |
52
- | `promptCaching` | Always `true` — Gemini reports cache-read hits (`cachedContentTokenCount`) via implicit caching and accepts explicit context caching. |
53
- | `audio` | Mirrors the `vision` inference (the multimodal Gemini families accept audio parts). Override with `audio: true \| false`. |
54
- | `pdf` | Mirrors the `vision` inference (the multimodal Gemini families accept PDF / document parts). Override with `pdf: true \| false`. |
47
+ | Flag | Default |
48
+ | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
49
+ | `structuredOutput` | `true` (via `responseMimeType` + `responseJsonSchema`) |
50
+ | `vision` | Inferred from model id substring. `true` when the id contains `gemini-1.5`, `gemini-2` (covers 2.x / 2.5), `gemini-exp`, `gemini-flash`, or `gemini-pro-vision`; `false` for bare `gemini-pro` / `gemini-1.0-pro` and any unknown id. Case-insensitive, tolerates date/preview/vendor-prefix suffixes (`models/gemini-1.5-flash-001`). |
51
+ | `reasoning` | `true` — every Gemini 2.5 model thinks; older families harmlessly ignore an empty budget. Set `reasoning: false` to stop forwarding `thinkingConfig` to a model that rejects it. |
52
+ | `promptCaching` | Always `true` — Gemini reports cache-read hits (`cachedContentTokenCount`) via implicit caching and accepts explicit context caching. |
53
+ | `audio` | Mirrors the `vision` inference (the multimodal Gemini families accept audio parts). Override with `audio: true \| false`. |
54
+ | `pdf` | Mirrors the `vision` inference (the multimodal Gemini families accept PDF / document parts). Override with `pdf: true \| false`. |
55
55
 
56
56
  Explicit config always wins (`google.model({ name, reasoning: false, audio: false, pdf: true })`).
57
57
 
@@ -64,6 +64,15 @@ Gemini content roles must be `"user"` or `"model"` — there is no `"system"` ro
64
64
  - Neutral `assistant` → Gemini `"model"`; `user` stays `"user"`.
65
65
  - Assistant tool calls → `"model"` content: optional leading `{ text }` + one `{ functionCall: { name, args } }` part per call (`id` deliberately omitted — see below).
66
66
  - Tool results (`role: "tool"`) → a `"user"` content with one `{ functionResponse: { name, response } }` part. `response` must be a JSON object, so a stringified-JSON tool result is parsed back to an object; a non-object result is wrapped as `{ result: <raw> }`.
67
+ - Adjacent contents with the same role merge into one, parts kept in order: parallel tool results become ONE `"user"` content with several `functionResponse` parts in call order (as `@google/genai`'s automatic function calling sends them), and an agent compaction summary merges with a same-role neighbour (user-framed summary + input; assistant summary + the next tool-call turn, text part first, each `functionCall` keeping its `thoughtSignature`). Roles therefore alternate `user`/`model`; `countTokens()` uses the same builder.
68
+
69
+ Neutral history and the `contents` the adapter sends for it:
70
+
71
+ ```text
72
+ user "look both up" → { role: "user", parts: [{ text }] }
73
+ assistant toolCalls [a, b] → { role: "model", parts: [{ functionCall: a }, { functionCall: b }] }
74
+ tool a, tool b → { role: "user", parts: [{ functionResponse: a }, { functionResponse: b }] }
75
+ ```
67
76
 
68
77
  **Gemini matches a result to its call by name, not id** — the Developer API assigns no function-call ids. The adapter sets neutral `id` = tool name so `toolCallId` is non-empty. The wire `id` is omitted from both `functionCall` and `functionResponse` (echoing an empty/synthetic id is rejected by Gemini). **Limitation:** two parallel calls to the same tool in one turn share an id — inherent to the Developer API's id-less design.
69
78
 
@@ -91,12 +100,12 @@ PDF and audio reach the wire only when the model declares the matching capabilit
91
100
 
92
101
  `google.image({ name })` returns an `ImageModelContract` for the `ai.image()` verb. **The model id picks the transport:**
93
102
 
94
- | Model id | Transport | Usage it can report |
95
- | --- | --- | --- |
103
+ | Model id | Transport | Usage it can report |
104
+ | ----------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
96
105
  | `gemini-*` (e.g. `gemini-3.1-flash-lite-image`) | `ai.models.generateContent` with `responseModalities` including `"IMAGE"` | whatever `usageMetadata` Google attaches — price with `{ input, output }` |
97
- | anything else (`imagen-*`, unknown ids) | `ai.models.generateImages` (Imagen) | none, always zero — price with `{ perImage }` |
106
+ | anything else (`imagen-*`, unknown ids) | `ai.models.generateImages` (Imagen) | none, always zero — price with `{ perImage }` |
98
107
 
99
- A `gemini-` id sent to `generateImages` comes back `404 … is not supported for predict` (observed verbatim from Google) — `generateImages` routes to `{model}:predict`. `generateContent` is the SDK's own named replacement: its runtime deprecation notice for `generateImages` says *"Please use the generateContent method with image models instead"*. Routing on the id is what lets both families go through one factory. A leading `models/` resource prefix is tolerated.
108
+ A `gemini-` id sent to `generateImages` comes back `404 … is not supported for predict` (observed verbatim from Google) — `generateImages` routes to `{model}:predict`. `generateContent` is the SDK's own named replacement: its runtime deprecation notice for `generateImages` says _"Please use the generateContent method with image models instead"_. Routing on the id is what lets both families go through one factory. A leading `models/` resource prefix is tolerated.
100
109
 
101
110
  This is **routing, not validation** — no id is refused locally. An id matching neither family takes the `generateImages` route (the pre-existing one) and fails at Google as a typed provider error, not with a local throw.
102
111
 
@@ -107,13 +116,13 @@ This is **routing, not validation** — no id is refused locally. An id matching
107
116
  ```ts
108
117
  const gemini = google.image({
109
118
  name: "gemini-3.1-flash-lite-image",
110
- pricing: { input: 0.3, output: 30 }, // token-metered
119
+ pricing: { input: 0.3, output: 30 }, // token-metered
111
120
  });
112
121
 
113
122
  const { data, error, usage } = await ai.image({
114
123
  model: gemini,
115
124
  prompt: "a red bicycle on a white background",
116
- aspectRatio: "16:9", // → config.imageConfig.aspectRatio
125
+ aspectRatio: "16:9", // → config.imageConfig.aspectRatio
117
126
  options: { imageSize: "2K", personGeneration: "ALLOW_ADULT" }, // → imageConfig passthroughs
118
127
  });
119
128
  ```
@@ -135,7 +144,7 @@ const imagen = google.image({ name: "imagen-4.0-generate-001", pricing: { perIma
135
144
  const { data, error } = await ai.image({
136
145
  model: imagen,
137
146
  prompt: "a watercolor lighthouse at dawn",
138
- aspectRatio: "3:4", // Imagen ratio (vs OpenAI's WxH `size`)
147
+ aspectRatio: "3:4", // Imagen ratio (vs OpenAI's WxH `size`)
139
148
  negativePrompt: "text, watermark",
140
149
  options: { imageSize: "2K", personGeneration: "allow_adult" }, // Imagen passthroughs
141
150
  });
@@ -143,7 +152,7 @@ const { data, error } = await ai.image({
143
152
 
144
153
  - Imagen is **per-image-metered** (price with `{ perImage }`) and returns base64 bytes — no hosted URL, and `usage` is an honest `{ input: 0, output: 0, total: 0 }`.
145
154
  - When every candidate is safety-filtered (`raiFilteredReason`), the run surfaces a typed `ContentFilterError` on `result.error`.
146
- - ⏳ **This path is on a clock.** `@google/genai` warns at runtime: *"The generateImages method is deprecated and will be removed in the next major release (not before Jan. 1 2027). Please use the generateContent method with image models instead."* This package depends on `@google/genai` `^2.17.1`, which does emit that warning. Nothing breaks today, but new work should prefer a `gemini-*` id.
155
+ - ⏳ **This path is on a clock.** `@google/genai` warns at runtime: _"The generateImages method is deprecated and will be removed in the next major release (not before Jan. 1 2027). Please use the generateContent method with image models instead."_ This package depends on `@google/genai` `^2.17.1`, which does emit that warning. Nothing breaks today, but new work should prefer a `gemini-*` id.
147
156
 
148
157
  ### Not implemented: the `interactions` API
149
158
 
@@ -155,6 +164,17 @@ The verb surface lives in the `generate-images` topic of the `warlock-js-ai` ski
155
164
 
156
165
  `model.stream()` drains `generateContentStream`. Each chunk's `.text` → `{ type: "delta" }`; `functionCall` parts (read from `candidates[0].content.parts`, not the `.functionCalls` getter — the getter drops `thoughtSignature`) are emitted as `{ type: "tool-call" }` **fully formed** (Gemini streams a complete call with parsed `args`, not partial JSON), carrying `providerMetadata.thoughtSignature` when present. Terminal `{ type: "done", finishReason, usage }` — usage from the final chunk's `usageMetadata`.
157
166
 
167
+ Opt-in chunks (`options.streamChunks`, set by `agent.stream()`): `{ type: "reasoning", delta }` per thought part (`thought: true`; sent only with `providerOptions.google.body.thinkingConfig.includeThoughts: true`) and `{ type: "usage", usage }` per chunk carrying `usageMetadata`. No `tool-args` (calls arrive whole).
168
+
169
+ **Provider-defined tools:** `ai.providerTool({ provider: "google", definition: { googleSearch: {} } })` (also `{ codeExecution: {} }`, `{ urlContext: {} }`) is appended verbatim as its own `Tool` after the function declarations; other providers' tools are skipped with a warning.
170
+
171
+ ```ts
172
+ ai.agent({
173
+ model: google.model({ name: "gemini-2.5-flash" }),
174
+ tools: [ai.providerTool({ provider: "google", definition: { googleSearch: {} } })],
175
+ });
176
+ ```
177
+
158
178
  ## Finish-reason mapping
159
179
 
160
180
  `STOP` → `stop` · `MAX_TOKENS` → `length` · `SAFETY` / `RECITATION` / `BLOCKLIST` / `PROHIBITED_CONTENT` / `MALFORMED_FUNCTION_CALL` / unknown / null → `error`. `tool_calls` is derived from function-call presence.
@@ -164,7 +184,7 @@ The verb surface lives in the `generate-images` topic of the `warlock-js-ai` ski
164
184
  ```ts
165
185
  const embedder = google.embedder({ name: "gemini-embedding-001" });
166
186
  const { vector } = await embedder.embed("Hello world");
167
- const { vectors } = await embedder.embedMany(["a", "b"]); // single batched call
187
+ const { vectors } = await embedder.embedMany(["a", "b"]); // single batched call
168
188
  ```
169
189
 
170
190
  `embedContent` accepts an array natively, so `embedMany` is **one request** (unlike Bedrock/Titan). Pass `dimensions` to forward Gemini's `outputDimensionality` truncation hint.
@@ -180,14 +200,31 @@ Wrapped into the typed `@warlock.js/ai` `AIError` hierarchy. Gemini has no machi
180
200
  - 400 with token/context phrasing → `ContextLengthExceededError`, else `InvalidRequestError`
181
201
  - 404 / any other 4xx → `InvalidRequestError`
182
202
  - 504 / `DEADLINE_EXCEEDED` / `ETIMEDOUT` / `ECONNABORTED` / `AbortError` → `ProviderTimeoutError`
203
+ - 503 / `UNAVAILABLE` / "overloaded" → `ProviderOverloadedError` (`code: "PROVIDER_OVERLOADED"`, `retryable: true`) — Gemini documents 503 as "temporarily overloaded or down" (https://ai.google.dev/gemini-api/docs/api-errors)
183
204
  - anything else (5xx and status-less) → `ProviderError`
184
205
 
206
+ Every wrapped error carries `status`, `retryable` and `billed`. Google states "If your request fails with a 400 or 500 error, you won't be charged for the tokens used" (https://ai.google.dev/gemini-api/docs/billing, checked 2026-10-17), so a 400 or 500 is `billed: false`; 401 / 403 are `false` too (never reached a billable account); everything else is `"unknown"`.
207
+
208
+ ```ts
209
+ import { ai, ProviderError, ProviderOverloadedError } from "@warlock.js/ai";
210
+ import { GoogleSDK } from "@warlock.js/ai-google";
211
+
212
+ const google = new GoogleSDK({ apiKey: process.env.GEMINI_API_KEY ?? "" });
213
+ const result = await ai.agent({ model: google.model({ name: "gemini-2.5-flash" }) }).execute("hi");
214
+
215
+ if (result.error instanceof ProviderOverloadedError) {
216
+ console.log(result.error.status, result.error.retryable); // 503, true — retry or fall back
217
+ } else if (result.error instanceof ProviderError) {
218
+ console.log(result.error.billed); // false for a 400 / 500
219
+ }
220
+ ```
221
+
185
222
  Timeout is checked first, so an aborted/`AbortError` request always classifies as `ProviderTimeoutError` regardless of status. `AIError` instances pass through unwrapped (no double-wrap).
186
223
 
187
224
  ## Token counting
188
225
 
189
226
  ```ts
190
- await google.count("some text") // approximate heuristic, offline
227
+ await google.count("some text"); // approximate heuristic, offline
191
228
  ```
192
229
 
193
230
  `count(text, model?)` accepts an optional model arg for signature parity but **ignores it** — the estimate is `approximateTokenCount` (≈ `ceil(chars / 4)`) from `@warlock.js/ai`, never a `countTokens` network call.
@@ -208,25 +245,70 @@ Gemini's `thinkingBudget` semantics (for direct `maxTokens` use): `0` disables t
208
245
 
209
246
  ## Token usage
210
247
 
211
- `complete()` / `stream()` map Gemini's `usageMetadata` into the neutral `Usage` (`{ input, output, total }`): `promptTokenCount` → `input`, `candidatesTokenCount` → `output`, `totalTokenCount` → `total` (falling back to `input + output` when absent). `cachedContentTokenCount`, when > 0, is surfaced as `usage.cachedTokens`; `thoughtsTokenCount` (a reasoning model's thinking-phase tokens), when > 0, as `usage.reasoningTokens`. Both are omitted when zero/absent. Embeddings have no usage (see above).
248
+ `complete()` / `stream()` map Gemini's `usageMetadata` into the neutral `Usage` (`{ input, output, total }`): `promptTokenCount` → `input`, `candidatesTokenCount` → `output`, `totalTokenCount` → `total` (falling back to `input + output` when absent). `cachedContentTokenCount`, when > 0, is surfaced as `usage.cachedTokens`; `thoughtsTokenCount` (a reasoning model's thinking-phase tokens), when > 0, as `usage.reasoningTokens`. Both are omitted when zero/absent. `promptTokenCount` "includes the number of tokens in the cached content" (https://ai.google.dev/api/generate-content#UsageMetadata), so the usage is marked `inputIncludesCache: true` and cost bills cached tokens out of `input` once. When the model is priced, `usage.cost` is attached on `complete()` and the streaming `done` chunk. Embeddings have no usage (see above).
212
249
 
213
250
  ## Pricing
214
251
 
215
- Optional USD pricing flows in two ways, resolved per `model()` call:
252
+ Resolution per `model()` call: per-model `pricing` > SDK-level registry entry > built-in `GOOGLE_MODEL_PRICING` > `undefined` (no cost computed). The resolved `ModelPricing` is attached to the produced model, and each response's `usage.cost` is computed from it.
216
253
 
217
254
  ```ts
218
- // SDK-level registry, keyed by model name:
255
+ import { totalCost } from "@warlock.js/ai";
256
+ import { GOOGLE_MODEL_PRICING, GoogleSDK, googlePricingFor } from "@warlock.js/ai-google";
257
+
219
258
  const google = new GoogleSDK({
220
- apiKey,
221
- pricing: { "gemini-2.5-flash": { input: 0.3, output: 2.5 } },
259
+ apiKey: process.env.GEMINI_API_KEY ?? "",
260
+ pricing: { "gemini-2.5-flash": { input: 0.3, output: 2.5, cachedInput: 0.03 } }, // text-input rate
222
261
  });
223
262
 
224
- google.model({ name: "gemini-2.5-flash" }); // → inherits the registry entry
225
- google.model({ name: "gemini-2.5-flash", pricing: {...} }); // → per-model pricing wins
226
- google.model({ name: "gemini-2.5-pro" }); // → no entry → pricing undefined (no cost computed)
263
+ google.model({ name: "gemini-2.5-flash" }); // → the registry entry
264
+ google.model({ name: "gemini-3.5-flash-lite" }).pricing; // → built-in { input: 0.3, output: 2.5, cachedInput: 0.03 }
265
+ google.model({ name: "gemini-2.5-pro" }).pricing; // → undefined (prompt-length tiers; pass pricing yourself)
266
+ googlePricingFor("gemini-3.5-flash-lite"); // the same built-in entry
267
+ Object.keys(GOOGLE_MODEL_PRICING); // the priced ids
268
+
269
+ const { usage } = await google
270
+ .model({ name: "gemini-3.5-flash-lite" })
271
+ .complete([{ role: "user", content: "hi" }]);
272
+ console.log(totalCost(usage.cost));
273
+ ```
274
+
275
+ `GOOGLE_MODEL_PRICING` comes from https://ai.google.dev/gemini-api/docs/pricing (paid tier, Standard), fetched 2026-10-17. Only models with a single flat token price are listed. Deliberately unpriced: the Pro models (priced by prompt length, split at 200k prompt tokens), models whose audio input bills higher than text (`Usage` does not split input by modality — price them with the text rate yourself only if you send no audio), and models with a scheduled price change. The hourly context-cache storage charge is not a per-token rate and is never included.
276
+
277
+ ## Provider config options and input-token counts
278
+
279
+ Native body fields merge after neutral options. When both values are plain
280
+ objects, their immediate keys merge and caller-provided keys win; untouched
281
+ neutral fields remain. Arrays, scalars and `null` replace the field. This merge
282
+ is one level deep: a nested object explicitly supplied by the caller replaces
283
+ that nested value.
284
+
285
+ `providerOptions.google.body` adds native `GenerateContentConfig` fields,
286
+ merged after neutral generation options for both completion and streaming:
287
+
288
+ ```ts
289
+ const options = { providerOptions: { google: { body: { topP: 0.8, topK: 4 } } } };
290
+ await model.complete(messages, options);
227
291
  ```
228
292
 
229
- Resolution order: per-model `pricing` > SDK-level registry entry > `undefined`. The resolved `ModelPricing` is attached to the produced model so the agent runtime can compute cost from usage.
293
+ The key stays `google` for custom provider labels. Other provider keys are
294
+ ignored. `GoogleCallOptions` is exported. `systemInstruction`, `tools`,
295
+ `abortSignal` and `httpOptions` are protected and throw `InvalidRequestError`;
296
+ use neutral messages/tools/signal and SDK transport configuration for them.
297
+
298
+ `model.countTokens(messages, options?)` calls the native counting endpoint with
299
+ the same converted contents. In Vertex mode it also counts the same system
300
+ instruction and tools as generation and forwards cancellation. In Developer API
301
+ mode, the installed Google SDK cannot count system instructions or tools through
302
+ its typed count endpoint: requests containing them fail explicitly before a
303
+ network call. Cached-content references also fail explicitly, since this SDK
304
+ count endpoint cannot reproduce them. The adapter never silently counts only a
305
+ subset of a prompt. `sdk.count(text)` remains a separate offline estimate.
306
+
307
+ ```ts
308
+ if (model.countTokens) {
309
+ const inputTokens = await model.countTokens(messages, options);
310
+ }
311
+ ```
230
312
 
231
313
  ## When NOT to use this skill
232
314
 
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 (PDF + audio input map to Gemini inlineData), .image({name, pricing?}) for image generation via ai.image — the id picks the transport: gemini-* → generateContent + responseModalities IMAGE (usage passed through; generateImages is deprecated by Google), everything else → generateImages (Imagen, per-image, zero usage) — extended thinking via options.reasoning → thinkingConfig.thinkingBudget, usage reasoningTokens (thoughtsTokenCount) / cachedTokens (cachedContentTokenCount). Triggers: `GoogleSDK`, `google.model`, `google.embedder`, `google.image`, `imagen`, `generateImages`, `gemini image`, `nano banana`, `responseModalities`, `imageConfig`, `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", "generate an image with gemini", "gemini image model 404 not supported for predict", "send a pdf / audio to gemini"; import `import { GoogleSDK } from "@warlock.js/ai-google"`. Skip: the ai.image verb surface — the `generate-images` topic of the `warlock-js-ai` skill; agent loop the `run-ai-agent` topic of the `warlock-js-ai` skill; provider picking the `pick-ai-provider` topic of the `warlock-js-ai` skill; embedder usage the `embed-text` topic of the `warlock-js-ai` skill; 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 image generation via ai.image — the id picks the transport: gemini-* → generateContent + responseModalities IMAGE (usage passed through; generateImages is deprecated by Google), everything else → generateImages (Imagen, per-image, zero usage) — extended thinking via options.reasoning → thinkingConfig.thinkingBudget, usage reasoningTokens (thoughtsTokenCount) / cachedTokens (cachedContentTokenCount). Triggers: `providerOptions`, `GoogleCallOptions`, `countTokens`, `GoogleSDK`, `google.model`, `google.embedder`, `google.image`, `imagen`, `generateImages`, `gemini image`, `nano banana`, `responseModalities`, `imageConfig`, `ai.image`, `inlineData`, `pdf input`, `audio input`, `thoughtSignature`, `responseJsonSchema`, `ai.providerTool`, `googleSearch`, `includeThoughts`, `streamChunks`, `vertexai`, `reasoning`, `thinkingConfig`, `thinkingBudget`, `thoughtsTokenCount`, `reasoningTokens`, `cachedTokens`, `inputIncludesCache`, `GOOGLE_MODEL_PRICING`, `googlePricingFor`, `ProviderOverloadedError`, `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", "generate an image with gemini", "gemini image model 404 not supported for predict", "send a pdf / audio to gemini"; import `import { GoogleSDK } from "@warlock.js/ai-google"`. Skip: the ai.image verb surface — the `generate-images` topic of the `warlock-js-ai` skill; agent loop the `run-ai-agent` topic of the `warlock-js-ai` skill; provider picking the `pick-ai-provider` topic of the `warlock-js-ai` skill; embedder usage the `embed-text` topic of the `warlock-js-ai` skill; 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,13 +15,13 @@
15
15
  "directory": "packages/ai-google"
16
16
  },
17
17
  "peerDependencies": {
18
- "@warlock.js/ai": "5.30.0"
18
+ "@warlock.js/ai": "5.31.0"
19
19
  },
20
20
  "dependencies": {
21
21
  "@google/genai": "^2.17.1",
22
- "@warlock.js/logger": "5.30.0"
22
+ "@warlock.js/logger": "5.31.0"
23
23
  },
24
- "version": "5.30.0",
24
+ "version": "5.31.0",
25
25
  "main": "./cjs/index.cjs",
26
26
  "module": "./esm/index.mjs",
27
27
  "types": "./esm/index.d.mts",