@warlock.js/ai-google 5.29.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +26 -1
  2. package/cjs/index.cjs +206 -31
  3. package/cjs/index.cjs.map +1 -1
  4. package/esm/config.type.d.mts +1 -1
  5. package/esm/config.type.d.mts.map +1 -1
  6. package/esm/embedder.mjs +1 -1
  7. package/esm/embedder.mjs.map +1 -1
  8. package/esm/gemini-image.d.mts +1 -1
  9. package/esm/gemini-image.d.mts.map +1 -1
  10. package/esm/gemini-image.mjs +1 -1
  11. package/esm/gemini-image.mjs.map +1 -1
  12. package/esm/image.d.mts +1 -1
  13. package/esm/image.d.mts.map +1 -1
  14. package/esm/image.mjs +1 -1
  15. package/esm/image.mjs.map +1 -1
  16. package/esm/index.d.mts +3 -1
  17. package/esm/index.mjs +2 -1
  18. package/esm/known-pricing.d.mts +33 -0
  19. package/esm/known-pricing.d.mts.map +1 -0
  20. package/esm/known-pricing.mjs +38 -0
  21. package/esm/known-pricing.mjs.map +1 -0
  22. package/esm/known-vision-models.mjs +1 -1
  23. package/esm/known-vision-models.mjs.map +1 -1
  24. package/esm/model.mjs +66 -10
  25. package/esm/model.mjs.map +1 -1
  26. package/esm/provider-options.d.mts +8 -0
  27. package/esm/provider-options.d.mts.map +1 -0
  28. package/esm/provider-options.mjs +36 -0
  29. package/esm/provider-options.mjs.map +1 -0
  30. package/esm/sdk.d.mts +4 -3
  31. package/esm/sdk.d.mts.map +1 -1
  32. package/esm/sdk.mjs +6 -4
  33. package/esm/sdk.mjs.map +1 -1
  34. package/esm/utils/apply-google-usage.mjs +6 -1
  35. package/esm/utils/apply-google-usage.mjs.map +1 -1
  36. package/esm/utils/map-finish-reason.mjs +1 -1
  37. package/esm/utils/map-finish-reason.mjs.map +1 -1
  38. package/esm/utils/to-google-contents.mjs +34 -2
  39. package/esm/utils/to-google-contents.mjs.map +1 -1
  40. package/esm/utils/to-google-tools.mjs +1 -1
  41. package/esm/utils/to-google-tools.mjs.map +1 -1
  42. package/esm/utils/wrap-google-error.mjs +22 -9
  43. package/esm/utils/wrap-google-error.mjs.map +1 -1
  44. package/llms-full.txt +113 -31
  45. package/llms.txt +1 -1
  46. package/package.json +5 -4
  47. package/skills/setup-google/SKILL.md +113 -31
package/CHANGELOG.md CHANGED
@@ -4,6 +4,30 @@ 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
+ ## 5.31.0 - 2026-10-18
8
+
9
+ ### Fixed
10
+
11
+ - Provider body options merge plain-object fields one level deep, preserving neutral reasoning, generation and structured-output settings unless the caller overrides their keys.
12
+ - Consecutive messages that map to the same Gemini role are sent as one content with their parts in order. The results of parallel tool calls now travel as a single `user` turn of `functionResponse` parts in call order instead of one turn per result, and an agent compaction summary next to a same-role turn merges into it, so requests keep alternating `user` and `model` turns. Each `functionCall` keeps its own `thoughtSignature`, and `countTokens()` counts the same contents.
13
+
14
+ ### Added
15
+
16
+ - Usage is marked `inputIncludesCache: true` (`promptTokenCount` includes cached content), and `complete()` / the streaming `done` chunk attach `usage.cost` when priced.
17
+ - A built-in list price for `gemini-3.5-flash-lite` (`GOOGLE_MODEL_PRICING`, `googlePricingFor`), from the Gemini API pricing page as of 2026-10-17. Models priced by prompt length, by input modality, or with a scheduled price change stay unpriced.
18
+ - 503 `UNAVAILABLE` throws `ProviderOverloadedError`. 400 and 500 failures are marked `billed: false`, as Google's billing docs state.
19
+ - An adapter contract test verifies that aborting `signal` rejects `complete()` and `stream()` on the model and cancels the HTTP request.
20
+ - With `streamChunks`, `stream()` yields `reasoning` for thought parts (sent when `thinkingConfig.includeThoughts` is on) and `usage` for every chunk carrying `usageMetadata`.
21
+ - `ai.providerTool({ provider: "google", definition })` entries (`{ googleSearch: {} }`, `{ codeExecution: {} }`, `{ urlContext: {} }`) are sent verbatim as their own `Tool` after the function declarations; other providers' tools are skipped with a warning.
22
+ - `providerOptions.google.body` forwards native per-call request options for completions and streams while protecting the model, conversation and tool definitions.
23
+ - Models expose native `countTokens()` preflight. Vertex counts system instructions and tools; unsupported Developer API system/tool and cached-content counts fail explicitly.
24
+
25
+ ## 5.30.0 - 2026-10-16
26
+
27
+ ### Changed
28
+
29
+ - Lockstep release; package APIs are unchanged.
30
+
7
31
  ## 5.29.0 - 2026-10-01
