@llumiverse/drivers 1.0.0 → 1.1.0-dev.20260427.054520Z

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/lib/cjs/bedrock/index.js +90 -10
  2. package/lib/cjs/bedrock/index.js.map +1 -1
  3. package/lib/cjs/openai/index.js +2 -0
  4. package/lib/cjs/openai/index.js.map +1 -1
  5. package/lib/cjs/vertexai/index.js +31 -22
  6. package/lib/cjs/vertexai/index.js.map +1 -1
  7. package/lib/cjs/vertexai/models/claude.js +99 -26
  8. package/lib/cjs/vertexai/models/claude.js.map +1 -1
  9. package/lib/cjs/vertexai/models/gemini.js +35 -335
  10. package/lib/cjs/vertexai/models/gemini.js.map +1 -1
  11. package/lib/esm/bedrock/index.js +90 -10
  12. package/lib/esm/bedrock/index.js.map +1 -1
  13. package/lib/esm/openai/index.js +2 -0
  14. package/lib/esm/openai/index.js.map +1 -1
  15. package/lib/esm/vertexai/index.js +31 -22
  16. package/lib/esm/vertexai/index.js.map +1 -1
  17. package/lib/esm/vertexai/models/claude.js +99 -28
  18. package/lib/esm/vertexai/models/claude.js.map +1 -1
  19. package/lib/esm/vertexai/models/gemini.js +36 -336
  20. package/lib/esm/vertexai/models/gemini.js.map +1 -1
  21. package/lib/types/bedrock/index.d.ts +5 -2
  22. package/lib/types/bedrock/index.d.ts.map +1 -1
  23. package/lib/types/openai/index.d.ts.map +1 -1
  24. package/lib/types/vertexai/index.d.ts +4 -1
  25. package/lib/types/vertexai/index.d.ts.map +1 -1
  26. package/lib/types/vertexai/models/claude.d.ts +16 -0
  27. package/lib/types/vertexai/models/claude.d.ts.map +1 -1
  28. package/lib/types/vertexai/models/gemini.d.ts +4 -8
  29. package/lib/types/vertexai/models/gemini.d.ts.map +1 -1
  30. package/package.json +8 -8
  31. package/src/bedrock/index.ts +104 -12
  32. package/src/bedrock/streaming-tool-use.test.ts +250 -0
  33. package/src/openai/index.ts +2 -0
  34. package/src/vertexai/index.ts +32 -22
  35. package/src/vertexai/models/claude-streaming-spacing.test.ts +174 -0
  36. package/src/vertexai/models/claude.ts +120 -29
  37. package/src/vertexai/models/gemini-conversation-mutation.test.ts +174 -0
  38. package/src/vertexai/models/gemini.ts +48 -391
@@ -1,32 +1,32 @@
1
1
  import type { ApiError } from "@google/genai";
2
2
  import {
3
- Content, FinishReason, FunctionCallingConfigMode, FunctionDeclaration, GenerateContentConfig, GenerateContentParameters,
4
- GenerateContentResponseUsageMetadata,
5
- HarmBlockThreshold, HarmCategory, Modality, Part,
3
+ type Content, FinishReason, FunctionCallingConfigMode, type FunctionDeclaration, type GenerateContentConfig, type GenerateContentParameters,
4
+ type GenerateContentResponseUsageMetadata,
5
+ HarmBlockThreshold, HarmCategory, Modality, type Part,
6
6
  ProminentPeople,
7
- SafetySetting, Schema, ThinkingConfig,
7
+ type SafetySetting, type ThinkingConfig,
8
8
  ThinkingLevel,
9
- Tool, Type
9
+ type Tool
10
10
  } from "@google/genai";
