@warlock.js/ai-google 5.30.0 → 5.32.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +34 -1
  2. package/cjs/index.cjs +273 -21
  3. package/cjs/index.cjs.map +1 -1
  4. package/esm/config.type.d.mts +4 -6
  5. package/esm/config.type.d.mts.map +1 -1
  6. package/esm/embedder.mjs.map +1 -1
  7. package/esm/gemini-image.d.mts +1 -3
  8. package/esm/gemini-image.d.mts.map +1 -1
  9. package/esm/gemini-image.mjs +1 -1
  10. package/esm/gemini-image.mjs.map +1 -1
  11. package/esm/image.d.mts +1 -3
  12. package/esm/image.d.mts.map +1 -1
  13. package/esm/image.mjs.map +1 -1
  14. package/esm/index.d.mts +3 -1
  15. package/esm/index.mjs +2 -1
  16. package/esm/known-pricing.d.mts +37 -0
  17. package/esm/known-pricing.d.mts.map +1 -0
  18. package/esm/known-pricing.mjs +42 -0
  19. package/esm/known-pricing.mjs.map +1 -0
  20. package/esm/model.mjs +110 -9
  21. package/esm/model.mjs.map +1 -1
  22. package/esm/provider-options.d.mts +8 -0
  23. package/esm/provider-options.d.mts.map +1 -0
  24. package/esm/provider-options.mjs +36 -0
  25. package/esm/provider-options.mjs.map +1 -0
  26. package/esm/sdk.d.mts +4 -5
  27. package/esm/sdk.d.mts.map +1 -1
  28. package/esm/sdk.mjs +5 -3
  29. package/esm/sdk.mjs.map +1 -1
  30. package/esm/utils/apply-google-usage.mjs +5 -0
  31. package/esm/utils/apply-google-usage.mjs.map +1 -1
  32. package/esm/utils/grounding.mjs +30 -0
  33. package/esm/utils/grounding.mjs.map +1 -0
  34. package/esm/utils/index.mjs +1 -1
  35. package/esm/utils/to-google-contents.mjs +33 -1
  36. package/esm/utils/to-google-contents.mjs.map +1 -1
  37. package/esm/utils/wrap-google-error.mjs +21 -8
  38. package/esm/utils/wrap-google-error.mjs.map +1 -1
  39. package/llms-full.txt +162 -31
  40. package/llms.txt +1 -1
  41. package/package.json +3 -3
  42. package/skills/setup-google/SKILL.md +162 -31
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,81 @@ 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));
227
273
  ```
228
274
 
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.
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);
291
+ ```
292
+
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?)` counts the converted contents, system
299
+ instructions and tools on both the Gemini Developer API (API key) and Vertex AI.
300
+ Developer API counting sends the full `generateContentRequest`, including tool
301
+ configuration and generation settings, as documented in the
302
+ [Gemini token-counting reference](https://ai.google.dev/api/tokens). It uses the
303
+ existing SDK transport, preserving API-key auth, custom fetch, base URL/proxy
304
+ settings, headers, retries and cancellation. Vertex retains its typed SDK path.
305
+
306
+ Cached-content references still throw `InvalidRequestError` with
307
+ `reason: "count-unsupported"`. Use `countInputTokens` for budget preflight: it
308
+ returns an estimate for this explicit limitation; authentication, network,
309
+ rate-limit and ordinary validation errors still propagate. `sdk.count(text)`
310
+ remains a separate offline estimate.
311
+
312
+ ```ts
313
+ import { countInputTokens } from "@warlock.js/ai";
314
+
315
+ const messages = [
316
+ { role: "system" as const, content: "Be concise." },
317
+ { role: "user" as const, content: "Hello" },
318
+ ];
319
+ const tools = [{ name: "lookup", description: "Look up data", execute: async () => "ok" }];
320
+ const { tokens, source } = await countInputTokens(model, messages, { tools });
321
+ // source is "native" for ordinary Developer API and Vertex requests, including tools.
322
+ ```
230
323
 
231
324
  ## When NOT to use this skill
232
325
 
@@ -239,4 +332,42 @@ Resolution order: per-model `pricing` > SDK-level registry entry > `undefined`.
239
332
  - The `pick-ai-provider` topic of the `warlock-js-ai` skill
240
333
  - The `embed-text` topic of the `warlock-js-ai` skill
241
334
 
335
+ ## Search grounding metadata and fees
336
+
337
+ `response.providerMetadata.google.groundingMetadata` retains the raw grounding
338
+ metadata for each indexed candidate: queries, chunks, supports and search entry
339
+ point. Streaming `done` carries the same metadata. Agent reports retain it per
340
+ trip. Hosted search never produces a local function call.
341
+
342
+ `usage.serverToolUse.webSearchRequests` counts unique nonempty
343
+ `groundingMetadata.webSearchQueries`; `googleWebSearchQueries` records the raw
344
+ array length, including duplicates and empty entries. Stream snapshots replace
345
+ prior metadata per candidate rather than accumulating repeated snapshots.
346
+ The [official schema](https://ai.google.dev/api/generate-content#GroundingMetadata)
347
+ defines the queries array.
348
+
349
+ [Google's pricing guide](https://ai.google.dev/gemini-api/docs/google-search#pricing)
350
+ (verified 2026-10-18) bills Gemini 3 and newer per unique nonempty query: $14 per
351
+ 1,000 queries after the shared monthly free allowance. You can explicitly use
352
+ the exported `GOOGLE_SERVER_TOOL_PRICING` as `pricing.serverTools` for that
353
+ model family. Gemini 2.5 and older bill per grounded prompt; their query count
354
+ is not a billable-prompt count, so leave this fee unpriced or account for it
355
+ outside this per-query table. Free allowances are not subtracted locally.
356
+ No web-fetch/code-execution count is inferred from result parts. See
357
+ [cost tracking](https://warlock.js.org/v/latest/ai/recipes/cost-tracking/#hosted-tool-request-fees).
358
+
359
+ ```ts
360
+ import { GOOGLE_SERVER_TOOL_PRICING } from "@warlock.js/ai-google";
361
+ const model = google.model({
362
+ name: "gemini-3.5-flash-lite",
363
+
364
+ pricing: { ...{ input: 0.3, output: 2.5 }, serverTools: GOOGLE_SERVER_TOOL_PRICING },
365
+ });
366
+ const response = await model.complete(messages, {
367
+ providerTools: [{ provider: "google", definition: { googleSearch: {} } }],
368
+ });
369
+ console.log(response.usage.serverToolUse?.webSearchRequests);
370
+ console.log(response.providerMetadata);
371
+ ```
372
+
242
373
 
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.32.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.32.0"
23
23
  },
24
- "version": "5.30.0",
24
+ "version": "5.32.0",
25
25
  "main": "./cjs/index.cjs",
26
26
  "module": "./esm/index.mjs",
27
27
  "types": "./esm/index.d.mts",