8
32
 
9
33
  ### Changed
@@ -75,6 +99,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
75
99
  ### Changed
76
100
 
77
101
  - Lockstep release maintenance and dependency refresh.
102
+
78
103
  ## 5.20.0 - 2026-09-24
79
104
 
80
105
  ### Changed
@@ -148,7 +173,7 @@ changelogs.
148
173
 
149
174
  ### Deprecated
150
175
 
151
- - **Google has deprecated `generateImages`, the transport the `imagen-*` path still uses.** Verbatim from the `@google/genai` runtime warning: *"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. See https://ai.google.dev/gemini-api/docs/deprecations#imagen-models and https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/capabilities/image-generation#generate-images"* (`editImage` carries the same notice.) Nothing breaks today and the Imagen path is unchanged, but it is on a clock: new image work should prefer a `gemini-` id. The warning is emitted by `@google/genai` ≥ 2.17; with the bump below, this package now prints it whenever the `imagen-*` path is used
176
+ - **Google has deprecated `generateImages`, the transport the `imagen-*` path still uses.** Verbatim from the `@google/genai` runtime warning: _"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. See https://ai.google.dev/gemini-api/docs/deprecations#imagen-models and https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/capabilities/image-generation#generate-images"_ (`editImage` carries the same notice.) Nothing breaks today and the Imagen path is unchanged, but it is on a clock: new image work should prefer a `gemini-` id. The warning is emitted by `@google/genai` ≥ 2.17; with the bump below, this package now prints it whenever the `imagen-*` path is used
152
177
 
153
178
  ### Changed
154
179
 
package/cjs/index.cjs CHANGED
@@ -3,7 +3,7 @@ let _google_genai = require("@google/genai");
3
3
  let _warlock_js_ai = require("@warlock.js/ai");
4
4
  let _warlock_js_logger = require("@warlock.js/logger");
5
5
 
6
- //#region ../ai-google/src/utils/apply-google-usage.ts
6
+ //#region ../../packages/ai-google/src/utils/apply-google-usage.ts
7
7
  /**
8
8
  * Fold a Gemini `usageMetadata` block into a running neutral `Usage`
9
9
  * accumulator. Shared by every `generateContent`-backed surface — the
@@ -19,8 +19,13 @@ let _warlock_js_logger = require("@warlock.js/logger");
19
19
  *
20
20
  * `total` falls back to `input + output` when Google omits
21
21
  * `totalTokenCount`.
22
+ *
23
+ * `promptTokenCount` "includes the number of tokens in the cached
24
+ * content" (https://ai.google.dev/api/generate-content#UsageMetadata), so
25
+ * the usage is marked `inputIncludesCache: true`.
22
26
  */