11
11
  import {
12
- AIModel, Completion, CompletionChunkObject, CompletionResult, ExecutionOptions,
13
- ExecutionTokenUsage,
12
+ type AIModel, type Completion, type CompletionChunkObject, type CompletionResult, type ExecutionOptions,
13
+ type ExecutionTokenUsage,
14
14
  getConversationMeta,
15
15
  getGeminiModelVersion,
16
16
  incrementConversationTurn,
17
17
  isGeminiModelVersionGte,
18
- JSONObject, JSONSchema, LlumiverseError, LlumiverseErrorContext, ModelType, PromptOptions, PromptRole,
19
- PromptSegment, readStreamAsBase64, StatelessExecutionOptions,
18
+ type JSONObject, LlumiverseError, type LlumiverseErrorContext, ModelType, type PromptOptions, PromptRole,
19
+ type PromptSegment, readStreamAsBase64, type StatelessExecutionOptions,
20
20
  stripBase64ImagesFromConversation,
21
21
  stripHeartbeatsFromConversation,
22
- ToolDefinition, ToolUse,
22
+ type ToolDefinition, type ToolUse,
23
23
  truncateLargeTextInConversation,
24
24
  unwrapConversationArray,
25
- VertexAIGeminiOptions
25
+ type VertexAIGeminiOptions
26
26
  } from "@llumiverse/core";
27
27
  import { asyncMap } from "@llumiverse/core/async";
28
- import { GenerateContentPrompt, VertexAIDriver } from "../index.js";
29
- import { ModelDefinition } from "../models.js";
28
+ import type { GenerateContentPrompt, VertexAIDriver } from "../index.js";
29
+ import type { ModelDefinition } from "../models.js";
30
30
 
