@gullabs/google 0.13.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { zodToStandardSchema, toConfigJsonSchema, createModelRegistry, LlmError, assertNever, computeCost, redactSecrets, classifyError } from '@gullabs/core';
1
+ import { maxOutputTokensSchema, zodToStandardSchema, toConfigJsonSchema, toConfigKeys, createModelRegistry, LlmError, classifyError, assertJsonSchemaProfile, assertInputMimeTypesAdmitted, assertModelMatchesDescriptor, assertNever, redactSecrets, assertMediaTypeAdmitted, parseRetryAfter, computeCost, isMediaTypeAdmitted, sha256Hex, canonicalJson } from '@gullabs/core';
2
2
  import { z } from 'zod';
3
3
 
4
4
  // src/adapter.ts
@@ -15,9 +15,18 @@ function requireApiKey(auth) {
15
15
  var FLEX_DEFAULT_TIMEOUT_MS = 15e5;
16
16
  var STANDARD_DEFAULT_TIMEOUT_MS = 3e5;
17
17
  var TRANSPORT_TIMEOUT_BUFFER_MS = 5e3;
18
+ var MAX_TIMER_MS = 2147483647;
19
+ var GOOGLE_MAX_TIMEOUT_MS = MAX_TIMER_MS - TRANSPORT_TIMEOUT_BUFFER_MS;
20
+ var GEMINI_API_ROOT = "https://generativelanguage.googleapis.com";
21
+ async function newGoogleGenAI(auth) {
22
+ const { GoogleGenAI: Sdk } = await import('@google/genai');
23
+ return new Sdk({
24
+ apiKey: requireApiKey(auth),
25
+ httpOptions: { baseUrl: `${GEMINI_API_ROOT}/` }
26
+ });
27
+ }
18
28
  async function buildGoogleClient(auth) {
19
- const { GoogleGenAI } = await import('@google/genai');
20
- const ai = new GoogleGenAI({ apiKey: requireApiKey(auth) });
29
+ const ai = await newGoogleGenAI(auth);
21
30
  return {
22
31
  models: {
23
32
  async generateContent(params) {
@@ -25,12 +34,59 @@ async function buildGoogleClient(auth) {
25
34
  return result;
26
35
  },
27
36
  async countTokens(params) {
37
+ if (params.systemInstruction !== void 0 || params.tools !== void 0) {
38
+ return countTokensWithRequest(requireApiKey(auth), params);
39
+ }
28
40
  const result = await ai.models.countTokens(params);
29
41
  return result;
30
42
  }
31
43
  }
32
44
  };
33
45
  }
46
+ var GEMINI_API_BASE = `${GEMINI_API_ROOT}/v1beta`;
47
+ var MAX_ERROR_BODY_CHARS = 500;
48
+ async function countTokensWithRequest(apiKey, params) {
49
+ const { ApiError } = await import('@google/genai');
50
+ const model = params.model.startsWith("models/") ? params.model : `models/${params.model}`;
51
+ const response = await fetch(`${GEMINI_API_BASE}/${model}:countTokens`, {
52
+ method: "POST",
53
+ headers: { "content-type": "application/json", "x-goog-api-key": apiKey },
54
+ body: JSON.stringify({
55
+ generateContentRequest: {
56
+ model,
57
+ contents: params.contents,
58
+ ...params.systemInstruction !== void 0 ? { systemInstruction: params.systemInstruction } : {},
59
+ ...params.tools !== void 0 ? { tools: params.tools } : {}
60
+ }
61
+ }),
62
+ ...params.config?.abortSignal !== void 0 ? { signal: params.config.abortSignal } : {}
63
+ });
64
+ const raw = await response.text();
65
+ let parsed;
66
+ try {
67
+ parsed = JSON.parse(raw);
68
+ } catch {
69
+ parsed = void 0;
70
+ }
71
+ if (!response.ok) {
72
+ const body = typeof parsed === "object" && parsed !== null && typeof parsed.error === "object" ? parsed : {
73
+ error: {
74
+ message: raw.length > MAX_ERROR_BODY_CHARS ? `${raw.slice(0, MAX_ERROR_BODY_CHARS)}\u2026` : raw,
75
+ code: response.status,
76
+ status: response.statusText
77
+ }
78
+ };
79
+ throw new ApiError({ message: JSON.stringify(body), status: response.status });
80
+ }
81
+ if (typeof parsed !== "object" || parsed === null) {
82
+ throw new LlmError("Gemini countTokens response is not a JSON object", {
83
+ kind: "server",
84
+ retryable: true,
85
+ provider: "google"
86
+ });
87
+ }
88
+ return parsed;
89
+ }
34
90
 
35
91
  // src/reasoning-budget.ts
36
92
  var GOOGLE_REASONING_EFFORT_BUDGET = {
@@ -39,6 +95,69 @@ var GOOGLE_REASONING_EFFORT_BUDGET = {
39
95
  medium: 8192,
40
96
  high: 24576
41
97
  };
98
+ var GOOGLE_HIGH_EFFORT_MIN_OUTPUT_TOKENS = 4096;
99
+
100
+ // src/json-schema.ts
101
+ var GEMINI_KEYWORDS = [
102
+ "type",
103
+ "properties",
104
+ "required",
105
+ "additionalProperties",
106
+ "enum",
107
+ "anyOf",
108
+ "$ref",
109
+ "$defs",
110
+ "items",
111
+ "prefixItems",
112
+ "minItems",
113
+ "maxItems",
114
+ "minimum",
115
+ "maximum",
116
+ "format",
117
+ "pattern",
118
+ "minLength",
119
+ "maxLength"
120
+ ];
121
+ var GEMMA_IGNORED_KEYWORDS = /* @__PURE__ */ new Set([
122
+ "format",
123
+ "minLength",
124
+ "maxLength"
125
+ ]);
126
+ var GOOGLE_FORMATS = ["date-time", "date", "email"];
127
+ var GEMINI_PROFILE = {
128
+ provider: "google",
129
+ keywords: GEMINI_KEYWORDS,
130
+ formats: GOOGLE_FORMATS,
131
+ limits: {},
132
+ circularRefs: true,
133
+ booleanItems: true,
134
+ patternSubset: true
135
+ };
136
+ var GEMMA_PROFILE = {
137
+ ...GEMINI_PROFILE,
138
+ keywords: GEMINI_KEYWORDS.filter((keyword) => !GEMMA_IGNORED_KEYWORDS.has(keyword))
139
+ };
140
+ function googleJsonSchemaProfile(canonicalModel) {
141
+ return canonicalModel.startsWith("gemma-") ? GEMMA_PROFILE : GEMINI_PROFILE;
142
+ }
143
+
144
+ // src/safety-settings.ts
145
+ var GOOGLE_SAFETY_CATEGORIES = [
146
+ "HARM_CATEGORY_HARASSMENT",
147
+ "HARM_CATEGORY_HATE_SPEECH",
148
+ "HARM_CATEGORY_SEXUALLY_EXPLICIT",
149
+ "HARM_CATEGORY_DANGEROUS_CONTENT",
150
+ "HARM_CATEGORY_CIVIC_INTEGRITY",
151
+ "HARM_CATEGORY_JAILBREAK"
152
+ ];
153
+ var GOOGLE_SAFETY_THRESHOLDS = [
154
+ "HARM_BLOCK_THRESHOLD_UNSPECIFIED",
155
+ "BLOCK_LOW_AND_ABOVE",
156
+ "BLOCK_MEDIUM_AND_ABOVE",
157
+ "BLOCK_ONLY_HIGH",
158
+ "BLOCK_NONE",
159
+ "OFF"
160
+ ];
42
161
 
43
162
  // src/grounding.ts
44
163
  function hostnameFrom(url) {
@@ -67,23 +186,116 @@ function toCitation(chunk) {
67
186
  sourceName
68
187
  };
69
188
  }
70
- function normalizeGroundingCitations(groundingMetadata) {
189
+ function utf16IndexAtByte(text, byte) {
190
+ if (!Number.isInteger(byte) || byte < 0) return void 0;
191
+ let bytes = 0;
192
+ let units = 0;
193
+ if (byte === 0) return 0;
194
+ for (const char of text) {
195
+ const code = char.codePointAt(0);
196
+ bytes += code < 128 ? 1 : code < 2048 ? 2 : code < 65536 ? 3 : 4;
197
+ units += char.length;
198
+ if (bytes === byte) return units;
199
+ if (bytes > byte) return void 0;
200
+ }
201
+ return void 0;
202
+ }
203
+ function segmentRange(segment, answerParts, joined, onDropped) {
204
+ if (segment === null || typeof segment !== "object") return void 0;
205
+ const raw = segment;
206
+ const partIndex = raw["partIndex"] ?? 0;
207
+ const startByte = raw["startIndex"] ?? 0;
208
+ const endByte = raw["endIndex"];
209
+ if (typeof partIndex !== "number" || typeof startByte !== "number" || typeof endByte !== "number") {
210
+ return void 0;
211
+ }
212
+ const part = answerParts[partIndex];
213
+ if (part === void 0) return void 0;
214
+ const start = utf16IndexAtByte(part.text, startByte);
215
+ const end = utf16IndexAtByte(part.text, endByte);
216
+ if (start === void 0 || end === void 0 || end <= start) return void 0;
217
+ const range = { start: part.offset + start, end: part.offset + end };
218
+ const expected = raw["text"];
219
+ if (typeof expected === "string" && joined.slice(range.start, range.end) !== expected) {
220
+ onDropped(
221
+ `google: dropped a textRange for a grounding segment (partIndex ${partIndex}, bytes ${startByte}-${endByte}): the answer at that range does not equal segment.text. The source stays cited without a range.`
222
+ );
223
+ return void 0;
224
+ }
225
+ return range;
226
+ }
227
+ function readSupports(groundingMetadata, answerParts, onDropped) {
228
+ const supports = groundingMetadata["groundingSupports"];
229
+ if (!Array.isArray(supports)) return void 0;
230
+ const joined = answerParts.map((part) => part?.text ?? "").join("");
231
+ const byChunk = /* @__PURE__ */ new Map();
232
+ for (const support of supports) {
233
+ if (support === null || typeof support !== "object") continue;
234
+ const record = support;
235
+ const indices = record["groundingChunkIndices"];
236
+ if (!Array.isArray(indices)) continue;
237
+ const range = segmentRange(record["segment"], answerParts, joined, onDropped);
238
+ for (const index of indices) {
239
+ if (typeof index !== "number") continue;
240
+ if (!byChunk.has(index) || byChunk.get(index) === void 0 && range) {
241
+ byChunk.set(index, range);
242
+ }
243
+ }
244
+ }
245
+ return byChunk;
246
+ }
247
+ function normalizeGroundingCitations(groundingMetadata, answerParts = [], onDropped = () => {
248
+ }) {
71
249
  if (groundingMetadata === null || typeof groundingMetadata !== "object") return [];
72
- const chunks = groundingMetadata["groundingChunks"];
250
+ const metadata = groundingMetadata;
251
+ const chunks = metadata["groundingChunks"];
73
252
  if (!Array.isArray(chunks)) return [];
74
- const seen = /* @__PURE__ */ new Set();
75
- const citations = [];
76
- for (const chunk of chunks) {
253
+ const supports = readSupports(metadata, answerParts, onDropped);
254
+ const byUrl = /* @__PURE__ */ new Map();
255
+ chunks.forEach((chunk, chunkIndex) => {
77
256
  const citation = toCitation(chunk);
78
- if (citation === void 0) continue;
79
- if (seen.has(citation.url)) continue;
80
- seen.add(citation.url);
81
- citations.push(citation);
257
+ if (citation === void 0) return;
258
+ const existing = byUrl.get(citation.url);
259
+ const target = existing ?? citation;
260
+ if (existing === void 0) byUrl.set(citation.url, citation);
261
+ if (supports === void 0) return;
262
+ if (supports.has(chunkIndex)) {
263
+ target.cited = true;
264
+ const range = supports.get(chunkIndex);
265
+ if (range !== void 0 && target.textRange === void 0) {
266
+ target.textRange = { ...range };
267
+ }
268
+ } else if (target.cited === void 0) {
269
+ target.cited = false;
270
+ }
271
+ });
272
+ return [...byUrl.values()];
273
+ }
274
+ function countWebSearchQueries(groundingMetadata) {
275
+ if (groundingMetadata === null || typeof groundingMetadata !== "object") {
276
+ return void 0;
82
277
  }
83
- return citations;
278
+ const queries = groundingMetadata["webSearchQueries"];
279
+ if (!Array.isArray(queries)) return void 0;
280
+ const named = queries.filter((q) => typeof q === "string" && q.length > 0).length;
281
+ return named === 0 && queries.length > 0 ? void 0 : named;
282
+ }
283
+ function readSearchEntryPoint(groundingMetadata) {
284
+ if (groundingMetadata === null || typeof groundingMetadata !== "object") {
285
+ return void 0;
286
+ }
287
+ const entry = groundingMetadata["searchEntryPoint"];
288
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
289
+ return void 0;
290
+ }
291
+ return Object.keys(entry).length > 0 ? entry : void 0;
84
292
  }
85
293
 
86
294
  // src/tool-call-id.ts
295
+ var SYNTHESIZED_PREFIX = "anyllm_call_";
296
+ function isSynthesizedToolCallId(id) {
297
+ return id.startsWith(SYNTHESIZED_PREFIX);
298
+ }
87
299
  function reserveProviderToolCallIds(ids) {
88
300
  const reserved = /* @__PURE__ */ new Set();
89
301
  for (const id of ids) {
@@ -96,7 +308,7 @@ function nextFallbackToolCallId(toolName, counters, reserved, counterKey = toolN
96
308
  let id;
97
309
  do {
98
310
  n += 1;
99
- id = `call_${toolName}_${n}`;
311
+ id = `${SYNTHESIZED_PREFIX}${toolName}_${n}`;
100
312
  } while (reserved.has(id));
101
313
  counters.set(counterKey, n);
102
314
  return id;
@@ -109,78 +321,645 @@ function resolveToolCallId(providerId, toolName, counters, reserved, counterKey)
109
321
  }
110
322
 
111
323
  // src/flex-fallback.ts
112
- var CAPACITY_PATTERNS = [
113
- /capacity/i,
114
- /overload/i,
115
- /overloaded/i,
116
- /unavailable/i,
117
- /no\s+capacity/i,
118
- /temporar(?:y|ily)/i,
119
- /try\s+again/i
120
- ];
121
- var QUOTA_PATTERNS = [
122
- /quota/i,
123
- /billing/i,
124
- /billable/i,
125
- /payment/i,
126
- /rate\s+limit/i,
127
- /exceeded/i,
128
- /insufficient/i
129
- ];
130
324
  function isGeminiCapacityError(err) {
131
- if (err.kind === "server") return err.httpStatus === 503;
132
- if (err.kind !== "rate_limited") return false;
133
- const message = err.message;
134
- if (QUOTA_PATTERNS.some((pattern) => pattern.test(message))) {
135
- return false;
136
- }
137
- return CAPACITY_PATTERNS.some((pattern) => pattern.test(message));
138
- }
139
- var GOOGLE_TRANSPORT_ERROR_PATTERN = /fetch failed|connection error|econnreset|econnrefused|etimedout|eai_again|epipe|socket hang up/i;
140
- function matchesGoogleTransportSignature(err) {
141
- if (!(err instanceof Error)) return false;
142
- if (GOOGLE_TRANSPORT_ERROR_PATTERN.test(err.message)) return true;
143
- const code = err.code;
144
- return typeof code === "string" && GOOGLE_TRANSPORT_ERROR_PATTERN.test(code);
145
- }
146
- function isGoogleTransportError(rawErr) {
147
- if (matchesGoogleTransportSignature(rawErr)) return true;
148
- if (rawErr instanceof Error) {
149
- const cause = rawErr.cause;
150
- if (matchesGoogleTransportSignature(cause)) return true;
151
- }
152
- return false;
153
- }
154
- function isGoogleModelNotFound(rawErr) {
155
- if (!(rawErr instanceof Error)) return false;
156
- if (rawErr.status !== 404) return false;
325
+ return err.httpStatus === 503;
326
+ }
327
+ function isRecord(value) {
328
+ return typeof value === "object" && value !== null && !Array.isArray(value);
329
+ }
330
+ function parseGoogleErrorBody(rawErr) {
331
+ const source = rawErr instanceof LlmError ? rawErr.cause : rawErr;
332
+ if (!(source instanceof Error)) return void 0;
333
+ let parsed;
157
334
  try {
158
- const parsed = JSON.parse(rawErr.message);
159
- return parsed.error?.code === 404 && parsed.error.status === "NOT_FOUND";
335
+ parsed = JSON.parse(source.message);
160
336
  } catch {
161
- return false;
337
+ return void 0;
338
+ }
339
+ if (!isRecord(parsed) || !isRecord(parsed["error"])) return void 0;
340
+ const error = parsed["error"];
341
+ const details = Array.isArray(error["details"]) ? error["details"].filter(isRecord) : [];
342
+ return {
343
+ ...typeof error["status"] === "string" ? { status: error["status"] } : {},
344
+ ...typeof error["message"] === "string" ? { message: error["message"] } : {},
345
+ details
346
+ };
347
+ }
348
+ function detailOfType(body, type) {
349
+ return body.details.filter((d) => d["@type"] === `type.googleapis.com/${type}`);
350
+ }
351
+ function errorInfoReasons(body) {
352
+ return detailOfType(body, "google.rpc.ErrorInfo").flatMap(
353
+ (d) => typeof d["reason"] === "string" ? [d["reason"]] : []
354
+ );
355
+ }
356
+ function isDailyQuota(body) {
357
+ return detailOfType(body, "google.rpc.QuotaFailure").some(
358
+ (failure) => Array.isArray(failure["violations"]) && failure["violations"].some(
359
+ (v) => isRecord(v) && typeof v["quotaId"] === "string" && v["quotaId"].includes("PerDay")
360
+ )
361
+ );
362
+ }
363
+ var PROTO_DURATION = /^\d+(?:\.\d{1,9})?s$/;
364
+ function durationText(delay) {
365
+ if (typeof delay === "string") return PROTO_DURATION.test(delay) ? delay : void 0;
366
+ if (!isRecord(delay)) return void 0;
367
+ const { seconds, nanos } = delay;
368
+ const whole = typeof seconds === "number" && Number.isSafeInteger(seconds) && seconds >= 0 ? String(seconds) : typeof seconds === "string" && /^\d+$/.test(seconds) ? seconds : seconds === void 0 ? "0" : void 0;
369
+ const fraction = typeof nanos === "number" && Number.isInteger(nanos) && nanos >= 0 && nanos < 1e9 ? String(nanos).padStart(9, "0") : nanos === void 0 ? "0" : void 0;
370
+ return whole !== void 0 && fraction !== void 0 ? `${whole}.${fraction}s` : void 0;
371
+ }
372
+ function retryDelayMs(body) {
373
+ for (const info of detailOfType(body, "google.rpc.RetryInfo")) {
374
+ const text = durationText(info["retryDelay"]);
375
+ if (text === void 0) continue;
376
+ const ms = parseRetryAfter({ "retry-after": text }, Date.now());
377
+ if (ms !== void 0) return ms;
162
378
  }
379
+ return void 0;
380
+ }
381
+ var API_KEY_REASONS = /* @__PURE__ */ new Set(["API_KEY_INVALID", "API_KEY_EXPIRED"]);
382
+ var STALE_CACHE_MESSAGE = "CachedContent not found";
383
+ function isGoogleNotFoundError(err) {
384
+ if (typeof err !== "object" || err === null) return false;
385
+ const obj = err;
386
+ const httpStatus = classifyError(err).httpStatus ?? numericStatus(obj["httpStatus"]);
387
+ if (httpStatus === 404 || obj["status"] === "NOT_FOUND" || obj["code"] === "NOT_FOUND") {
388
+ return true;
389
+ }
390
+ const body = parseGoogleErrorBody(err);
391
+ const message = body?.message ?? (typeof obj["message"] === "string" ? obj["message"] : "");
392
+ if (httpStatus === 403) return /may not exist|not found/i.test(message);
393
+ const statusKnown = httpStatus !== void 0 || body?.status !== void 0 || typeof obj["status"] === "string" || [obj["status"], obj["code"]].some((value) => typeof value === "number");
394
+ if (statusKnown) return false;
395
+ return /not\s*found|404/i.test(message) && /file|cachedcontent/i.test(message);
396
+ }
397
+ function numericStatus(value) {
398
+ const n = typeof value === "number" ? value : typeof value === "string" && /^\d{3}$/.test(value.trim()) ? Number(value) : void 0;
399
+ return n !== void 0 && Number.isInteger(n) && n >= 100 && n <= 599 ? n : void 0;
163
400
  }
164
401
  function classifyGoogleError(rawErr, extra) {
165
402
  const base = classifyError(rawErr);
166
- const reclassifyAsTransport = base.kind === "unknown" && isGoogleTransportError(rawErr);
167
- const reclassifyAsBadRequest = base.kind === "unknown" && isGoogleModelNotFound(rawErr);
168
- return new LlmError(base.message, {
169
- kind: reclassifyAsTransport ? "server" : reclassifyAsBadRequest ? "bad_request" : base.kind,
170
- retryable: reclassifyAsTransport ? true : base.retryable,
403
+ const body = parseGoogleErrorBody(rawErr);
404
+ let kind = base.kind;
405
+ let retryable = base.retryable;
406
+ let reason = base.reason;
407
+ let retryAfterMs = base.retryAfterMs;
408
+ let message = base.message;
409
+ if (extra?.transportTimeout !== void 0) {
410
+ kind = "timeout";
411
+ retryable = false;
412
+ reason = "transport_timeout";
413
+ retryAfterMs = void 0;
414
+ message = extra.transportTimeout;
415
+ } else if (!(rawErr instanceof LlmError) && base.kind === "timeout" && base.httpStatus === void 0) {
416
+ retryable = false;
417
+ reason = "transport_timeout";
418
+ retryAfterMs = void 0;
419
+ }
420
+ if (body !== void 0 && base.httpStatus !== void 0) {
421
+ if (errorInfoReasons(body).some((r) => API_KEY_REASONS.has(r))) {
422
+ kind = "invalid_auth";
423
+ retryable = false;
424
+ } else if (base.httpStatus === 403 && body.message?.startsWith(STALE_CACHE_MESSAGE) === true) {
425
+ kind = "bad_request";
426
+ retryable = false;
427
+ reason = "cache_not_found";
428
+ } else if (base.httpStatus === 429) {
429
+ if (isDailyQuota(body)) {
430
+ kind = "rate_limited";
431
+ retryable = false;
432
+ reason = "daily_quota";
433
+ retryAfterMs = void 0;
434
+ } else {
435
+ retryAfterMs = retryDelayMs(body) ?? retryAfterMs;
436
+ }
437
+ }
438
+ }
439
+ return new LlmError(message, {
440
+ kind,
441
+ retryable,
442
+ ...reason !== void 0 ? { reason } : {},
171
443
  ...base.httpStatus !== void 0 ? { httpStatus: base.httpStatus } : {},
172
- ...base.retryAfterMs !== void 0 ? { retryAfterMs: base.retryAfterMs } : {},
444
+ ...retryAfterMs !== void 0 ? { retryAfterMs } : {},
173
445
  provider: "google",
174
446
  cause: base.cause ?? rawErr,
175
447
  ...extra?.servedServiceTier !== void 0 ? { servedServiceTier: extra.servedServiceTier } : {}
176
448
  });
177
449
  }
178
450
 
451
+ // src/platform-scheduler.ts
452
+ var PLATFORM_SCHEDULER = {
453
+ setTimeout: (callback, ms) => globalThis.setTimeout(callback, ms),
454
+ clearTimeout: (handle) => {
455
+ globalThis.clearTimeout(handle);
456
+ }
457
+ };
458
+
459
+ // src/pricing.ts
460
+ var pricingVersion = "gemini-2026-10-03";
461
+ var GEMINI_PRICED_TIERS = ["standard", "flex"];
462
+ function freezeRates(rates) {
463
+ if (rates.gt200k !== void 0) Object.freeze(rates.gt200k);
464
+ if (rates.audio !== void 0) Object.freeze(rates.audio);
465
+ return Object.freeze(rates);
466
+ }
467
+ function tiers(standard, flex) {
468
+ return Object.freeze({ standard: freezeRates(standard), flex: freezeRates(flex) });
469
+ }
470
+ var GEMINI_PRICING = Object.freeze({
471
+ // Gemini 2.5 Pro. Flex cached equals standard on both context bands. No separate audio price.
472
+ "gemini-2.5-pro": tiers(
473
+ {
474
+ inputPerM: 125e4,
475
+ cachedPerM: 125e3,
476
+ outputPerM: 1e7,
477
+ gt200k: { inputPerM: 25e5, cachedPerM: 25e4, outputPerM: 15e6 }
478
+ },
479
+ {
480
+ inputPerM: 625e3,
481
+ cachedPerM: 125e3,
482
+ outputPerM: 5e6,
483
+ gt200k: { inputPerM: 125e4, cachedPerM: 25e4, outputPerM: 75e5 }
484
+ }
485
+ ),
486
+ // Gemini 2.5 Flash. Flex cached stays $0.03. Audio: $1.00 / cached $0.10 standard,
487
+ // $0.50 / cached $0.10 flex.
488
+ "gemini-2.5-flash": tiers(
489
+ {
490
+ inputPerM: 3e5,
491
+ cachedPerM: 3e4,
492
+ outputPerM: 25e5,
493
+ audio: { inputPerM: 1e6, cachedPerM: 1e5 }
494
+ },
495
+ {
496
+ inputPerM: 15e4,
497
+ cachedPerM: 3e4,
498
+ outputPerM: 125e4,
499
+ audio: { inputPerM: 5e5, cachedPerM: 1e5 }
500
+ }
501
+ ),
502
+ // Gemini 2.5 Flash-Lite. Flex cached stays $0.01. Audio: $0.30 / cached $0.03
503
+ // standard, $0.15 / cached $0.03 flex.
504
+ "gemini-2.5-flash-lite": tiers(
505
+ {
506
+ inputPerM: 1e5,
507
+ cachedPerM: 1e4,
508
+ outputPerM: 4e5,
509
+ audio: { inputPerM: 3e5, cachedPerM: 3e4 }
510
+ },
511
+ {
512
+ inputPerM: 5e4,
513
+ cachedPerM: 1e4,
514
+ outputPerM: 2e5,
515
+ audio: { inputPerM: 15e4, cachedPerM: 3e4 }
516
+ }
517
+ ),
518
+ // Gemini 3.1 Flash-Lite. Flex cached is the published $0.0125. Audio: $0.50 /
519
+ // cached $0.05 standard, $0.25 / cached $0.025 flex.
520
+ "gemini-3.1-flash-lite": tiers(
521
+ {
522
+ inputPerM: 25e4,
523
+ cachedPerM: 25e3,
524
+ outputPerM: 15e5,
525
+ audio: { inputPerM: 5e5, cachedPerM: 5e4 }
526
+ },
527
+ {
528
+ inputPerM: 125e3,
529
+ cachedPerM: 12500,
530
+ outputPerM: 75e4,
531
+ audio: { inputPerM: 25e4, cachedPerM: 25e3 }
532
+ }
533
+ ),
534
+ // Gemini 3.8 / 3.7 / 3.6 Flash intro rates (2026-09-25), one rate for all
535
+ // modalities. Flex cached is half of the intro cached rate. Re-snapshot on 2027-01-01.
536
+ "gemini-3.8-flash": tiers(
537
+ { inputPerM: 75e4, cachedPerM: 75e3, outputPerM: 375e4 },
538
+ { inputPerM: 375e3, cachedPerM: 37500, outputPerM: 1875e3 }
539
+ ),
540
+ "gemini-3.7-flash": tiers(
541
+ { inputPerM: 75e4, cachedPerM: 75e3, outputPerM: 375e4 },
542
+ { inputPerM: 375e3, cachedPerM: 37500, outputPerM: 1875e3 }
543
+ ),
544
+ "gemini-3.6-flash": tiers(
545
+ { inputPerM: 75e4, cachedPerM: 75e3, outputPerM: 375e4 },
546
+ { inputPerM: 375e3, cachedPerM: 37500, outputPerM: 1875e3 }
547
+ ),
548
+ // Gemini 3.5 Flash-Lite, one rate for all modalities (audio included). Flex
549
+ // cached is the published $0.02, not half of $0.03.
550
+ "gemini-3.5-flash-lite": tiers(
551
+ { inputPerM: 3e5, cachedPerM: 3e4, outputPerM: 25e5 },
552
+ { inputPerM: 15e4, cachedPerM: 2e4, outputPerM: 125e4 }
553
+ ),
554
+ // Gemini 3.1 Pro Preview. Flex cached equals standard on both bands. No separate audio price.
555
+ "gemini-3.1-pro-preview": tiers(
556
+ {
557
+ inputPerM: 2e6,
558
+ cachedPerM: 2e5,
559
+ outputPerM: 12e6,
560
+ gt200k: { inputPerM: 4e6, cachedPerM: 4e5, outputPerM: 18e6 }
561
+ },
562
+ {
563
+ inputPerM: 1e6,
564
+ cachedPerM: 2e5,
565
+ outputPerM: 6e6,
566
+ gt200k: { inputPerM: 2e6, cachedPerM: 4e5, outputPerM: 9e6 }
567
+ }
568
+ )
569
+ });
570
+ var PER_QUERY = Object.freeze({
571
+ unit: "query",
572
+ microUsdPerUnit: 14e3
573
+ });
574
+ var PER_GROUNDED_PROMPT = Object.freeze({
575
+ unit: "prompt",
576
+ microUsdPerUnit: 35e3
577
+ });
578
+ var GEMINI_GROUNDING_PRICING = Object.freeze({
579
+ "gemini-2.5-pro": PER_GROUNDED_PROMPT,
580
+ "gemini-2.5-flash": PER_GROUNDED_PROMPT,
581
+ "gemini-2.5-flash-lite": PER_GROUNDED_PROMPT,
582
+ "gemini-3.1-flash-lite": PER_QUERY,
583
+ "gemini-3.8-flash": PER_QUERY,
584
+ "gemini-3.7-flash": PER_QUERY,
585
+ "gemini-3.6-flash": PER_QUERY,
586
+ "gemini-3.5-flash-lite": PER_QUERY,
587
+ "gemini-3.1-pro-preview": PER_QUERY
588
+ });
589
+ function resolveGeminiGroundingRate(model) {
590
+ return Object.hasOwn(GEMINI_GROUNDING_PRICING, model) ? GEMINI_GROUNDING_PRICING[model] : void 0;
591
+ }
592
+ function isPricedGeminiTier(tier) {
593
+ return GEMINI_PRICED_TIERS.includes(tier);
594
+ }
595
+ function lookupGeminiTierRates(model, tier) {
596
+ const entry = Object.hasOwn(GEMINI_PRICING, model) ? GEMINI_PRICING[model] : void 0;
597
+ if (entry === void 0) return void 0;
598
+ if (tier !== void 0 && !isPricedGeminiTier(tier)) return void 0;
599
+ return entry;
600
+ }
601
+ function resolveGeminiRates(model, tier) {
602
+ const entry = lookupGeminiTierRates(model, tier);
603
+ if (entry === void 0) return void 0;
604
+ const key = tier ?? "standard";
605
+ return entry[key];
606
+ }
607
+
608
+ // src/cost.ts
609
+ function tokenDetail(usage, key) {
610
+ const value = usage.details[key];
611
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.floor(value) : void 0;
612
+ }
613
+ function modalitySplitTotal(usage, prefix) {
614
+ let total;
615
+ for (const [key, value] of Object.entries(usage.details)) {
616
+ if (key.startsWith(prefix) && Number.isFinite(value) && value >= 0) {
617
+ total = (total ?? 0) + value;
618
+ }
619
+ }
620
+ return total;
621
+ }
622
+ function promptLanes(usage) {
623
+ const total = usage.inputTokens;
624
+ const cachedReported = usage.cachedInputTokens ?? 0;
625
+ const promptAudio = tokenDetail(usage, "input_audio");
626
+ const cachedAudio = tokenDetail(usage, "cached_audio");
627
+ const gaps = [];
628
+ const gap = (reason) => {
629
+ if (!gaps.includes(reason)) gaps.push(reason);
630
+ };
631
+ const cached = Math.min(cachedReported, total);
632
+ if (cachedReported > total) gap("inconsistent");
633
+ const audioCached = Math.min(cachedAudio ?? 0, cached);
634
+ if ((cachedAudio ?? 0) > cached) gap("inconsistent");
635
+ if (promptAudio !== void 0 && promptAudio < audioCached) gap("inconsistent");
636
+ const audioTotal = Math.min(Math.max(promptAudio ?? 0, audioCached), total);
637
+ if ((promptAudio ?? 0) > total) gap("inconsistent");
638
+ const audioUncached = Math.min(audioTotal - audioCached, total - cached);
639
+ if (audioTotal - audioCached > total - cached) gap("inconsistent");
640
+ if (usage.details["audio_input_requested"] === 1 && !((promptAudio ?? 0) > 0)) {
641
+ gap("audio-unreported");
642
+ }
643
+ const promptSplit = modalitySplitTotal(usage, "input_");
644
+ const promptHoldsNoAudio = promptAudio === 0 || promptAudio === void 0 && promptSplit !== void 0 && promptSplit >= total;
645
+ if (cached > 0 && cachedAudio === void 0 && !promptHoldsNoAudio) {
646
+ gap("cached-audio-unknown");
647
+ }
648
+ return {
649
+ audioUncached,
650
+ audioCached,
651
+ otherUncached: total - cached - audioUncached,
652
+ otherCached: cached - audioCached,
653
+ gaps
654
+ };
655
+ }
656
+ function priceCall(model, usage, tier) {
657
+ if (usage.details["usage_missing"] === 1) {
658
+ return {
659
+ microUsd: null,
660
+ usd: null,
661
+ pricingVersion,
662
+ confidence: "estimated",
663
+ details: { input: 0, cached: 0, output: 0, tools: 0 },
664
+ unpricedReason: "The response carried no usageMetadata, so the tokens billed are unknown."
665
+ };
666
+ }
667
+ const modelRates = resolveGeminiRates(model, tier);
668
+ const audioRates = modelRates?.audio;
669
+ let cost;
670
+ if (audioRates === void 0) {
671
+ cost = computeCost(model, usage, tier, resolveGeminiRates, pricingVersion);
672
+ } else {
673
+ const lanes = promptLanes(usage);
674
+ const textUsage = {
675
+ ...usage,
676
+ inputTokens: lanes.otherUncached + lanes.otherCached,
677
+ cachedInputTokens: lanes.otherCached
678
+ };
679
+ const text = computeCost(model, textUsage, tier, resolveGeminiRates, pricingVersion);
680
+ const audioUncachedCost = Math.round(
681
+ lanes.audioUncached * audioRates.inputPerM / 1e6
682
+ );
683
+ const audioCachedCost = Math.round(
684
+ lanes.audioCached * audioRates.cachedPerM / 1e6
685
+ );
686
+ const microUsd2 = (text.microUsd ?? 0) + audioUncachedCost + audioCachedCost;
687
+ cost = {
688
+ ...text,
689
+ microUsd: microUsd2,
690
+ usd: microUsd2 / 1e6,
691
+ // An audio share the response does not pin down can understate the amount.
692
+ ...lanes.gaps.length > 0 ? { confidence: "estimated" } : {},
693
+ details: {
694
+ ...text.details,
695
+ input: text.details.input + audioUncachedCost,
696
+ cached: text.details.cached + audioCachedCost
697
+ }
698
+ };
699
+ }
700
+ if (cost.microUsd === null) return cost;
701
+ if (usage.details["web_search_requested"] !== 1) return cost;
702
+ const calls = usage.details["web_search_calls"];
703
+ if (calls === 0) return cost;
704
+ const rate = resolveGeminiGroundingRate(model);
705
+ const tools = rate === void 0 || calls === void 0 ? 0 : rate.unit === "query" ? Math.round(calls * rate.microUsdPerUnit) : rate.microUsdPerUnit;
706
+ const microUsd = cost.microUsd + tools;
707
+ return {
708
+ ...cost,
709
+ microUsd,
710
+ usd: microUsd / 1e6,
711
+ confidence: "estimated",
712
+ details: { ...cost.details, tools }
713
+ };
714
+ }
715
+ function geminiPricingSource() {
716
+ return {
717
+ version: pricingVersion,
718
+ price(model, usage, tier) {
719
+ return priceCall(model, usage, tier);
720
+ },
721
+ hasModel(model) {
722
+ return resolveGeminiRates(model, void 0) !== void 0;
723
+ },
724
+ listModels() {
725
+ return Object.keys(GEMINI_PRICING);
726
+ }
727
+ };
728
+ }
729
+
730
+ // src/utf8.ts
731
+ function utf8ByteLength(text) {
732
+ let bytes = 0;
733
+ for (let i = 0; i < text.length; i += 1) {
734
+ const unit = text.charCodeAt(i);
735
+ if (unit < 128) bytes += 1;
736
+ else if (unit < 2048) bytes += 2;
737
+ else if (unit >= 55296 && unit <= 56319) {
738
+ const next = text.charCodeAt(i + 1);
739
+ if (next >= 56320 && next <= 57343) {
740
+ bytes += 4;
741
+ i += 1;
742
+ } else bytes += 3;
743
+ } else bytes += 3;
744
+ }
745
+ return bytes;
746
+ }
747
+ var STATE_PATH = "transientProviderState";
748
+ var SHA256_HEX = /^[0-9a-f]{64}$/;
749
+ var ENTRY_KEYS = /* @__PURE__ */ new Set([
750
+ "messageIndex",
751
+ "partIndex",
752
+ "kind",
753
+ "model",
754
+ "partSha256",
755
+ "signature"
756
+ ]);
757
+ function badState(path, why) {
758
+ return new LlmError(`${path}: ${why}`, {
759
+ kind: "bad_request",
760
+ retryable: false,
761
+ provider: "google",
762
+ issues: [{ path, message: why }]
763
+ });
764
+ }
765
+ function isRecord2(value) {
766
+ return typeof value === "object" && value !== null && !Array.isArray(value);
767
+ }
768
+ function hashedForm(part, path) {
769
+ switch (part.kind) {
770
+ case "text":
771
+ return { kind: "text", text: part.text };
772
+ case "tool-call":
773
+ return {
774
+ kind: "tool-call",
775
+ toolCallId: part.toolCallId,
776
+ toolName: part.toolName,
777
+ args: part.args
778
+ };
779
+ default:
780
+ throw badState(
781
+ path,
782
+ `a thought signature cannot be attached to a "${part.kind}" part`
783
+ );
784
+ }
785
+ }
786
+ function partSha256(part, path = "part") {
787
+ return sha256Hex(canonicalJson(hashedForm(part, path)));
788
+ }
789
+ function parseSignatureState(value) {
790
+ if (value === void 0) return [];
791
+ if (!isRecord2(value)) {
792
+ throw badState(STATE_PATH, "must be { google: { signatures: [...] } }");
793
+ }
794
+ for (const key of Object.keys(value)) {
795
+ if (key !== "google") {
796
+ throw badState(
797
+ `${STATE_PATH}.${key}`,
798
+ `holds another provider's state; Google state is { google: { signatures: [...] } }`
799
+ );
800
+ }
801
+ }
802
+ const google = value["google"];
803
+ if (!isRecord2(google)) {
804
+ throw badState(`${STATE_PATH}.google`, "must be { signatures: [...] }");
805
+ }
806
+ for (const key of Object.keys(google)) {
807
+ if (key !== "signatures") {
808
+ throw badState(`${STATE_PATH}.google.${key}`, "is not a known key");
809
+ }
810
+ }
811
+ const list = google["signatures"];
812
+ if (!Array.isArray(list)) {
813
+ throw badState(`${STATE_PATH}.google.signatures`, "must be an array");
814
+ }
815
+ return list.map((raw, index) => {
816
+ const path = `${STATE_PATH}.google.signatures.${index}`;
817
+ if (!isRecord2(raw)) throw badState(path, "must be an object");
818
+ for (const key of Object.keys(raw)) {
819
+ if (!ENTRY_KEYS.has(key)) throw badState(`${path}.${key}`, "is not a known key");
820
+ }
821
+ const { messageIndex, partIndex, kind, model, partSha256: digest, signature } = raw;
822
+ if (typeof messageIndex !== "number" || !Number.isInteger(messageIndex) || messageIndex < 0) {
823
+ throw badState(`${path}.messageIndex`, "must be a non-negative integer");
824
+ }
825
+ if (typeof partIndex !== "number" || !Number.isInteger(partIndex) || partIndex < 0) {
826
+ throw badState(`${path}.partIndex`, "must be a non-negative integer");
827
+ }
828
+ if (kind !== "text" && kind !== "tool-call") {
829
+ throw badState(`${path}.kind`, 'must be "text" or "tool-call"');
830
+ }
831
+ if (typeof model !== "string" || model.length === 0) {
832
+ throw badState(`${path}.model`, "must be a non-empty string");
833
+ }
834
+ if (typeof digest !== "string" || !SHA256_HEX.test(digest)) {
835
+ throw badState(`${path}.partSha256`, "must be 64 lowercase hex characters");
836
+ }
837
+ if (typeof signature !== "string" || signature.length === 0) {
838
+ throw badState(`${path}.signature`, "must be a non-empty string");
839
+ }
840
+ return { messageIndex, partIndex, kind, model, partSha256: digest, signature };
841
+ });
842
+ }
843
+ var STALE = "history was edited, reordered or produced by another model after the signature was issued";
844
+ function resolveSignatures(entries, messages, model) {
845
+ const bySlot = /* @__PURE__ */ new Map();
846
+ const kept = [];
847
+ const dropped = [];
848
+ const seen = /* @__PURE__ */ new Set();
849
+ for (const [index, entry] of entries.entries()) {
850
+ const path = `${STATE_PATH}.google.signatures.${index}`;
851
+ const slot = `${entry.messageIndex}:${entry.partIndex}`;
852
+ const where = `messages.${entry.messageIndex}.parts.${entry.partIndex}`;
853
+ const stale = (field, why) => {
854
+ if (entry.kind === "tool-call") throw badState(`${path}.${field}`, why);
855
+ dropped.push(`${where}: ${why}`);
856
+ };
857
+ if (seen.has(slot)) throw badState(path, `duplicates the entry for ${where}`);
858
+ seen.add(slot);
859
+ if (entry.model !== model) {
860
+ stale(
861
+ "model",
862
+ `was issued for model "${entry.model}" but this request names "${model}"; signatures are not replayed across models (${STALE})`
863
+ );
864
+ continue;
865
+ }
866
+ const message = messages[entry.messageIndex];
867
+ if (message === void 0 || message.role !== "assistant") {
868
+ stale(
869
+ "messageIndex",
870
+ `does not point at an assistant message in messages (${STALE})`
871
+ );
872
+ continue;
873
+ }
874
+ const part = message.parts[entry.partIndex];
875
+ if (part === void 0) {
876
+ stale("partIndex", `is out of range for the message (${STALE})`);
877
+ continue;
878
+ }
879
+ if (part.kind !== entry.kind) {
880
+ stale(
881
+ "kind",
882
+ `is for a "${entry.kind}" part but ${where} is a "${part.kind}" part (${STALE})`
883
+ );
884
+ continue;
885
+ }
886
+ if (partSha256(part, where) !== entry.partSha256) {
887
+ stale("partSha256", `does not match ${where} (${STALE})`);
888
+ continue;
889
+ }
890
+ bySlot.set(slot, entry.signature);
891
+ kept.push(entry);
892
+ }
893
+ for (const [mi, message] of messages.entries()) {
894
+ if (message.role !== "assistant") continue;
895
+ const first = message.parts.findIndex((part) => part.kind === "tool-call");
896
+ if (first === -1 || bySlot.has(`${mi}:${first}`)) continue;
897
+ const call = message.parts[first];
898
+ throw badState(
899
+ `messages.${mi}.parts.${first}`,
900
+ `replays tool call "${call?.kind === "tool-call" ? call.toolCallId : ""}" without its thought signature in transientProviderState; Gemini 3 rejects a function call that lost it. Send the transientProviderState from the result that produced this message, and the history unedited, with the same model string. If you removed messages from the history, remove their entries with dropMessagesFromSignatureState`
901
+ );
902
+ }
903
+ return { bySlot, kept, dropped };
904
+ }
905
+ function signatureEntry(messageIndex, partIndex, model, part, signature) {
906
+ if (part.kind !== "text" && part.kind !== "tool-call") {
907
+ throw badState(
908
+ `messages.${messageIndex}.parts.${partIndex}`,
909
+ `a thought signature cannot be attached to a "${part.kind}" part`
910
+ );
911
+ }
912
+ return {
913
+ messageIndex,
914
+ partIndex,
915
+ kind: part.kind,
916
+ model,
917
+ partSha256: partSha256(part),
918
+ signature
919
+ };
920
+ }
921
+ function dropMessagesFromSignatureState(state, indices) {
922
+ const removed = /* @__PURE__ */ new Set();
923
+ for (const [i, value] of indices.entries()) {
924
+ if (!Number.isInteger(value) || value < 0) {
925
+ throw new LlmError(
926
+ `dropMessagesFromSignatureState: indices.${i} must be a non-negative integer.`,
927
+ {
928
+ kind: "bad_request",
929
+ retryable: false,
930
+ issues: [{ path: `indices.${i}`, message: "must be a non-negative integer" }]
931
+ }
932
+ );
933
+ }
934
+ if (removed.has(value)) {
935
+ throw new LlmError(
936
+ `dropMessagesFromSignatureState: indices.${i} repeats message ${value}.`,
937
+ {
938
+ kind: "bad_request",
939
+ retryable: false,
940
+ issues: [{ path: `indices.${i}`, message: "is a duplicate" }]
941
+ }
942
+ );
943
+ }
944
+ removed.add(value);
945
+ }
946
+ const sorted = [...removed].sort((a, b) => a - b);
947
+ const signatures = [];
948
+ for (const entry of parseSignatureState(state)) {
949
+ if (removed.has(entry.messageIndex)) continue;
950
+ const shift = sorted.filter((index) => index < entry.messageIndex).length;
951
+ signatures.push({ ...entry, messageIndex: entry.messageIndex - shift });
952
+ }
953
+ return signatures.length > 0 ? { google: { signatures } } : void 0;
954
+ }
955
+
179
956
  // src/adapter.ts
180
957
  var ALLOWED_GOOGLE_PROVIDER_OPTION_KEYS = /* @__PURE__ */ new Set([
958
+ "allowSchemaWithSearch",
181
959
  "cachedContent",
182
960
  "flexFallback",
183
961
  "httpOptions",
962
+ "requireGrounding",
184
963
  "safetySettings",
185
964
  "tools"
186
965
  ]);
@@ -241,6 +1020,8 @@ function parseGoogleTool(tool, model) {
241
1020
  );
242
1021
  }
243
1022
  }
1023
+ var SAFETY_CATEGORY_SET = new Set(GOOGLE_SAFETY_CATEGORIES);
1024
+ var SAFETY_THRESHOLD_SET = new Set(GOOGLE_SAFETY_THRESHOLDS);
244
1025
  function parseGoogleSafetySetting(setting, index, model) {
245
1026
  if (!isPlainRecord(setting)) {
246
1027
  throw badGoogleProviderOptions(
@@ -256,14 +1037,14 @@ function parseGoogleSafetySetting(setting, index, model) {
256
1037
  )}] for model "${model}". Allowed keys: category, threshold.`
257
1038
  );
258
1039
  }
259
- if (typeof setting["category"] !== "string" || setting["category"].length === 0) {
1040
+ if (typeof setting["category"] !== "string" || !SAFETY_CATEGORY_SET.has(setting["category"])) {
260
1041
  throw badGoogleProviderOptions(
261
- `providerOptions.google.safetySettings[${index}].category must be a non-empty string for model "${model}".`
1042
+ `providerOptions.google.safetySettings[${index}].category must be one of ${GOOGLE_SAFETY_CATEGORIES.join(", ")} for model "${model}".`
262
1043
  );
263
1044
  }
264
- if (typeof setting["threshold"] !== "string" || setting["threshold"].length === 0) {
1045
+ if (typeof setting["threshold"] !== "string" || !SAFETY_THRESHOLD_SET.has(setting["threshold"])) {
265
1046
  throw badGoogleProviderOptions(
266
- `providerOptions.google.safetySettings[${index}].threshold must be a non-empty string for model "${model}".`
1047
+ `providerOptions.google.safetySettings[${index}].threshold must be one of ${GOOGLE_SAFETY_THRESHOLDS.join(", ")} for model "${model}".`
267
1048
  );
268
1049
  }
269
1050
  return {
@@ -271,11 +1052,28 @@ function parseGoogleSafetySetting(setting, index, model) {
271
1052
  threshold: setting["threshold"]
272
1053
  };
273
1054
  }
1055
+ function describeType(value) {
1056
+ return value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
1057
+ }
1058
+ function noSchemaWithSearchEvidence(model) {
1059
+ return `Structured output with googleSearch is not supported for model "${model}": no live capture shows Search running when a response schema is attached to this model (the captures cover Gemini 3.x only), so there is no measured behaviour to opt into and providerOptions.google.allowSchemaWithSearch does not apply. Make two calls instead: grounded research without a schema, then structured synthesis (the two-call recipe in docs/grounded-structured.md).`;
1060
+ }
1061
+ function assertSchemaWithSearchAllowed(model, structuredOutputWithTools, allowSchemaWithSearch, declaredBy) {
1062
+ if (structuredOutputWithTools === void 0) {
1063
+ throw badGoogleProviderOptions(noSchemaWithSearchEvidence(model));
1064
+ }
1065
+ if (allowSchemaWithSearch !== true) {
1066
+ throw badGoogleProviderOptions(
1067
+ `Structured output with googleSearch is not enabled for model "${model}" (Search is declared by ${declaredBy}): the provider accepts the request but Search does not reliably run when a response schema is attached. Make two calls instead: grounded research without a schema, then structured synthesis (the two-call recipe in docs/grounded-structured.md). To send both in one call anyway, set providerOptions.google.allowSchemaWithSearch: true; the call then fails unless the response proves Search ran (requireGrounding), and that failure is not retryable because the same call keeps missing.`
1068
+ );
1069
+ }
1070
+ }
274
1071
  function mapGoogleProviderOptions({
275
1072
  googleOpts,
276
1073
  model,
277
1074
  structuredOutputRequested,
278
1075
  descriptorGrounding,
1076
+ descriptorCaching,
279
1077
  structuredOutputWithTools
280
1078
  }) {
281
1079
  if (googleOpts === void 0) {
@@ -297,23 +1095,43 @@ function mapGoogleProviderOptions({
297
1095
  );
298
1096
  }
299
1097
  const unknownKeys = Object.keys(googleOpts).filter(
300
- (key) => !ALLOWED_GOOGLE_PROVIDER_OPTION_KEYS.has(key) && !RESERVED_GOOGLE_PROVIDER_OPTION_KEYS.has(key)
1098
+ (key) => !ALLOWED_GOOGLE_PROVIDER_OPTION_KEYS.has(key)
301
1099
  );
302
1100
  if (unknownKeys.length > 0) {
303
1101
  throw badGoogleProviderOptions(
304
1102
  `providerOptions.google contains unsupported keys [${unknownKeys.join(
305
1103
  ", "
306
- )}] for model "${model}". Allowed keys: cachedContent, flexFallback, httpOptions, safetySettings, tools.`
1104
+ )}] for model "${model}". Allowed keys: allowSchemaWithSearch, cachedContent, flexFallback, httpOptions, requireGrounding, safetySettings, tools.`
307
1105
  );
308
1106
  }
309
1107
  const mapped = {};
310
1108
  if (googleOpts["cachedContent"] !== void 0) {
311
- if (typeof googleOpts["cachedContent"] !== "string" || googleOpts["cachedContent"].length === 0) {
1109
+ if (!descriptorCaching) {
1110
+ throw badGoogleProviderOptions(
1111
+ `providerOptions.google.cachedContent is not supported for model "${model}": the model has no explicit-caching capability.`
1112
+ );
1113
+ }
1114
+ const cached = googleOpts["cachedContent"];
1115
+ if (typeof cached === "string" && cached.length > 0) {
1116
+ mapped.cachedContent = cached;
1117
+ } else if (isPlainRecord(cached)) {
1118
+ const extraKeys = Object.keys(cached).filter(
1119
+ (key) => key !== "cacheName" && key !== "toolKinds"
1120
+ );
1121
+ const cacheName = cached["cacheName"];
1122
+ const toolKinds = cached["toolKinds"];
1123
+ if (extraKeys.length > 0 || typeof cacheName !== "string" || cacheName.length === 0 || toolKinds !== void 0 && (!Array.isArray(toolKinds) || !toolKinds.every((kind) => typeof kind === "string"))) {
1124
+ throw badGoogleProviderOptions(
1125
+ `providerOptions.google.cachedContent must be a non-empty cache name, or { cacheName: non-empty string, toolKinds?: string[] } and nothing else (pass handle.cacheName and handle.toolKinds, not the whole handle), for model "${model}".`
1126
+ );
1127
+ }
1128
+ mapped.cachedContent = cacheName;
1129
+ if (toolKinds !== void 0) mapped.cachedToolKinds = toolKinds;
1130
+ } else {
312
1131
  throw badGoogleProviderOptions(
313
- `providerOptions.google.cachedContent must be a non-empty string for model "${model}".`
1132
+ `providerOptions.google.cachedContent must be a non-empty cache name, or { cacheName: non-empty string, toolKinds?: string[] }, for model "${model}".`
314
1133
  );
315
1134
  }
316
- mapped.cachedContent = googleOpts["cachedContent"];
317
1135
  }
318
1136
  if (googleOpts["flexFallback"] !== void 0) {
319
1137
  if (typeof googleOpts["flexFallback"] !== "boolean") {
@@ -341,9 +1159,9 @@ function mapGoogleProviderOptions({
341
1159
  }
342
1160
  const timeout = googleOpts["httpOptions"]["timeout"];
343
1161
  if (timeout !== void 0) {
344
- if (typeof timeout !== "number" || !Number.isInteger(timeout) || timeout <= 0) {
1162
+ if (typeof timeout !== "number" || !Number.isInteger(timeout) || timeout <= 0 || timeout > MAX_TIMER_MS) {
345
1163
  throw badGoogleProviderOptions(
346
- `providerOptions.google.httpOptions.timeout must be a positive integer for model "${model}".`
1164
+ `providerOptions.google.httpOptions.timeout must be a positive integer of at most ${MAX_TIMER_MS} ms (the longest delay a Node timer holds; a larger one aborts the call after 1 ms) for model "${model}".`
347
1165
  );
348
1166
  }
349
1167
  mapped.httpOptions = { timeout };
@@ -361,6 +1179,18 @@ function mapGoogleProviderOptions({
361
1179
  (setting, index) => parseGoogleSafetySetting(setting, index, model)
362
1180
  );
363
1181
  }
1182
+ const allowSchemaWithSearch = googleOpts["allowSchemaWithSearch"];
1183
+ if (allowSchemaWithSearch !== void 0 && typeof allowSchemaWithSearch !== "boolean") {
1184
+ throw badGoogleProviderOptions(
1185
+ `providerOptions.google.allowSchemaWithSearch must be a boolean for model "${model}", received ${describeType(allowSchemaWithSearch)}.`
1186
+ );
1187
+ }
1188
+ const requireGrounding = googleOpts["requireGrounding"];
1189
+ if (requireGrounding !== void 0 && typeof requireGrounding !== "boolean") {
1190
+ throw badGoogleProviderOptions(
1191
+ `providerOptions.google.requireGrounding must be a boolean for model "${model}", received ${describeType(requireGrounding)}.`
1192
+ );
1193
+ }
364
1194
  if (googleOpts["tools"] !== void 0) {
365
1195
  if (!Array.isArray(googleOpts["tools"])) {
366
1196
  throw badGoogleProviderOptions(
@@ -374,12 +1204,54 @@ function mapGoogleProviderOptions({
374
1204
  );
375
1205
  }
376
1206
  if (structuredOutputRequested && structuredOutputWithTools !== true) {
377
- throw badGoogleProviderOptions(
378
- `Structured output with googleSearch is not supported for model "${model}".`
1207
+ assertSchemaWithSearchAllowed(
1208
+ model,
1209
+ structuredOutputWithTools,
1210
+ allowSchemaWithSearch,
1211
+ "providerOptions.google.tools"
379
1212
  );
380
1213
  }
381
1214
  mapped.tools = tools;
382
1215
  }
1216
+ const searchInCache = mapped.cachedToolKinds?.includes("googleSearch") === true;
1217
+ if (searchInCache) {
1218
+ if (descriptorGrounding !== true) {
1219
+ throw badGoogleProviderOptions(
1220
+ `providerOptions.google.cachedContent.toolKinds lists googleSearch, which is not supported for model "${model}": the model does not support grounding.`
1221
+ );
1222
+ }
1223
+ if (structuredOutputRequested && structuredOutputWithTools !== true) {
1224
+ assertSchemaWithSearchAllowed(
1225
+ model,
1226
+ structuredOutputWithTools,
1227
+ allowSchemaWithSearch,
1228
+ "the cache handle (cachedContent.toolKinds)"
1229
+ );
1230
+ }
1231
+ }
1232
+ const searchSent = searchInCache || mapped.tools?.some((tool) => "googleSearch" in tool) === true;
1233
+ if (allowSchemaWithSearch === true) {
1234
+ if (descriptorGrounding !== true) {
1235
+ throw badGoogleProviderOptions(
1236
+ `providerOptions.google.allowSchemaWithSearch is not supported for model "${model}": the model does not support grounding.`
1237
+ );
1238
+ }
1239
+ if (!searchSent || !structuredOutputRequested) {
1240
+ throw badGoogleProviderOptions(
1241
+ `providerOptions.google.allowSchemaWithSearch requires both Search (providerOptions.google.tools: [{ googleSearch: {} }], or a cachedContent handle whose toolKinds lists googleSearch) and output.jsonSchema for model "${model}".`
1242
+ );
1243
+ }
1244
+ if (structuredOutputWithTools === void 0) {
1245
+ throw badGoogleProviderOptions(noSchemaWithSearchEvidence(model));
1246
+ }
1247
+ }
1248
+ if (requireGrounding === true && !searchSent) {
1249
+ throw badGoogleProviderOptions(
1250
+ `providerOptions.google.requireGrounding requires Search: providerOptions.google.tools: [{ googleSearch: {} }], or a cachedContent handle whose toolKinds lists googleSearch, for model "${model}".`
1251
+ );
1252
+ }
1253
+ const effectiveRequireGrounding = requireGrounding ?? allowSchemaWithSearch === true;
1254
+ if (effectiveRequireGrounding) mapped.requireGrounding = true;
383
1255
  return mapped;
384
1256
  }
385
1257
  function assertSamplingAllowed(config, model, sampling) {
@@ -403,6 +1275,99 @@ function assertSamplingAllowed(config, model, sampling) {
403
1275
  );
404
1276
  }
405
1277
  }
1278
+ var MAX_INLINE_REQUEST_BYTES = 100 * 1024 * 1024;
1279
+ var MAX_INLINE_PDF_BYTES = 50 * 1024 * 1024;
1280
+ var PDF_MEDIA_TYPES = ["application/pdf"];
1281
+ function assertInlinePayloadWithinLimits(contents, system) {
1282
+ let total = system === void 0 ? 0 : utf8ByteLength(system);
1283
+ contents.forEach((content, mi) => {
1284
+ content.parts.forEach((part, pi) => {
1285
+ if ("text" in part) total += utf8ByteLength(part.text);
1286
+ if (!("inlineData" in part)) return;
1287
+ const { mimeType, data } = part.inlineData;
1288
+ total += data.length;
1289
+ if (isMediaTypeAdmitted(mimeType, PDF_MEDIA_TYPES)) {
1290
+ const decoded = Math.floor(data.length * 3 / 4);
1291
+ if (decoded > MAX_INLINE_PDF_BYTES) {
1292
+ throw new LlmError(
1293
+ `messages[${mi}].parts[${pi}] is an inline PDF of about ${decoded} bytes, over Google's 50 MB inline PDF limit. Upload it with GoogleFileStore and send a file-uri part.`,
1294
+ {
1295
+ kind: "bad_request",
1296
+ retryable: false,
1297
+ provider: "google",
1298
+ issues: [
1299
+ {
1300
+ path: `messages[${mi}].parts[${pi}]`,
1301
+ message: "inline PDF over 50 MB"
1302
+ }
1303
+ ]
1304
+ }
1305
+ );
1306
+ }
1307
+ }
1308
+ });
1309
+ });
1310
+ if (total > MAX_INLINE_REQUEST_BYTES) {
1311
+ throw new LlmError(
1312
+ `The request carries at least ${total} bytes of inline data and text, over Google's 100 MB request limit. Upload large media with GoogleFileStore and send file-uri parts.`,
1313
+ {
1314
+ kind: "bad_request",
1315
+ retryable: false,
1316
+ provider: "google",
1317
+ issues: [{ path: "messages", message: "request over 100 MB" }]
1318
+ }
1319
+ );
1320
+ }
1321
+ }
1322
+ var CANDIDATE_METADATA_KEYS = [
1323
+ "finishReason",
1324
+ "finishMessage",
1325
+ "safetyRatings",
1326
+ "citationMetadata",
1327
+ "urlContextMetadata"
1328
+ ];
1329
+ var MAX_FINISH_MESSAGE_CHARS = 512;
1330
+ var MAX_METADATA_ARRAY = 50;
1331
+ var MAX_METADATA_STRING = 2048;
1332
+ var MAX_METADATA_DEPTH = 8;
1333
+ var MAX_RATINGS_IN_MESSAGE = 12;
1334
+ function truncateText(text, max) {
1335
+ return text.length > max ? `${text.slice(0, max)}\u2026` : text;
1336
+ }
1337
+ function boundMetadata(value, state, depth = 0) {
1338
+ if (typeof value === "string") {
1339
+ if (value.length > MAX_METADATA_STRING) state.truncated = true;
1340
+ return truncateText(value, MAX_METADATA_STRING);
1341
+ }
1342
+ if (value === null || typeof value !== "object") return value;
1343
+ if (depth >= MAX_METADATA_DEPTH) {
1344
+ state.truncated = true;
1345
+ return null;
1346
+ }
1347
+ if (Array.isArray(value)) {
1348
+ if (value.length > MAX_METADATA_ARRAY) state.truncated = true;
1349
+ return value.slice(0, MAX_METADATA_ARRAY).map((item) => boundMetadata(item, state, depth + 1));
1350
+ }
1351
+ const out = {};
1352
+ for (const [key, item] of Object.entries(value)) {
1353
+ if (item !== void 0) out[key] = boundMetadata(item, state, depth + 1);
1354
+ }
1355
+ return out;
1356
+ }
1357
+ function describeSafetyRatings(ratings) {
1358
+ if (!Array.isArray(ratings) || ratings.length === 0) return "";
1359
+ const parts = ratings.slice(0, MAX_RATINGS_IN_MESSAGE).flatMap((rating) => {
1360
+ if (typeof rating !== "object" || rating === null) return [];
1361
+ const { category, probability, blocked } = rating;
1362
+ if (typeof category !== "string") return [];
1363
+ return [
1364
+ `${truncateText(category, 80)}=${typeof probability === "string" ? truncateText(probability, 40) : "unknown"}${blocked === true ? " (blocked)" : ""}`
1365
+ ];
1366
+ });
1367
+ if (parts.length === 0) return "";
1368
+ const more = ratings.length > MAX_RATINGS_IN_MESSAGE ? ` and ${ratings.length - MAX_RATINGS_IN_MESSAGE} more` : "";
1369
+ return `${parts.join(", ")}${more}`;
1370
+ }
406
1371
  function mapFinishReason(raw) {
407
1372
  if (raw === void 0) return void 0;
408
1373
  switch (raw) {
@@ -414,7 +1379,10 @@ function mapFinishReason(raw) {
414
1379
  case "RECITATION":
415
1380
  case "BLOCKLIST":
416
1381
  case "PROHIBITED_CONTENT":
1382
+ case "SPII":
417
1383
  case "IMAGE_SAFETY":
1384
+ case "IMAGE_PROHIBITED_CONTENT":
1385
+ case "IMAGE_RECITATION":
418
1386
  return "content_filter";
419
1387
  default:
420
1388
  return "other";
@@ -437,6 +1405,7 @@ function mapUsage(meta) {
437
1405
  const candidatesTokenCount = meta?.candidatesTokenCount ?? 0;
438
1406
  const cachedContentTokenCount = meta?.cachedContentTokenCount;
439
1407
  const thoughtsTokenCount = meta?.thoughtsTokenCount;
1408
+ const toolUsePromptTokenCount = meta?.toolUsePromptTokenCount;
440
1409
  const outputTokens = candidatesTokenCount + (thoughtsTokenCount ?? 0);
441
1410
  const inputTokens = promptTokenCount;
442
1411
  const totalTokens = meta?.totalTokenCount;
@@ -444,8 +1413,36 @@ function mapUsage(meta) {
444
1413
  input: inputTokens,
445
1414
  output: outputTokens,
446
1415
  ...cachedContentTokenCount !== void 0 ? { cached: cachedContentTokenCount } : {},
447
- ...thoughtsTokenCount !== void 0 ? { thinking: thoughtsTokenCount } : {}
1416
+ ...thoughtsTokenCount !== void 0 ? { thinking: thoughtsTokenCount } : {},
1417
+ // Tokens of Search results fed back to the model. They sit in
1418
+ // `totalTokenCount` but outside `promptTokenCount`. Google's pricing page
1419
+ // (read 2026-10) says retrieved search results are not charged
1420
+ // as input tokens, so they are recorded and not priced; no live billing
1421
+ // reconciliation has confirmed it, so the total mismatch still marks the
1422
+ // cost `estimated`.
1423
+ ...toolUsePromptTokenCount !== void 0 ? { tool_use_prompt: toolUsePromptTokenCount } : {}
448
1424
  };
1425
+ let cachedSplitTokens;
1426
+ for (const [prefix, entries] of [
1427
+ ["input", meta?.promptTokensDetails],
1428
+ ["cached", meta?.cacheTokensDetails]
1429
+ ]) {
1430
+ if (!Array.isArray(entries)) continue;
1431
+ if (prefix === "cached") cachedSplitTokens = 0;
1432
+ for (const entry of entries) {
1433
+ if (typeof entry !== "object" || entry === null) continue;
1434
+ const { modality, tokenCount: count } = entry;
1435
+ if (typeof modality !== "string" || modality === "" || typeof count !== "number" || !Number.isFinite(count) || count < 0) {
1436
+ continue;
1437
+ }
1438
+ const key = `${prefix}_${modality.toLowerCase()}`;
1439
+ details[key] = (details[key] ?? 0) + count;
1440
+ if (prefix === "cached") cachedSplitTokens = (cachedSplitTokens ?? 0) + count;
1441
+ }
1442
+ }
1443
+ if (cachedSplitTokens !== void 0 && cachedContentTokenCount !== void 0 && cachedContentTokenCount > 0 && cachedSplitTokens >= cachedContentTokenCount && details["cached_audio"] === void 0) {
1444
+ details["cached_audio"] = 0;
1445
+ }
449
1446
  const raw = meta !== void 0 ? meta : null;
450
1447
  const usage = {
451
1448
  inputTokens,
@@ -464,10 +1461,20 @@ function mapGoogleToolChoice(choice) {
464
1461
  if (choice === "none") return { mode: "NONE" };
465
1462
  return { mode: "ANY", allowedFunctionNames: [choice.name] };
466
1463
  }
467
- function mapPart(p) {
1464
+ function toFunctionResponseObject(p) {
1465
+ if (p.isError === true) return { error: p.result };
1466
+ if (typeof p.result === "object" && p.result !== null && !Array.isArray(p.result)) {
1467
+ return p.result;
1468
+ }
1469
+ return { output: p.result };
1470
+ }
1471
+ function mapPart(p, signature) {
468
1472
  switch (p.kind) {
469
1473
  case "text":
470
- return { text: p.text };
1474
+ return {
1475
+ text: p.text,
1476
+ ...signature !== void 0 ? { thoughtSignature: signature } : {}
1477
+ };
471
1478
  case "inline-media": {
472
1479
  return {
473
1480
  inlineData: {
@@ -494,30 +1501,35 @@ function mapPart(p) {
494
1501
  case "tool-call":
495
1502
  return {
496
1503
  functionCall: {
497
- id: p.toolCallId,
1504
+ // A synthesized id never reached Gemini; sending it would only be noise.
1505
+ ...isSynthesizedToolCallId(p.toolCallId) ? {} : { id: p.toolCallId },
498
1506
  name: p.toolName,
499
1507
  args: p.args
500
- }
1508
+ },
1509
+ ...signature !== void 0 ? { thoughtSignature: signature } : {}
501
1510
  };
502
1511
  case "tool-result":
503
1512
  return {
504
1513
  functionResponse: {
505
- id: p.toolCallId,
1514
+ ...isSynthesizedToolCallId(p.toolCallId) ? {} : { id: p.toolCallId },
506
1515
  name: p.toolName,
507
- response: p.isError === true ? { error: p.result } : p.result
1516
+ response: toFunctionResponseObject(p)
508
1517
  }
509
1518
  };
510
1519
  default:
511
1520
  return assertNever(p);
512
1521
  }
513
1522
  }
514
- function mapMessagesToGeminiContents(messages) {
515
- return messages.map((msg) => ({
1523
+ function mapMessagesToGeminiContents(messages, signatures) {
1524
+ return messages.map((msg, mi) => ({
516
1525
  role: msg.role === "assistant" ? "model" : "user",
517
- parts: msg.parts.map(mapPart)
1526
+ parts: msg.parts.map((part, pi) => mapPart(part, signatures?.get(`${mi}:${pi}`)))
518
1527
  }));
519
1528
  }
520
1529
  function geminiAdapter(opts) {
1530
+ return geminiAdapterWithClientFactory(opts?.client, buildGoogleClient);
1531
+ }
1532
+ function geminiAdapterWithClientFactory(injectedClient, buildClient) {
521
1533
  return {
522
1534
  id: "google",
523
1535
  async run(req, ctx) {
@@ -530,23 +1542,37 @@ function geminiAdapter(opts) {
530
1542
  const warnings = [];
531
1543
  const model = req.model;
532
1544
  const descriptor = req.modelDescriptor;
533
- if (descriptor?.model !== model || descriptor.provider !== "google") {
534
- throw new LlmError(`No matching Google model descriptor for "${model}".`, {
1545
+ assertModelMatchesDescriptor(req, descriptor, "google");
1546
+ assertInputMimeTypesAdmitted(req.messages, descriptor, "google");
1547
+ const signsHistory = descriptor.capabilities?.providerState === true;
1548
+ if (req.transientProviderState !== void 0 && !signsHistory) {
1549
+ throw new LlmError(`Model "${model}" does not admit transientProviderState.`, {
535
1550
  kind: "bad_request",
536
1551
  retryable: false
537
1552
  });
538
1553
  }
539
- if (req.transientProviderState !== void 0) {
540
- throw new LlmError(`Model "${model}" does not admit transientProviderState.`, {
541
- kind: "bad_request",
542
- retryable: false
1554
+ const resolved = signsHistory ? resolveSignatures(
1555
+ parseSignatureState(req.transientProviderState),
1556
+ req.messages,
1557
+ model
1558
+ ) : void 0;
1559
+ const incomingSignatures = resolved?.kept ?? [];
1560
+ if (resolved !== void 0 && resolved.dropped.length > 0) {
1561
+ warnings.push({
1562
+ type: "other",
1563
+ message: `google: dropped ${resolved.dropped.length} stale text signature(s) from transientProviderState (${resolved.dropped.join("; ")}); Google treats text signatures as optional, so nothing required was lost.`
543
1564
  });
544
1565
  }
545
- const contents = mapMessagesToGeminiContents(req.messages);
1566
+ const contents = mapMessagesToGeminiContents(
1567
+ req.messages,
1568
+ resolved?.bySlot
1569
+ );
1570
+ const system = req.system !== void 0 && req.system !== "" ? req.system : void 0;
1571
+ assertInlinePayloadWithinLimits(contents, system);
546
1572
  const genConfig = req.config;
547
1573
  const config = {};
548
- if (req.system !== void 0) {
549
- config.systemInstruction = { parts: [{ text: req.system }] };
1574
+ if (system !== void 0) {
1575
+ config.systemInstruction = { parts: [{ text: system }] };
550
1576
  }
551
1577
  if (genConfig.temperature !== void 0) {
552
1578
  config.temperature = genConfig.temperature;
@@ -603,6 +1629,12 @@ function geminiAdapter(opts) {
603
1629
  );
604
1630
  }
605
1631
  const budget = reasoning.budgetTokens !== void 0 ? reasoning.budgetTokens : reasoning.effort !== void 0 ? GOOGLE_REASONING_EFFORT_BUDGET[reasoning.effort] : void 0;
1632
+ if (budget !== void 0 && budget > 0 && genConfig.maxOutputTokens !== void 0 && budget >= genConfig.maxOutputTokens) {
1633
+ warnings.push({
1634
+ type: "other",
1635
+ message: `google: thinkingBudget (${budget}) is not below maxOutputTokens (${genConfig.maxOutputTokens}); thinking may consume the whole cap and leave no answer. Raise maxOutputTokens or lower the reasoning budget.`
1636
+ });
1637
+ }
606
1638
  config.thinkingConfig = {
607
1639
  ...budget !== void 0 ? { thinkingBudget: budget } : {},
608
1640
  ...reasoning.includeThoughts === true ? { includeThoughts: true } : {}
@@ -646,6 +1678,12 @@ function geminiAdapter(opts) {
646
1678
  assertNever(reasoning.effort);
647
1679
  }
648
1680
  }
1681
+ if (thinkingLevel === "HIGH" && genConfig.maxOutputTokens !== void 0 && genConfig.maxOutputTokens < GOOGLE_HIGH_EFFORT_MIN_OUTPUT_TOKENS) {
1682
+ warnings.push({
1683
+ type: "other",
1684
+ message: `google: reasoning.effort "high" can spend several thousand thinking tokens (measured up to 8,859) and maxOutputTokens is ${genConfig.maxOutputTokens}, below ${GOOGLE_HIGH_EFFORT_MIN_OUTPUT_TOKENS}; thinking may consume the whole cap and leave no answer. Raise maxOutputTokens or lower the effort.`
1685
+ });
1686
+ }
649
1687
  config.thinkingConfig = {
650
1688
  ...thinkingLevel !== void 0 ? { thinkingLevel } : {},
651
1689
  ...reasoning.includeThoughts === true ? { includeThoughts: true } : {}
@@ -659,24 +1697,148 @@ function geminiAdapter(opts) {
659
1697
  }
660
1698
  const structuredOutputRequested = req.outputJsonSchema !== void 0;
661
1699
  if (structuredOutputRequested) {
662
- const nativeStructuredOutput = descriptor.capabilities?.nativeStructuredOutput !== false;
663
- if (nativeStructuredOutput) {
664
- config.responseMimeType = "application/json";
665
- config.responseSchema = req.outputJsonSchema;
1700
+ if (descriptor.capabilities?.nativeStructuredOutput === false) {
1701
+ throw new LlmError(
1702
+ `output.jsonSchema is not supported for model "${model}": the model has no native structured output and the Google adapter has no other path.`,
1703
+ {
1704
+ kind: "bad_request",
1705
+ retryable: false,
1706
+ provider: "google",
1707
+ issues: [
1708
+ {
1709
+ path: "output.jsonSchema",
1710
+ message: "model has no native structured output"
1711
+ }
1712
+ ]
1713
+ }
1714
+ );
666
1715
  }
1716
+ assertJsonSchemaProfile(
1717
+ req.outputJsonSchema,
1718
+ "output.jsonSchema",
1719
+ googleJsonSchemaProfile(descriptor.model)
1720
+ );
1721
+ config.responseMimeType = "application/json";
1722
+ config.responseJsonSchema = req.outputJsonSchema;
667
1723
  }
668
1724
  const googleProviderConfig = mapGoogleProviderOptions({
669
1725
  googleOpts: genConfig.providerOptions?.["google"],
670
1726
  model,
671
1727
  structuredOutputRequested,
672
1728
  descriptorGrounding: descriptor.capabilities?.grounding,
1729
+ descriptorCaching: descriptor.capabilities?.caching !== void 0,
673
1730
  structuredOutputWithTools: descriptor.capabilities?.structuredOutputWithTools
674
1731
  });
1732
+ const googleSearchSent = googleProviderConfig.tools?.some((tool) => "googleSearch" in tool) === true;
1733
+ const searchDeclared = googleSearchSent || googleProviderConfig.cachedToolKinds?.includes("googleSearch") === true;
1734
+ const requireGrounding = googleProviderConfig.requireGrounding === true;
1735
+ const schemaSearchUndeclared = structuredOutputRequested && !searchDeclared && descriptor.capabilities?.structuredOutputWithTools !== true;
1736
+ const audioRequested = req.messages.some(
1737
+ (message) => message.parts.some(
1738
+ (part) => (part.kind === "inline-media" || part.kind === "file-uri") && part.mimeType.toLowerCase().startsWith("audio/")
1739
+ )
1740
+ );
1741
+ const mapUsageWithAudioMarker = (meta) => {
1742
+ const mapped = mapUsage(meta);
1743
+ if (audioRequested) mapped.details["audio_input_requested"] = 1;
1744
+ return mapped;
1745
+ };
1746
+ const usageFor = (meta, groundingMetadata2) => {
1747
+ const mapped = mapUsageWithAudioMarker(meta);
1748
+ const queries = countWebSearchQueries(groundingMetadata2);
1749
+ if (searchDeclared) {
1750
+ mapped.details["web_search_requested"] = 1;
1751
+ if (queries !== void 0) mapped.details["web_search_calls"] = queries;
1752
+ } else if (groundingMetadata2 !== void 0) {
1753
+ mapped.details["web_search_requested"] = 1;
1754
+ if (queries !== void 0 && queries > 0) {
1755
+ mapped.details["web_search_calls"] = queries;
1756
+ }
1757
+ }
1758
+ return mapped;
1759
+ };
1760
+ const groundingWarnings = (groundingMetadata2) => {
1761
+ if (!searchDeclared) {
1762
+ if (groundingMetadata2 === void 0) return [];
1763
+ const queries = countWebSearchQueries(groundingMetadata2);
1764
+ const undeclaredSearchWarnings = schemaSearchUndeclared && queries !== void 0 && queries > 0 ? [
1765
+ {
1766
+ type: "other",
1767
+ message: `google: this call attached a response schema and the response reports search queries, so Search ran from the cache named in cachedContent. Schema plus Search is not admitted by default for model "${model}" (Search does not reliably run when a schema is attached) and no googleSearch was declared, so nothing checked that it would run or opted in; the result is returned, the Search fee is priced from the observed queries (cost.confidence is "estimated"), and this pattern is unreliable. Declare Search with cachedContent: { cacheName, toolKinds: ['googleSearch'] } together with allowSchemaWithSearch: true (the call then fails unless the response proves Search ran), or use the two-call recipe in docs/grounded-structured.md.`
1768
+ }
1769
+ ] : [];
1770
+ return [
1771
+ ...undeclaredSearchWarnings,
1772
+ {
1773
+ type: "other",
1774
+ message: queries !== void 0 && queries > 0 ? `google: the response reports ${queries} search quer${queries === 1 ? "y" : "ies"} but the request did not declare googleSearch (a cachedContent cache can hold the tool); the Search fee was priced from the observed queries, and cost.confidence is "estimated" because the free allowance is unknowable per call.` : 'google: the response carries groundingMetadata but the request did not declare googleSearch and the metadata names no query, so the number of searches is unknown; grounding fees are not included in cost, so cost.confidence is "estimated".'
1775
+ }
1776
+ ];
1777
+ }
1778
+ if (groundingMetadata2 === void 0) {
1779
+ return [
1780
+ {
1781
+ type: "other",
1782
+ message: 'google: googleSearch was requested (sent, or held by the cache named in cachedContent) but the response carries no groundingMetadata, so Search may not have run, or may have run without being reported; grounding fees are not included in cost, so cost.confidence is "estimated".'
1783
+ }
1784
+ ];
1785
+ }
1786
+ if (countWebSearchQueries(groundingMetadata2) === void 0) {
1787
+ return [
1788
+ {
1789
+ type: "other",
1790
+ message: 'google: groundingMetadata has no webSearchQueries, so the number of searches is unknown; grounding fees are not included in cost, so cost.confidence is "estimated".'
1791
+ }
1792
+ ];
1793
+ }
1794
+ return [];
1795
+ };
1796
+ const modalityWarnings = (meta) => {
1797
+ if (meta === void 0) return [];
1798
+ const warnings2 = [];
1799
+ const { gaps } = promptLanes(mapUsageWithAudioMarker(meta));
1800
+ if (gaps.includes("audio-unreported")) {
1801
+ warnings2.push({
1802
+ type: "other",
1803
+ message: 'google: the request carries audio but usageMetadata.promptTokensDetails reports no AUDIO tokens, so the audio input rate could not be applied; on a model that prices audio apart from text, cost.confidence is "estimated" and the amount can understate.'
1804
+ });
1805
+ }
1806
+ if (gaps.includes("cached-audio-unknown")) {
1807
+ warnings2.push({
1808
+ type: "other",
1809
+ message: 'google: usageMetadata reports cached tokens without showing how many are audio (no AUDIO entry in cacheTokensDetails, and no per-modality prompt split that rules audio out), so audio in the cached content cannot be ruled out; on a model that prices audio apart from text, cost.confidence is "estimated" and the amount can understate.'
1810
+ });
1811
+ }
1812
+ if (gaps.includes("inconsistent")) {
1813
+ warnings2.push({
1814
+ type: "other",
1815
+ message: 'google: the per-modality token counts in usageMetadata contradict each other (audio above the prompt or the cache, or lanes that exceed the prompt); the counts were clamped, and on a model that prices audio apart from text, cost.confidence is "estimated".'
1816
+ });
1817
+ }
1818
+ return warnings2;
1819
+ };
1820
+ const billedFailure = (meta, groundingMetadata2) => {
1821
+ if (meta === void 0) return {};
1822
+ const failureWarnings = [
1823
+ ...groundingWarnings(groundingMetadata2),
1824
+ ...modalityWarnings(meta)
1825
+ ];
1826
+ return {
1827
+ usage: usageFor(meta, groundingMetadata2),
1828
+ ...failureWarnings.length > 0 ? { warnings: failureWarnings } : {}
1829
+ };
1830
+ };
675
1831
  if (googleProviderConfig.cachedContent !== void 0) {
676
1832
  config.cachedContent = googleProviderConfig.cachedContent;
677
1833
  }
678
1834
  if (googleProviderConfig.httpOptions !== void 0) {
679
1835
  config.httpOptions = googleProviderConfig.httpOptions;
1836
+ const sdkTimeout = googleProviderConfig.httpOptions.timeout;
1837
+ if (sdkTimeout !== void 0 && genConfig.timeoutMs !== void 0 && sdkTimeout < genConfig.timeoutMs + TRANSPORT_TIMEOUT_BUFFER_MS) {
1838
+ throw badGoogleProviderOptions(
1839
+ `providerOptions.google.httpOptions.timeout (${sdkTimeout} ms) must be at least timeoutMs + ${TRANSPORT_TIMEOUT_BUFFER_MS} ms (${genConfig.timeoutMs + TRANSPORT_TIMEOUT_BUFFER_MS} ms) for model "${model}": a shorter SDK timer would abort the call before the engine's own timeout and surface a raw SDK abort.`
1840
+ );
1841
+ }
680
1842
  }
681
1843
  if (googleProviderConfig.safetySettings !== void 0) {
682
1844
  config.safetySettings = googleProviderConfig.safetySettings;
@@ -685,7 +1847,7 @@ function geminiAdapter(opts) {
685
1847
  config.tools = googleProviderConfig.tools;
686
1848
  }
687
1849
  if (req.tools !== void 0 && req.tools.length > 0) {
688
- if (req.modelDescriptor?.capabilities?.functionCalling !== true) {
1850
+ if (descriptor.capabilities?.functionCalling !== true) {
689
1851
  throw new LlmError(
690
1852
  `tools is not supported for google model "${model}" (capabilities.functionCalling is not true).`,
691
1853
  { kind: "bad_request", retryable: false, provider: "google" }
@@ -693,16 +1855,24 @@ function geminiAdapter(opts) {
693
1855
  }
694
1856
  if (googleProviderConfig.tools !== void 0) {
695
1857
  throw new LlmError(
696
- "tools cannot be combined with providerOptions.google.tools (googleSearch) in this iteration.",
1858
+ "tools cannot be combined with providerOptions.google.tools (googleSearch): this adapter does not send function declarations and Search in one request.",
697
1859
  { kind: "bad_request", retryable: false, provider: "google" }
698
1860
  );
699
1861
  }
1862
+ const toolProfile = googleJsonSchemaProfile(descriptor.model);
1863
+ req.tools.forEach((tool, index) => {
1864
+ assertJsonSchemaProfile(
1865
+ tool.inputJsonSchema,
1866
+ `tools[${index}].inputJsonSchema`,
1867
+ toolProfile
1868
+ );
1869
+ });
700
1870
  config.tools = [
701
1871
  {
702
1872
  functionDeclarations: req.tools.map((tool) => ({
703
1873
  name: tool.name,
704
1874
  description: tool.description,
705
- parameters: tool.inputJsonSchema
1875
+ parametersJsonSchema: tool.inputJsonSchema
706
1876
  }))
707
1877
  }
708
1878
  ];
@@ -712,11 +1882,37 @@ function geminiAdapter(opts) {
712
1882
  };
713
1883
  }
714
1884
  }
715
- assertSamplingAllowed(config, model, req.modelDescriptor?.capabilities?.sampling);
1885
+ if (config.cachedContent !== void 0) {
1886
+ const conflicts = [
1887
+ ...system !== void 0 ? ["system"] : [],
1888
+ ...req.tools !== void 0 && req.tools.length > 0 ? ["tools"] : [],
1889
+ ...googleProviderConfig.tools !== void 0 ? ["providerOptions.google.tools"] : []
1890
+ ];
1891
+ if (conflicts.length > 0) {
1892
+ throw new LlmError(
1893
+ `providerOptions.google.cachedContent cannot be combined with ${conflicts.join(
1894
+ " or "
1895
+ )} for model "${model}": Gemini requires the system instruction and tools to be stored in the cache. Put them in GoogleCacheStore.create and omit them from the request.`,
1896
+ {
1897
+ kind: "bad_request",
1898
+ retryable: false,
1899
+ provider: "google",
1900
+ issues: conflicts.map((path) => ({
1901
+ path,
1902
+ message: "cannot be sent with cachedContent"
1903
+ }))
1904
+ }
1905
+ );
1906
+ }
1907
+ }
1908
+ assertSamplingAllowed(config, model, descriptor.capabilities?.sampling);
1909
+ const timers = ctx.scheduler ?? PLATFORM_SCHEDULER;
716
1910
  let tierTimeoutHandle;
1911
+ let ceilingFiredMs;
717
1912
  const clearTierTimeout = () => {
1913
+ ceilingFiredMs = void 0;
718
1914
  if (tierTimeoutHandle !== void 0) {
719
- clearTimeout(tierTimeoutHandle);
1915
+ timers.clearTimeout(tierTimeoutHandle);
720
1916
  tierTimeoutHandle = void 0;
721
1917
  }
722
1918
  };
@@ -731,7 +1927,8 @@ function geminiAdapter(opts) {
731
1927
  `${tierLabel} timeout: call exceeded ${defaultTimeoutMs}ms client-side ceiling (@google/genai #1277 belt-and-suspenders)`,
732
1928
  "TimeoutError"
733
1929
  );
734
- tierTimeoutHandle = setTimeout(() => {
1930
+ tierTimeoutHandle = timers.setTimeout(() => {
1931
+ ceilingFiredMs = defaultTimeoutMs;
735
1932
  tierController.abort(timeoutReason);
736
1933
  }, defaultTimeoutMs);
737
1934
  config.abortSignal = ctx.signal !== void 0 ? AbortSignal.any([tierController.signal, ctx.signal]) : tierController.signal;
@@ -761,8 +1958,7 @@ function geminiAdapter(opts) {
761
1958
  contents,
762
1959
  config: dispatchConfig
763
1960
  };
764
- const buildClient = opts?._clientFactory ?? buildGoogleClient;
765
- const client = opts?.client !== void 0 ? opts.client : await buildClient(ctx.auth);
1961
+ const client = injectedClient !== void 0 ? injectedClient : await buildClient(ctx.auth);
766
1962
  ctx.logger.debug(
767
1963
  {
768
1964
  model,
@@ -773,16 +1969,32 @@ function geminiAdapter(opts) {
773
1969
  );
774
1970
  return client.models.generateContent(params);
775
1971
  };
1972
+ const transportTimeoutOf = (rawErr) => {
1973
+ if (ctx.signal?.aborted === true) return void 0;
1974
+ if (ceilingFiredMs !== void 0) {
1975
+ return `Google call hit the ${ceilingFiredMs}ms client-side ceiling`;
1976
+ }
1977
+ const sdkTimeoutMs = config.httpOptions?.timeout;
1978
+ if (sdkTimeoutMs !== void 0 && rawErr instanceof Error && rawErr.name === "AbortError") {
1979
+ return `Google call hit the SDK transport timeout of ${sdkTimeoutMs}ms`;
1980
+ }
1981
+ return void 0;
1982
+ };
776
1983
  try {
777
1984
  response = await dispatch();
778
1985
  } catch (rawErr) {
779
- const typed = classifyGoogleError(
780
- rawErr,
781
- servedServiceTier !== void 0 ? { servedServiceTier } : void 0
782
- );
1986
+ const transportTimeout = transportTimeoutOf(rawErr);
1987
+ const typed = classifyGoogleError(rawErr, {
1988
+ ...servedServiceTier !== void 0 ? { servedServiceTier } : {},
1989
+ ...transportTimeout !== void 0 ? { transportTimeout } : {}
1990
+ });
783
1991
  if (config.serviceTier === "flex" && googleProviderConfig.flexFallback !== false && isGeminiCapacityError(typed)) {
784
1992
  config.serviceTier = "standard";
785
1993
  servedServiceTier = "standard";
1994
+ warnings.push({
1995
+ type: "other",
1996
+ message: `google: the flex call hit a capacity error (${typed.httpStatus ?? "no status"}) and was sent again at the standard tier, billed at standard rates.${genConfig.timeoutMs === void 0 ? ` Without timeoutMs the standard attempt runs under the ${STANDARD_DEFAULT_TIMEOUT_MS} ms client-side ceiling, not the flex ${FLEX_DEFAULT_TIMEOUT_MS} ms one.` : ""}`
1997
+ });
786
1998
  const fallbackTimeout = genConfig.timeoutMs !== void 0 ? genConfig.timeoutMs + TRANSPORT_TIMEOUT_BUFFER_MS : STANDARD_DEFAULT_TIMEOUT_MS;
787
1999
  config.httpOptions = {
788
2000
  timeout: fallbackTimeout,
@@ -792,7 +2004,11 @@ function geminiAdapter(opts) {
792
2004
  try {
793
2005
  response = await dispatch();
794
2006
  } catch (fallbackRawErr) {
795
- throw classifyGoogleError(fallbackRawErr, { servedServiceTier: "standard" });
2007
+ const fallbackTransportTimeout = transportTimeoutOf(fallbackRawErr);
2008
+ throw classifyGoogleError(fallbackRawErr, {
2009
+ servedServiceTier: "standard",
2010
+ ...fallbackTransportTimeout !== void 0 ? { transportTimeout: fallbackTransportTimeout } : {}
2011
+ });
796
2012
  }
797
2013
  } else {
798
2014
  throw typed;
@@ -811,49 +2027,105 @@ function geminiAdapter(opts) {
811
2027
  servedServiceTier = echoedTier;
812
2028
  }
813
2029
  const hasBlockReason = response.promptFeedback?.blockReason !== void 0;
814
- const hasCandidates = response.candidates !== void 0 && response.candidates.length > 0;
815
- if (hasBlockReason || !hasCandidates) {
2030
+ const candidate = response.candidates?.[0];
2031
+ if (hasBlockReason || candidate === void 0) {
816
2032
  const reason = response.promptFeedback?.blockReason ?? "NO_CANDIDATES";
817
- throw new LlmError(`Gemini response has no usable candidate: ${reason}`, {
818
- kind: hasBlockReason ? "content_filter" : "server",
819
- retryable: !hasBlockReason,
820
- provider: "google",
821
- ...response.usageMetadata !== void 0 ? { usage: mapUsage(response.usageMetadata) } : {},
822
- ...servedServiceTier !== void 0 ? { servedServiceTier } : {}
823
- });
2033
+ const thoughtTokens = response.usageMetadata?.thoughtsTokenCount ?? 0;
2034
+ const reasoningHint = !hasBlockReason && thoughtTokens > 0 ? `. The call billed ${thoughtTokens} reasoning tokens, and maxOutputTokens (${config.maxOutputTokens ?? "the provider default"}) includes reasoning tokens, so a low cap can be used up by reasoning before any answer is produced` : "";
2035
+ const promptRatings = hasBlockReason ? describeSafetyRatings(response.promptFeedback?.safetyRatings) : "";
2036
+ throw new LlmError(
2037
+ `Gemini response has no usable candidate: ${reason}${promptRatings !== "" ? ` (safetyRatings: ${promptRatings})` : ""}${reasoningHint}`,
2038
+ {
2039
+ kind: hasBlockReason ? "content_filter" : "server",
2040
+ retryable: !hasBlockReason && thoughtTokens === 0,
2041
+ provider: "google",
2042
+ ...billedFailure(response.usageMetadata),
2043
+ ...servedServiceTier !== void 0 ? { servedServiceTier } : {}
2044
+ }
2045
+ );
824
2046
  }
825
- const candidates = response.candidates;
826
- if (candidates === void 0 || candidates.length === 0) {
827
- throw new LlmError("Gemini response has no usable candidate: NO_CANDIDATES", {
828
- kind: "server",
829
- retryable: true,
830
- provider: "google",
831
- ...response.usageMetadata !== void 0 ? { usage: mapUsage(response.usageMetadata) } : {},
832
- ...servedServiceTier !== void 0 ? { servedServiceTier } : {}
833
- });
2047
+ const groundingMetadata = candidate.groundingMetadata;
2048
+ const parts = candidate.content?.parts ?? [];
2049
+ const callsComplete = candidate.finishReason === void 0 || candidate.finishReason === "STOP";
2050
+ const isFunctionCall = (part) => part.functionCall !== void 0 && typeof part.functionCall.name === "string";
2051
+ const hasAnswer = parts.some(
2052
+ (part) => part.thought !== true && typeof part.text === "string" && part.text.length > 0 || callsComplete && isFunctionCall(part)
2053
+ );
2054
+ const filteredCandidateError = (note) => {
2055
+ const finishMessage = candidate.finishMessage !== void 0 ? truncateText(candidate.finishMessage, MAX_FINISH_MESSAGE_CHARS) : void 0;
2056
+ const ratings = describeSafetyRatings(candidate.safetyRatings);
2057
+ const bounded = { truncated: false };
2058
+ return new LlmError(
2059
+ `Gemini candidate was filtered (finishReason ${candidate.finishReason}${finishMessage !== void 0 ? `: ${finishMessage}` : ""}${ratings !== "" ? `; safetyRatings: ${ratings}` : ""}); ${note}. The attempt was billed.`,
2060
+ {
2061
+ kind: "content_filter",
2062
+ retryable: false,
2063
+ provider: "google",
2064
+ // The raw evidence, bounded: which category blocked, and why.
2065
+ cause: {
2066
+ finishReason: candidate.finishReason ?? null,
2067
+ ...finishMessage !== void 0 ? { finishMessage } : {},
2068
+ ...candidate.safetyRatings !== void 0 ? { safetyRatings: boundMetadata(candidate.safetyRatings, bounded) } : {}
2069
+ },
2070
+ ...billedFailure(response.usageMetadata, groundingMetadata),
2071
+ ...servedServiceTier !== void 0 ? { servedServiceTier } : {}
2072
+ }
2073
+ );
2074
+ };
2075
+ if (mapFinishReason(candidate.finishReason) === "content_filter" && !hasAnswer) {
2076
+ throw filteredCandidateError(
2077
+ "it carries no answer text and no complete tool call"
2078
+ );
834
2079
  }
835
- const candidate = candidates[0];
836
- if (candidate === void 0) {
837
- throw new LlmError("Gemini response has no usable candidate: NO_CANDIDATES", {
838
- kind: "server",
839
- retryable: true,
840
- provider: "google",
841
- ...response.usageMetadata !== void 0 ? { usage: mapUsage(response.usageMetadata) } : {},
842
- ...servedServiceTier !== void 0 ? { servedServiceTier } : {}
843
- });
2080
+ if (requireGrounding) {
2081
+ const queries = countWebSearchQueries(groundingMetadata);
2082
+ if (groundingMetadata === void 0 || queries === void 0 || queries < 1) {
2083
+ const finishReason2 = mapFinishReason(candidate.finishReason);
2084
+ if (finishReason2 === "content_filter") {
2085
+ throw filteredCandidateError("the grounding check was not applied");
2086
+ }
2087
+ if (candidate.finishReason === void 0 || candidate.finishReason === "STOP") {
2088
+ const why = groundingMetadata === void 0 ? "the response has no groundingMetadata" : queries === void 0 ? "groundingMetadata has no webSearchQueries" : "groundingMetadata reports zero webSearchQueries";
2089
+ const retryable = !structuredOutputRequested;
2090
+ throw new LlmError(
2091
+ `google: requireGrounding is set but there is no evidence that Search ran: ${why}. The attempt was billed for its tokens${retryable ? "; a retry may ground." : "; it is not retryable, because a call with a response schema attached keeps missing (use the two-call recipe in docs/grounded-structured.md)."}`,
2092
+ {
2093
+ kind: "server",
2094
+ retryable,
2095
+ reason: "grounding_missing",
2096
+ provider: "google",
2097
+ ...billedFailure(response.usageMetadata, groundingMetadata),
2098
+ ...servedServiceTier !== void 0 ? { servedServiceTier } : {}
2099
+ }
2100
+ );
2101
+ }
2102
+ }
844
2103
  }
845
- const parts = candidate.content?.parts ?? [];
846
2104
  const textParts = [];
847
2105
  const thoughtParts = [];
848
2106
  const toolCalls = [];
2107
+ const messageParts = [];
2108
+ const issuedSignatures = [];
2109
+ let droppedSignatures = 0;
2110
+ const droppedCalls = [];
849
2111
  const nameCounts = /* @__PURE__ */ new Map();
850
- const reservedIds = reserveProviderToolCallIds(
851
- parts.map((part) => part.functionCall?.id)
852
- );
2112
+ const reservedIds = reserveProviderToolCallIds([
2113
+ ...parts.map((part) => part.functionCall?.id),
2114
+ ...req.messages.flatMap(
2115
+ (message) => message.parts.flatMap(
2116
+ (part) => part.kind === "tool-call" || part.kind === "tool-result" ? [part.toolCallId] : []
2117
+ )
2118
+ )
2119
+ ]);
853
2120
  for (const part of parts) {
854
- if (part.functionCall !== void 0 && typeof part.functionCall.name === "string") {
2121
+ const signature = typeof part.thoughtSignature === "string" && part.thoughtSignature.length > 0 ? part.thoughtSignature : void 0;
2122
+ let represented = false;
2123
+ if (isFunctionCall(part) && !callsComplete) {
2124
+ droppedCalls.push(part.functionCall?.name ?? "");
2125
+ represented = true;
2126
+ } else if (part.functionCall !== void 0 && typeof part.functionCall.name === "string") {
855
2127
  const toolName = part.functionCall.name;
856
- toolCalls.push({
2128
+ const call = {
857
2129
  toolCallId: resolveToolCallId(
858
2130
  part.functionCall.id,
859
2131
  toolName,
@@ -862,31 +2134,115 @@ function geminiAdapter(opts) {
862
2134
  ),
863
2135
  toolName,
864
2136
  args: part.functionCall.args ?? {}
865
- });
2137
+ };
2138
+ toolCalls.push(call);
2139
+ if (signature !== void 0) {
2140
+ issuedSignatures.push({ partIndex: messageParts.length, signature });
2141
+ }
2142
+ messageParts.push({ kind: "tool-call", ...call });
2143
+ represented = true;
866
2144
  }
867
2145
  if (part.text !== void 0) {
868
2146
  if (part.thought === true) {
869
2147
  thoughtParts.push(part.text);
870
2148
  } else {
871
2149
  textParts.push(part.text);
2150
+ if (part.text.length > 0) {
2151
+ if (signature !== void 0 && !represented) {
2152
+ issuedSignatures.push({ partIndex: messageParts.length, signature });
2153
+ }
2154
+ messageParts.push({ kind: "text", text: part.text });
2155
+ represented = true;
2156
+ }
872
2157
  }
873
2158
  }
2159
+ if (signature !== void 0 && !represented) droppedSignatures += 1;
2160
+ }
2161
+ if (droppedCalls.length > 0) {
2162
+ const named = droppedCalls.map((name) => `"${name}"`).join(", ");
2163
+ warnings.push({
2164
+ type: "other",
2165
+ message: `google: dropped ${droppedCalls.length} function call(s) (${named}) because the candidate ended with finishReason ${candidate.finishReason ?? "unspecified"}, not STOP: ${candidate.finishReason === "MAX_TOKENS" ? "the call was cut by the output cap and is incomplete" : "a call beside an abnormal stop is not a call to run"}. The result carries no tool call; finishReason is "${mapFinishReason(candidate.finishReason) ?? "other"}".`
2166
+ });
874
2167
  }
875
2168
  const text = textParts.join("");
876
2169
  const reasoningText = thoughtParts.length > 0 ? thoughtParts.join("") : void 0;
2170
+ let transientProviderState;
2171
+ if (signsHistory) {
2172
+ const issued = [];
2173
+ for (const { partIndex, signature } of issuedSignatures) {
2174
+ const part = messageParts[partIndex];
2175
+ try {
2176
+ issued.push(
2177
+ signatureEntry(req.messages.length, partIndex, model, part, signature)
2178
+ );
2179
+ } catch (error) {
2180
+ if (!(error instanceof LlmError)) throw error;
2181
+ warnings.push({
2182
+ type: "other",
2183
+ message: `google: no signature entry for messages.${req.messages.length}.parts.${partIndex} (a "${part.kind}" part): ${error.message} The result is returned without it; ${part.kind === "tool-call" ? "replaying this function call on the next turn will be rejected" : "a text signature is optional, so nothing required is lost"}.`
2184
+ });
2185
+ }
2186
+ }
2187
+ const signatures = [...incomingSignatures, ...issued];
2188
+ if (signatures.length > 0) {
2189
+ transientProviderState = { google: { signatures } };
2190
+ }
2191
+ if (droppedSignatures > 0) {
2192
+ warnings.push({
2193
+ type: "other",
2194
+ message: `google: dropped ${droppedSignatures} thoughtSignature(s) on parts that have no message representation (thought or empty parts); Gemini requires only the function-call ones.`
2195
+ });
2196
+ }
2197
+ const firstCall = messageParts.findIndex((part) => part.kind === "tool-call");
2198
+ if (firstCall !== -1 && !issuedSignatures.some((entry) => entry.partIndex === firstCall)) {
2199
+ warnings.push({
2200
+ type: "other",
2201
+ message: "google: the first function call in this response carries no thoughtSignature; replaying it on the next turn will be rejected."
2202
+ });
2203
+ }
2204
+ }
877
2205
  let rawStructured;
878
2206
  if (structuredOutputRequested && text.length > 0) {
879
2207
  try {
880
2208
  rawStructured = JSON.parse(text);
881
2209
  } catch {
2210
+ if (descriptor.model.startsWith("gemma-") && /^\s*```/.test(text)) {
2211
+ warnings.push({
2212
+ type: "other",
2213
+ message: `google: gemma_fenced_json: the answer from "${model}" is wrapped in a markdown code fence, so it is not parseable JSON and outputParsed is false. Gemma 4 fenced 67 of 162 schema answers (41%) in a live probe (2026-10-03) although the schema is native. The text is returned as the model sent it; call again, or unwrap the fence on the host side.`
2214
+ });
2215
+ }
882
2216
  }
883
2217
  }
884
- const usage = mapUsage(response.usageMetadata);
2218
+ const usage = usageFor(response.usageMetadata, groundingMetadata);
2219
+ if (response.usageMetadata === void 0) {
2220
+ usage.details["usage_missing"] = 1;
2221
+ warnings.push({
2222
+ type: "other",
2223
+ message: 'google: the response carries no usageMetadata, so token usage is unknown; the call is recorded with zero tokens and is not priced (cost.microUsd is null, cost.confidence is "estimated").'
2224
+ });
2225
+ }
885
2226
  const finishReason = mapFinishReason(candidate.finishReason);
2227
+ warnings.push(...groundingWarnings(groundingMetadata));
2228
+ warnings.push(...modalityWarnings(response.usageMetadata));
2229
+ const answerParts = [];
2230
+ let answerOffset = 0;
2231
+ for (const part of parts) {
2232
+ if (part.thought === true) continue;
2233
+ if (typeof part.text === "string") {
2234
+ answerParts.push({ text: part.text, offset: answerOffset });
2235
+ answerOffset += part.text.length;
2236
+ } else {
2237
+ answerParts.push(void 0);
2238
+ }
2239
+ }
886
2240
  const result = {
887
2241
  model,
2242
+ message: { role: "assistant", parts: messageParts },
888
2243
  usage,
889
2244
  warnings,
2245
+ ...transientProviderState !== void 0 ? { transientProviderState } : {},
890
2246
  ...servedServiceTier !== void 0 ? { servedServiceTier } : {},
891
2247
  ...text.length > 0 ? { text } : {},
892
2248
  ...reasoningText !== void 0 ? { reasoningText } : {},
@@ -894,24 +2250,59 @@ function geminiAdapter(opts) {
894
2250
  ...toolCalls.length > 0 ? { toolCalls, finishReason: "tool_calls" } : finishReason !== void 0 ? { finishReason } : {},
895
2251
  ...response.modelVersion !== void 0 ? { modelVersion: response.modelVersion } : {},
896
2252
  ...response.responseId !== void 0 ? { responseId: response.responseId } : {},
897
- // Build providerMetadata — merge promptFeedback + groundingMetadata when present.
2253
+ // Build providerMetadata — merge promptFeedback + groundingMetadata when
2254
+ // present. `google.searchEntryPoint` is the Search Suggestions widget
2255
+ // Google requires a grounded answer to display.
898
2256
  ...(() => {
899
2257
  const pf = response.promptFeedback;
900
- const gm = candidate.groundingMetadata;
901
- if (pf === void 0 && gm === void 0) return {};
2258
+ const gm = groundingMetadata;
2259
+ const candidateFields = {};
2260
+ const bounded = { truncated: false };
2261
+ for (const key of CANDIDATE_METADATA_KEYS) {
2262
+ const value = candidate[key];
2263
+ if (value === void 0) continue;
2264
+ candidateFields[key] = key === "finishMessage" && typeof value === "string" ? (() => {
2265
+ if (value.length > MAX_FINISH_MESSAGE_CHARS) bounded.truncated = true;
2266
+ return truncateText(value, MAX_FINISH_MESSAGE_CHARS);
2267
+ })() : boundMetadata(value, bounded);
2268
+ }
2269
+ const googleMeta = {};
2270
+ if (Object.keys(candidateFields).length > 0) {
2271
+ googleMeta["candidate"] = candidateFields;
2272
+ }
2273
+ if (pf === void 0 && gm === void 0 && Object.keys(googleMeta).length === 0) {
2274
+ return {};
2275
+ }
902
2276
  const meta = {};
903
2277
  if (pf !== void 0) {
904
- meta["promptFeedback"] = pf;
2278
+ meta["promptFeedback"] = boundMetadata(pf, bounded);
905
2279
  }
906
2280
  if (gm !== void 0) {
907
- meta["groundingMetadata"] = gm;
2281
+ const searchEntryPoint = readSearchEntryPoint(gm);
2282
+ if (searchEntryPoint !== void 0) {
2283
+ const { searchEntryPoint: _widget, ...rest } = gm;
2284
+ meta["groundingMetadata"] = boundMetadata(rest, bounded);
2285
+ googleMeta["searchEntryPoint"] = searchEntryPoint;
2286
+ } else {
2287
+ meta["groundingMetadata"] = boundMetadata(gm, bounded);
2288
+ }
908
2289
  }
2290
+ if (bounded.truncated) {
2291
+ warnings.push({
2292
+ type: "other",
2293
+ message: `google: providerMetadata was truncated (finishMessage over ${MAX_FINISH_MESSAGE_CHARS} characters, a string over ${MAX_METADATA_STRING}, a list over ${MAX_METADATA_ARRAY} entries or nesting over ${MAX_METADATA_DEPTH} levels, in the candidate fields, promptFeedback or groundingMetadata); the citations are built from the full response.`
2294
+ });
2295
+ }
2296
+ if (Object.keys(googleMeta).length > 0) meta["google"] = googleMeta;
909
2297
  return { providerMetadata: meta };
910
2298
  })(),
911
2299
  ...(() => {
912
- const gm = candidate.groundingMetadata;
913
- if (gm === void 0) return {};
914
- const citations = normalizeGroundingCitations(gm);
2300
+ if (groundingMetadata === void 0) return {};
2301
+ const citations = normalizeGroundingCitations(
2302
+ groundingMetadata,
2303
+ answerParts,
2304
+ (message) => warnings.push({ type: "other", message })
2305
+ );
915
2306
  return citations.length > 0 ? { citations } : {};
916
2307
  })()
917
2308
  };
@@ -924,30 +2315,49 @@ function geminiAdapter(opts) {
924
2315
  { kind: "bad_request", retryable: false }
925
2316
  );
926
2317
  }
927
- const contents = mapMessagesToGeminiContents(req.messages);
928
- const countTools = req.tools !== void 0 && req.tools.length > 0 ? [
929
- {
930
- functionDeclarations: req.tools.map((tool) => ({
931
- name: tool.name,
932
- description: tool.description,
933
- parameters: tool.inputJsonSchema
934
- }))
2318
+ const system = req.system !== void 0 && req.system !== "" ? req.system : void 0;
2319
+ const tools = req.tools !== void 0 && req.tools.length > 0 ? req.tools : void 0;
2320
+ if (tools !== void 0) {
2321
+ const descriptor = ctx.modelDescriptor;
2322
+ if (descriptor !== void 0 && descriptor.capabilities?.functionCalling !== true) {
2323
+ throw new LlmError(
2324
+ `tools is not supported for google model "${req.model}" (capabilities.functionCalling is not true).`,
2325
+ { kind: "bad_request", retryable: false, provider: "google" }
2326
+ );
935
2327
  }
936
- ] : void 0;
2328
+ const toolProfile = googleJsonSchemaProfile(descriptor?.model ?? req.model);
2329
+ tools.forEach((tool, index) => {
2330
+ assertJsonSchemaProfile(
2331
+ tool.inputJsonSchema,
2332
+ `tools[${index}].inputJsonSchema`,
2333
+ toolProfile
2334
+ );
2335
+ });
2336
+ }
2337
+ if (ctx.modelDescriptor !== void 0) {
2338
+ assertInputMimeTypesAdmitted(req.messages, ctx.modelDescriptor, "google");
2339
+ }
2340
+ const contents = mapMessagesToGeminiContents(req.messages);
2341
+ assertInlinePayloadWithinLimits(contents, system);
937
2342
  const params = {
938
2343
  model: req.model,
939
2344
  contents,
940
- ...req.system !== void 0 || ctx.signal !== void 0 || countTools !== void 0 ? {
941
- config: {
942
- ...req.system !== void 0 ? { systemInstruction: { parts: [{ text: req.system }] } } : {},
943
- ...ctx.signal !== void 0 ? { abortSignal: ctx.signal } : {},
944
- ...countTools !== void 0 ? { tools: countTools } : {}
945
- }
946
- } : {}
2345
+ ...system !== void 0 ? { systemInstruction: { parts: [{ text: system }] } } : {},
2346
+ ...tools !== void 0 ? {
2347
+ tools: [
2348
+ {
2349
+ functionDeclarations: tools.map((tool) => ({
2350
+ name: tool.name,
2351
+ description: tool.description,
2352
+ parametersJsonSchema: tool.inputJsonSchema
2353
+ }))
2354
+ }
2355
+ ]
2356
+ } : {},
2357
+ ...ctx.signal !== void 0 ? { config: { abortSignal: ctx.signal } } : {}
947
2358
  };
948
2359
  try {
949
- const buildClient = opts?._clientFactory ?? buildGoogleClient;
950
- const client = opts?.client !== void 0 ? opts.client : await buildClient(ctx.auth);
2360
+ const client = injectedClient !== void 0 ? injectedClient : await buildClient(ctx.auth);
951
2361
  const response = await client.models.countTokens(params);
952
2362
  if (response.totalTokens === void 0) {
953
2363
  throw new LlmError(
@@ -956,9 +2366,12 @@ function geminiAdapter(opts) {
956
2366
  );
957
2367
  }
958
2368
  const details = response.cachedContentTokenCount !== void 0 ? { cached: response.cachedContentTokenCount } : void 0;
2369
+ const omitsSignatures = ctx.modelDescriptor?.capabilities?.providerState === true && req.messages.some(
2370
+ (message) => message.role === "assistant" && message.parts.some((part) => part.kind === "tool-call")
2371
+ );
959
2372
  return {
960
2373
  totalTokens: response.totalTokens,
961
- accuracy: "exact",
2374
+ accuracy: omitsSignatures ? "estimated" : "exact",
962
2375
  ...details !== void 0 ? { details } : {},
963
2376
  raw: response
964
2377
  };
@@ -968,129 +2381,52 @@ function geminiAdapter(opts) {
968
2381
  }
969
2382
  };
970
2383
  }
971
-
972
- // src/pricing.ts
973
- var pricingVersion = "gemini-2026-09-25";
974
- var GEMINI_PRICED_TIERS = ["standard", "flex", "batch"];
975
- function tiers(standard, flex, batch) {
976
- return Object.freeze({ standard, flex, batch });
977
- }
978
- var GEMINI_PRICING = Object.freeze({
979
- // Gemini 2.5 Pro. Flex/batch cached equals standard on both context bands.
980
- "gemini-2.5-pro": tiers(
981
- {
982
- inputPerM: 125e4,
983
- cachedPerM: 125e3,
984
- outputPerM: 1e7,
985
- gt200k: { inputPerM: 25e5, cachedPerM: 25e4, outputPerM: 15e6 }
986
- },
987
- {
988
- inputPerM: 625e3,
989
- cachedPerM: 125e3,
990
- outputPerM: 5e6,
991
- gt200k: { inputPerM: 125e4, cachedPerM: 25e4, outputPerM: 75e5 }
992
- },
993
- {
994
- inputPerM: 625e3,
995
- cachedPerM: 125e3,
996
- outputPerM: 5e6,
997
- gt200k: { inputPerM: 125e4, cachedPerM: 25e4, outputPerM: 75e5 }
998
- }
999
- ),
1000
- // Gemini 2.5 Flash. Flex/batch cached stays $0.03.
1001
- "gemini-2.5-flash": tiers(
1002
- { inputPerM: 3e5, cachedPerM: 3e4, outputPerM: 25e5 },
1003
- { inputPerM: 15e4, cachedPerM: 3e4, outputPerM: 125e4 },
1004
- { inputPerM: 15e4, cachedPerM: 3e4, outputPerM: 125e4 }
1005
- ),
1006
- // Gemini 2.5 Flash-Lite. Flex/batch cached stays $0.01.
1007
- "gemini-2.5-flash-lite": tiers(
1008
- { inputPerM: 1e5, cachedPerM: 1e4, outputPerM: 4e5 },
1009
- { inputPerM: 5e4, cachedPerM: 1e4, outputPerM: 2e5 },
1010
- { inputPerM: 5e4, cachedPerM: 1e4, outputPerM: 2e5 }
1011
- ),
1012
- // Gemini 3.1 Flash-Lite. Flex/batch cached is the published $0.0125.
1013
- "gemini-3.1-flash-lite": tiers(
1014
- { inputPerM: 25e4, cachedPerM: 25e3, outputPerM: 15e5 },
1015
- { inputPerM: 125e3, cachedPerM: 12500, outputPerM: 75e4 },
1016
- { inputPerM: 125e3, cachedPerM: 12500, outputPerM: 75e4 }
1017
- ),
1018
- // Gemini 3.8 / 3.7 / 3.6 Flash intro rates (2026-09-25). Flex/batch cached
1019
- // is half of the intro cached rate. Re-snapshot on 2027-01-01.
1020
- "gemini-3.8-flash": tiers(
1021
- { inputPerM: 75e4, cachedPerM: 75e3, outputPerM: 375e4 },
1022
- { inputPerM: 375e3, cachedPerM: 37500, outputPerM: 1875e3 },
1023
- { inputPerM: 375e3, cachedPerM: 37500, outputPerM: 1875e3 }
1024
- ),
1025
- "gemini-3.7-flash": tiers(
1026
- { inputPerM: 75e4, cachedPerM: 75e3, outputPerM: 375e4 },
1027
- { inputPerM: 375e3, cachedPerM: 37500, outputPerM: 1875e3 },
1028
- { inputPerM: 375e3, cachedPerM: 37500, outputPerM: 1875e3 }
1029
- ),
1030
- "gemini-3.6-flash": tiers(
1031
- { inputPerM: 75e4, cachedPerM: 75e3, outputPerM: 375e4 },
1032
- { inputPerM: 375e3, cachedPerM: 37500, outputPerM: 1875e3 },
1033
- { inputPerM: 375e3, cachedPerM: 37500, outputPerM: 1875e3 }
1034
- ),
1035
- // Gemini 3.5 Flash-Lite. Flex/batch cached is the published $0.02, not half of $0.03.
1036
- "gemini-3.5-flash-lite": tiers(
1037
- { inputPerM: 3e5, cachedPerM: 3e4, outputPerM: 25e5 },
1038
- { inputPerM: 15e4, cachedPerM: 2e4, outputPerM: 125e4 },
1039
- { inputPerM: 15e4, cachedPerM: 2e4, outputPerM: 125e4 }
1040
- ),
1041
- // Gemini 3.1 Pro Preview. Flex/batch cached equals standard on both bands.
1042
- "gemini-3.1-pro-preview": tiers(
1043
- {
1044
- inputPerM: 2e6,
1045
- cachedPerM: 2e5,
1046
- outputPerM: 12e6,
1047
- gt200k: { inputPerM: 4e6, cachedPerM: 4e5, outputPerM: 18e6 }
1048
- },
1049
- {
1050
- inputPerM: 1e6,
1051
- cachedPerM: 2e5,
1052
- outputPerM: 6e6,
1053
- gt200k: { inputPerM: 2e6, cachedPerM: 4e5, outputPerM: 9e6 }
1054
- },
1055
- {
1056
- inputPerM: 1e6,
1057
- cachedPerM: 2e5,
1058
- outputPerM: 6e6,
1059
- gt200k: { inputPerM: 2e6, cachedPerM: 4e5, outputPerM: 9e6 }
1060
- }
1061
- )
2384
+ var cachedContentSchema = z.union([
2385
+ z.string().min(1),
2386
+ z.strictObject({
2387
+ cacheName: z.string().min(1).meta({
2388
+ title: "Cache Name",
2389
+ description: "Google cached content resource name."
2390
+ }),
2391
+ toolKinds: z.array(z.string().min(1)).optional().meta({
2392
+ title: "Tool Kinds",
2393
+ description: "The tool kinds the cache holds (`GoogleCacheHandle.toolKinds`), e.g. googleSearch."
2394
+ })
2395
+ })
2396
+ ]).optional().meta({
2397
+ title: "Cached Content",
2398
+ description: "Google cached content: the resource name, or { cacheName, toolKinds } from a cache handle."
1062
2399
  });
1063
- function isPricedGeminiTier(tier) {
1064
- return GEMINI_PRICED_TIERS.includes(tier);
1065
- }
1066
- function lookupGeminiTierRates(model, tier) {
1067
- const entry = Object.hasOwn(GEMINI_PRICING, model) ? GEMINI_PRICING[model] : void 0;
1068
- if (entry === void 0) return void 0;
1069
- if (tier !== void 0 && !isPricedGeminiTier(tier)) return void 0;
1070
- return entry;
1071
- }
1072
- function resolveGeminiRates(model, tier) {
1073
- const entry = lookupGeminiTierRates(model, tier);
1074
- if (entry === void 0) return void 0;
1075
- const key = tier ?? "standard";
1076
- return entry[key];
1077
- }
1078
2400
 
1079
- // src/cost.ts
1080
- function geminiPricingSource() {
1081
- return {
1082
- version: pricingVersion,
1083
- price(model, usage, tier) {
1084
- return computeCost(model, usage, tier, resolveGeminiRates, pricingVersion);
1085
- },
1086
- hasModel(model) {
1087
- return resolveGeminiRates(model, void 0) !== void 0;
1088
- },
1089
- listModels() {
1090
- return Object.keys(GEMINI_PRICING);
1091
- }
1092
- };
1093
- }
2401
+ // src/model-limits.ts
2402
+ var geminiLimits = () => Object.freeze({ contextWindow: 1048576, maxOutputTokens: 65536 });
2403
+ var gemma4Limits = () => Object.freeze({ contextWindow: 262144, maxOutputTokens: null });
2404
+ var GOOGLE_MODEL_LIMITS = {
2405
+ "gemini-2.5-pro": geminiLimits(),
2406
+ "gemini-2.5-flash": geminiLimits(),
2407
+ "gemini-2.5-flash-lite": geminiLimits(),
2408
+ "gemini-3.1-flash-lite": geminiLimits(),
2409
+ "gemini-3.1-pro-preview": geminiLimits(),
2410
+ "gemini-3.8-flash": geminiLimits(),
2411
+ "gemini-3.7-flash": geminiLimits(),
2412
+ "gemini-3.6-flash": geminiLimits(),
2413
+ "gemini-3.5-flash-lite": geminiLimits(),
2414
+ "gemma-4-31b-it": gemma4Limits(),
2415
+ "gemma-4-26b-a4b-it": gemma4Limits()
2416
+ };
2417
+ var GEMINI_INPUT_MIME_TYPES = Object.freeze([
2418
+ "application/pdf",
2419
+ "text/*",
2420
+ "image/*",
2421
+ "audio/*",
2422
+ "video/*"
2423
+ ]);
2424
+ var GEMMA_INPUT_MIME_TYPES = Object.freeze([
2425
+ "image/*",
2426
+ "video/*"
2427
+ ]);
2428
+
2429
+ // src/model-config/gemini-2.5-pro.ts
1094
2430
  var Gemini25ProConfigSchema = z.union([
1095
2431
  z.strictObject({
1096
2432
  temperature: z.number().min(0).max(2).optional().meta({
@@ -1106,7 +2442,7 @@ var Gemini25ProConfigSchema = z.union([
1106
2442
  title: "Top K",
1107
2443
  description: "Top-k sampling limit for gemini-2.5-pro."
1108
2444
  }),
1109
- maxOutputTokens: z.number().int().positive().optional().meta({
2445
+ maxOutputTokens: maxOutputTokensSchema(GOOGLE_MODEL_LIMITS["gemini-2.5-pro"]).optional().meta({
1110
2446
  title: "Max Output Tokens",
1111
2447
  description: "Maximum output token cap for gemini-2.5-pro."
1112
2448
  }),
@@ -1149,25 +2485,26 @@ var Gemini25ProConfigSchema = z.union([
1149
2485
  title: "Reasoning",
1150
2486
  description: "Gemini 2.5 Pro thinkingBudget configuration."
1151
2487
  }),
1152
- timeoutMs: z.number().int().positive().optional().meta({
2488
+ timeoutMs: z.number().int().positive().max(GOOGLE_MAX_TIMEOUT_MS).optional().meta({
1153
2489
  title: "Timeout",
1154
2490
  description: "Logical request timeout in milliseconds."
1155
2491
  }),
1156
2492
  providerOptions: z.strictObject({
1157
2493
  google: z.strictObject({
1158
- cachedContent: z.string().min(1).optional().meta({
1159
- title: "Cached Content",
1160
- description: "Google cached content resource name."
2494
+ requireGrounding: z.boolean().optional().meta({
2495
+ title: "Require Grounding",
2496
+ description: "Fail the call unless the response proves Google Search ran (grounding metadata with at least one query). Judged only on a candidate that finished with STOP: a MAX_TOKENS or other abnormal finish returns with its own finish reason, a filtered one throws content_filter. The error is retryable only when no response schema is attached."
1161
2497
  }),
2498
+ cachedContent: cachedContentSchema,
1162
2499
  safetySettings: z.array(
1163
2500
  z.strictObject({
1164
- category: z.string().min(1).meta({
2501
+ category: z.enum(GOOGLE_SAFETY_CATEGORIES).meta({
1165
2502
  title: "Safety Category",
1166
- description: "Google safety category identifier."
2503
+ description: "Documented Google safety category."
1167
2504
  }),
1168
- threshold: z.string().min(1).meta({
2505
+ threshold: z.enum(GOOGLE_SAFETY_THRESHOLDS).meta({
1169
2506
  title: "Safety Threshold",
1170
- description: "Google safety threshold identifier."
2507
+ description: "Documented Google safety threshold."
1171
2508
  })
1172
2509
  })
1173
2510
  ).optional().meta({
@@ -1186,9 +2523,9 @@ var Gemini25ProConfigSchema = z.union([
1186
2523
  description: "Allowlisted Google tools for gemini-2.5-pro."
1187
2524
  }),
1188
2525
  httpOptions: z.strictObject({
1189
- timeout: z.number().int().positive().optional().meta({
2526
+ timeout: z.number().int().positive().max(MAX_TIMER_MS).optional().meta({
1190
2527
  title: "HTTP Timeout",
1191
- description: "Per-request Google transport timeout in milliseconds."
2528
+ description: "Per-request Google transport timeout in milliseconds, at most 2147483647; with timeoutMs set, at least timeoutMs + 5000."
1192
2529
  })
1193
2530
  }).optional().meta({
1194
2531
  title: "HTTP Options",
@@ -1221,7 +2558,7 @@ var Gemini25ProConfigSchema = z.union([
1221
2558
  title: "Top K",
1222
2559
  description: "Top-k sampling limit for gemini-2.5-pro."
1223
2560
  }),
1224
- maxOutputTokens: z.number().int().positive().optional().meta({
2561
+ maxOutputTokens: maxOutputTokensSchema(GOOGLE_MODEL_LIMITS["gemini-2.5-pro"]).optional().meta({
1225
2562
  title: "Max Output Tokens",
1226
2563
  description: "Maximum output token cap for gemini-2.5-pro."
1227
2564
  }),
@@ -1264,25 +2601,26 @@ var Gemini25ProConfigSchema = z.union([
1264
2601
  title: "Reasoning",
1265
2602
  description: "Gemini 2.5 Pro thinkingBudget configuration."
1266
2603
  }),
1267
- timeoutMs: z.number().int().positive().optional().meta({
2604
+ timeoutMs: z.number().int().positive().max(GOOGLE_MAX_TIMEOUT_MS).optional().meta({
1268
2605
  title: "Timeout",
1269
2606
  description: "Logical request timeout in milliseconds."
1270
2607
  }),
1271
2608
  providerOptions: z.strictObject({
1272
2609
  google: z.strictObject({
1273
- cachedContent: z.string().min(1).optional().meta({
1274
- title: "Cached Content",
1275
- description: "Google cached content resource name."
2610
+ requireGrounding: z.boolean().optional().meta({
2611
+ title: "Require Grounding",
2612
+ description: "Fail the call unless the response proves Google Search ran (grounding metadata with at least one query). Judged only on a candidate that finished with STOP: a MAX_TOKENS or other abnormal finish returns with its own finish reason, a filtered one throws content_filter. The error is retryable only when no response schema is attached."
1276
2613
  }),
2614
+ cachedContent: cachedContentSchema,
1277
2615
  safetySettings: z.array(
1278
2616
  z.strictObject({
1279
- category: z.string().min(1).meta({
2617
+ category: z.enum(GOOGLE_SAFETY_CATEGORIES).meta({
1280
2618
  title: "Safety Category",
1281
- description: "Google safety category identifier."
2619
+ description: "Documented Google safety category."
1282
2620
  }),
1283
- threshold: z.string().min(1).meta({
2621
+ threshold: z.enum(GOOGLE_SAFETY_THRESHOLDS).meta({
1284
2622
  title: "Safety Threshold",
1285
- description: "Google safety threshold identifier."
2623
+ description: "Documented Google safety threshold."
1286
2624
  })
1287
2625
  })
1288
2626
  ).optional().meta({
@@ -1301,9 +2639,9 @@ var Gemini25ProConfigSchema = z.union([
1301
2639
  description: "Allowlisted Google tools for gemini-2.5-pro."
1302
2640
  }),
1303
2641
  httpOptions: z.strictObject({
1304
- timeout: z.number().int().positive().optional().meta({
2642
+ timeout: z.number().int().positive().max(MAX_TIMER_MS).optional().meta({
1305
2643
  title: "HTTP Timeout",
1306
- description: "Per-request Google transport timeout in milliseconds."
2644
+ description: "Per-request Google transport timeout in milliseconds, at most 2147483647; with timeoutMs set, at least timeoutMs + 5000."
1307
2645
  })
1308
2646
  }).optional().meta({
1309
2647
  title: "HTTP Options",
@@ -1338,7 +2676,7 @@ var Gemini25FlashConfigSchema = z.union([
1338
2676
  title: "Top K",
1339
2677
  description: "Top-k sampling limit for gemini-2.5-flash."
1340
2678
  }),
1341
- maxOutputTokens: z.number().int().positive().optional().meta({
2679
+ maxOutputTokens: maxOutputTokensSchema(GOOGLE_MODEL_LIMITS["gemini-2.5-flash"]).optional().meta({
1342
2680
  title: "Max Output Tokens",
1343
2681
  description: "Maximum output token cap for gemini-2.5-flash."
1344
2682
  }),
@@ -1381,25 +2719,26 @@ var Gemini25FlashConfigSchema = z.union([
1381
2719
  title: "Reasoning",
1382
2720
  description: "Gemini 2.5 Flash thinkingBudget configuration."
1383
2721
  }),
1384
- timeoutMs: z.number().int().positive().optional().meta({
2722
+ timeoutMs: z.number().int().positive().max(GOOGLE_MAX_TIMEOUT_MS).optional().meta({
1385
2723
  title: "Timeout",
1386
2724
  description: "Logical request timeout in milliseconds."
1387
2725
  }),
1388
2726
  providerOptions: z.strictObject({
1389
2727
  google: z.strictObject({
1390
- cachedContent: z.string().min(1).optional().meta({
1391
- title: "Cached Content",
1392
- description: "Google cached content resource name."
2728
+ requireGrounding: z.boolean().optional().meta({
2729
+ title: "Require Grounding",
2730
+ description: "Fail the call unless the response proves Google Search ran (grounding metadata with at least one query). Judged only on a candidate that finished with STOP: a MAX_TOKENS or other abnormal finish returns with its own finish reason, a filtered one throws content_filter. The error is retryable only when no response schema is attached."
1393
2731
  }),
2732
+ cachedContent: cachedContentSchema,
1394
2733
  safetySettings: z.array(
1395
2734
  z.strictObject({
1396
- category: z.string().min(1).meta({
2735
+ category: z.enum(GOOGLE_SAFETY_CATEGORIES).meta({
1397
2736
  title: "Safety Category",
1398
- description: "Google safety category identifier."
2737
+ description: "Documented Google safety category."
1399
2738
  }),
1400
- threshold: z.string().min(1).meta({
2739
+ threshold: z.enum(GOOGLE_SAFETY_THRESHOLDS).meta({
1401
2740
  title: "Safety Threshold",
1402
- description: "Google safety threshold identifier."
2741
+ description: "Documented Google safety threshold."
1403
2742
  })
1404
2743
  })
1405
2744
  ).optional().meta({
@@ -1418,9 +2757,9 @@ var Gemini25FlashConfigSchema = z.union([
1418
2757
  description: "Allowlisted Google tools for gemini-2.5-flash."
1419
2758
  }),
1420
2759
  httpOptions: z.strictObject({
1421
- timeout: z.number().int().positive().optional().meta({
2760
+ timeout: z.number().int().positive().max(MAX_TIMER_MS).optional().meta({
1422
2761
  title: "HTTP Timeout",
1423
- description: "Per-request Google transport timeout in milliseconds."
2762
+ description: "Per-request Google transport timeout in milliseconds, at most 2147483647; with timeoutMs set, at least timeoutMs + 5000."
1424
2763
  })
1425
2764
  }).optional().meta({
1426
2765
  title: "HTTP Options",
@@ -1453,7 +2792,7 @@ var Gemini25FlashConfigSchema = z.union([
1453
2792
  title: "Top K",
1454
2793
  description: "Top-k sampling limit for gemini-2.5-flash."
1455
2794
  }),
1456
- maxOutputTokens: z.number().int().positive().optional().meta({
2795
+ maxOutputTokens: maxOutputTokensSchema(GOOGLE_MODEL_LIMITS["gemini-2.5-flash"]).optional().meta({
1457
2796
  title: "Max Output Tokens",
1458
2797
  description: "Maximum output token cap for gemini-2.5-flash."
1459
2798
  }),
@@ -1496,25 +2835,26 @@ var Gemini25FlashConfigSchema = z.union([
1496
2835
  title: "Reasoning",
1497
2836
  description: "Gemini 2.5 Flash thinkingBudget configuration."
1498
2837
  }),
1499
- timeoutMs: z.number().int().positive().optional().meta({
2838
+ timeoutMs: z.number().int().positive().max(GOOGLE_MAX_TIMEOUT_MS).optional().meta({
1500
2839
  title: "Timeout",
1501
2840
  description: "Logical request timeout in milliseconds."
1502
2841
  }),
1503
2842
  providerOptions: z.strictObject({
1504
2843
  google: z.strictObject({
1505
- cachedContent: z.string().min(1).optional().meta({
1506
- title: "Cached Content",
1507
- description: "Google cached content resource name."
2844
+ requireGrounding: z.boolean().optional().meta({
2845
+ title: "Require Grounding",
2846
+ description: "Fail the call unless the response proves Google Search ran (grounding metadata with at least one query). Judged only on a candidate that finished with STOP: a MAX_TOKENS or other abnormal finish returns with its own finish reason, a filtered one throws content_filter. The error is retryable only when no response schema is attached."
1508
2847
  }),
2848
+ cachedContent: cachedContentSchema,
1509
2849
  safetySettings: z.array(
1510
2850
  z.strictObject({
1511
- category: z.string().min(1).meta({
2851
+ category: z.enum(GOOGLE_SAFETY_CATEGORIES).meta({
1512
2852
  title: "Safety Category",
1513
- description: "Google safety category identifier."
2853
+ description: "Documented Google safety category."
1514
2854
  }),
1515
- threshold: z.string().min(1).meta({
2855
+ threshold: z.enum(GOOGLE_SAFETY_THRESHOLDS).meta({
1516
2856
  title: "Safety Threshold",
1517
- description: "Google safety threshold identifier."
2857
+ description: "Documented Google safety threshold."
1518
2858
  })
1519
2859
  })
1520
2860
  ).optional().meta({
@@ -1533,9 +2873,9 @@ var Gemini25FlashConfigSchema = z.union([
1533
2873
  description: "Allowlisted Google tools for gemini-2.5-flash."
1534
2874
  }),
1535
2875
  httpOptions: z.strictObject({
1536
- timeout: z.number().int().positive().optional().meta({
2876
+ timeout: z.number().int().positive().max(MAX_TIMER_MS).optional().meta({
1537
2877
  title: "HTTP Timeout",
1538
- description: "Per-request Google transport timeout in milliseconds."
2878
+ description: "Per-request Google transport timeout in milliseconds, at most 2147483647; with timeoutMs set, at least timeoutMs + 5000."
1539
2879
  })
1540
2880
  }).optional().meta({
1541
2881
  title: "HTTP Options",
@@ -1570,7 +2910,7 @@ var Gemini25FlashLiteConfigSchema = z.union([
1570
2910
  title: "Top K",
1571
2911
  description: "Top-k sampling limit for gemini-2.5-flash-lite."
1572
2912
  }),
1573
- maxOutputTokens: z.number().int().positive().optional().meta({
2913
+ maxOutputTokens: maxOutputTokensSchema(GOOGLE_MODEL_LIMITS["gemini-2.5-flash-lite"]).optional().meta({
1574
2914
  title: "Max Output Tokens",
1575
2915
  description: "Maximum output token cap for gemini-2.5-flash-lite."
1576
2916
  }),
@@ -1613,25 +2953,26 @@ var Gemini25FlashLiteConfigSchema = z.union([
1613
2953
  title: "Reasoning",
1614
2954
  description: "Gemini 2.5 Flash-Lite thinkingBudget configuration."
1615
2955
  }),
1616
- timeoutMs: z.number().int().positive().optional().meta({
2956
+ timeoutMs: z.number().int().positive().max(GOOGLE_MAX_TIMEOUT_MS).optional().meta({
1617
2957
  title: "Timeout",
1618
2958
  description: "Logical request timeout in milliseconds."
1619
2959
  }),
1620
2960
  providerOptions: z.strictObject({
1621
2961
  google: z.strictObject({
1622
- cachedContent: z.string().min(1).optional().meta({
1623
- title: "Cached Content",
1624
- description: "Google cached content resource name."
2962
+ requireGrounding: z.boolean().optional().meta({
2963
+ title: "Require Grounding",
2964
+ description: "Fail the call unless the response proves Google Search ran (grounding metadata with at least one query). Judged only on a candidate that finished with STOP: a MAX_TOKENS or other abnormal finish returns with its own finish reason, a filtered one throws content_filter. The error is retryable only when no response schema is attached."
1625
2965
  }),
2966
+ cachedContent: cachedContentSchema,
1626
2967
  safetySettings: z.array(
1627
2968
  z.strictObject({
1628
- category: z.string().min(1).meta({
2969
+ category: z.enum(GOOGLE_SAFETY_CATEGORIES).meta({
1629
2970
  title: "Safety Category",
1630
- description: "Google safety category identifier."
2971
+ description: "Documented Google safety category."
1631
2972
  }),
1632
- threshold: z.string().min(1).meta({
2973
+ threshold: z.enum(GOOGLE_SAFETY_THRESHOLDS).meta({
1633
2974
  title: "Safety Threshold",
1634
- description: "Google safety threshold identifier."
2975
+ description: "Documented Google safety threshold."
1635
2976
  })
1636
2977
  })
1637
2978
  ).optional().meta({
@@ -1650,9 +2991,9 @@ var Gemini25FlashLiteConfigSchema = z.union([
1650
2991
  description: "Allowlisted Google tools for gemini-2.5-flash-lite."
1651
2992
  }),
1652
2993
  httpOptions: z.strictObject({
1653
- timeout: z.number().int().positive().optional().meta({
2994
+ timeout: z.number().int().positive().max(MAX_TIMER_MS).optional().meta({
1654
2995
  title: "HTTP Timeout",
1655
- description: "Per-request Google transport timeout in milliseconds."
2996
+ description: "Per-request Google transport timeout in milliseconds, at most 2147483647; with timeoutMs set, at least timeoutMs + 5000."
1656
2997
  })
1657
2998
  }).optional().meta({
1658
2999
  title: "HTTP Options",
@@ -1685,7 +3026,7 @@ var Gemini25FlashLiteConfigSchema = z.union([
1685
3026
  title: "Top K",
1686
3027
  description: "Top-k sampling limit for gemini-2.5-flash-lite."
1687
3028
  }),
1688
- maxOutputTokens: z.number().int().positive().optional().meta({
3029
+ maxOutputTokens: maxOutputTokensSchema(GOOGLE_MODEL_LIMITS["gemini-2.5-flash-lite"]).optional().meta({
1689
3030
  title: "Max Output Tokens",
1690
3031
  description: "Maximum output token cap for gemini-2.5-flash-lite."
1691
3032
  }),
@@ -1728,25 +3069,26 @@ var Gemini25FlashLiteConfigSchema = z.union([
1728
3069
  title: "Reasoning",
1729
3070
  description: "Gemini 2.5 Flash-Lite thinkingBudget configuration."
1730
3071
  }),
1731
- timeoutMs: z.number().int().positive().optional().meta({
3072
+ timeoutMs: z.number().int().positive().max(GOOGLE_MAX_TIMEOUT_MS).optional().meta({
1732
3073
  title: "Timeout",
1733
3074
  description: "Logical request timeout in milliseconds."
1734
3075
  }),
1735
3076
  providerOptions: z.strictObject({
1736
3077
  google: z.strictObject({
1737
- cachedContent: z.string().min(1).optional().meta({
1738
- title: "Cached Content",
1739
- description: "Google cached content resource name."
3078
+ requireGrounding: z.boolean().optional().meta({
3079
+ title: "Require Grounding",
3080
+ description: "Fail the call unless the response proves Google Search ran (grounding metadata with at least one query). Judged only on a candidate that finished with STOP: a MAX_TOKENS or other abnormal finish returns with its own finish reason, a filtered one throws content_filter. The error is retryable only when no response schema is attached."
1740
3081
  }),
3082
+ cachedContent: cachedContentSchema,
1741
3083
  safetySettings: z.array(
1742
3084
  z.strictObject({
1743
- category: z.string().min(1).meta({
3085
+ category: z.enum(GOOGLE_SAFETY_CATEGORIES).meta({
1744
3086
  title: "Safety Category",
1745
- description: "Google safety category identifier."
3087
+ description: "Documented Google safety category."
1746
3088
  }),
1747
- threshold: z.string().min(1).meta({
3089
+ threshold: z.enum(GOOGLE_SAFETY_THRESHOLDS).meta({
1748
3090
  title: "Safety Threshold",
1749
- description: "Google safety threshold identifier."
3091
+ description: "Documented Google safety threshold."
1750
3092
  })
1751
3093
  })
1752
3094
  ).optional().meta({
@@ -1765,9 +3107,9 @@ var Gemini25FlashLiteConfigSchema = z.union([
1765
3107
  description: "Allowlisted Google tools for gemini-2.5-flash-lite."
1766
3108
  }),
1767
3109
  httpOptions: z.strictObject({
1768
- timeout: z.number().int().positive().optional().meta({
3110
+ timeout: z.number().int().positive().max(MAX_TIMER_MS).optional().meta({
1769
3111
  title: "HTTP Timeout",
1770
- description: "Per-request Google transport timeout in milliseconds."
3112
+ description: "Per-request Google transport timeout in milliseconds, at most 2147483647; with timeoutMs set, at least timeoutMs + 5000."
1771
3113
  })
1772
3114
  }).optional().meta({
1773
3115
  title: "HTTP Options",
@@ -1789,7 +3131,7 @@ var Gemini25FlashLiteConfigSchema = z.union([
1789
3131
  });
1790
3132
  var Gemini31FlashLiteConfigSchema = z.union([
1791
3133
  z.strictObject({
1792
- maxOutputTokens: z.number().int().positive().optional().meta({
3134
+ maxOutputTokens: maxOutputTokensSchema(GOOGLE_MODEL_LIMITS["gemini-3.1-flash-lite"]).optional().meta({
1793
3135
  title: "Max Output Tokens",
1794
3136
  description: "Maximum output token cap for gemini-3.1-flash-lite."
1795
3137
  }),
@@ -1822,25 +3164,30 @@ var Gemini31FlashLiteConfigSchema = z.union([
1822
3164
  title: "Reasoning",
1823
3165
  description: "Gemini 3.1 Flash-Lite thinkingLevel configuration."
1824
3166
  }),
1825
- timeoutMs: z.number().int().positive().optional().meta({
3167
+ timeoutMs: z.number().int().positive().max(GOOGLE_MAX_TIMEOUT_MS).optional().meta({
1826
3168
  title: "Timeout",
1827
3169
  description: "Logical request timeout in milliseconds."
1828
3170
  }),
1829
3171
  providerOptions: z.strictObject({
1830
3172
  google: z.strictObject({
1831
- cachedContent: z.string().min(1).optional().meta({
1832
- title: "Cached Content",
1833
- description: "Google cached content resource name."
3173
+ allowSchemaWithSearch: z.boolean().optional().meta({
3174
+ title: "Allow Schema With Search",
3175
+ description: "Admit googleSearch together with a response schema. Turns requireGrounding on unless it is false."
1834
3176
  }),
3177
+ requireGrounding: z.boolean().optional().meta({
3178
+ title: "Require Grounding",
3179
+ description: "Fail the call unless the response proves Google Search ran (grounding metadata with at least one query). Judged only on a candidate that finished with STOP: a MAX_TOKENS or other abnormal finish returns with its own finish reason, a filtered one throws content_filter. The error is retryable only when no response schema is attached."
3180
+ }),
3181
+ cachedContent: cachedContentSchema,
1835
3182
  safetySettings: z.array(
1836
3183
  z.strictObject({
1837
- category: z.string().min(1).meta({
3184
+ category: z.enum(GOOGLE_SAFETY_CATEGORIES).meta({
1838
3185
  title: "Safety Category",
1839
- description: "Google safety category identifier."
3186
+ description: "Documented Google safety category."
1840
3187
  }),
1841
- threshold: z.string().min(1).meta({
3188
+ threshold: z.enum(GOOGLE_SAFETY_THRESHOLDS).meta({
1842
3189
  title: "Safety Threshold",
1843
- description: "Google safety threshold identifier."
3190
+ description: "Documented Google safety threshold."
1844
3191
  })
1845
3192
  })
1846
3193
  ).optional().meta({
@@ -1859,9 +3206,9 @@ var Gemini31FlashLiteConfigSchema = z.union([
1859
3206
  description: "Allowlisted Google tools for gemini-3.1-flash-lite."
1860
3207
  }),
1861
3208
  httpOptions: z.strictObject({
1862
- timeout: z.number().int().positive().optional().meta({
3209
+ timeout: z.number().int().positive().max(MAX_TIMER_MS).optional().meta({
1863
3210
  title: "HTTP Timeout",
1864
- description: "Per-request Google transport timeout in milliseconds."
3211
+ description: "Per-request Google transport timeout in milliseconds, at most 2147483647; with timeoutMs set, at least timeoutMs + 5000."
1865
3212
  })
1866
3213
  }).optional().meta({
1867
3214
  title: "HTTP Options",
@@ -1881,7 +3228,7 @@ var Gemini31FlashLiteConfigSchema = z.union([
1881
3228
  })
1882
3229
  }),
1883
3230
  z.strictObject({
1884
- maxOutputTokens: z.number().int().positive().optional().meta({
3231
+ maxOutputTokens: maxOutputTokensSchema(GOOGLE_MODEL_LIMITS["gemini-3.1-flash-lite"]).optional().meta({
1885
3232
  title: "Max Output Tokens",
1886
3233
  description: "Maximum output token cap for gemini-3.1-flash-lite."
1887
3234
  }),
@@ -1914,25 +3261,30 @@ var Gemini31FlashLiteConfigSchema = z.union([
1914
3261
  title: "Reasoning",
1915
3262
  description: "Gemini 3.1 Flash-Lite thinkingLevel configuration."
1916
3263
  }),
1917
- timeoutMs: z.number().int().positive().optional().meta({
3264
+ timeoutMs: z.number().int().positive().max(GOOGLE_MAX_TIMEOUT_MS).optional().meta({
1918
3265
  title: "Timeout",
1919
3266
  description: "Logical request timeout in milliseconds."
1920
3267
  }),
1921
3268
  providerOptions: z.strictObject({
1922
3269
  google: z.strictObject({
1923
- cachedContent: z.string().min(1).optional().meta({
1924
- title: "Cached Content",
1925
- description: "Google cached content resource name."
3270
+ allowSchemaWithSearch: z.boolean().optional().meta({
3271
+ title: "Allow Schema With Search",
3272
+ description: "Admit googleSearch together with a response schema. Turns requireGrounding on unless it is false."
3273
+ }),
3274
+ requireGrounding: z.boolean().optional().meta({
3275
+ title: "Require Grounding",
3276
+ description: "Fail the call unless the response proves Google Search ran (grounding metadata with at least one query). Judged only on a candidate that finished with STOP: a MAX_TOKENS or other abnormal finish returns with its own finish reason, a filtered one throws content_filter. The error is retryable only when no response schema is attached."
1926
3277
  }),
3278
+ cachedContent: cachedContentSchema,
1927
3279
  safetySettings: z.array(
1928
3280
  z.strictObject({
1929
- category: z.string().min(1).meta({
3281
+ category: z.enum(GOOGLE_SAFETY_CATEGORIES).meta({
1930
3282
  title: "Safety Category",
1931
- description: "Google safety category identifier."
3283
+ description: "Documented Google safety category."
1932
3284
  }),
1933
- threshold: z.string().min(1).meta({
3285
+ threshold: z.enum(GOOGLE_SAFETY_THRESHOLDS).meta({
1934
3286
  title: "Safety Threshold",
1935
- description: "Google safety threshold identifier."
3287
+ description: "Documented Google safety threshold."
1936
3288
  })
1937
3289
  })
1938
3290
  ).optional().meta({
@@ -1951,9 +3303,9 @@ var Gemini31FlashLiteConfigSchema = z.union([
1951
3303
  description: "Allowlisted Google tools for gemini-3.1-flash-lite."
1952
3304
  }),
1953
3305
  httpOptions: z.strictObject({
1954
- timeout: z.number().int().positive().optional().meta({
3306
+ timeout: z.number().int().positive().max(MAX_TIMER_MS).optional().meta({
1955
3307
  title: "HTTP Timeout",
1956
- description: "Per-request Google transport timeout in milliseconds."
3308
+ description: "Per-request Google transport timeout in milliseconds, at most 2147483647; with timeoutMs set, at least timeoutMs + 5000."
1957
3309
  })
1958
3310
  }).optional().meta({
1959
3311
  title: "HTTP Options",
@@ -1975,7 +3327,9 @@ var Gemini31FlashLiteConfigSchema = z.union([
1975
3327
  });
1976
3328
  var Gemini31ProPreviewConfigSchema = z.union([
1977
3329
  z.strictObject({
1978
- maxOutputTokens: z.number().int().positive().optional().meta({
3330
+ maxOutputTokens: maxOutputTokensSchema(
3331
+ GOOGLE_MODEL_LIMITS["gemini-3.1-pro-preview"]
3332
+ ).optional().meta({
1979
3333
  title: "Max Output Tokens",
1980
3334
  description: "Maximum output token cap for gemini-3.1-pro-preview."
1981
3335
  }),
@@ -2008,25 +3362,30 @@ var Gemini31ProPreviewConfigSchema = z.union([
2008
3362
  title: "Reasoning",
2009
3363
  description: "Gemini 3.1 Pro Preview thinkingLevel configuration."
2010
3364
  }),
2011
- timeoutMs: z.number().int().positive().optional().meta({
3365
+ timeoutMs: z.number().int().positive().max(GOOGLE_MAX_TIMEOUT_MS).optional().meta({
2012
3366
  title: "Timeout",
2013
3367
  description: "Logical request timeout in milliseconds."
2014
3368
  }),
2015
3369
  providerOptions: z.strictObject({
2016
3370
  google: z.strictObject({
2017
- cachedContent: z.string().min(1).optional().meta({
2018
- title: "Cached Content",
2019
- description: "Google cached content resource name."
3371
+ allowSchemaWithSearch: z.boolean().optional().meta({
3372
+ title: "Allow Schema With Search",
3373
+ description: "Admit googleSearch together with a response schema. Turns requireGrounding on unless it is false."
2020
3374
  }),
3375
+ requireGrounding: z.boolean().optional().meta({
3376
+ title: "Require Grounding",
3377
+ description: "Fail the call unless the response proves Google Search ran (grounding metadata with at least one query). Judged only on a candidate that finished with STOP: a MAX_TOKENS or other abnormal finish returns with its own finish reason, a filtered one throws content_filter. The error is retryable only when no response schema is attached."
3378
+ }),
3379
+ cachedContent: cachedContentSchema,
2021
3380
  safetySettings: z.array(
2022
3381
  z.strictObject({
2023
- category: z.string().min(1).meta({
3382
+ category: z.enum(GOOGLE_SAFETY_CATEGORIES).meta({
2024
3383
  title: "Safety Category",
2025
- description: "Google safety category identifier."
3384
+ description: "Documented Google safety category."
2026
3385
  }),
2027
- threshold: z.string().min(1).meta({
3386
+ threshold: z.enum(GOOGLE_SAFETY_THRESHOLDS).meta({
2028
3387
  title: "Safety Threshold",
2029
- description: "Google safety threshold identifier."
3388
+ description: "Documented Google safety threshold."
2030
3389
  })
2031
3390
  })
2032
3391
  ).optional().meta({
@@ -2045,9 +3404,9 @@ var Gemini31ProPreviewConfigSchema = z.union([
2045
3404
  description: "Allowlisted Google tools for gemini-3.1-pro-preview."
2046
3405
  }),
2047
3406
  httpOptions: z.strictObject({
2048
- timeout: z.number().int().positive().optional().meta({
3407
+ timeout: z.number().int().positive().max(MAX_TIMER_MS).optional().meta({
2049
3408
  title: "HTTP Timeout",
2050
- description: "Per-request Google transport timeout in milliseconds."
3409
+ description: "Per-request Google transport timeout in milliseconds, at most 2147483647; with timeoutMs set, at least timeoutMs + 5000."
2051
3410
  })
2052
3411
  }).optional().meta({
2053
3412
  title: "HTTP Options",
@@ -2067,7 +3426,9 @@ var Gemini31ProPreviewConfigSchema = z.union([
2067
3426
  })
2068
3427
  }),
2069
3428
  z.strictObject({
2070
- maxOutputTokens: z.number().int().positive().optional().meta({
3429
+ maxOutputTokens: maxOutputTokensSchema(
3430
+ GOOGLE_MODEL_LIMITS["gemini-3.1-pro-preview"]
3431
+ ).optional().meta({
2071
3432
  title: "Max Output Tokens",
2072
3433
  description: "Maximum output token cap for gemini-3.1-pro-preview."
2073
3434
  }),
@@ -2100,25 +3461,30 @@ var Gemini31ProPreviewConfigSchema = z.union([
2100
3461
  title: "Reasoning",
2101
3462
  description: "Gemini 3.1 Pro Preview thinkingLevel configuration."
2102
3463
  }),
2103
- timeoutMs: z.number().int().positive().optional().meta({
3464
+ timeoutMs: z.number().int().positive().max(GOOGLE_MAX_TIMEOUT_MS).optional().meta({
2104
3465
  title: "Timeout",
2105
3466
  description: "Logical request timeout in milliseconds."
2106
3467
  }),
2107
3468
  providerOptions: z.strictObject({
2108
3469
  google: z.strictObject({
2109
- cachedContent: z.string().min(1).optional().meta({
2110
- title: "Cached Content",
2111
- description: "Google cached content resource name."
3470
+ allowSchemaWithSearch: z.boolean().optional().meta({
3471
+ title: "Allow Schema With Search",
3472
+ description: "Admit googleSearch together with a response schema. Turns requireGrounding on unless it is false."
3473
+ }),
3474
+ requireGrounding: z.boolean().optional().meta({
3475
+ title: "Require Grounding",
3476
+ description: "Fail the call unless the response proves Google Search ran (grounding metadata with at least one query). Judged only on a candidate that finished with STOP: a MAX_TOKENS or other abnormal finish returns with its own finish reason, a filtered one throws content_filter. The error is retryable only when no response schema is attached."
2112
3477
  }),
3478
+ cachedContent: cachedContentSchema,
2113
3479
  safetySettings: z.array(
2114
3480
  z.strictObject({
2115
- category: z.string().min(1).meta({
3481
+ category: z.enum(GOOGLE_SAFETY_CATEGORIES).meta({
2116
3482
  title: "Safety Category",
2117
- description: "Google safety category identifier."
3483
+ description: "Documented Google safety category."
2118
3484
  }),
2119
- threshold: z.string().min(1).meta({
3485
+ threshold: z.enum(GOOGLE_SAFETY_THRESHOLDS).meta({
2120
3486
  title: "Safety Threshold",
2121
- description: "Google safety threshold identifier."
3487
+ description: "Documented Google safety threshold."
2122
3488
  })
2123
3489
  })
2124
3490
  ).optional().meta({
@@ -2137,9 +3503,9 @@ var Gemini31ProPreviewConfigSchema = z.union([
2137
3503
  description: "Allowlisted Google tools for gemini-3.1-pro-preview."
2138
3504
  }),
2139
3505
  httpOptions: z.strictObject({
2140
- timeout: z.number().int().positive().optional().meta({
3506
+ timeout: z.number().int().positive().max(MAX_TIMER_MS).optional().meta({
2141
3507
  title: "HTTP Timeout",
2142
- description: "Per-request Google transport timeout in milliseconds."
3508
+ description: "Per-request Google transport timeout in milliseconds, at most 2147483647; with timeoutMs set, at least timeoutMs + 5000."
2143
3509
  })
2144
3510
  }).optional().meta({
2145
3511
  title: "HTTP Options",
@@ -2161,7 +3527,7 @@ var Gemini31ProPreviewConfigSchema = z.union([
2161
3527
  });
2162
3528
  var Gemini35FlashLiteConfigSchema = z.union([
2163
3529
  z.strictObject({
2164
- maxOutputTokens: z.number().int().positive().optional().meta({
3530
+ maxOutputTokens: maxOutputTokensSchema(GOOGLE_MODEL_LIMITS["gemini-3.5-flash-lite"]).optional().meta({
2165
3531
  title: "Max Output Tokens",
2166
3532
  description: "Maximum output token cap for gemini-3.5-flash-lite."
2167
3533
  }),
@@ -2194,25 +3560,30 @@ var Gemini35FlashLiteConfigSchema = z.union([
2194
3560
  title: "Reasoning",
2195
3561
  description: "Gemini 3.5 Flash-Lite thinkingLevel configuration."
2196
3562
  }),
2197
- timeoutMs: z.number().int().positive().optional().meta({
3563
+ timeoutMs: z.number().int().positive().max(GOOGLE_MAX_TIMEOUT_MS).optional().meta({
2198
3564
  title: "Timeout",
2199
3565
  description: "Logical request timeout in milliseconds."
2200
3566
  }),
2201
3567
  providerOptions: z.strictObject({
2202
3568
  google: z.strictObject({
2203
- cachedContent: z.string().min(1).optional().meta({
2204
- title: "Cached Content",
2205
- description: "Google cached content resource name."
3569
+ allowSchemaWithSearch: z.boolean().optional().meta({
3570
+ title: "Allow Schema With Search",
3571
+ description: "Admit googleSearch together with a response schema. Turns requireGrounding on unless it is false."
2206
3572
  }),
3573
+ requireGrounding: z.boolean().optional().meta({
3574
+ title: "Require Grounding",
3575
+ description: "Fail the call unless the response proves Google Search ran (grounding metadata with at least one query). Judged only on a candidate that finished with STOP: a MAX_TOKENS or other abnormal finish returns with its own finish reason, a filtered one throws content_filter. The error is retryable only when no response schema is attached."
3576
+ }),
3577
+ cachedContent: cachedContentSchema,
2207
3578
  safetySettings: z.array(
2208
3579
  z.strictObject({
2209
- category: z.string().min(1).meta({
3580
+ category: z.enum(GOOGLE_SAFETY_CATEGORIES).meta({
2210
3581
  title: "Safety Category",
2211
- description: "Google safety category identifier."
3582
+ description: "Documented Google safety category."
2212
3583
  }),
2213
- threshold: z.string().min(1).meta({
3584
+ threshold: z.enum(GOOGLE_SAFETY_THRESHOLDS).meta({
2214
3585
  title: "Safety Threshold",
2215
- description: "Google safety threshold identifier."
3586
+ description: "Documented Google safety threshold."
2216
3587
  })
2217
3588
  })
2218
3589
  ).optional().meta({
@@ -2231,9 +3602,9 @@ var Gemini35FlashLiteConfigSchema = z.union([
2231
3602
  description: "Allowlisted Google tools for gemini-3.5-flash-lite."
2232
3603
  }),
2233
3604
  httpOptions: z.strictObject({
2234
- timeout: z.number().int().positive().optional().meta({
3605
+ timeout: z.number().int().positive().max(MAX_TIMER_MS).optional().meta({
2235
3606
  title: "HTTP Timeout",
2236
- description: "Per-request Google transport timeout in milliseconds."
3607
+ description: "Per-request Google transport timeout in milliseconds, at most 2147483647; with timeoutMs set, at least timeoutMs + 5000."
2237
3608
  })
2238
3609
  }).optional().meta({
2239
3610
  title: "HTTP Options",
@@ -2253,7 +3624,7 @@ var Gemini35FlashLiteConfigSchema = z.union([
2253
3624
  })
2254
3625
  }),
2255
3626
  z.strictObject({
2256
- maxOutputTokens: z.number().int().positive().optional().meta({
3627
+ maxOutputTokens: maxOutputTokensSchema(GOOGLE_MODEL_LIMITS["gemini-3.5-flash-lite"]).optional().meta({
2257
3628
  title: "Max Output Tokens",
2258
3629
  description: "Maximum output token cap for gemini-3.5-flash-lite."
2259
3630
  }),
@@ -2286,25 +3657,30 @@ var Gemini35FlashLiteConfigSchema = z.union([
2286
3657
  title: "Reasoning",
2287
3658
  description: "Gemini 3.5 Flash-Lite thinkingLevel configuration."
2288
3659
  }),
2289
- timeoutMs: z.number().int().positive().optional().meta({
3660
+ timeoutMs: z.number().int().positive().max(GOOGLE_MAX_TIMEOUT_MS).optional().meta({
2290
3661
  title: "Timeout",
2291
3662
  description: "Logical request timeout in milliseconds."
2292
3663
  }),
2293
3664
  providerOptions: z.strictObject({
2294
3665
  google: z.strictObject({
2295
- cachedContent: z.string().min(1).optional().meta({
2296
- title: "Cached Content",
2297
- description: "Google cached content resource name."
3666
+ allowSchemaWithSearch: z.boolean().optional().meta({
3667
+ title: "Allow Schema With Search",
3668
+ description: "Admit googleSearch together with a response schema. Turns requireGrounding on unless it is false."
3669
+ }),
3670
+ requireGrounding: z.boolean().optional().meta({
3671
+ title: "Require Grounding",
3672
+ description: "Fail the call unless the response proves Google Search ran (grounding metadata with at least one query). Judged only on a candidate that finished with STOP: a MAX_TOKENS or other abnormal finish returns with its own finish reason, a filtered one throws content_filter. The error is retryable only when no response schema is attached."
2298
3673
  }),
3674
+ cachedContent: cachedContentSchema,
2299
3675
  safetySettings: z.array(
2300
3676
  z.strictObject({
2301
- category: z.string().min(1).meta({
3677
+ category: z.enum(GOOGLE_SAFETY_CATEGORIES).meta({
2302
3678
  title: "Safety Category",
2303
- description: "Google safety category identifier."
3679
+ description: "Documented Google safety category."
2304
3680
  }),
2305
- threshold: z.string().min(1).meta({
3681
+ threshold: z.enum(GOOGLE_SAFETY_THRESHOLDS).meta({
2306
3682
  title: "Safety Threshold",
2307
- description: "Google safety threshold identifier."
3683
+ description: "Documented Google safety threshold."
2308
3684
  })
2309
3685
  })
2310
3686
  ).optional().meta({
@@ -2323,9 +3699,9 @@ var Gemini35FlashLiteConfigSchema = z.union([
2323
3699
  description: "Allowlisted Google tools for gemini-3.5-flash-lite."
2324
3700
  }),
2325
3701
  httpOptions: z.strictObject({
2326
- timeout: z.number().int().positive().optional().meta({
3702
+ timeout: z.number().int().positive().max(MAX_TIMER_MS).optional().meta({
2327
3703
  title: "HTTP Timeout",
2328
- description: "Per-request Google transport timeout in milliseconds."
3704
+ description: "Per-request Google transport timeout in milliseconds, at most 2147483647; with timeoutMs set, at least timeoutMs + 5000."
2329
3705
  })
2330
3706
  }).optional().meta({
2331
3707
  title: "HTTP Options",
@@ -2347,7 +3723,7 @@ var Gemini35FlashLiteConfigSchema = z.union([
2347
3723
  });
2348
3724
  var Gemini36FlashConfigSchema = z.union([
2349
3725
  z.strictObject({
2350
- maxOutputTokens: z.number().int().positive().optional().meta({
3726
+ maxOutputTokens: maxOutputTokensSchema(GOOGLE_MODEL_LIMITS["gemini-3.6-flash"]).optional().meta({
2351
3727
  title: "Max Output Tokens",
2352
3728
  description: "Maximum output token cap for gemini-3.6-flash."
2353
3729
  }),
@@ -2380,25 +3756,30 @@ var Gemini36FlashConfigSchema = z.union([
2380
3756
  title: "Reasoning",
2381
3757
  description: "Gemini 3.6 Flash thinkingLevel configuration."
2382
3758
  }),
2383
- timeoutMs: z.number().int().positive().optional().meta({
3759
+ timeoutMs: z.number().int().positive().max(GOOGLE_MAX_TIMEOUT_MS).optional().meta({
2384
3760
  title: "Timeout",
2385
3761
  description: "Logical request timeout in milliseconds."
2386
3762
  }),
2387
3763
  providerOptions: z.strictObject({
2388
3764
  google: z.strictObject({
2389
- cachedContent: z.string().min(1).optional().meta({
2390
- title: "Cached Content",
2391
- description: "Google cached content resource name."
3765
+ allowSchemaWithSearch: z.boolean().optional().meta({
3766
+ title: "Allow Schema With Search",
3767
+ description: "Admit googleSearch together with a response schema. Turns requireGrounding on unless it is false."
2392
3768
  }),
3769
+ requireGrounding: z.boolean().optional().meta({
3770
+ title: "Require Grounding",
3771
+ description: "Fail the call unless the response proves Google Search ran (grounding metadata with at least one query). Judged only on a candidate that finished with STOP: a MAX_TOKENS or other abnormal finish returns with its own finish reason, a filtered one throws content_filter. The error is retryable only when no response schema is attached."
3772
+ }),
3773
+ cachedContent: cachedContentSchema,
2393
3774
  safetySettings: z.array(
2394
3775
  z.strictObject({
2395
- category: z.string().min(1).meta({
3776
+ category: z.enum(GOOGLE_SAFETY_CATEGORIES).meta({
2396
3777
  title: "Safety Category",
2397
- description: "Google safety category identifier."
3778
+ description: "Documented Google safety category."
2398
3779
  }),
2399
- threshold: z.string().min(1).meta({
3780
+ threshold: z.enum(GOOGLE_SAFETY_THRESHOLDS).meta({
2400
3781
  title: "Safety Threshold",
2401
- description: "Google safety threshold identifier."
3782
+ description: "Documented Google safety threshold."
2402
3783
  })
2403
3784
  })
2404
3785
  ).optional().meta({
@@ -2417,9 +3798,9 @@ var Gemini36FlashConfigSchema = z.union([
2417
3798
  description: "Allowlisted Google tools for gemini-3.6-flash."
2418
3799
  }),
2419
3800
  httpOptions: z.strictObject({
2420
- timeout: z.number().int().positive().optional().meta({
3801
+ timeout: z.number().int().positive().max(MAX_TIMER_MS).optional().meta({
2421
3802
  title: "HTTP Timeout",
2422
- description: "Per-request Google transport timeout in milliseconds."
3803
+ description: "Per-request Google transport timeout in milliseconds, at most 2147483647; with timeoutMs set, at least timeoutMs + 5000."
2423
3804
  })
2424
3805
  }).optional().meta({
2425
3806
  title: "HTTP Options",
@@ -2439,7 +3820,7 @@ var Gemini36FlashConfigSchema = z.union([
2439
3820
  })
2440
3821
  }),
2441
3822
  z.strictObject({
2442
- maxOutputTokens: z.number().int().positive().optional().meta({
3823
+ maxOutputTokens: maxOutputTokensSchema(GOOGLE_MODEL_LIMITS["gemini-3.6-flash"]).optional().meta({
2443
3824
  title: "Max Output Tokens",
2444
3825
  description: "Maximum output token cap for gemini-3.6-flash."
2445
3826
  }),
@@ -2472,25 +3853,30 @@ var Gemini36FlashConfigSchema = z.union([
2472
3853
  title: "Reasoning",
2473
3854
  description: "Gemini 3.6 Flash thinkingLevel configuration."
2474
3855
  }),
2475
- timeoutMs: z.number().int().positive().optional().meta({
3856
+ timeoutMs: z.number().int().positive().max(GOOGLE_MAX_TIMEOUT_MS).optional().meta({
2476
3857
  title: "Timeout",
2477
3858
  description: "Logical request timeout in milliseconds."
2478
3859
  }),
2479
3860
  providerOptions: z.strictObject({
2480
3861
  google: z.strictObject({
2481
- cachedContent: z.string().min(1).optional().meta({
2482
- title: "Cached Content",
2483
- description: "Google cached content resource name."
3862
+ allowSchemaWithSearch: z.boolean().optional().meta({
3863
+ title: "Allow Schema With Search",
3864
+ description: "Admit googleSearch together with a response schema. Turns requireGrounding on unless it is false."
3865
+ }),
3866
+ requireGrounding: z.boolean().optional().meta({
3867
+ title: "Require Grounding",
3868
+ description: "Fail the call unless the response proves Google Search ran (grounding metadata with at least one query). Judged only on a candidate that finished with STOP: a MAX_TOKENS or other abnormal finish returns with its own finish reason, a filtered one throws content_filter. The error is retryable only when no response schema is attached."
2484
3869
  }),
3870
+ cachedContent: cachedContentSchema,
2485
3871
  safetySettings: z.array(
2486
3872
  z.strictObject({
2487
- category: z.string().min(1).meta({
3873
+ category: z.enum(GOOGLE_SAFETY_CATEGORIES).meta({
2488
3874
  title: "Safety Category",
2489
- description: "Google safety category identifier."
3875
+ description: "Documented Google safety category."
2490
3876
  }),
2491
- threshold: z.string().min(1).meta({
3877
+ threshold: z.enum(GOOGLE_SAFETY_THRESHOLDS).meta({
2492
3878
  title: "Safety Threshold",
2493
- description: "Google safety threshold identifier."
3879
+ description: "Documented Google safety threshold."
2494
3880
  })
2495
3881
  })
2496
3882
  ).optional().meta({
@@ -2509,9 +3895,9 @@ var Gemini36FlashConfigSchema = z.union([
2509
3895
  description: "Allowlisted Google tools for gemini-3.6-flash."
2510
3896
  }),
2511
3897
  httpOptions: z.strictObject({
2512
- timeout: z.number().int().positive().optional().meta({
3898
+ timeout: z.number().int().positive().max(MAX_TIMER_MS).optional().meta({
2513
3899
  title: "HTTP Timeout",
2514
- description: "Per-request Google transport timeout in milliseconds."
3900
+ description: "Per-request Google transport timeout in milliseconds, at most 2147483647; with timeoutMs set, at least timeoutMs + 5000."
2515
3901
  })
2516
3902
  }).optional().meta({
2517
3903
  title: "HTTP Options",
@@ -2533,7 +3919,7 @@ var Gemini36FlashConfigSchema = z.union([
2533
3919
  });
2534
3920
  var Gemini37FlashConfigSchema = z.union([
2535
3921
  z.strictObject({
2536
- maxOutputTokens: z.number().int().positive().optional().meta({
3922
+ maxOutputTokens: maxOutputTokensSchema(GOOGLE_MODEL_LIMITS["gemini-3.7-flash"]).optional().meta({
2537
3923
  title: "Max Output Tokens",
2538
3924
  description: "Maximum output token cap for gemini-3.7-flash."
2539
3925
  }),
@@ -2566,25 +3952,30 @@ var Gemini37FlashConfigSchema = z.union([
2566
3952
  title: "Reasoning",
2567
3953
  description: "Gemini 3.7 Flash thinkingLevel configuration."
2568
3954
  }),
2569
- timeoutMs: z.number().int().positive().optional().meta({
3955
+ timeoutMs: z.number().int().positive().max(GOOGLE_MAX_TIMEOUT_MS).optional().meta({
2570
3956
  title: "Timeout",
2571
3957
  description: "Logical request timeout in milliseconds."
2572
3958
  }),
2573
3959
  providerOptions: z.strictObject({
2574
3960
  google: z.strictObject({
2575
- cachedContent: z.string().min(1).optional().meta({
2576
- title: "Cached Content",
2577
- description: "Google cached content resource name."
3961
+ allowSchemaWithSearch: z.boolean().optional().meta({
3962
+ title: "Allow Schema With Search",
3963
+ description: "Admit googleSearch together with a response schema. Turns requireGrounding on unless it is false."
2578
3964
  }),
3965
+ requireGrounding: z.boolean().optional().meta({
3966
+ title: "Require Grounding",
3967
+ description: "Fail the call unless the response proves Google Search ran (grounding metadata with at least one query). Judged only on a candidate that finished with STOP: a MAX_TOKENS or other abnormal finish returns with its own finish reason, a filtered one throws content_filter. The error is retryable only when no response schema is attached."
3968
+ }),
3969
+ cachedContent: cachedContentSchema,
2579
3970
  safetySettings: z.array(
2580
3971
  z.strictObject({
2581
- category: z.string().min(1).meta({
3972
+ category: z.enum(GOOGLE_SAFETY_CATEGORIES).meta({
2582
3973
  title: "Safety Category",
2583
- description: "Google safety category identifier."
3974
+ description: "Documented Google safety category."
2584
3975
  }),
2585
- threshold: z.string().min(1).meta({
3976
+ threshold: z.enum(GOOGLE_SAFETY_THRESHOLDS).meta({
2586
3977
  title: "Safety Threshold",
2587
- description: "Google safety threshold identifier."
3978
+ description: "Documented Google safety threshold."
2588
3979
  })
2589
3980
  })
2590
3981
  ).optional().meta({
@@ -2603,9 +3994,9 @@ var Gemini37FlashConfigSchema = z.union([
2603
3994
  description: "Allowlisted Google tools for gemini-3.7-flash."
2604
3995
  }),
2605
3996
  httpOptions: z.strictObject({
2606
- timeout: z.number().int().positive().optional().meta({
3997
+ timeout: z.number().int().positive().max(MAX_TIMER_MS).optional().meta({
2607
3998
  title: "HTTP Timeout",
2608
- description: "Per-request Google transport timeout in milliseconds."
3999
+ description: "Per-request Google transport timeout in milliseconds, at most 2147483647; with timeoutMs set, at least timeoutMs + 5000."
2609
4000
  })
2610
4001
  }).optional().meta({
2611
4002
  title: "HTTP Options",
@@ -2625,7 +4016,7 @@ var Gemini37FlashConfigSchema = z.union([
2625
4016
  })
2626
4017
  }),
2627
4018
  z.strictObject({
2628
- maxOutputTokens: z.number().int().positive().optional().meta({
4019
+ maxOutputTokens: maxOutputTokensSchema(GOOGLE_MODEL_LIMITS["gemini-3.7-flash"]).optional().meta({
2629
4020
  title: "Max Output Tokens",
2630
4021
  description: "Maximum output token cap for gemini-3.7-flash."
2631
4022
  }),
@@ -2658,25 +4049,30 @@ var Gemini37FlashConfigSchema = z.union([
2658
4049
  title: "Reasoning",
2659
4050
  description: "Gemini 3.7 Flash thinkingLevel configuration."
2660
4051
  }),
2661
- timeoutMs: z.number().int().positive().optional().meta({
4052
+ timeoutMs: z.number().int().positive().max(GOOGLE_MAX_TIMEOUT_MS).optional().meta({
2662
4053
  title: "Timeout",
2663
4054
  description: "Logical request timeout in milliseconds."
2664
4055
  }),
2665
4056
  providerOptions: z.strictObject({
2666
4057
  google: z.strictObject({
2667
- cachedContent: z.string().min(1).optional().meta({
2668
- title: "Cached Content",
2669
- description: "Google cached content resource name."
4058
+ allowSchemaWithSearch: z.boolean().optional().meta({
4059
+ title: "Allow Schema With Search",
4060
+ description: "Admit googleSearch together with a response schema. Turns requireGrounding on unless it is false."
4061
+ }),
4062
+ requireGrounding: z.boolean().optional().meta({
4063
+ title: "Require Grounding",
4064
+ description: "Fail the call unless the response proves Google Search ran (grounding metadata with at least one query). Judged only on a candidate that finished with STOP: a MAX_TOKENS or other abnormal finish returns with its own finish reason, a filtered one throws content_filter. The error is retryable only when no response schema is attached."
2670
4065
  }),
4066
+ cachedContent: cachedContentSchema,
2671
4067
  safetySettings: z.array(
2672
4068
  z.strictObject({
2673
- category: z.string().min(1).meta({
4069
+ category: z.enum(GOOGLE_SAFETY_CATEGORIES).meta({
2674
4070
  title: "Safety Category",
2675
- description: "Google safety category identifier."
4071
+ description: "Documented Google safety category."
2676
4072
  }),
2677
- threshold: z.string().min(1).meta({
4073
+ threshold: z.enum(GOOGLE_SAFETY_THRESHOLDS).meta({
2678
4074
  title: "Safety Threshold",
2679
- description: "Google safety threshold identifier."
4075
+ description: "Documented Google safety threshold."
2680
4076
  })
2681
4077
  })
2682
4078
  ).optional().meta({
@@ -2695,9 +4091,9 @@ var Gemini37FlashConfigSchema = z.union([
2695
4091
  description: "Allowlisted Google tools for gemini-3.7-flash."
2696
4092
  }),
2697
4093
  httpOptions: z.strictObject({
2698
- timeout: z.number().int().positive().optional().meta({
4094
+ timeout: z.number().int().positive().max(MAX_TIMER_MS).optional().meta({
2699
4095
  title: "HTTP Timeout",
2700
- description: "Per-request Google transport timeout in milliseconds."
4096
+ description: "Per-request Google transport timeout in milliseconds, at most 2147483647; with timeoutMs set, at least timeoutMs + 5000."
2701
4097
  })
2702
4098
  }).optional().meta({
2703
4099
  title: "HTTP Options",
@@ -2719,7 +4115,7 @@ var Gemini37FlashConfigSchema = z.union([
2719
4115
  });
2720
4116
  var Gemini38FlashConfigSchema = z.union([
2721
4117
  z.strictObject({
2722
- maxOutputTokens: z.number().int().positive().optional().meta({
4118
+ maxOutputTokens: maxOutputTokensSchema(GOOGLE_MODEL_LIMITS["gemini-3.8-flash"]).optional().meta({
2723
4119
  title: "Max Output Tokens",
2724
4120
  description: "Maximum output token cap for gemini-3.8-flash."
2725
4121
  }),
@@ -2752,25 +4148,30 @@ var Gemini38FlashConfigSchema = z.union([
2752
4148
  title: "Reasoning",
2753
4149
  description: "Gemini 3.8 Flash thinkingLevel configuration."
2754
4150
  }),
2755
- timeoutMs: z.number().int().positive().optional().meta({
4151
+ timeoutMs: z.number().int().positive().max(GOOGLE_MAX_TIMEOUT_MS).optional().meta({
2756
4152
  title: "Timeout",
2757
4153
  description: "Logical request timeout in milliseconds."
2758
4154
  }),
2759
4155
  providerOptions: z.strictObject({
2760
4156
  google: z.strictObject({
2761
- cachedContent: z.string().min(1).optional().meta({
2762
- title: "Cached Content",
2763
- description: "Google cached content resource name."
4157
+ allowSchemaWithSearch: z.boolean().optional().meta({
4158
+ title: "Allow Schema With Search",
4159
+ description: "Admit googleSearch together with a response schema. Turns requireGrounding on unless it is false."
2764
4160
  }),
4161
+ requireGrounding: z.boolean().optional().meta({
4162
+ title: "Require Grounding",
4163
+ description: "Fail the call unless the response proves Google Search ran (grounding metadata with at least one query). Judged only on a candidate that finished with STOP: a MAX_TOKENS or other abnormal finish returns with its own finish reason, a filtered one throws content_filter. The error is retryable only when no response schema is attached."
4164
+ }),
4165
+ cachedContent: cachedContentSchema,
2765
4166
  safetySettings: z.array(
2766
4167
  z.strictObject({
2767
- category: z.string().min(1).meta({
4168
+ category: z.enum(GOOGLE_SAFETY_CATEGORIES).meta({
2768
4169
  title: "Safety Category",
2769
- description: "Google safety category identifier."
4170
+ description: "Documented Google safety category."
2770
4171
  }),
2771
- threshold: z.string().min(1).meta({
4172
+ threshold: z.enum(GOOGLE_SAFETY_THRESHOLDS).meta({
2772
4173
  title: "Safety Threshold",
2773
- description: "Google safety threshold identifier."
4174
+ description: "Documented Google safety threshold."
2774
4175
  })
2775
4176
  })
2776
4177
  ).optional().meta({
@@ -2789,9 +4190,9 @@ var Gemini38FlashConfigSchema = z.union([
2789
4190
  description: "Allowlisted Google tools for gemini-3.8-flash."
2790
4191
  }),
2791
4192
  httpOptions: z.strictObject({
2792
- timeout: z.number().int().positive().optional().meta({
4193
+ timeout: z.number().int().positive().max(MAX_TIMER_MS).optional().meta({
2793
4194
  title: "HTTP Timeout",
2794
- description: "Per-request Google transport timeout in milliseconds."
4195
+ description: "Per-request Google transport timeout in milliseconds, at most 2147483647; with timeoutMs set, at least timeoutMs + 5000."
2795
4196
  })
2796
4197
  }).optional().meta({
2797
4198
  title: "HTTP Options",
@@ -2811,7 +4212,7 @@ var Gemini38FlashConfigSchema = z.union([
2811
4212
  })
2812
4213
  }),
2813
4214
  z.strictObject({
2814
- maxOutputTokens: z.number().int().positive().optional().meta({
4215
+ maxOutputTokens: maxOutputTokensSchema(GOOGLE_MODEL_LIMITS["gemini-3.8-flash"]).optional().meta({
2815
4216
  title: "Max Output Tokens",
2816
4217
  description: "Maximum output token cap for gemini-3.8-flash."
2817
4218
  }),
@@ -2844,25 +4245,30 @@ var Gemini38FlashConfigSchema = z.union([
2844
4245
  title: "Reasoning",
2845
4246
  description: "Gemini 3.8 Flash thinkingLevel configuration."
2846
4247
  }),
2847
- timeoutMs: z.number().int().positive().optional().meta({
4248
+ timeoutMs: z.number().int().positive().max(GOOGLE_MAX_TIMEOUT_MS).optional().meta({
2848
4249
  title: "Timeout",
2849
4250
  description: "Logical request timeout in milliseconds."
2850
4251
  }),
2851
4252
  providerOptions: z.strictObject({
2852
4253
  google: z.strictObject({
2853
- cachedContent: z.string().min(1).optional().meta({
2854
- title: "Cached Content",
2855
- description: "Google cached content resource name."
4254
+ allowSchemaWithSearch: z.boolean().optional().meta({
4255
+ title: "Allow Schema With Search",
4256
+ description: "Admit googleSearch together with a response schema. Turns requireGrounding on unless it is false."
4257
+ }),
4258
+ requireGrounding: z.boolean().optional().meta({
4259
+ title: "Require Grounding",
4260
+ description: "Fail the call unless the response proves Google Search ran (grounding metadata with at least one query). Judged only on a candidate that finished with STOP: a MAX_TOKENS or other abnormal finish returns with its own finish reason, a filtered one throws content_filter. The error is retryable only when no response schema is attached."
2856
4261
  }),
4262
+ cachedContent: cachedContentSchema,
2857
4263
  safetySettings: z.array(
2858
4264
  z.strictObject({
2859
- category: z.string().min(1).meta({
4265
+ category: z.enum(GOOGLE_SAFETY_CATEGORIES).meta({
2860
4266
  title: "Safety Category",
2861
- description: "Google safety category identifier."
4267
+ description: "Documented Google safety category."
2862
4268
  }),
2863
- threshold: z.string().min(1).meta({
4269
+ threshold: z.enum(GOOGLE_SAFETY_THRESHOLDS).meta({
2864
4270
  title: "Safety Threshold",
2865
- description: "Google safety threshold identifier."
4271
+ description: "Documented Google safety threshold."
2866
4272
  })
2867
4273
  })
2868
4274
  ).optional().meta({
@@ -2881,9 +4287,9 @@ var Gemini38FlashConfigSchema = z.union([
2881
4287
  description: "Allowlisted Google tools for gemini-3.8-flash."
2882
4288
  }),
2883
4289
  httpOptions: z.strictObject({
2884
- timeout: z.number().int().positive().optional().meta({
4290
+ timeout: z.number().int().positive().max(MAX_TIMER_MS).optional().meta({
2885
4291
  title: "HTTP Timeout",
2886
- description: "Per-request Google transport timeout in milliseconds."
4292
+ description: "Per-request Google transport timeout in milliseconds, at most 2147483647; with timeoutMs set, at least timeoutMs + 5000."
2887
4293
  })
2888
4294
  }).optional().meta({
2889
4295
  title: "HTTP Options",
@@ -2917,7 +4323,7 @@ var Gemma431bItConfigSchema = z.strictObject({
2917
4323
  title: "Top K",
2918
4324
  description: "Top-k sampling limit for gemma-4-31b-it."
2919
4325
  }),
2920
- maxOutputTokens: z.number().int().positive().optional().meta({
4326
+ maxOutputTokens: maxOutputTokensSchema(GOOGLE_MODEL_LIMITS["gemma-4-31b-it"]).optional().meta({
2921
4327
  title: "Max Output Tokens",
2922
4328
  description: "Maximum output token cap for gemma-4-31b-it."
2923
4329
  }),
@@ -2946,25 +4352,25 @@ var Gemma431bItConfigSchema = z.strictObject({
2946
4352
  title: "Reasoning",
2947
4353
  description: "Gemma 4 31B IT thinkingLevel configuration."
2948
4354
  }),
2949
- timeoutMs: z.number().int().positive().optional().meta({
4355
+ timeoutMs: z.number().int().positive().max(GOOGLE_MAX_TIMEOUT_MS).optional().meta({
2950
4356
  title: "Timeout",
2951
4357
  description: "Logical request timeout in milliseconds."
2952
4358
  }),
2953
4359
  providerOptions: z.strictObject({
2954
4360
  google: z.strictObject({
2955
- cachedContent: z.string().min(1).optional().meta({
2956
- title: "Cached Content",
2957
- description: "Google cached content resource name."
4361
+ requireGrounding: z.boolean().optional().meta({
4362
+ title: "Require Grounding",
4363
+ description: "Fail the call unless the response proves Google Search ran (grounding metadata with at least one query). Judged only on a candidate that finished with STOP: a MAX_TOKENS or other abnormal finish returns with its own finish reason, a filtered one throws content_filter. The error is retryable only when no response schema is attached."
2958
4364
  }),
2959
4365
  safetySettings: z.array(
2960
4366
  z.strictObject({
2961
- category: z.string().min(1).meta({
4367
+ category: z.enum(GOOGLE_SAFETY_CATEGORIES).meta({
2962
4368
  title: "Safety Category",
2963
- description: "Google safety category identifier."
4369
+ description: "Documented Google safety category."
2964
4370
  }),
2965
- threshold: z.string().min(1).meta({
4371
+ threshold: z.enum(GOOGLE_SAFETY_THRESHOLDS).meta({
2966
4372
  title: "Safety Threshold",
2967
- description: "Google safety threshold identifier."
4373
+ description: "Documented Google safety threshold."
2968
4374
  })
2969
4375
  })
2970
4376
  ).optional().meta({
@@ -2983,9 +4389,9 @@ var Gemma431bItConfigSchema = z.strictObject({
2983
4389
  description: "Allowlisted Google tools for gemma-4-31b-it."
2984
4390
  }),
2985
4391
  httpOptions: z.strictObject({
2986
- timeout: z.number().int().positive().optional().meta({
4392
+ timeout: z.number().int().positive().max(MAX_TIMER_MS).optional().meta({
2987
4393
  title: "HTTP Timeout",
2988
- description: "Per-request Google transport timeout in milliseconds."
4394
+ description: "Per-request Google transport timeout in milliseconds, at most 2147483647; with timeoutMs set, at least timeoutMs + 5000."
2989
4395
  })
2990
4396
  }).optional().meta({
2991
4397
  title: "HTTP Options",
@@ -3018,7 +4424,7 @@ var Gemma426bA4bItConfigSchema = z.strictObject({
3018
4424
  title: "Top K",
3019
4425
  description: "Top-k sampling limit for gemma-4-26b-a4b-it."
3020
4426
  }),
3021
- maxOutputTokens: z.number().int().positive().optional().meta({
4427
+ maxOutputTokens: maxOutputTokensSchema(GOOGLE_MODEL_LIMITS["gemma-4-26b-a4b-it"]).optional().meta({
3022
4428
  title: "Max Output Tokens",
3023
4429
  description: "Maximum output token cap for gemma-4-26b-a4b-it."
3024
4430
  }),
@@ -3047,25 +4453,25 @@ var Gemma426bA4bItConfigSchema = z.strictObject({
3047
4453
  title: "Reasoning",
3048
4454
  description: "Gemma 4 26B A4B IT thinkingLevel configuration."
3049
4455
  }),
3050
- timeoutMs: z.number().int().positive().optional().meta({
4456
+ timeoutMs: z.number().int().positive().max(GOOGLE_MAX_TIMEOUT_MS).optional().meta({
3051
4457
  title: "Timeout",
3052
4458
  description: "Logical request timeout in milliseconds."
3053
4459
  }),
3054
4460
  providerOptions: z.strictObject({
3055
4461
  google: z.strictObject({
3056
- cachedContent: z.string().min(1).optional().meta({
3057
- title: "Cached Content",
3058
- description: "Google cached content resource name."
4462
+ requireGrounding: z.boolean().optional().meta({
4463
+ title: "Require Grounding",
4464
+ description: "Fail the call unless the response proves Google Search ran (grounding metadata with at least one query). Judged only on a candidate that finished with STOP: a MAX_TOKENS or other abnormal finish returns with its own finish reason, a filtered one throws content_filter. The error is retryable only when no response schema is attached."
3059
4465
  }),
3060
4466
  safetySettings: z.array(
3061
4467
  z.strictObject({
3062
- category: z.string().min(1).meta({
4468
+ category: z.enum(GOOGLE_SAFETY_CATEGORIES).meta({
3063
4469
  title: "Safety Category",
3064
- description: "Google safety category identifier."
4470
+ description: "Documented Google safety category."
3065
4471
  }),
3066
- threshold: z.string().min(1).meta({
4472
+ threshold: z.enum(GOOGLE_SAFETY_THRESHOLDS).meta({
3067
4473
  title: "Safety Threshold",
3068
- description: "Google safety threshold identifier."
4474
+ description: "Documented Google safety threshold."
3069
4475
  })
3070
4476
  })
3071
4477
  ).optional().meta({
@@ -3084,9 +4490,9 @@ var Gemma426bA4bItConfigSchema = z.strictObject({
3084
4490
  description: "Allowlisted Google tools for gemma-4-26b-a4b-it."
3085
4491
  }),
3086
4492
  httpOptions: z.strictObject({
3087
- timeout: z.number().int().positive().optional().meta({
4493
+ timeout: z.number().int().positive().max(MAX_TIMER_MS).optional().meta({
3088
4494
  title: "HTTP Timeout",
3089
- description: "Per-request Google transport timeout in milliseconds."
4495
+ description: "Per-request Google transport timeout in milliseconds, at most 2147483647; with timeoutMs set, at least timeoutMs + 5000."
3090
4496
  })
3091
4497
  }).optional().meta({
3092
4498
  title: "HTTP Options",
@@ -3124,12 +4530,13 @@ var geminiModelDescriptors = [
3124
4530
  {
3125
4531
  model: "gemini-2.5-pro",
3126
4532
  provider: "google",
4533
+ limits: GOOGLE_MODEL_LIMITS["gemini-2.5-pro"],
3127
4534
  pricingFamily: "gemini-2.5-pro",
3128
4535
  capabilities: {
4536
+ inputMimeTypes: GEMINI_INPUT_MIME_TYPES,
3129
4537
  reasoning: true,
3130
4538
  structuredOutput: true,
3131
4539
  nativeStructuredOutput: true,
3132
- vision: true,
3133
4540
  reasoningApi: "budget",
3134
4541
  admittedReasoningEfforts: GEMINI_STANDARD_REASONING_EFFORTS,
3135
4542
  sampling: "tunable",
@@ -3139,18 +4546,20 @@ var geminiModelDescriptors = [
3139
4546
  serviceTiers: ["flex", "standard"]
3140
4547
  },
3141
4548
  configSchema: Gemini25ProConfigSchema,
4549
+ configKeys: toConfigKeys(Gemini25ProConfigSchema),
3142
4550
  configJsonSchema: toConfigJsonSchema(Gemini25ProConfigSchema),
3143
4551
  validateConfig: zodToStandardSchema(Gemini25ProConfigSchema)
3144
4552
  },
3145
4553
  {
3146
4554
  model: "gemini-2.5-flash",
3147
4555
  provider: "google",
4556
+ limits: GOOGLE_MODEL_LIMITS["gemini-2.5-flash"],
3148
4557
  pricingFamily: "gemini-2.5-flash",
3149
4558
  capabilities: {
4559
+ inputMimeTypes: GEMINI_INPUT_MIME_TYPES,
3150
4560
  reasoning: true,
3151
4561
  structuredOutput: true,
3152
4562
  nativeStructuredOutput: true,
3153
- vision: true,
3154
4563
  reasoningApi: "budget",
3155
4564
  admittedReasoningEfforts: GEMINI_LEVEL_WITH_NONE_REASONING_EFFORTS,
3156
4565
  sampling: "tunable",
@@ -3160,18 +4569,20 @@ var geminiModelDescriptors = [
3160
4569
  serviceTiers: ["flex", "standard"]
3161
4570
  },
3162
4571
  configSchema: Gemini25FlashConfigSchema,
4572
+ configKeys: toConfigKeys(Gemini25FlashConfigSchema),
3163
4573
  configJsonSchema: toConfigJsonSchema(Gemini25FlashConfigSchema),
3164
4574
  validateConfig: zodToStandardSchema(Gemini25FlashConfigSchema)
3165
4575
  },
3166
4576
  {
3167
4577
  model: "gemini-2.5-flash-lite",
3168
4578
  provider: "google",
4579
+ limits: GOOGLE_MODEL_LIMITS["gemini-2.5-flash-lite"],
3169
4580
  pricingFamily: "gemini-2.5-flash-lite",
3170
4581
  capabilities: {
4582
+ inputMimeTypes: GEMINI_INPUT_MIME_TYPES,
3171
4583
  reasoning: true,
3172
4584
  structuredOutput: true,
3173
4585
  nativeStructuredOutput: true,
3174
- vision: true,
3175
4586
  reasoningApi: "budget",
3176
4587
  admittedReasoningEfforts: GEMINI_LEVEL_WITH_NONE_REASONING_EFFORTS,
3177
4588
  sampling: "tunable",
@@ -3181,138 +4592,167 @@ var geminiModelDescriptors = [
3181
4592
  serviceTiers: ["flex", "standard"]
3182
4593
  },
3183
4594
  configSchema: Gemini25FlashLiteConfigSchema,
4595
+ configKeys: toConfigKeys(Gemini25FlashLiteConfigSchema),
3184
4596
  configJsonSchema: toConfigJsonSchema(Gemini25FlashLiteConfigSchema),
3185
4597
  validateConfig: zodToStandardSchema(Gemini25FlashLiteConfigSchema)
3186
4598
  },
3187
4599
  {
3188
4600
  model: "gemini-3.1-flash-lite",
3189
4601
  provider: "google",
4602
+ // Google's deprecations page (https://ai.google.dev/gemini-api/docs/deprecations,
4603
+ // "Page last updated" 2026-10-01, read 2026-10-03) lists a May 7, 2027
4604
+ // shutdown, replacement gemini-3.5-flash-lite.
4605
+ shutdownDate: "2027-05-07",
4606
+ limits: GOOGLE_MODEL_LIMITS["gemini-3.1-flash-lite"],
3190
4607
  pricingFamily: "gemini-3.1-flash-lite",
3191
4608
  capabilities: {
4609
+ inputMimeTypes: GEMINI_INPUT_MIME_TYPES,
3192
4610
  reasoning: true,
3193
4611
  structuredOutput: true,
3194
4612
  nativeStructuredOutput: true,
3195
- vision: true,
3196
4613
  reasoningApi: "level",
3197
4614
  admittedReasoningEfforts: GEMINI_LEVEL_WITH_NONE_REASONING_EFFORTS,
3198
4615
  sampling: "fixed",
3199
4616
  caching: { explicit: true, minTokens: 1024 },
3200
4617
  grounding: true,
3201
4618
  functionCalling: true,
3202
- structuredOutputWithTools: true,
4619
+ structuredOutputWithTools: false,
4620
+ continuation: "history",
4621
+ providerState: true,
3203
4622
  serviceTiers: ["flex", "standard"]
3204
4623
  },
3205
4624
  configSchema: Gemini31FlashLiteConfigSchema,
4625
+ configKeys: toConfigKeys(Gemini31FlashLiteConfigSchema),
3206
4626
  configJsonSchema: toConfigJsonSchema(Gemini31FlashLiteConfigSchema),
3207
4627
  validateConfig: zodToStandardSchema(Gemini31FlashLiteConfigSchema)
3208
4628
  },
3209
4629
  {
3210
4630
  model: "gemini-3.1-pro-preview",
3211
4631
  provider: "google",
4632
+ limits: GOOGLE_MODEL_LIMITS["gemini-3.1-pro-preview"],
3212
4633
  pricingFamily: "gemini-3.1-pro-preview",
3213
4634
  capabilities: {
4635
+ inputMimeTypes: GEMINI_INPUT_MIME_TYPES,
3214
4636
  reasoning: true,
3215
4637
  structuredOutput: true,
3216
4638
  nativeStructuredOutput: true,
3217
- vision: true,
3218
4639
  reasoningApi: "level",
3219
4640
  admittedReasoningEfforts: GEMINI_STANDARD_REASONING_EFFORTS,
3220
4641
  sampling: "fixed",
3221
4642
  caching: { explicit: true, minTokens: 1024 },
3222
4643
  grounding: true,
3223
4644
  functionCalling: true,
3224
- structuredOutputWithTools: true,
4645
+ structuredOutputWithTools: false,
4646
+ continuation: "history",
4647
+ providerState: true,
3225
4648
  serviceTiers: ["flex", "standard"]
3226
4649
  },
3227
4650
  configSchema: Gemini31ProPreviewConfigSchema,
4651
+ configKeys: toConfigKeys(Gemini31ProPreviewConfigSchema),
3228
4652
  configJsonSchema: toConfigJsonSchema(Gemini31ProPreviewConfigSchema),
3229
4653
  validateConfig: zodToStandardSchema(Gemini31ProPreviewConfigSchema)
3230
4654
  },
3231
4655
  {
3232
4656
  model: "gemini-3.8-flash",
3233
4657
  provider: "google",
4658
+ limits: GOOGLE_MODEL_LIMITS["gemini-3.8-flash"],
3234
4659
  pricingFamily: "gemini-3.8-flash",
3235
4660
  capabilities: {
4661
+ inputMimeTypes: GEMINI_INPUT_MIME_TYPES,
3236
4662
  reasoning: true,
3237
4663
  structuredOutput: true,
3238
4664
  nativeStructuredOutput: true,
3239
- vision: true,
3240
4665
  reasoningApi: "level",
3241
4666
  admittedReasoningEfforts: GEMINI_STANDARD_REASONING_EFFORTS,
3242
4667
  sampling: "fixed",
3243
4668
  caching: { explicit: true, minTokens: 1024 },
3244
4669
  grounding: true,
3245
4670
  functionCalling: true,
3246
- structuredOutputWithTools: true,
4671
+ structuredOutputWithTools: false,
4672
+ continuation: "history",
4673
+ providerState: true,
3247
4674
  serviceTiers: ["flex", "standard"]
3248
4675
  },
3249
4676
  configSchema: Gemini38FlashConfigSchema,
4677
+ configKeys: toConfigKeys(Gemini38FlashConfigSchema),
3250
4678
  configJsonSchema: toConfigJsonSchema(Gemini38FlashConfigSchema),
3251
4679
  validateConfig: zodToStandardSchema(Gemini38FlashConfigSchema)
3252
4680
  },
3253
4681
  {
3254
4682
  model: "gemini-3.7-flash",
3255
4683
  provider: "google",
4684
+ limits: GOOGLE_MODEL_LIMITS["gemini-3.7-flash"],
3256
4685
  pricingFamily: "gemini-3.7-flash",
3257
4686
  capabilities: {
4687
+ inputMimeTypes: GEMINI_INPUT_MIME_TYPES,
3258
4688
  reasoning: true,
3259
4689
  structuredOutput: true,
3260
4690
  nativeStructuredOutput: true,
3261
- vision: true,
3262
4691
  reasoningApi: "level",
3263
4692
  admittedReasoningEfforts: GEMINI_STANDARD_REASONING_EFFORTS,
3264
4693
  sampling: "fixed",
3265
4694
  caching: { explicit: true, minTokens: 1024 },
3266
4695
  grounding: true,
3267
4696
  functionCalling: true,
3268
- structuredOutputWithTools: true,
4697
+ structuredOutputWithTools: false,
4698
+ continuation: "history",
4699
+ providerState: true,
3269
4700
  serviceTiers: ["flex", "standard"]
3270
4701
  },
3271
4702
  configSchema: Gemini37FlashConfigSchema,
4703
+ configKeys: toConfigKeys(Gemini37FlashConfigSchema),
3272
4704
  configJsonSchema: toConfigJsonSchema(Gemini37FlashConfigSchema),
3273
4705
  validateConfig: zodToStandardSchema(Gemini37FlashConfigSchema)
3274
4706
  },
3275
4707
  {
3276
4708
  model: "gemini-3.6-flash",
3277
4709
  provider: "google",
4710
+ limits: GOOGLE_MODEL_LIMITS["gemini-3.6-flash"],
3278
4711
  pricingFamily: "gemini-3.6-flash",
3279
4712
  capabilities: {
4713
+ inputMimeTypes: GEMINI_INPUT_MIME_TYPES,
3280
4714
  reasoning: true,
3281
4715
  structuredOutput: true,
3282
4716
  nativeStructuredOutput: true,
3283
- vision: true,
3284
4717
  reasoningApi: "level",
3285
4718
  admittedReasoningEfforts: GEMINI_LEVEL_WITH_NONE_REASONING_EFFORTS,
3286
4719
  sampling: "fixed",
3287
4720
  caching: { explicit: true, minTokens: 1024 },
3288
4721
  grounding: true,
3289
4722
  functionCalling: true,
3290
- structuredOutputWithTools: true,
4723
+ structuredOutputWithTools: false,
4724
+ continuation: "history",
4725
+ providerState: true,
3291
4726
  serviceTiers: ["flex", "standard"]
3292
4727
  },
3293
4728
  configSchema: Gemini36FlashConfigSchema,
4729
+ configKeys: toConfigKeys(Gemini36FlashConfigSchema),
3294
4730
  configJsonSchema: toConfigJsonSchema(Gemini36FlashConfigSchema),
3295
4731
  validateConfig: zodToStandardSchema(Gemini36FlashConfigSchema)
3296
4732
  },
3297
4733
  {
3298
4734
  model: "gemini-3.5-flash-lite",
3299
4735
  provider: "google",
4736
+ limits: GOOGLE_MODEL_LIMITS["gemini-3.5-flash-lite"],
3300
4737
  pricingFamily: "gemini-3.5-flash-lite",
3301
4738
  capabilities: {
4739
+ inputMimeTypes: GEMINI_INPUT_MIME_TYPES,
3302
4740
  reasoning: true,
3303
4741
  structuredOutput: true,
3304
4742
  nativeStructuredOutput: true,
3305
- vision: true,
3306
4743
  reasoningApi: "level",
3307
4744
  admittedReasoningEfforts: GEMINI_LEVEL_WITH_NONE_REASONING_EFFORTS,
3308
4745
  sampling: "fixed",
3309
4746
  caching: { explicit: true, minTokens: 1024 },
3310
4747
  grounding: true,
3311
4748
  functionCalling: true,
3312
- structuredOutputWithTools: true,
4749
+ structuredOutputWithTools: false,
4750
+ continuation: "history",
4751
+ providerState: true,
3313
4752
  serviceTiers: ["flex", "standard"]
3314
4753
  },
3315
4754
  configSchema: Gemini35FlashLiteConfigSchema,
4755
+ configKeys: toConfigKeys(Gemini35FlashLiteConfigSchema),
3316
4756
  configJsonSchema: toConfigJsonSchema(Gemini35FlashLiteConfigSchema),
3317
4757
  validateConfig: zodToStandardSchema(Gemini35FlashLiteConfigSchema)
3318
4758
  }
@@ -3321,34 +4761,38 @@ var gemmaModelDescriptors = [
3321
4761
  {
3322
4762
  model: "gemma-4-31b-it",
3323
4763
  provider: "google",
4764
+ limits: GOOGLE_MODEL_LIMITS["gemma-4-31b-it"],
3324
4765
  capabilities: {
4766
+ inputMimeTypes: GEMMA_INPUT_MIME_TYPES,
3325
4767
  reasoning: true,
3326
4768
  reasoningApi: "level",
3327
4769
  admittedReasoningEfforts: GEMMA_REASONING_EFFORTS,
3328
4770
  structuredOutput: true,
3329
4771
  nativeStructuredOutput: true,
3330
4772
  grounding: true,
3331
- vision: true,
3332
4773
  sampling: "tunable"
3333
4774
  },
3334
4775
  configSchema: Gemma431bItConfigSchema,
4776
+ configKeys: toConfigKeys(Gemma431bItConfigSchema),
3335
4777
  configJsonSchema: toConfigJsonSchema(Gemma431bItConfigSchema),
3336
4778
  validateConfig: zodToStandardSchema(Gemma431bItConfigSchema)
3337
4779
  },
3338
4780
  {
3339
4781
  model: "gemma-4-26b-a4b-it",
3340
4782
  provider: "google",
4783
+ limits: GOOGLE_MODEL_LIMITS["gemma-4-26b-a4b-it"],
3341
4784
  capabilities: {
4785
+ inputMimeTypes: GEMMA_INPUT_MIME_TYPES,
3342
4786
  reasoning: true,
3343
4787
  reasoningApi: "level",
3344
4788
  admittedReasoningEfforts: GEMMA_REASONING_EFFORTS,
3345
4789
  structuredOutput: true,
3346
4790
  nativeStructuredOutput: true,
3347
4791
  grounding: true,
3348
- vision: true,
3349
4792
  sampling: "tunable"
3350
4793
  },
3351
4794
  configSchema: Gemma426bA4bItConfigSchema,
4795
+ configKeys: toConfigKeys(Gemma426bA4bItConfigSchema),
3352
4796
  configJsonSchema: toConfigJsonSchema(Gemma426bA4bItConfigSchema),
3353
4797
  validateConfig: zodToStandardSchema(Gemma426bA4bItConfigSchema)
3354
4798
  }
@@ -3368,16 +4812,32 @@ function googleProvider(opts) {
3368
4812
  }
3369
4813
  var DEFAULT_INTERVAL_MS = 3e3;
3370
4814
  var DEFAULT_TIMEOUT_MS = 3e5;
3371
- var realSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
4815
+ var waitOn = (scheduler) => (ms) => {
4816
+ let timer;
4817
+ const promise = new Promise((resolve) => {
4818
+ timer = scheduler.setTimeout(resolve, ms);
4819
+ });
4820
+ return {
4821
+ promise,
4822
+ cancel: () => {
4823
+ if (timer !== void 0) scheduler.clearTimeout(timer);
4824
+ }
4825
+ };
4826
+ };
4827
+ function pollTimeoutError(name) {
4828
+ return new LlmError(`Timed out waiting for uploaded file "${name}" to become ACTIVE`, {
4829
+ kind: "server",
4830
+ retryable: false,
4831
+ provider: "google"
4832
+ });
4833
+ }
3372
4834
  async function buildFilesClient(auth) {
3373
- const { GoogleGenAI } = await import('@google/genai');
3374
- const ai = new GoogleGenAI({ apiKey: requireApiKey(auth) });
4835
+ const ai = await newGoogleGenAI(auth);
3375
4836
  return {
3376
4837
  async upload(params) {
3377
- const fileArg = params.file instanceof Uint8Array ? new Blob(
3378
- [Uint8Array.from(params.file)],
3379
- params.config?.mimeType !== void 0 && params.config.mimeType.length > 0 ? { type: params.config.mimeType } : {}
3380
- ) : params.file;
4838
+ const fileArg = params.file instanceof Uint8Array ? new Blob([Uint8Array.from(params.file)], {
4839
+ ...params.config?.mimeType !== void 0 ? { type: params.config.mimeType } : {}
4840
+ }) : params.file;
3381
4841
  const result = await ai.files.upload({
3382
4842
  file: fileArg,
3383
4843
  ...params.config !== void 0 ? { config: params.config } : {}
@@ -3409,7 +4869,8 @@ var GoogleFileStore = class {
3409
4869
  logger;
3410
4870
  intervalMs;
3411
4871
  timeoutMs;
3412
- sleep;
4872
+ startWait;
4873
+ scheduler;
3413
4874
  now;
3414
4875
  /** Memoised client promise — built at most once per store instance. */
3415
4876
  clientPromise;
@@ -3420,19 +4881,22 @@ var GoogleFileStore = class {
3420
4881
  this.onDeleteError = opts.onDeleteError ?? ((name, err) => {
3421
4882
  if (this.logger !== void 0) {
3422
4883
  this.logger.error(
3423
- { name, error: redactSecrets(classifyError(err).message) },
4884
+ { name, error: redactSecrets(classifyGoogleError(err).message) },
3424
4885
  "gemini.file.delete.failed"
3425
4886
  );
3426
4887
  } else {
3427
4888
  console.error(
3428
4889
  `[GoogleFileStore] delete failed for "${name}":`,
3429
- redactSecrets(classifyError(err).message)
4890
+ redactSecrets(classifyGoogleError(err).message)
3430
4891
  );
3431
4892
  }
3432
4893
  });
3433
4894
  this.intervalMs = opts.poll?.intervalMs ?? DEFAULT_INTERVAL_MS;
3434
4895
  this.timeoutMs = opts.poll?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
3435
- this.sleep = opts.sleep ?? realSleep;
4896
+ this.scheduler = opts.scheduler ?? PLATFORM_SCHEDULER;
4897
+ const customSleep = opts.sleep;
4898
+ this.startWait = customSleep !== void 0 ? (ms) => ({ promise: customSleep(ms), cancel: () => {
4899
+ } }) : waitOn(this.scheduler);
3436
4900
  this.now = opts.now ?? (() => Date.now());
3437
4901
  }
3438
4902
  getClient() {
@@ -3446,23 +4910,41 @@ var GoogleFileStore = class {
3446
4910
  * Upload bytes to the Gemini File API and wait until the file is ACTIVE.
3447
4911
  *
3448
4912
  * @param source - Raw bytes or Blob.
3449
- * @param mimeType - IANA media type, e.g. `"image/png"`.
4913
+ * @param mimeType - IANA media type, e.g. `"image/png"`. It must pass the same
4914
+ * admission rule `generate` applies to a Gemini model's parts (one shared
4915
+ * function, so a file that uploads can be used): an empty or unadmitted type
4916
+ * is `bad_request` before any bytes are sent. The string is sent to Google
4917
+ * unchanged.
3450
4918
  * @param opts - Optional display name.
3451
4919
  */
3452
4920
  async upload(source, mimeType, opts) {
3453
4921
  const signal = opts?.signal;
4922
+ assertMediaTypeAdmitted(
4923
+ mimeType,
4924
+ GEMINI_INPUT_MIME_TYPES,
4925
+ "mimeType",
4926
+ "google",
4927
+ "a Google file upload"
4928
+ );
3454
4929
  const client = await this.getClient();
4930
+ if (signal?.aborted === true) {
4931
+ throw new LlmError("File upload aborted", { kind: "aborted", retryable: false });
4932
+ }
3455
4933
  let uploadResp;
3456
4934
  try {
3457
- uploadResp = await client.upload({
3458
- file: source,
3459
- config: {
3460
- mimeType,
3461
- ...opts?.displayName !== void 0 ? { displayName: opts.displayName } : {}
3462
- }
3463
- });
4935
+ uploadResp = await abortable(
4936
+ client.upload({
4937
+ file: source,
4938
+ config: {
4939
+ mimeType,
4940
+ ...opts?.displayName !== void 0 ? { displayName: opts.displayName } : {},
4941
+ ...signal !== void 0 ? { abortSignal: signal } : {}
4942
+ }
4943
+ }),
4944
+ signal
4945
+ );
3464
4946
  } catch (e) {
3465
- throw classifyError(e);
4947
+ throw classifyGoogleError(e);
3466
4948
  }
3467
4949
  const { name, uri } = uploadResp;
3468
4950
  if (name === void 0 || name.length === 0 || uri === void 0 || uri.length === 0) {
@@ -3477,10 +4959,7 @@ var GoogleFileStore = class {
3477
4959
  return makeHandle(uploadResp, fallback);
3478
4960
  }
3479
4961
  if (uploadResp.state === "FAILED") {
3480
- throw new LlmError("File processing failed immediately after upload", {
3481
- kind: "bad_request",
3482
- retryable: false
3483
- });
4962
+ throw failedFile("File processing failed immediately after upload", uploadResp);
3484
4963
  }
3485
4964
  const deadline = this.now() + this.timeoutMs;
3486
4965
  if (signal?.aborted === true) {
@@ -3489,49 +4968,102 @@ var GoogleFileStore = class {
3489
4968
  retryable: false
3490
4969
  });
3491
4970
  }
4971
+ let onAbort;
3492
4972
  const abortRacePromise = signal !== void 0 ? new Promise((_, reject) => {
3493
- signal.addEventListener(
3494
- "abort",
4973
+ onAbort = () => {
4974
+ reject(
4975
+ new LlmError("File upload polling aborted", {
4976
+ kind: "aborted",
4977
+ retryable: false
4978
+ })
4979
+ );
4980
+ };
4981
+ signal.addEventListener("abort", onAbort, { once: true });
4982
+ }) : void 0;
4983
+ abortRacePromise?.catch(() => {
4984
+ });
4985
+ try {
4986
+ return await this.pollUntilActive(
4987
+ client,
4988
+ name,
4989
+ fallback,
4990
+ deadline,
4991
+ signal,
4992
+ abortRacePromise
4993
+ );
4994
+ } finally {
4995
+ if (signal !== void 0 && onAbort !== void 0) {
4996
+ signal.removeEventListener("abort", onAbort);
4997
+ }
4998
+ }
4999
+ }
5000
+ /**
5001
+ * `work` raced against the abort promise and the time left until `deadline`
5002
+ * (the deadline is a non-retryable `server` error). `work` is observed, so a
5003
+ * late rejection after the race is lost is not unhandled; the deadline timer
5004
+ * is always cleared.
5005
+ */
5006
+ async raceDeadline(work, name, deadline, abortRacePromise) {
5007
+ work.catch(() => {
5008
+ });
5009
+ const remainingMs = deadline - this.now();
5010
+ let deadlineTimer;
5011
+ const deadlineRace = new Promise((_, reject) => {
5012
+ if (remainingMs > MAX_TIMER_MS) return;
5013
+ deadlineTimer = this.scheduler.setTimeout(
3495
5014
  () => {
3496
- reject(
3497
- new LlmError("File upload polling aborted", {
3498
- kind: "aborted",
3499
- retryable: false
3500
- })
3501
- );
5015
+ reject(pollTimeoutError(name));
3502
5016
  },
3503
- { once: true }
5017
+ Math.max(remainingMs, 0)
3504
5018
  );
3505
- }) : void 0;
5019
+ });
5020
+ deadlineRace.catch(() => {
5021
+ });
5022
+ try {
5023
+ return await Promise.race(
5024
+ abortRacePromise !== void 0 ? [work, deadlineRace, abortRacePromise] : [work, deadlineRace]
5025
+ );
5026
+ } finally {
5027
+ if (deadlineTimer !== void 0) this.scheduler.clearTimeout(deadlineTimer);
5028
+ }
5029
+ }
5030
+ /** Polls `name` until ACTIVE; see {@link GoogleFileStore.upload}. */
5031
+ async pollUntilActive(client, name, fallback, deadline, signal, abortRacePromise) {
3506
5032
  for (; ; ) {
3507
5033
  if (this.now() >= deadline) {
3508
- throw new LlmError("Timed out waiting for uploaded file to become ACTIVE", {
3509
- kind: "timeout",
3510
- retryable: true
3511
- });
5034
+ throw pollTimeoutError(name);
3512
5035
  }
3513
- const sleepCall = this.sleep(this.intervalMs);
3514
- await (abortRacePromise !== void 0 ? Promise.race([sleepCall, abortRacePromise]) : sleepCall);
3515
- let pollResp;
5036
+ const wait = this.startWait(this.intervalMs);
3516
5037
  try {
3517
- pollResp = await client.get({ name });
3518
- } catch (e) {
3519
- throw classifyError(e);
5038
+ await this.raceDeadline(wait.promise, name, deadline, abortRacePromise);
5039
+ } finally {
5040
+ wait.cancel();
5041
+ }
5042
+ if (this.now() >= deadline) {
5043
+ throw pollTimeoutError(name);
3520
5044
  }
5045
+ const pollCall = (async () => {
5046
+ try {
5047
+ return await client.get({ name });
5048
+ } catch (e) {
5049
+ throw classifyGoogleError(e);
5050
+ }
5051
+ })();
5052
+ const pollResp = await this.raceDeadline(pollCall, name, deadline, abortRacePromise);
3521
5053
  if (signal?.aborted === true) {
3522
5054
  throw new LlmError("File upload polling aborted", {
3523
5055
  kind: "aborted",
3524
5056
  retryable: false
3525
5057
  });
3526
5058
  }
5059
+ if (this.now() >= deadline) {
5060
+ throw pollTimeoutError(name);
5061
+ }
3527
5062
  if (pollResp.state === "ACTIVE") {
3528
5063
  return makeHandle(pollResp, fallback);
3529
5064
  }
3530
5065
  if (pollResp.state === "FAILED") {
3531
- throw new LlmError("File processing failed during polling", {
3532
- kind: "bad_request",
3533
- retryable: false
3534
- });
5066
+ throw failedFile("File processing failed during polling", pollResp);
3535
5067
  }
3536
5068
  }
3537
5069
  }
@@ -3568,15 +5100,7 @@ var GoogleFileStore = class {
3568
5100
  if (isGoogleNotFoundError(err)) {
3569
5101
  return;
3570
5102
  }
3571
- const classified = err instanceof LlmError ? err : classifyError(err);
3572
- const withProvider = classified.provider === void 0 ? new LlmError(classified.message, {
3573
- kind: classified.kind,
3574
- retryable: classified.retryable,
3575
- ...classified.httpStatus !== void 0 ? { httpStatus: classified.httpStatus } : {},
3576
- ...classified.retryAfterMs !== void 0 ? { retryAfterMs: classified.retryAfterMs } : {},
3577
- provider: "google",
3578
- cause: classified.cause ?? err
3579
- }) : classified;
5103
+ const withProvider = classifyGoogleError(err);
3580
5104
  if (failClosed) {
3581
5105
  throw withProvider;
3582
5106
  }
@@ -3598,31 +5122,58 @@ var GoogleFileStore = class {
3598
5122
  await Promise.allSettled(handles.map((h) => this.delete(h, opts)));
3599
5123
  }
3600
5124
  };
3601
- function isGoogleNotFoundError(err) {
3602
- if (err instanceof LlmError && err.httpStatus === 404) {
3603
- return true;
3604
- }
3605
- if (typeof err !== "object" || err === null) {
3606
- return false;
3607
- }
3608
- const obj = err;
3609
- if (obj["status"] === 404 || obj["httpStatus"] === 404 || obj["code"] === 404) {
3610
- return true;
3611
- }
3612
- if (obj["status"] === "NOT_FOUND" || obj["code"] === "NOT_FOUND") {
3613
- return true;
3614
- }
3615
- const msg = typeof obj["message"] === "string" ? obj["message"] : "";
3616
- if (/not\s*found|404/i.test(msg) && /file/i.test(msg)) {
3617
- return true;
5125
+ var TRANSIENT_FILE_STATUS_CODES = /* @__PURE__ */ new Set([4, 13, 14]);
5126
+ function failedFile(prefix, resp) {
5127
+ const providerMessage = resp.error?.message;
5128
+ const transient = typeof resp.error?.code === "number" && TRANSIENT_FILE_STATUS_CODES.has(resp.error.code);
5129
+ return new LlmError(
5130
+ providerMessage !== void 0 && providerMessage.length > 0 ? `${prefix}: ${providerMessage}` : prefix,
5131
+ {
5132
+ kind: transient ? "server" : "bad_request",
5133
+ retryable: transient,
5134
+ provider: "google",
5135
+ ...resp.error !== void 0 ? { cause: resp.error } : {}
5136
+ }
5137
+ );
5138
+ }
5139
+ async function abortable(promise, signal) {
5140
+ if (signal === void 0) return promise;
5141
+ promise.catch(() => {
5142
+ });
5143
+ let onAbort;
5144
+ const aborted = new Promise((_, reject) => {
5145
+ onAbort = () => {
5146
+ reject(new LlmError("File upload aborted", { kind: "aborted", retryable: false }));
5147
+ };
5148
+ signal.addEventListener("abort", onAbort, { once: true });
5149
+ if (signal.aborted) onAbort();
5150
+ });
5151
+ try {
5152
+ return await Promise.race([promise, aborted]);
5153
+ } finally {
5154
+ if (onAbort !== void 0) signal.removeEventListener("abort", onAbort);
3618
5155
  }
3619
- return false;
3620
5156
  }
3621
5157
  var DEFAULT_SKEW_SECONDS = 30;
3622
5158
  var DEFAULT_EXTENSION_SECONDS = 3600;
5159
+ function toolKindsOf(tools) {
5160
+ const kinds = /* @__PURE__ */ new Set();
5161
+ for (const tool of tools ?? []) {
5162
+ for (const [kind, value] of Object.entries(tool)) {
5163
+ if (value !== void 0) kinds.add(kind);
5164
+ }
5165
+ }
5166
+ return [...kinds];
5167
+ }
5168
+ function expiryOf(expireTime, fallbackMs) {
5169
+ if (expireTime !== void 0 && expireTime.length > 0) {
5170
+ const parsed = new Date(expireTime);
5171
+ if (!Number.isNaN(parsed.getTime())) return parsed;
5172
+ }
5173
+ return new Date(fallbackMs);
5174
+ }
3623
5175
  async function buildCachesClient(auth) {
3624
- const { GoogleGenAI } = await import('@google/genai');
3625
- const ai = new GoogleGenAI({ apiKey: requireApiKey(auth) });
5176
+ const ai = await newGoogleGenAI(auth);
3626
5177
  return {
3627
5178
  async create(params) {
3628
5179
  const result = await ai.caches.create(params);
@@ -3661,13 +5212,13 @@ var GoogleCacheStore = class {
3661
5212
  this.onDeleteError = opts.onDeleteError ?? ((cacheName, err) => {
3662
5213
  if (this.logger !== void 0) {
3663
5214
  this.logger.error(
3664
- { name: cacheName, error: redactSecrets(classifyError(err).message) },
5215
+ { name: cacheName, error: redactSecrets(classifyGoogleError(err).message) },
3665
5216
  "gemini.cache.delete.failed"
3666
5217
  );
3667
5218
  } else {
3668
5219
  console.error(
3669
5220
  `[GoogleCacheStore] delete failed for "${cacheName}":`,
3670
- redactSecrets(classifyError(err).message)
5221
+ redactSecrets(classifyGoogleError(err).message)
3671
5222
  );
3672
5223
  }
3673
5224
  });
@@ -3692,11 +5243,18 @@ var GoogleCacheStore = class {
3692
5243
  * `now + ttlSeconds * 1000`.
3693
5244
  */
3694
5245
  async create(input) {
5246
+ if (!Number.isInteger(input.ttlSeconds) || input.ttlSeconds <= 0) {
5247
+ throw new LlmError(
5248
+ `GoogleCacheStore.create: ttlSeconds must be a positive integer, got ${String(input.ttlSeconds)}.`,
5249
+ { kind: "bad_request", retryable: false, provider: "google" }
5250
+ );
5251
+ }
3695
5252
  if (this.preflight !== void 0) {
3696
5253
  const counted = await this.preflight.countTokens({
3697
5254
  model: input.model,
3698
5255
  ...input.contents !== void 0 ? { contents: input.contents } : {},
3699
- ...input.systemInstruction !== void 0 ? { systemInstruction: input.systemInstruction } : {}
5256
+ ...input.systemInstruction !== void 0 ? { systemInstruction: input.systemInstruction } : {},
5257
+ ...input.tools !== void 0 ? { tools: input.tools } : {}
3700
5258
  });
3701
5259
  if (counted < this.preflight.minTokens) {
3702
5260
  throw new LlmError(
@@ -3710,12 +5268,14 @@ var GoogleCacheStore = class {
3710
5268
  if (input.contents !== void 0) config.contents = input.contents;
3711
5269
  if (input.systemInstruction !== void 0)
3712
5270
  config.systemInstruction = input.systemInstruction;
5271
+ if (input.tools !== void 0) config.tools = input.tools;
5272
+ if (input.toolConfig !== void 0) config.toolConfig = input.toolConfig;
3713
5273
  if (input.displayName !== void 0) config.displayName = input.displayName;
3714
5274
  let resp;
3715
5275
  try {
3716
5276
  resp = await client.create({ model: input.model, config });
3717
5277
  } catch (e) {
3718
- throw classifyError(e);
5278
+ throw classifyGoogleError(e);
3719
5279
  }
3720
5280
  if (resp.name === void 0 || resp.name.length === 0) {
3721
5281
  throw new LlmError("Cache create response missing required field: name", {
@@ -3724,12 +5284,14 @@ var GoogleCacheStore = class {
3724
5284
  provider: "google"
3725
5285
  });
3726
5286
  }
3727
- const fallbackExpiry = new Date(this.now() + input.ttlSeconds * 1e3);
3728
- const expiresAt = resp.expireTime !== void 0 && resp.expireTime.length > 0 ? new Date(resp.expireTime) : fallbackExpiry;
5287
+ const expiresAt = expiryOf(resp.expireTime, this.now() + input.ttlSeconds * 1e3);
5288
+ const totalTokenCount = resp.usageMetadata?.totalTokenCount;
3729
5289
  return {
3730
5290
  cacheName: resp.name,
3731
5291
  model: resp.model ?? input.model,
3732
- expiresAt
5292
+ expiresAt,
5293
+ ...typeof totalTokenCount === "number" && Number.isFinite(totalTokenCount) ? { totalTokenCount } : {},
5294
+ toolKinds: toolKindsOf(input.tools)
3733
5295
  };
3734
5296
  }
3735
5297
  /**
@@ -3744,8 +5306,9 @@ var GoogleCacheStore = class {
3744
5306
  async getOrCreate(key, factory) {
3745
5307
  const mapKey = `${key.model}:${key.stableKey}`;
3746
5308
  const existing = this.entries.get(mapKey);
3747
- if (existing !== void 0 && this.isLive(existing.handle)) {
3748
- return existing.handle;
5309
+ if (existing !== void 0) {
5310
+ if (this.isLive(existing.handle)) return existing.handle;
5311
+ this.entries.delete(mapKey);
3749
5312
  }
3750
5313
  if (this.coalesce) {
3751
5314
  const inFlight = this.inflight.get(mapKey);
@@ -3759,7 +5322,9 @@ var GoogleCacheStore = class {
3759
5322
  model: key.model,
3760
5323
  ttlSeconds: factoryResult.ttlSeconds,
3761
5324
  ...factoryResult.contents !== void 0 ? { contents: factoryResult.contents } : {},
3762
- ...factoryResult.systemInstruction !== void 0 ? { systemInstruction: factoryResult.systemInstruction } : {}
5325
+ ...factoryResult.systemInstruction !== void 0 ? { systemInstruction: factoryResult.systemInstruction } : {},
5326
+ ...factoryResult.tools !== void 0 ? { tools: factoryResult.tools } : {},
5327
+ ...factoryResult.toolConfig !== void 0 ? { toolConfig: factoryResult.toolConfig } : {}
3763
5328
  });
3764
5329
  this.entries.set(mapKey, { handle, ttlSeconds: factoryResult.ttlSeconds });
3765
5330
  return handle;
@@ -3806,12 +5371,13 @@ var GoogleCacheStore = class {
3806
5371
  name: handle.cacheName,
3807
5372
  config: { ttl: `${extensionSeconds}s` }
3808
5373
  });
3809
- const fallbackExpiry = new Date(this.now() + extensionSeconds * 1e3);
3810
- const newExpiresAt = resp.expireTime !== void 0 && resp.expireTime.length > 0 ? new Date(resp.expireTime) : fallbackExpiry;
5374
+ const newExpiresAt = expiryOf(resp.expireTime, this.now() + extensionSeconds * 1e3);
3811
5375
  const newHandle = {
3812
5376
  cacheName: handle.cacheName,
3813
5377
  model: handle.model,
3814
- expiresAt: newExpiresAt
5378
+ expiresAt: newExpiresAt,
5379
+ ...handle.totalTokenCount !== void 0 ? { totalTokenCount: handle.totalTokenCount } : {},
5380
+ ...handle.toolKinds !== void 0 ? { toolKinds: handle.toolKinds } : {}
3815
5381
  };
3816
5382
  for (const [k, entry] of this.entries.entries()) {
3817
5383
  if (entry.handle.cacheName === handle.cacheName) {
@@ -3825,9 +5391,11 @@ var GoogleCacheStore = class {
3825
5391
  }
3826
5392
  }
3827
5393
  /**
3828
- * Delete a cached content resource.
5394
+ * Delete a cached content resource. Idempotent: a cache that is already gone
5395
+ * (HTTP 404, or the 403 `CachedContent not found` Google sends for an expired
5396
+ * one) is success, not an error.
3829
5397
  *
3830
- * Errors are forwarded to `onDeleteError` and NOT rethrown.
5398
+ * Any other error is forwarded to `onDeleteError` and NOT rethrown.
3831
5399
  * The handle is removed from the in-process entries map regardless.
3832
5400
  */
3833
5401
  async delete(handle) {
@@ -3841,6 +5409,7 @@ var GoogleCacheStore = class {
3841
5409
  const client = await this.getClient();
3842
5410
  await client.delete({ name: handle.cacheName });
3843
5411
  } catch (err) {
5412
+ if (isGoogleNotFoundError(err)) return;
3844
5413
  this.onDeleteError(handle.cacheName, err);
3845
5414
  }
3846
5415
  }
@@ -3882,7 +5451,7 @@ function convertMediaResolution(mediaResolution, location) {
3882
5451
  return mapMediaResolutionLevel(mediaResolution.level, location);
3883
5452
  }
3884
5453
  function convertPart(part, location, nameCounts, reservedIds) {
3885
- const keys = definedKeys(part);
5454
+ const keys = definedKeys(part).filter((key) => key !== "thoughtSignature");
3886
5455
  const baseKeys = keys.filter(
3887
5456
  (key) => key === "text" || key === "inlineData" || key === "fileData"
3888
5457
  );
@@ -3991,6 +5560,20 @@ function convertPart(part, location, nameCounts, reservedIds) {
3991
5560
  }
3992
5561
  throw badRequest(`${location}: Part has no recognized fields set.`);
3993
5562
  }
5563
+ function convertPartWithSignature(part, location, role, nameCounts, reservedIds) {
5564
+ const converted = convertPart(part, location, nameCounts, reservedIds);
5565
+ const signature = part.thoughtSignature;
5566
+ if (signature === void 0) return { part: converted };
5567
+ if (typeof signature !== "string" || signature.length === 0) {
5568
+ throw badRequest(`${location}: Part.thoughtSignature must be a non-empty string.`);
5569
+ }
5570
+ if (role !== "assistant" || converted.kind !== "text" && converted.kind !== "tool-call") {
5571
+ throw badRequest(
5572
+ `${location}: Part.thoughtSignature can only be imported from a model text or functionCall part.`
5573
+ );
5574
+ }
5575
+ return { part: converted, signature };
5576
+ }
3994
5577
  function convertRole(role, location) {
3995
5578
  if (role === "user") return "user";
3996
5579
  if (role === "model") return "assistant";
@@ -4036,6 +5619,7 @@ function geminiContentToMessages(input) {
4036
5619
  })
4037
5620
  )
4038
5621
  );
5622
+ const signatures = [];
4039
5623
  const messages = input.contents.map((content, contentIndex) => {
4040
5624
  const location = `contents[${contentIndex}]`;
4041
5625
  const envelopeExtraKeys = definedKeys(content).filter(
@@ -4047,14 +5631,42 @@ function geminiContentToMessages(input) {
4047
5631
  );
4048
5632
  }
4049
5633
  const role = convertRole(content.role, location);
4050
- const parts = (content.parts ?? []).map(
4051
- (part, partIndex) => convertPart(part, `${location}.parts[${partIndex}]`, nameCounts, reservedIds)
4052
- );
5634
+ const parts = (content.parts ?? []).map((part, partIndex) => {
5635
+ const partLocation = `${location}.parts[${partIndex}]`;
5636
+ const converted = convertPartWithSignature(
5637
+ part,
5638
+ partLocation,
5639
+ role,
5640
+ nameCounts,
5641
+ reservedIds
5642
+ );
5643
+ if (converted.signature !== void 0) {
5644
+ if (input.model === void 0) {
5645
+ throw badRequest(
5646
+ `${partLocation}: Part.thoughtSignature requires the \`model\` input; a signature is bound to the model that issued it.`
5647
+ );
5648
+ }
5649
+ signatures.push(
5650
+ signatureEntry(
5651
+ contentIndex,
5652
+ partIndex,
5653
+ input.model,
5654
+ converted.part,
5655
+ converted.signature
5656
+ )
5657
+ );
5658
+ }
5659
+ return converted.part;
5660
+ });
4053
5661
  return { role, parts };
4054
5662
  });
4055
- return { ...system !== void 0 ? { system } : {}, messages };
5663
+ return {
5664
+ ...system !== void 0 ? { system } : {},
5665
+ messages,
5666
+ ...signatures.length > 0 ? { transientProviderState: { google: { signatures } } } : {}
5667
+ };
4056
5668
  }
4057
5669
 
4058
- export { FLEX_DEFAULT_TIMEOUT_MS, GEMINI_PRICED_TIERS, GEMINI_PRICING, GoogleCacheStore, GoogleFileStore, STANDARD_DEFAULT_TIMEOUT_MS, TRANSPORT_TIMEOUT_BUFFER_MS, buildGoogleClient, defaultGeminiRegistry, geminiAdapter, geminiContentToMessages, geminiModelDescriptors, geminiPricingSource, gemmaModelDescriptors, googleProvider, isGeminiCapacityError, pricingVersion, requireApiKey, resolveGeminiRates };
5670
+ export { FLEX_DEFAULT_TIMEOUT_MS, GEMINI_GROUNDING_PRICING, GEMINI_INPUT_MIME_TYPES, GEMINI_PRICED_TIERS, GEMINI_PRICING, GoogleCacheStore, GoogleFileStore, STANDARD_DEFAULT_TIMEOUT_MS, TRANSPORT_TIMEOUT_BUFFER_MS, buildGoogleClient, classifyGoogleError, defaultGeminiRegistry, dropMessagesFromSignatureState, geminiAdapter, geminiContentToMessages, geminiModelDescriptors, geminiPricingSource, gemmaModelDescriptors, googleProvider, isGeminiCapacityError, pricingVersion, requireApiKey, resolveGeminiRates };
4059
5671
  //# sourceMappingURL=index.js.map
4060
5672
  //# sourceMappingURL=index.js.map