@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/CHANGELOG.md CHANGED
@@ -4,6 +4,38 @@ 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.32.0 - 2026-10-18
8
+
9
+ ### Changed
10
+
11
+ - Pin the package development compiler to TypeScript 7.0.2 and use explicit Node types and source roots with supported module resolution.
12
+
13
+ ### Fixed
14
+
15
+ - Gemini Developer API token counting sends the full `generateContentRequest`, including system instructions, tools and generation settings, through the existing SDK transport with cancellation and error mapping. Vertex counting is unchanged; unsupported cached-content counts carry the typed signal used by `countInputTokens` to estimate.
16
+
17
+ ### Added
18
+
19
+ - Preserve raw candidate grounding metadata and normalize unique nonempty search queries in completions and streams. Export opt-in Gemini 3+ request prices as `GOOGLE_SERVER_TOOL_PRICING`; document older models’ per-prompt billing.
20
+
21
+ ## 5.31.0 - 2026-10-18
22
+
23
+ ### Fixed
24
+
25
+ - Provider body options merge plain-object fields one level deep, preserving neutral reasoning, generation and structured-output settings unless the caller overrides their keys.
26
+ - 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.
27
+
28
+ ### Added
29
+
30
+ - Usage is marked `inputIncludesCache: true` (`promptTokenCount` includes cached content), and `complete()` / the streaming `done` chunk attach `usage.cost` when priced.
31
+ - 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.
32
+ - 503 `UNAVAILABLE` throws `ProviderOverloadedError`. 400 and 500 failures are marked `billed: false`, as Google's billing docs state.
33
+ - An adapter contract test verifies that aborting `signal` rejects `complete()` and `stream()` on the model and cancels the HTTP request.
34
+ - With `streamChunks`, `stream()` yields `reasoning` for thought parts (sent when `thinkingConfig.includeThoughts` is on) and `usage` for every chunk carrying `usageMetadata`.
35
+ - `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.
36
+ - `providerOptions.google.body` forwards native per-call request options for completions and streams while protecting the model, conversation and tool definitions.
37
+ - Models expose native `countTokens()` preflight. Vertex counts system instructions and tools; unsupported Developer API system/tool and cached-content counts fail explicitly.
38
+
7
39
  ## 5.30.0 - 2026-10-16
8
40
 
9
41
  ### Changed
@@ -81,6 +113,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
81
113
  ### Changed
82
114
 
83
115
  - Lockstep release maintenance and dependency refresh.
116
+
84
117
  ## 5.20.0 - 2026-09-24
85
118
 
86
119
  ### Changed
@@ -154,7 +187,7 @@ changelogs.
154
187
 
155
188
  ### Deprecated
156
189
 
157
- - **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
190
+ - **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
158
191
 
159
192
  ### Changed
160
193
 
package/cjs/index.cjs CHANGED
@@ -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;
@@ -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.
@@ -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
  /**
@@ -449,7 +499,7 @@ const DEFAULT_RESPONSE_MODALITIES = ["TEXT", "IMAGE"];
449
499
  * or image generation being "stopped" for safety, prohibited content,
450
500
  * recitation, blocklist, or SPII.
451
501
  */
452
- const FILTERED_FINISH_REASONS = new Set([
502
+ const FILTERED_FINISH_REASONS = /* @__PURE__ */ new Set([
453
503
  "SAFETY",
454
504
  "IMAGE_SAFETY",
455
505
  "PROHIBITED_CONTENT",
@@ -747,6 +797,106 @@ var GoogleImageModel = class {
747
797
  }
748
798
  };
749
799
 
800
+ //#endregion
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
+ /** USD per nonempty unique query for Gemini 3+, before free monthly allowances.
836
+ * Gemini 2.5 and older bill per grounded prompt; do not use this rate for them.
837
+ * https://ai.google.dev/gemini-api/docs/google-search#pricing — verified 2026-10-18. */
838
+ const GOOGLE_SERVER_TOOL_PRICING = { webSearch: .014 };
839
+
840
+ //#endregion
841
+ //#region ../../packages/ai-google/src/utils/grounding.ts
842
+ /** Candidate snapshots are replaced, not added: streamed metadata may repeat.
843
+ * Installed @google/genai 2.19.0 GroundingMetadata.webSearchQueries; REST schema:
844
+ * https://ai.google.dev/api/generate-content#GroundingMetadata
845
+ * Billing ignores empty queries and counts unique queries for Gemini 3+:
846
+ * https://ai.google.dev/gemini-api/docs/google-search#pricing
847
+ * Gemini 2.5 and older bill per grounded prompt; this counter is NOT that unit. */
848
+ function applyGrounding(usage, snapshots, response) {
849
+ for (const [position, candidate] of (response.candidates ?? []).entries()) if (candidate.groundingMetadata) snapshots.set(candidate.index ?? position, candidate.groundingMetadata);
850
+ const reported = [...snapshots.values()].filter((metadata) => metadata.webSearchQueries !== void 0);
851
+ if (reported.length > 0) {
852
+ const queries = reported.flatMap((metadata) => metadata.webSearchQueries ?? []);
853
+ usage.serverToolUse = {
854
+ ...usage.serverToolUse,
855
+ webSearchRequests: new Set(queries.filter((query) => query !== "")).size,
856
+ googleWebSearchQueries: queries.length
857
+ };
858
+ }
859
+ }
860
+ /** Retain chunks, supports, search entry point and queries without flattening them. */
861
+ function groundingMetadata(snapshots) {
862
+ return snapshots.size ? { google: { groundingMetadata: [...snapshots].map(([index, metadata]) => ({
863
+ index,
864
+ ...metadata
865
+ })) } } : void 0;
866
+ }
867
+
868
+ //#endregion
869
+ //#region ../../packages/ai-google/src/provider-options.ts
870
+ const RESERVED_FIELDS = /* @__PURE__ */ new Set([
871
+ "systemInstruction",
872
+ "tools",
873
+ "abortSignal",
874
+ "httpOptions"
875
+ ]);
876
+ function mergeProviderBody(built, options) {
877
+ const body = (options?.providerOptions?.google)?.body ?? {};
878
+ const reserved = Object.keys(body).filter((key) => RESERVED_FIELDS.has(key));
879
+ 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 } });
880
+ const defaults = built;
881
+ const overrides = Object.fromEntries(Object.entries(body).map(([key, value]) => {
882
+ const previous = defaults[key];
883
+ return [key, isPlainObject(previous) && isPlainObject(value) ? {
884
+ ...previous,
885
+ ...value
886
+ } : value];
887
+ }));
888
+ return {
889
+ ...built,
890
+ ...overrides
891
+ };
892
+ }
893
+ /** Merge only plain objects; arrays, null and provider scalar values replace the field. */
894
+ function isPlainObject(value) {
895
+ if (value === null || typeof value !== "object") return false;
896
+ const prototype = Object.getPrototypeOf(value);
897
+ return prototype === Object.prototype || prototype === null;
898
+ }
899
+
750
900
  //#endregion
751
901
  //#region ../../packages/ai-google/src/known-vision-models.ts
752
902
  /**
@@ -787,6 +937,8 @@ function inferVisionCapability(modelId) {
787
937
  //#endregion
788
938
  //#region ../../packages/ai-google/src/model.ts
789
939
  const LOG_MODULE = "ai.google";
940
+ /** `provider` value of `ai.providerTool()` entries this adapter sends verbatim. */
941
+ const GOOGLE_WIRE_FORMAT = "google";
790
942
  /**
791
943
  * Bucketed `thinkingBudget` (token caps) for the neutral
792
944
  * `reasoning.effort` levels when the caller gives no explicit
@@ -881,22 +1033,90 @@ var GoogleModel = class {
881
1033
  const toolCalls = this.extractToolCalls(response);
882
1034
  const finishReason = toolCalls ? "tool_calls" : mapFinishReason(response.candidates?.[0]?.finishReason);
883
1035
  const usage = this.extractUsage(response);
1036
+ const grounding = /* @__PURE__ */ new Map();
1037
+ applyGrounding(usage, grounding, response);
1038
+ const providerMetadata = groundingMetadata(grounding);
1039
+ (0, _warlock_js_ai.attachCost)(usage, this.pricing);
884
1040
  this.logger.debug(LOG_MODULE, "response", "generateContent call succeeded", {
885
1041
  finishReason,
886
1042
  usage
887
1043
  });
888
1044
  return {
1045
+ ...providerMetadata ? { providerMetadata } : {},
889
1046
  content: response.text ?? "",
890
1047
  finishReason,
891
1048
  usage,
892
1049
  toolCalls
893
1050
  };
894
1051
  }
1052
+ /** Count native input tokens with the same converted content, system and tools. */
1053
+ async countTokens(messages, options) {
1054
+ const { systemInstruction, contents } = toGoogleContents(messages);
1055
+ const config = this.buildConfig(systemInstruction, options);
1056
+ if (config.cachedContent !== void 0) throw new _warlock_js_ai.InvalidRequestError("The Google SDK countTokens endpoint cannot count a cachedContent reference.", { reason: "count-unsupported" });
1057
+ try {
1058
+ const response = this.ai.vertexai ? await this.ai.models.countTokens({
1059
+ model: this.name,
1060
+ contents,
1061
+ config: {
1062
+ ...config.systemInstruction !== void 0 ? { systemInstruction: config.systemInstruction } : {},
1063
+ ...config.tools !== void 0 ? { tools: config.tools } : {},
1064
+ ...config.abortSignal ? { abortSignal: config.abortSignal } : {},
1065
+ ...config.httpOptions ? { httpOptions: config.httpOptions } : {}
1066
+ }
1067
+ }) : await this.countDeveloperTokens(contents, config);
1068
+ if (response.totalTokens === void 0) throw new _warlock_js_ai.InvalidRequestError("Google countTokens returned no totalTokens.");
1069
+ return response.totalTokens;
1070
+ } catch (thrown) {
1071
+ throw this.logAndWrap(thrown);
1072
+ }
1073
+ }
1074
+ /**
1075
+ * REST counts the full generation request, unlike the typed Developer API
1076
+ * converter in @google/genai 2.19.0. See https://ai.google.dev/api/tokens.
1077
+ * extraBody keeps the SDK's auth, fetch, base URL, retries and cancellation.
1078
+ */
1079
+ countDeveloperTokens(contents, config) {
1080
+ config.abortSignal?.throwIfAborted();
1081
+ const { systemInstruction, serviceTier, tools, toolConfig, safetySettings, cachedContent, abortSignal, httpOptions, ...generationConfig } = config;
1082
+ delete generationConfig.automaticFunctionCalling;
1083
+ const model = this.name.startsWith("models/") ? this.name : `models/${this.name}`;
1084
+ return this.ai.models.countTokens({
1085
+ model: this.name,
1086
+ contents,
1087
+ config: {
1088
+ abortSignal,
1089
+ httpOptions: {
1090
+ ...httpOptions,
1091
+ extraBody: {
1092
+ ...httpOptions?.extraBody,
1093
+ contents: void 0,
1094
+ generateContentRequest: {
1095
+ model,
1096
+ contents,
1097
+ ...serviceTier !== void 0 ? { serviceTier } : {},
1098
+ ...systemInstruction !== void 0 ? { systemInstruction: { parts: [{ text: systemInstruction }] } } : {},
1099
+ ...tools !== void 0 ? { tools } : {},
1100
+ ...toolConfig !== void 0 ? { toolConfig } : {},
1101
+ ...safetySettings !== void 0 ? { safetySettings } : {},
1102
+ ...cachedContent !== void 0 ? { cachedContent } : {},
1103
+ ...Object.keys(generationConfig).length > 0 ? { generationConfig } : {}
1104
+ }
1105
+ }
1106
+ }
1107
+ }
1108
+ });
1109
+ }
895
1110
  /**
896
1111
  * Incremental streaming completion via `generateContentStream`.
897
1112
  * Yields neutral `ModelStreamChunk`s — `delta` for text, `tool-call`
898
1113
  * per function call (Gemini emits a fully-formed call, not partial
899
1114
  * JSON), and a terminal `done` with the final finish reason + usage.
1115
+ *
1116
+ * With `options.streamChunks` it also yields `reasoning` for thought
1117
+ * parts (only sent when `thinkingConfig.includeThoughts` is on — set it
1118
+ * through `providerOptions.google.body`) and `usage` whenever a chunk
1119
+ * carries `usageMetadata`. No `tool-args`: calls arrive whole.
900
1120
  */
901
1121
  async *stream(messages, options) {
902
1122
  this.logger.debug(LOG_MODULE, "request", "Starting generateContentStream call", {
@@ -923,8 +1143,17 @@ var GoogleModel = class {
923
1143
  output: 0,
924
1144
  total: 0
925
1145
  };
1146
+ const want = (0, _warlock_js_ai.resolveStreamChunks)(options);
1147
+ const grounding = /* @__PURE__ */ new Map();
926
1148
  try {
927
1149
  for await (const chunk of iterable) {
1150
+ applyGrounding(usage, grounding, chunk);
1151
+ if (want.reasoning) {
1152
+ for (const part of chunk.candidates?.[0]?.content?.parts ?? []) if (part.thought === true && typeof part.text === "string" && part.text !== "") yield {
1153
+ type: "reasoning",
1154
+ delta: part.text
1155
+ };
1156
+ }
928
1157
  const text = chunk.text;
929
1158
  if (text) yield {
930
1159
  type: "delta",
@@ -944,20 +1173,29 @@ var GoogleModel = class {
944
1173
  }
945
1174
  const candidateFinish = chunk.candidates?.[0]?.finishReason;
946
1175
  if (candidateFinish) rawFinishReason = candidateFinish;
947
- if (chunk.usageMetadata) applyGoogleUsage(usage, chunk.usageMetadata);
1176
+ if (chunk.usageMetadata) {
1177
+ applyGoogleUsage(usage, chunk.usageMetadata);
1178
+ if (want.usage) yield {
1179
+ type: "usage",
1180
+ usage: { ...usage }
1181
+ };
1182
+ }
948
1183
  }
949
1184
  } catch (thrown) {
950
1185
  throw this.logAndWrap(thrown);
951
1186
  }
952
1187
  const finishReason = sawToolCall ? "tool_calls" : mapFinishReason(rawFinishReason);
1188
+ (0, _warlock_js_ai.attachCost)(usage, this.pricing);
953
1189
  this.logger.debug(LOG_MODULE, "response", "generateContentStream call succeeded", {
954
1190
  finishReason,
955
1191
  usage
956
1192
  });
1193
+ const providerMetadata = groundingMetadata(grounding);
957
1194
  yield {
958
1195
  type: "done",
959
1196
  finishReason,
960
- usage
1197
+ usage,
1198
+ ...providerMetadata ? { providerMetadata } : {}
961
1199
  };
962
1200
  }
963
1201
  /**
@@ -969,15 +1207,15 @@ var GoogleModel = class {
969
1207
  buildConfig(systemInstruction, options) {
970
1208
  const temperature = options?.temperature ?? this.config.temperature;
971
1209
  const maxOutputTokens = options?.maxTokens ?? this.config.maxTokens;
972
- return {
1210
+ return mergeProviderBody({
973
1211
  ...systemInstruction ? { systemInstruction } : {},
974
1212
  ...temperature !== void 0 ? { temperature } : {},
975
1213
  ...maxOutputTokens !== void 0 ? { maxOutputTokens } : {},
976
1214
  ...options?.signal ? { abortSignal: options.signal } : {},
977
- ...this.buildTools(options?.tools),
1215
+ ...this.buildTools(options),
978
1216
  ...this.buildStructuredOutput(options?.responseSchema),
979
1217
  ...this.buildThinking(options?.reasoning)
980
- };
1218
+ }, options);
981
1219
  }
982
1220
  /**
983
1221
  * Translate the neutral `reasoning` option into Gemini's
@@ -1002,11 +1240,21 @@ var GoogleModel = class {
1002
1240
  }
1003
1241
  /**
1004
1242
  * Spread-friendly tools fragment. Empty object when no tools were
1005
- * supplied so the caller can unconditionally spread it.
1243
+ * supplied so the caller can unconditionally spread it. Function tools
1244
+ * come first; `ai.providerTool({ provider: "google" })` definitions
1245
+ * (`{ googleSearch: {} }`, `{ codeExecution: {} }`, `{ urlContext: {} }`,
1246
+ * …) follow verbatim as their own `Tool` entries. Other providers'
1247
+ * definitions are skipped with a warning.
1006
1248
  */
1007
- buildTools(tools) {
1008
- const mapped = toGoogleTools(tools);
1009
- return mapped ? { tools: mapped } : {};
1249
+ buildTools(options) {
1250
+ const { tools, definitions } = (0, _warlock_js_ai.selectProviderTools)(options, {
1251
+ accepts: [GOOGLE_WIRE_FORMAT, this.provider],
1252
+ module: LOG_MODULE,
1253
+ model: this.name,
1254
+ logger: this.logger
1255
+ });
1256
+ const mapped = [...toGoogleTools(tools) ?? [], ...definitions];
1257
+ return mapped.length > 0 ? { tools: mapped } : {};
1010
1258
  }
1011
1259
  /**
1012
1260
  * Translate the neutral `responseSchema` into Gemini's native JSON
@@ -1154,11 +1402,12 @@ var GoogleSDK = class {
1154
1402
  * `GoogleGenAI` client. The SDK's `provider` label is forwarded.
1155
1403
  *
1156
1404
  * Pricing resolution: per-model `config.pricing` wins; otherwise the
1157
- * SDK-level registry entry keyed by `config.name`; otherwise
1158
- * `undefined` (no cost computed).
1405
+ * SDK-level registry entry keyed by `config.name`; otherwise the
1406
+ * built-in list price (`GOOGLE_MODEL_PRICING`); otherwise `undefined`
1407
+ * (no cost computed).
1159
1408
  */
1160
1409
  model(config) {
1161
- const resolvedPricing = config.pricing ?? this.pricing?.[config.name];
1410
+ const resolvedPricing = config.pricing ?? this.pricing?.[config.name] ?? googlePricingFor(config.name);
1162
1411
  const resolvedConfig = resolvedPricing === config.pricing ? config : {
1163
1412
  ...config,
1164
1413
  pricing: resolvedPricing
@@ -1219,7 +1468,10 @@ var GoogleSDK = class {
1219
1468
  };
1220
1469
 
1221
1470
  //#endregion
1471
+ exports.GOOGLE_MODEL_PRICING = GOOGLE_MODEL_PRICING;
1472
+ exports.GOOGLE_SERVER_TOOL_PRICING = GOOGLE_SERVER_TOOL_PRICING;
1222
1473
  exports.GeminiImageModel = GeminiImageModel;
1223
1474
  exports.GoogleImageModel = GoogleImageModel;
1224
1475
  exports.GoogleSDK = GoogleSDK;
1476
+ exports.googlePricingFor = googlePricingFor;
1225
1477
  //# sourceMappingURL=index.cjs.map