31
31
  function supportsStructuredOutput(options: PromptOptions): boolean {
32
32
  // Gemini 1.0 Ultra does not support JSON output, 1.0 Pro does.
@@ -79,13 +79,15 @@ function getGeminiPayload(options: ExecutionOptions, prompt: GenerateContentProm
79
79
  const tools = getToolDefinitions(options.tools);
80
80
 
81
81
  // When no tools are provided but conversation contains functionCall/functionResponse parts
82
- // (e.g. checkpoint summary calls), convert them to text to avoid API errors
83
- if (!tools && prompt.contents) {
84
- const hasToolParts = prompt.contents.some(c =>
82
+ // (e.g. checkpoint summary calls), convert them to text to avoid API errors.
83
+ // Use a local variable to avoid mutating the caller's conversation object.
84
+ let payloadContents = prompt.contents;
85
+ if (!tools && payloadContents) {
86
+ const hasToolParts = payloadContents.some(c =>
85
87
  c.parts?.some(p => p.functionCall || p.functionResponse)
86
88
  );
87
89
  if (hasToolParts) {
88
- prompt.contents = convertGeminiFunctionPartsToText(prompt.contents);
90
+ payloadContents = convertGeminiFunctionPartsToText(payloadContents);
89
91
  }
90
92
  }
91
93
 
@@ -102,6 +104,7 @@ function getGeminiPayload(options: ExecutionOptions, prompt: GenerateContentProm
102
104
  maxOutputTokens: model_options?.max_tokens,
103
105
  stopSequences: model_options?.stop_sequence,
104
106
  thinkingConfig: geminiThinkingConfig(options),
107
+ labels: options.labels,
105
108
  imageConfig: {
106
109
  imageSize: model_options?.image_size,
107
110
  aspectRatio: model_options?.image_aspect_ratio,
@@ -124,7 +127,7 @@ function getGeminiPayload(options: ExecutionOptions, prompt: GenerateContentProm
124
127
  candidateCount: 1,
125
128
  //JSON/Structured output
126
129
  responseMimeType: useStructuredOutput ? "application/json" : undefined,
127
- responseSchema: useStructuredOutput ? parseJSONtoSchema(options.result_schema, true) : undefined,
130
+ responseJsonSchema: useStructuredOutput ? options.result_schema : undefined,
128
131
  //Model options
129
132
  temperature: model_options?.temperature,
130
133
  topP: model_options?.top_p,
@@ -135,340 +138,16 @@ function getGeminiPayload(options: ExecutionOptions, prompt: GenerateContentProm
135
138
  frequencyPenalty: model_options?.frequency_penalty,
136
139
  seed: model_options?.seed,
137
140
  thinkingConfig: geminiThinkingConfig(options),
141
+ labels: options.labels,
138
142
  }
139
143
 
140
144
  return {
141
145
  model: options.model,
142
- contents: prompt.contents,
146
+ contents: payloadContents,
143
147
  config: options.model.toLowerCase().includes("image") ? configNanoBanana : config,
144
148
  };
145
149
  }
146
150
 
147
- /**
148
- * Convert JSONSchema to Gemini Schema,
149
- * Make all properties required by default
150
- * Properties previously marked as optional will be marked as nullable.
151
- */
152
- function parseJSONtoSchema(schema?: JSONSchema, requiredAll = false): Schema {
153
- if (!schema) {
154
- return {};
155
- }
156
-
157
- return convertSchema(schema, 0, requiredAll);
158
- }
159
-
160
- /**
161
- * Convert JSONSchema type to Gemini Schema Type
162
- */
163
- function convertType(type?: string | string[]): Type | undefined {
164
- if (!type) return undefined;
165
-
166
- // Handle single type
167
- if (typeof type === 'string') {
168
- switch (type) {
169
- case 'string': return Type.STRING;
170
- case 'number': return Type.NUMBER;
171
- case 'integer': return Type.INTEGER;
172
- case 'boolean': return Type.BOOLEAN;
173
- case 'object': return Type.OBJECT;
174
- case 'array': return Type.ARRAY;
175
- default: return type as Type; // For unsupported types, return as is
176
- }
177
- }
178
-
179
- // For array of types, take the first valid one as the primary type
180
- // The full set of types will be handled with anyOf
181
- for (const t of type) {
182
- const converted = convertType(t);
183
- if (converted) return converted;
184
- }
185
-
186
- return undefined;
187
- }
188
-
189
- /**
190
- * Deep clone and convert the schema from JSONSchema to Gemini Schema
191
- * @throws {Error} If circular references are detected (max depth exceeded)
192
- */
193
- function convertSchema(jsSchema?: JSONSchema, depth: number = 0, requiredAll = false): Schema {
194
- // Prevent circular references
195
- if (depth > 20) {
196
- throw new Error("Maximum schema depth (20) exceeded. Possible circular reference detected.");
197
- }
198
-
199
- if (!jsSchema) return {};
200
-
201
- // Create new schema object rather than mutating
202
- const result: Schema = {};
203
-
204
- // Handle types
205
- result.type = convertSchemaType(jsSchema);
206
-
207
- // Handle description
208
- if (jsSchema.description) {
209
- result.description = jsSchema.description;
210
- }
211
-
212
- // Handle properties and required fields
213
- if (jsSchema.properties) {
214
- const propertyResult = convertSchemaProperties(jsSchema, depth + 1, requiredAll);
215
- Object.assign(result, propertyResult);
216
- }
217
-
218
- // Handle items for arrays
219
- if (jsSchema.items) {
220
- result.items = convertSchema(jsSchema.items, depth + 1);
221
- }
222
-
223
- // Handle enum values
224
- if (jsSchema.enum) {
225
- result.enum = [...jsSchema.enum]; // Create a copy instead of reference
226
- }
227
-
228
- // Copy constraints
229
- Object.assign(result, extractConstraints(jsSchema));
230
-
231
- return result;
232
- }
233
-
234
- /**
235
- * Convert schema type information, handling anyOf for multiple types
236
- */
237
- function convertSchemaType(jsSchema: JSONSchema): Type | undefined {
238
- // Handle multiple types using anyOf
239
- if (jsSchema.type && Array.isArray(jsSchema.type) && jsSchema.type.length > 1) {
240
- // Since anyOf is an advanced type, we'll return the first valid type
241
- // and handle the multi-type case separately in the schema
242
- return convertType(jsSchema.type[0]);
243
- }
244
- // Handle single type
245
- else if (jsSchema.type) {
246
- return convertType(jsSchema.type);
247
- }
248
-
249
- return undefined;
250
- }
251
-
252
- /**
253
- * Handle properties conversion and required fields
254
- */
255
- function convertSchemaProperties(jsSchema: JSONSchema, depth: number, requiredAll: boolean): Partial<Schema> {
256
- const result: Partial<Schema> = { properties: {} };
257
- if (jsSchema.required) {
258
- result.required = [...jsSchema.required]; // Create a copy
259
- }
260
-
261
- // Extract property ordering from the object keys
262
- const propertyNames = Object.keys(jsSchema.properties || {});
263
-
264
- // Set property ordering based on the existing order in the schema
265
- if (propertyNames.length > 0) {
266
- result.propertyOrdering = propertyNames;
267
-
268
- if (requiredAll) {
269
- // Mark all properties as required by default
270
- // This ensures the model fills all fields
271
- result.required = propertyNames;
272
-
273
- // Get the original required properties
274
- const originalRequired = jsSchema.required || [];
275
-
276
- // Make previously optional properties nullable since we're marking them as required
277
- for (const key of propertyNames) {
278
- const propSchema = jsSchema.properties?.[key];
279
- if (propSchema && !originalRequired.includes(key)) {
280
- // Initialize the property if needed
281
- if (!result.properties![key]) {
282
- result.properties![key] = {};
283
- }
284
-
285
- // Mark as nullable
286
- result.properties![key].nullable = true;
287
- }
288
- }
289
- }
290
- }
291
-
292
- // Convert each property schema
293
- for (const [key, value] of Object.entries(jsSchema.properties || {})) {
294
- if (!result.properties![key]) {
295
- result.properties![key] = {};
296
- }
297
-
298
- // Merge with converted schema
299
- result.properties![key] = {
300
- ...result.properties![key],
301
- ...convertSchema(value, depth)
302
- };
303
- }
304
-
305
- // Override with explicit propertyOrdering if present
306
- if (jsSchema.propertyOrdering) {
307
- result.propertyOrdering = [...jsSchema.propertyOrdering]; // Create a copy
308
- }
309
-
310
- return result;
311
- }
312
-
313
- /**
314
- * Extract schema constraints (min/max values, formats, etc.)
315
- */
316
- function extractConstraints(jsSchema: JSONSchema): Partial<Schema> {
317
- const constraints: Partial<Schema> = {};
318
-
319
- if (jsSchema.minimum !== undefined) constraints.minimum = jsSchema.minimum;
320
- if (jsSchema.maximum !== undefined) constraints.maximum = jsSchema.maximum;
321
- if (jsSchema.minLength !== undefined) constraints.minLength = jsSchema.minLength;
322
- if (jsSchema.maxLength !== undefined) constraints.maxLength = jsSchema.maxLength;
323
- if (jsSchema.minItems !== undefined) constraints.minItems = jsSchema.minItems;
324
- if (jsSchema.maxItems !== undefined) constraints.maxItems = jsSchema.maxItems;
325
- if (jsSchema.nullable !== undefined) constraints.nullable = jsSchema.nullable;
326
- if (jsSchema.pattern) constraints.pattern = jsSchema.pattern;
327
- if (jsSchema.format) constraints.format = jsSchema.format;
328
- if (jsSchema.default !== undefined) constraints.default = jsSchema.default;
329
- if (jsSchema.example !== undefined) constraints.example = jsSchema.example;
330
-
331
- return constraints;
332
- }
333
-
334
- /**
335
- * Check if a value is empty (null, undefined, empty string, empty array, empty object)
336
- * @param value The value to check
337
- * @returns True if the value is considered empty
338
- */
339
- function isEmpty(value: any): boolean {
340
- if (value === null || value === undefined) {
341
- return true;
342
- }
343
-
344
- if (typeof value === 'string' && value.trim() === '') {
345
- return true;
346
- }
347
-
348
- if (Array.isArray(value) && value.length === 0) {
349
- return true;
350
- }
351
-
352
- // Check for empty object (no own enumerable properties)
353
- if (typeof value === 'object' && Object.keys(value).length === 0) {
354
- return true;
355
- }
356
-
357
- // Check for array of empty objects
358
- if (Array.isArray(value) && value.every(item => isEmpty(item))) {
359
- return true;
360
- }
361
-
362
- return false;
363
- }
364
-
365
- // No array cleaning function needed as we're only working with JSONObjects
366
-
367
- /**
368
- * Clean up the JSON result by removing empty values for optional fields
369
- * Uses immutable patterns to create a new Content object rather than modifying the original
370
- * @param content The original content from Gemini
371
- * @param result_schema The JSON schema to use for cleaning
372
- * @returns A new Content object with cleaned JSON text
373
- */
374
- function cleanEmptyFieldsContent(content: Content, result_schema?: JSONSchema): Content {
375
- // If no schema provided, return original content
376
- if (!result_schema) {
377
- return content;
378
- }
379
-
380
- // Create a new content object (shallow copy)
381
- const cleanedContent: Content = { ...content };
382
-
383
- // Create a new parts array if it exists
384
- if (cleanedContent.parts) {
385
- cleanedContent.parts = cleanedContent.parts.map(part => {
386
- // Only process parts with text
387
- if (!part.text) {
388
- return part; // Return unchanged if no text
389
- }
390
-
391
- // Create a new part object
392
- const newPart = { ...part };
393
-
394
- try {
395
- // Parse JSON, clean it based on schema, then stringify
396
- const jsonText = JSON.parse(part.text);
397
- // Skip cleaning if not an object
398
- if (typeof jsonText === 'object' && jsonText !== null && !Array.isArray(jsonText)) {
399
- const cleanedJson = removeEmptyFields(jsonText, result_schema);
400
- newPart.text = JSON.stringify(cleanedJson);
401
- } else {
402
- // Keep original if not an object (string, number, array, etc.)
403
- newPart.text = part.text;
404
- }
405
- } catch (e) {
406
- // On error, keep the original text
407
- console.warn("Error parsing Gemini output to JSON in part:", e);
408
- }
409
-
410
- return newPart;
411
- });
412
- }
413
-
414
- return cleanedContent;
415
- }
416
-
417
- /**
418
- * Removes empty optional fields from the JSON result based on the provided schema
419
- * @param object The object to clean
420
- * @param schema The JSON schema to use for cleaning
421
- * @returns A new object with empty optional fields removed
422
- */
423
- function removeEmptyFields(object: JSONObject | any[], schema: JSONSchema): JSONObject | any[] {
424
- if (!object) {
425
- return object
426
- }
427
-
428
- if (Array.isArray(object)) {
429
- return removeEmptyJSONArray(object, schema);
430
- }
431
- if (typeof object == 'object' || object === null) {
432
- return removeEmptyJSONObject(object, schema);
433
- }
434
-
435
- return object;
436
- }
437
-
438
- function removeEmptyJSONObject(object: JSONObject, schema: JSONSchema): JSONObject {
439
- // Get the original required properties from schema
440
- const requiredProps = schema.required || [];
441
- const cleanedResult: JSONObject = { ...object };
442
-
443
- // Process each property
444
- for (const [key, value] of Object.entries(object)) {
445
- const isRequired = requiredProps.includes(key);
446
- const propSchema = schema.properties?.[key];
447
-
448
- // Recursively clean nested objects based on their schema
449
- cleanedResult[key] = removeEmptyFields(value as JSONObject, propSchema ?? {});
450
-
451
- if (isEmpty(value)) {
452
- if (isRequired) {
453
- continue; // Keep required fields even if empty
454
- } else {
455
- delete cleanedResult[key]; // Remove empty optional fields
456
- }
457
- }
458
- }
459
-
460
- return cleanedResult;
461
- }
462
-
463
- function removeEmptyJSONArray(array: any[], schema: JSONSchema): any[] {
464
- const cleanedArray = array.map(item => {
465
- return removeEmptyFields(item, schema);
466
- });
467
-
468
- // Filter out empty objects from the array
469
- return cleanedArray.filter(item => !isEmpty(item));
470
- }
471
-
472
151
  /**
473
152
  * Collect all parts (text and images) from content in order.
474
153
  * This preserves the original ordering of text and image parts.
@@ -569,7 +248,7 @@ function geminiThinkingBudget(option: StatelessExecutionOptions) {
569
248
  }
570
249
  // Set minimum thinking level by default.
571
250
  // Docs: https://ai.google.dev/gemini-api/docs/thinking#set-budget
572
- if (getGeminiModelVersion(option.model) == '2.5') {
251
+ if (getGeminiModelVersion(option.model) === '2.5') {
573
252
  if (option.model.includes("pro")) {
574
253
  return 128;
575
254
  }
@@ -595,6 +274,13 @@ function geminiThinkingConfig(option: StatelessExecutionOptions): ThinkingConfig
595
274
  // Docs: https://ai.google.dev/gemini-api/docs/thinking#set-budget
596
275
  // https://docs.cloud.google.com/vertex-ai/generative-ai/docs/thinking
597
276
  if (isGeminiModelVersionGte(option.model, '3.0')) {
277
+ if (option.model.includes("gemini-3-pro-image")) {
278
+ // Does not support thinking level.
279
+ return {
280
+ includeThoughts: include_thoughts,
281
+ thinkingBudget: -1
282
+ };
283
+ }
598
284
  return {
599
285
  includeThoughts: include_thoughts,
600
286
  thinkingLevel: ThinkingLevel.LOW
@@ -623,31 +309,6 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
623
309
  } satisfies AIModel;
624
310
  }
625
311
 
626
- preValidationProcessing(result: Completion, options: ExecutionOptions): { result: Completion, options: ExecutionOptions } {
627
- // Guard clause, if no result_schema, error, or tool use, skip processing
628
- if (!options.result_schema || !result.result || result.tool_use || result.error) {
629
- return { result, options };
630
- }
631
- try {
632
- // Extract text content for JSON processing - only process first text result
633
- const textResult = result.result.find(r => r.type === 'text')?.value;
634
- if (textResult) {
635
- const jsonResult = JSON.parse(textResult);
636
- const cleanedJson = JSON.stringify(removeEmptyFields(jsonResult, options.result_schema));
637
- // Replace the text result with cleaned version
638
- result.result = result.result.map(r =>
639
- r.type === 'text' ? { ...r, value: cleanedJson } : r
640
- );
641
- }
642
- return { result, options };
643
- } catch (error) {
644
- // Log error during processing but don't fail the completion
645
- console.warn('Error during Gemini JSON pre-validation: ', error);
646
- // Return original result if cleanup fails
647
- return { result, options };
648
- }
649
- }
650
-
651
312
  async createPrompt(_driver: VertexAIDriver, segments: PromptSegment[], options: ExecutionOptions): Promise<GenerateContentPrompt> {
652
313
  const splits = options.model.split("/");
653
314
  const modelName = splits[splits.length - 1];
@@ -779,14 +440,19 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
779
440
  if (!usageMetadata || !usageMetadata.totalTokenCount) {
780
441
  return {};
781
442
  }
782
- const tokenUsage: ExecutionTokenUsage = { total: usageMetadata.totalTokenCount, prompt: usageMetadata.promptTokenCount };
443
+ const tokenUsage: ExecutionTokenUsage = {
444
+ total: usageMetadata.totalTokenCount,
445
+ prompt: usageMetadata.promptTokenCount,
446
+ prompt_cached: usageMetadata.cachedContentTokenCount ?? undefined,
447
+ prompt_new: (usageMetadata.promptTokenCount ?? 0) - (usageMetadata.cachedContentTokenCount ?? 0),
448
+ };
783
449
 
784
450
  //Output/Response side
785
451
  tokenUsage.result = (usageMetadata.candidatesTokenCount ?? 0)
786
452
  + (usageMetadata.thoughtsTokenCount ?? 0)
787
453
  + (usageMetadata.toolUsePromptTokenCount ?? 0);
788
454
 
789
- if ((tokenUsage.total ?? 0) != (tokenUsage.prompt ?? 0) + tokenUsage.result) {
455
+ if ((tokenUsage.total ?? 0) !== (tokenUsage.prompt ?? 0) + tokenUsage.result) {
790
456
  console.warn("[VertexAI] Gemini token usage mismatch: total does not equal prompt + result", {
791
457
  total: tokenUsage.total,
792
458
  prompt: tokenUsage.prompt,
@@ -827,7 +493,8 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
827
493
  region = "global"; // Gemini Flash Image only available in global region, this is for nano-banana model
828
494
  }
829
495
 
830
- const client = driver.getGoogleGenAIClient(region);
496
+ const model_options = options.model_options as VertexAIGeminiOptions | undefined;
497
+ const client = driver.getGoogleGenAIClient(region, model_options?.flex ?? false);
831
498
 
832
499
  const payload = getGeminiPayload(options, prompt);
833
500
  const response = await client.models.generateContent(payload);
@@ -863,11 +530,8 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
863
530
  `Model tried to call undeclared tool(s): ${tool_use.map(t => t.tool_name).join(', ')}`);
864
531
  }
865
532
 
866
- // We clean the content before validation, so we can update the conversation.
867
- const cleanedContent = cleanEmptyFieldsContent(content, options.result_schema);
868
- // Collect all parts in order (text and images)
869
- result = extractCompletionResults(cleanedContent);
870
- conversation = updateConversation(conversation, [cleanedContent]);
533
+ result = extractCompletionResults(content);
534
+ conversation = updateConversation(conversation, [content]);
871
535
  }
872
536
  }
873
537
 
@@ -937,7 +601,8 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
937
601
  region = "global"; // Gemini Flash Image only available in global region, this is for nano-banana model
938
602
  }
939
603
 
940
- const client = driver.getGoogleGenAIClient(region);
604
+ const model_options = options.model_options as VertexAIGeminiOptions | undefined;
605
+ const client = driver.getGoogleGenAIClient(region, model_options?.flex ?? false);
941
606
 
942
607
  const payload = getGeminiPayload(options, prompt);
943
608
  const response = await client.models.generateContentStream(payload);
@@ -1174,21 +839,13 @@ function getToolDefinitions(tools: ToolDefinition[] | undefined | null): Tool |
1174
839
  }
1175
840
 
1176
841
  function getToolFunction(tool: ToolDefinition): FunctionDeclaration {
1177
- // If input_schema is a string, parse it; if it's already an object, use it directly
1178
- let toolSchema: Schema | undefined;
1179
-
1180
- // Using a try-catch for safety, as the input_schema might not be a valid JSONSchema
1181
- try {
1182
- toolSchema = parseJSONtoSchema(tool.input_schema as JSONSchema, false);
1183
- }
1184
- catch (e) {
1185
- toolSchema = { ...tool.input_schema, type: Type.OBJECT } as unknown as Schema;
1186
- }
1187
-
1188
842
  return {
1189
843
  name: tool.name,
1190
844
  description: tool.description,
1191
- parameters: toolSchema,
845
+ // Pass the input_schema directly as a JSON Schema object.
846
+ // parametersJsonSchema accepts standard JSON Schema and is mutually exclusive
847
+ // with the legacy parameters field (which required a proprietary Gemini Schema type).
848
+ parametersJsonSchema: tool.input_schema,
1192
849
  };
1193
850
  }
1194
851