23
27
  function applyGoogleUsage(usage, raw) {
28
+ usage.inputIncludesCache = true;
24
29
  usage.input = raw.promptTokenCount ?? usage.input;
25
30
  usage.output = raw.candidatesTokenCount ?? usage.output;
26
31
  usage.total = raw.totalTokenCount ?? usage.input + usage.output;
@@ -31,7 +36,7 @@ function applyGoogleUsage(usage, raw) {
31
36
  }
32
37
 
33
38
  //#endregion
34
- //#region ../ai-google/src/utils/map-finish-reason.ts
39
+ //#region ../../packages/ai-google/src/utils/map-finish-reason.ts
35
40
  const finishReasonMap = {
36
41
  STOP: "stop",
37
42
  MAX_TOKENS: "length"
@@ -63,7 +68,7 @@ function mapFinishReason(raw) {
63
68
  }
64
69
 
65
70
  //#endregion
66
- //#region ../ai-google/src/utils/to-google-contents.ts
71
+ //#region ../../packages/ai-google/src/utils/to-google-contents.ts
67
72
  /**
68
73
  * Convert vendor-neutral `Message[]` into Gemini's request shape.
69
74
  *
@@ -78,6 +83,12 @@ function mapFinishReason(raw) {
78
83
  * 4. **Tool calls are `functionCall` parts.** An assistant message
79
84
  * with `toolCalls` becomes a `"model"` content: an optional leading
80
85
  * `text` part followed by one `functionCall` part per call.
86
+ * 5. **Adjacent same-role turns merge.** Consecutive contents with the
87
+ * same role become one content with their parts in order, so the
88
+ * results of parallel tool calls travel as one `user` turn (as the
89
+ * `@google/genai` SDK's own automatic function calling sends them)
90
+ * and a compaction summary next to a same-role turn does not break
91
+ * the user/model alternation.
81
92
  *
82
93
  * @example
83
94
  * const { systemInstruction, contents } = toGoogleContents([
@@ -137,10 +148,36 @@ function toGoogleContents(messages) {
137
148
  }
138
149
  return {
139
150
  systemInstruction: systemParts.length > 0 ? systemParts.join("\n\n") : void 0,
140
- contents
151
+ contents: mergeAdjacentContents(contents)
141
152
  };
142
153
  }
143
154
  /**
155
+ * Join consecutive contents that share a role into one, keeping part
156
+ * order: parallel `functionResponse` parts stay in their `functionCall`
157
+ * order, and each `functionCall` keeps its own `thoughtSignature`.
158
+ * Returns new content objects; the input contents are not modified.
159
+ *
160
+ * Source for the single-turn shape: `@google/genai` 2.19.0
161
+ * `Models.generateContent` (automatic function calling) sends every
162
+ * function response of one model turn as a single `{ role: "user",
163
+ * parts }` content, in call order.
164
+ */
165
+ function mergeAdjacentContents(contents) {
166
+ const merged = [];
167
+ for (const content of contents) {
168
+ const previous = merged.at(-1);
169
+ if (previous && previous.role === content.role) {
170
+ merged[merged.length - 1] = {
171
+ ...previous,
172
+ parts: [...previous.parts ?? [], ...content.parts ?? []]
173
+ };
174
+ continue;
175
+ }
176
+ merged.push(content);
177
+ }
178
+ return merged;
179
+ }
180
+ /**
144
181
  * Multipart content is only meaningful on user messages — for any
145
182
  * other role collapse a `ContentPart[]` to concatenated text. Plain
146
183
  * strings pass through unchanged.
@@ -188,7 +225,7 @@ function toGooglePart(part) {
188
225
  }
189
226
 
190
227
  //#endregion
191
- //#region ../ai-google/src/utils/to-google-tools.ts
228
+ //#region ../../packages/ai-google/src/utils/to-google-tools.ts
192
229
  /**
193
230
  * Convert vendor-neutral `ToolConfig[]` into Gemini's `tools` array —
194
231
  * a single `Tool` carrying one `functionDeclarations` entry per tool.
@@ -225,7 +262,7 @@ function toJsonSchema(input) {
225
262
  }
226
263
 
227
264
  //#endregion
228
- //#region ../ai-google/src/utils/wrap-google-error.ts
265
+ //#region ../../packages/ai-google/src/utils/wrap-google-error.ts
229
266
  /**
230
267
  * Wrap any thrown value caught inside the Gemini adapter into the
231
268
  * appropriate `@warlock.js/ai` `AIError` subclass.
@@ -252,35 +289,48 @@ function wrapGoogleError(thrown) {
252
289
  const shape = toShape(thrown);
253
290
  const context = buildContext(shape);
254
291
  const message = shape.message ?? (thrown instanceof Error ? thrown.message : String(thrown));
292
+ const billed = shape.status === 400 || shape.status === 500 ? false : void 0;
255
293
  if (isTimeout(shape)) return new _warlock_js_ai.ProviderTimeoutError(message, {
256
294
  cause: thrown,
257
- context
295
+ context,
296
+ billed
258
297
  });
259
298
  if (shape.status === 401 || shape.status === 403 || /permission_denied|api key not valid|unauthenticated/i.test(message)) return new _warlock_js_ai.ProviderAuthError(message, {
260
299
  cause: thrown,
261
- context
300
+ context,
301
+ billed
262
302
  });
263
303
  if (shape.status === 429 || /resource_exhausted|quota/i.test(message)) return new _warlock_js_ai.ProviderRateLimitError(message, {
264
304
  cause: thrown,
265
- context
305
+ context,
306
+ billed
266
307
  });
267
308
  if (shape.status === 400) {
268
309
  if (/token count|context length|exceeds the maximum|input is too long/i.test(message)) return new _warlock_js_ai.ContextLengthExceededError(message, {
269
310
  cause: thrown,
270
- context
311
+ context,
312
+ billed
271
313
  });
272
314
  return new _warlock_js_ai.InvalidRequestError(message, {
273
315
  cause: thrown,
274
- context
316
+ context,
317
+ billed
275
318
  });
276
319
  }
277
320
  if (shape.status === 404 || isClientStatus(shape.status)) return new _warlock_js_ai.InvalidRequestError(message, {
278
321
  cause: thrown,
279
- context
322
+ context,
323
+ billed
324
+ });
325
+ if (shape.status === 503 || /\bUNAVAILABLE\b/.test(message) || /overloaded/i.test(message)) return new _warlock_js_ai.ProviderOverloadedError(message, {
326
+ cause: thrown,
327
+ context,
328
+ billed
280
329
  });
281
330
  return new _warlock_js_ai.ProviderError(message, {
282
331
  cause: thrown,
283
- context
332
+ context,
333
+ billed
284
334
  });
285
335
  }
286
336
  /**
@@ -328,7 +378,7 @@ function buildContext(shape) {
328
378
  }
329
379
 
330
380
  //#endregion
331
- //#region ../ai-google/src/embedder.ts
381
+ //#region ../../packages/ai-google/src/embedder.ts
332
382
  const LOG_MODULE$3 = "ai.google";
333
383
  /**
334
384
  * Token usage is not returned by Gemini's `embedContent`, so every
@@ -425,7 +475,7 @@ var GoogleEmbedder = class {
425
475
  };
426
476
 
427
477
  //#endregion
428
- //#region ../ai-google/src/gemini-image.ts
478
+ //#region ../../packages/ai-google/src/gemini-image.ts
429
479
  const LOG_MODULE$2 = "ai.google";
430
480
  /**
431
481
  * Response modalities requested when the caller names none.
@@ -648,7 +698,7 @@ function excerpt(text) {
648
698
  }
649
699
 
650
700
  //#endregion
651
- //#region ../ai-google/src/image.ts
701
+ //#region ../../packages/ai-google/src/image.ts
652
702
  const LOG_MODULE$1 = "ai.google";
653
703
  /** Map a neutral output container hint to an IANA media type. */
654
704
  function mediaTypeFor(format) {
@@ -748,7 +798,75 @@ var GoogleImageModel = class {
748
798
  };
749
799
 
750
800
  //#endregion
751
- //#region ../ai-google/src/known-vision-models.ts
801
+ //#region ../../packages/ai-google/src/known-pricing.ts
802
+ /**
803
+ * Built-in Gemini API list prices (paid tier, Standard), USD per
804
+ * 1,000,000 tokens, keyed by exact model id. `cachedInput` is the
805
+ * context-cache read rate; the hourly cache-storage charge is not a token
806
+ * rate and is never included.
807
+ *
808
+ * Source: https://ai.google.dev/gemini-api/docs/pricing, fetched
809
+ * 2026-10-17. Only models with one flat token price are listed. Left out
810
+ * on purpose — pass `pricing` yourself for them:
811
+ *
812
+ * - Models priced by prompt length (the Pro models, split at 200k
813
+ * prompt tokens).
814
+ * - Models with a higher audio-input rate (most Flash / Flash-Lite
815
+ * models): `Usage` does not split input by modality.
816
+ * - Models with a scheduled price change (gemini-4-argon, the 3.8 / 3.6
817
+ * Flash models).
818
+ */
819
+ const GOOGLE_MODEL_PRICING = { "gemini-3.5-flash-lite": {
820
+ input: .3,
821
+ output: 2.5,
822
+ cachedInput: .03
823
+ } };
824
+ /**
825
+ * Look up the built-in list price for a Gemini model id (exact match),
826
+ * or `undefined` so cost stays unknown rather than wrong.
827
+ *
828
+ * @example
829
+ * googlePricingFor("gemini-3.5-flash-lite"); // { input: 0.3, output: 2.5, cachedInput: 0.03 }
830
+ * googlePricingFor("gemini-2.5-pro"); // undefined — priced by prompt length
831
+ */
832
+ function googlePricingFor(modelName) {
833
+ return GOOGLE_MODEL_PRICING[modelName];
834
+ }
835
+
836
+ //#endregion
837
+ //#region ../../packages/ai-google/src/provider-options.ts
838
+ const RESERVED_FIELDS = new Set([
839
+ "systemInstruction",
840
+ "tools",
841
+ "abortSignal",
842
+ "httpOptions"
843
+ ]);
844
+ function mergeProviderBody(built, options) {
845
+ const body = (options?.providerOptions?.google)?.body ?? {};
846
+ const reserved = Object.keys(body).filter((key) => RESERVED_FIELDS.has(key));
847
+ if (reserved.length > 0) throw new _warlock_js_ai.InvalidRequestError(`providerOptions.google.body cannot set ${reserved.join(", ")}: use the call's messages, tools and model options.`, { context: { fields: reserved } });
848
+ const defaults = built;
849
+ const overrides = Object.fromEntries(Object.entries(body).map(([key, value]) => {
850
+ const previous = defaults[key];
851
+ return [key, isPlainObject(previous) && isPlainObject(value) ? {
852
+ ...previous,
853
+ ...value
854
+ } : value];
855
+ }));
856
+ return {
857
+ ...built,
858
+ ...overrides
859
+ };
860
+ }
861
+ /** Merge only plain objects; arrays, null and provider scalar values replace the field. */
862
+ function isPlainObject(value) {
863
+ if (value === null || typeof value !== "object") return false;
864
+ const prototype = Object.getPrototypeOf(value);
865
+ return prototype === Object.prototype || prototype === null;
866
+ }
867
+
868
+ //#endregion
869
+ //#region ../../packages/ai-google/src/known-vision-models.ts
752
870
  /**
753
871
  * Substrings identifying Gemini model ids whose family accepts image
754
872
  * input (vision).
@@ -785,8 +903,10 @@ function inferVisionCapability(modelId) {
785
903
  }
786
904
 
787
905
  //#endregion
788
- //#region ../ai-google/src/model.ts
906
+ //#region ../../packages/ai-google/src/model.ts
789
907
  const LOG_MODULE = "ai.google";
908
+ /** `provider` value of `ai.providerTool()` entries this adapter sends verbatim. */
909
+ const GOOGLE_WIRE_FORMAT = "google";
790
910
  /**
791
911
  * Bucketed `thinkingBudget` (token caps) for the neutral
792
912
  * `reasoning.effort` levels when the caller gives no explicit
@@ -880,7 +1000,7 @@ var GoogleModel = class {
880
1000
  }
881
1001
  const toolCalls = this.extractToolCalls(response);
882
1002
  const finishReason = toolCalls ? "tool_calls" : mapFinishReason(response.candidates?.[0]?.finishReason);
883
- const usage = this.extractUsage(response);
1003
+ const usage = (0, _warlock_js_ai.attachCost)(this.extractUsage(response), this.pricing);
884
1004
  this.logger.debug(LOG_MODULE, "response", "generateContent call succeeded", {
885
1005
  finishReason,
886
1006
  usage
@@ -892,11 +1012,39 @@ var GoogleModel = class {
892
1012
  toolCalls
893
1013
  };
894
1014
  }
1015
+ /** Count native input tokens with the same converted content, system and tools. */
1016
+ async countTokens(messages, options) {
1017
+ const { systemInstruction, contents } = toGoogleContents(messages);
1018
+ const config = this.buildConfig(systemInstruction, options);
1019
+ if (!this.ai.vertexai && (config.systemInstruction !== void 0 || config.tools !== void 0)) throw new _warlock_js_ai.InvalidRequestError("The Google SDK countTokens endpoint in Developer API mode cannot count system instructions or tools. Use Vertex mode for this preflight, or SDK.count() for an offline estimate.");
1020
+ if (config.cachedContent !== void 0) throw new _warlock_js_ai.InvalidRequestError("The Google SDK countTokens endpoint cannot count a cachedContent reference.");
1021
+ try {
1022
+ const response = await this.ai.models.countTokens({
1023
+ model: this.name,
1024
+ contents,
1025
+ config: {
1026
+ ...config.systemInstruction !== void 0 ? { systemInstruction: config.systemInstruction } : {},
1027
+ ...config.tools !== void 0 ? { tools: config.tools } : {},
1028
+ ...config.abortSignal ? { abortSignal: config.abortSignal } : {},
1029
+ ...config.httpOptions ? { httpOptions: config.httpOptions } : {}
1030
+ }
1031
+ });
1032
+ if (response.totalTokens === void 0) throw new _warlock_js_ai.InvalidRequestError("Google countTokens returned no totalTokens.");
1033
+ return response.totalTokens;
1034
+ } catch (thrown) {
1035
+ throw this.logAndWrap(thrown);
1036
+ }
1037
+ }
895
1038
  /**
896
1039
  * Incremental streaming completion via `generateContentStream`.
897
1040
  * Yields neutral `ModelStreamChunk`s — `delta` for text, `tool-call`
898
1041
  * per function call (Gemini emits a fully-formed call, not partial
899
1042
  * JSON), and a terminal `done` with the final finish reason + usage.
1043
+ *
1044
+ * With `options.streamChunks` it also yields `reasoning` for thought
1045
+ * parts (only sent when `thinkingConfig.includeThoughts` is on — set it
1046
+ * through `providerOptions.google.body`) and `usage` whenever a chunk
1047
+ * carries `usageMetadata`. No `tool-args`: calls arrive whole.
900
1048
  */
901
1049
  async *stream(messages, options) {
902
1050
  this.logger.debug(LOG_MODULE, "request", "Starting generateContentStream call", {
@@ -923,8 +1071,15 @@ var GoogleModel = class {
923
1071
  output: 0,
924
1072
  total: 0
925
1073
  };
1074
+ const want = (0, _warlock_js_ai.resolveStreamChunks)(options);
926
1075
  try {
927
1076
  for await (const chunk of iterable) {
1077
+ if (want.reasoning) {
1078
+ for (const part of chunk.candidates?.[0]?.content?.parts ?? []) if (part.thought === true && typeof part.text === "string" && part.text !== "") yield {
1079
+ type: "reasoning",
1080
+ delta: part.text
1081
+ };
1082
+ }
928
1083
  const text = chunk.text;
929
1084
  if (text) yield {
930
1085
  type: "delta",
@@ -944,12 +1099,19 @@ var GoogleModel = class {
944
1099
  }
945
1100
  const candidateFinish = chunk.candidates?.[0]?.finishReason;
946
1101
  if (candidateFinish) rawFinishReason = candidateFinish;
947
- if (chunk.usageMetadata) applyGoogleUsage(usage, chunk.usageMetadata);
1102
+ if (chunk.usageMetadata) {
1103
+ applyGoogleUsage(usage, chunk.usageMetadata);
1104
+ if (want.usage) yield {
1105
+ type: "usage",
1106
+ usage: { ...usage }
1107
+ };
1108
+ }
948
1109
  }
949
1110
  } catch (thrown) {
950
1111
  throw this.logAndWrap(thrown);
951
1112
  }
952
1113
  const finishReason = sawToolCall ? "tool_calls" : mapFinishReason(rawFinishReason);
1114
+ (0, _warlock_js_ai.attachCost)(usage, this.pricing);
953
1115
  this.logger.debug(LOG_MODULE, "response", "generateContentStream call succeeded", {
954
1116
  finishReason,
955
1117
  usage
@@ -969,15 +1131,15 @@ var GoogleModel = class {
969
1131
  buildConfig(systemInstruction, options) {
970
1132
  const temperature = options?.temperature ?? this.config.temperature;
971
1133
  const maxOutputTokens = options?.maxTokens ?? this.config.maxTokens;
972
- return {
1134
+ return mergeProviderBody({
973
1135
  ...systemInstruction ? { systemInstruction } : {},
974
1136
  ...temperature !== void 0 ? { temperature } : {},
975
1137
  ...maxOutputTokens !== void 0 ? { maxOutputTokens } : {},
976
1138
  ...options?.signal ? { abortSignal: options.signal } : {},
977
- ...this.buildTools(options?.tools),
1139
+ ...this.buildTools(options),
978
1140
  ...this.buildStructuredOutput(options?.responseSchema),
979
1141
  ...this.buildThinking(options?.reasoning)
980
- };
1142
+ }, options);
981
1143
  }
982
1144
  /**
983
1145
  * Translate the neutral `reasoning` option into Gemini's
@@ -1002,11 +1164,21 @@ var GoogleModel = class {
1002
1164
  }
1003
1165
  /**
1004
1166
  * Spread-friendly tools fragment. Empty object when no tools were
1005
- * supplied so the caller can unconditionally spread it.
1167
+ * supplied so the caller can unconditionally spread it. Function tools
1168
+ * come first; `ai.providerTool({ provider: "google" })` definitions
1169
+ * (`{ googleSearch: {} }`, `{ codeExecution: {} }`, `{ urlContext: {} }`,
1170
+ * …) follow verbatim as their own `Tool` entries. Other providers'
1171
+ * definitions are skipped with a warning.
1006
1172
  */
1007
- buildTools(tools) {
1008
- const mapped = toGoogleTools(tools);
1009
- return mapped ? { tools: mapped } : {};
1173
+ buildTools(options) {
1174
+ const { tools, definitions } = (0, _warlock_js_ai.selectProviderTools)(options, {
1175
+ accepts: [GOOGLE_WIRE_FORMAT, this.provider],
1176
+ module: LOG_MODULE,
1177
+ model: this.name,
1178
+ logger: this.logger
1179
+ });
1180
+ const mapped = [...toGoogleTools(tools) ?? [], ...definitions];
1181
+ return mapped.length > 0 ? { tools: mapped } : {};
1010
1182
  }
1011
1183
  /**
1012
1184
  * Translate the neutral `responseSchema` into Gemini's native JSON
@@ -1086,7 +1258,7 @@ var GoogleModel = class {
1086
1258
  };
1087
1259
 
1088
1260
  //#endregion
1089
- //#region ../ai-google/src/sdk.ts
1261
+ //#region ../../packages/ai-google/src/sdk.ts
1090
1262
  /**
1091
1263
  * Pick the transport for an image model id.
1092
1264
  *
@@ -1154,11 +1326,12 @@ var GoogleSDK = class {
1154
1326
  * `GoogleGenAI` client. The SDK's `provider` label is forwarded.
1155
1327
  *
1156
1328
  * Pricing resolution: per-model `config.pricing` wins; otherwise the
1157
- * SDK-level registry entry keyed by `config.name`; otherwise
1158
- * `undefined` (no cost computed).
1329
+ * SDK-level registry entry keyed by `config.name`; otherwise the
1330
+ * built-in list price (`GOOGLE_MODEL_PRICING`); otherwise `undefined`
1331
+ * (no cost computed).
1159
1332
  */
1160
1333
  model(config) {
1161
- const resolvedPricing = config.pricing ?? this.pricing?.[config.name];
1334
+ const resolvedPricing = config.pricing ?? this.pricing?.[config.name] ?? googlePricingFor(config.name);
1162
1335
  const resolvedConfig = resolvedPricing === config.pricing ? config : {
1163
1336
  ...config,
1164
1337
  pricing: resolvedPricing
@@ -1219,7 +1392,9 @@ var GoogleSDK = class {
1219
1392
  };
1220
1393
 
1221
1394
  //#endregion
1395
+ exports.GOOGLE_MODEL_PRICING = GOOGLE_MODEL_PRICING;
1222
1396
  exports.GeminiImageModel = GeminiImageModel;
1223
1397
  exports.GoogleImageModel = GoogleImageModel;
1224
1398
  exports.GoogleSDK = GoogleSDK;
1399
+ exports.googlePricingFor = googlePricingFor;
1225
1400
  //# sourceMappingURL=index.cjs.map