@llumiverse/drivers 1.0.0-dev.20260202.145450Z → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (240) hide show
  1. package/lib/cjs/adobe/firefly.js +120 -0
  2. package/lib/cjs/adobe/firefly.js.map +1 -0
  3. package/lib/cjs/azure/azure_foundry.js +432 -0
  4. package/lib/cjs/azure/azure_foundry.js.map +1 -0
  5. package/lib/cjs/bedrock/converse.js +359 -0
  6. package/lib/cjs/bedrock/converse.js.map +1 -0
  7. package/lib/cjs/bedrock/index.js +1441 -0
  8. package/lib/cjs/bedrock/index.js.map +1 -0
  9. package/lib/cjs/bedrock/nova-image-payload.js +207 -0
  10. package/lib/cjs/bedrock/nova-image-payload.js.map +1 -0
  11. package/lib/cjs/bedrock/payloads.js +3 -0
  12. package/lib/cjs/bedrock/payloads.js.map +1 -0
  13. package/lib/cjs/bedrock/s3.js +107 -0
  14. package/lib/cjs/bedrock/s3.js.map +1 -0
  15. package/lib/cjs/bedrock/twelvelabs.js +87 -0
  16. package/lib/cjs/bedrock/twelvelabs.js.map +1 -0
  17. package/lib/cjs/groq/index.js +326 -0
  18. package/lib/cjs/groq/index.js.map +1 -0
  19. package/lib/cjs/huggingface_ie.js +201 -0
  20. package/lib/cjs/huggingface_ie.js.map +1 -0
  21. package/lib/cjs/index.js +31 -0
  22. package/lib/cjs/index.js.map +1 -0
  23. package/lib/cjs/mistral/index.js +176 -0
  24. package/lib/cjs/mistral/index.js.map +1 -0
  25. package/lib/cjs/mistral/types.js +83 -0
  26. package/lib/cjs/mistral/types.js.map +1 -0
  27. package/lib/cjs/openai/azure_openai.js +72 -0
  28. package/lib/cjs/openai/azure_openai.js.map +1 -0
  29. package/lib/cjs/openai/index.js +1100 -0
  30. package/lib/cjs/openai/index.js.map +1 -0
  31. package/lib/cjs/openai/openai.js +21 -0
  32. package/lib/cjs/openai/openai.js.map +1 -0
  33. package/lib/cjs/openai/openai_compatible.js +63 -0
  34. package/lib/cjs/openai/openai_compatible.js.map +1 -0
  35. package/lib/cjs/openai/openai_format.js +131 -0
  36. package/lib/cjs/openai/openai_format.js.map +1 -0
  37. package/lib/cjs/package.json +3 -0
  38. package/lib/cjs/replicate.js +275 -0
  39. package/lib/cjs/replicate.js.map +1 -0
  40. package/lib/cjs/test-driver/TestErrorCompletionStream.js +20 -0
  41. package/lib/cjs/test-driver/TestErrorCompletionStream.js.map +1 -0
  42. package/lib/cjs/test-driver/TestValidationErrorCompletionStream.js +24 -0
  43. package/lib/cjs/test-driver/TestValidationErrorCompletionStream.js.map +1 -0
  44. package/lib/cjs/test-driver/index.js +109 -0
  45. package/lib/cjs/test-driver/index.js.map +1 -0
  46. package/lib/cjs/test-driver/utils.js +30 -0
  47. package/lib/cjs/test-driver/utils.js.map +1 -0
  48. package/lib/cjs/togetherai/index.js +126 -0
  49. package/lib/cjs/togetherai/index.js.map +1 -0
  50. package/lib/cjs/togetherai/interfaces.js +3 -0
  51. package/lib/cjs/togetherai/interfaces.js.map +1 -0
  52. package/lib/cjs/vertexai/debug.js +12 -0
  53. package/lib/cjs/vertexai/debug.js.map +1 -0
  54. package/lib/cjs/vertexai/embeddings/embeddings-image.js +27 -0
  55. package/lib/cjs/vertexai/embeddings/embeddings-image.js.map +1 -0
  56. package/lib/cjs/vertexai/embeddings/embeddings-text.js +23 -0
  57. package/lib/cjs/vertexai/embeddings/embeddings-text.js.map +1 -0
  58. package/lib/cjs/vertexai/index.js +635 -0
  59. package/lib/cjs/vertexai/index.js.map +1 -0
  60. package/lib/cjs/vertexai/models/claude.js +842 -0
  61. package/lib/cjs/vertexai/models/claude.js.map +1 -0
  62. package/lib/cjs/vertexai/models/gemini.js +1110 -0
  63. package/lib/cjs/vertexai/models/gemini.js.map +1 -0
  64. package/lib/cjs/vertexai/models/imagen.js +303 -0
  65. package/lib/cjs/vertexai/models/imagen.js.map +1 -0
  66. package/lib/cjs/vertexai/models/llama.js +183 -0
  67. package/lib/cjs/vertexai/models/llama.js.map +1 -0
  68. package/lib/cjs/vertexai/models.js +35 -0
  69. package/lib/cjs/vertexai/models.js.map +1 -0
  70. package/lib/cjs/watsonx/index.js +161 -0
  71. package/lib/cjs/watsonx/index.js.map +1 -0
  72. package/lib/cjs/watsonx/interfaces.js +3 -0
  73. package/lib/cjs/watsonx/interfaces.js.map +1 -0
  74. package/lib/cjs/xai/index.js +65 -0
  75. package/lib/cjs/xai/index.js.map +1 -0
  76. package/lib/esm/adobe/firefly.js +116 -0
  77. package/lib/esm/adobe/firefly.js.map +1 -0
  78. package/lib/esm/azure/azure_foundry.js +426 -0
  79. package/lib/esm/azure/azure_foundry.js.map +1 -0
  80. package/lib/esm/bedrock/converse.js +352 -0
  81. package/lib/esm/bedrock/converse.js.map +1 -0
  82. package/lib/esm/bedrock/index.js +1434 -0
  83. package/lib/esm/bedrock/index.js.map +1 -0
  84. package/lib/esm/bedrock/nova-image-payload.js +203 -0
  85. package/lib/esm/bedrock/nova-image-payload.js.map +1 -0
  86. package/lib/esm/bedrock/payloads.js +2 -0
  87. package/lib/esm/bedrock/payloads.js.map +1 -0
  88. package/lib/esm/bedrock/s3.js +99 -0
  89. package/lib/esm/bedrock/s3.js.map +1 -0
  90. package/lib/esm/bedrock/twelvelabs.js +84 -0
  91. package/lib/esm/bedrock/twelvelabs.js.map +1 -0
  92. package/lib/esm/groq/index.js +319 -0
  93. package/lib/esm/groq/index.js.map +1 -0
  94. package/lib/esm/huggingface_ie.js +197 -0
  95. package/lib/esm/huggingface_ie.js.map +1 -0
  96. package/lib/esm/index.js +15 -0
  97. package/lib/esm/index.js.map +1 -0
  98. package/lib/esm/mistral/index.js +172 -0
  99. package/lib/esm/mistral/index.js.map +1 -0
  100. package/lib/esm/mistral/types.js +80 -0
  101. package/lib/esm/mistral/types.js.map +1 -0
  102. package/lib/esm/openai/azure_openai.js +68 -0
  103. package/lib/esm/openai/azure_openai.js.map +1 -0
  104. package/lib/esm/openai/index.js +1093 -0
  105. package/lib/esm/openai/index.js.map +1 -0
  106. package/lib/esm/openai/openai.js +14 -0
  107. package/lib/esm/openai/openai.js.map +1 -0
  108. package/lib/esm/openai/openai_compatible.js +56 -0
  109. package/lib/esm/openai/openai_compatible.js.map +1 -0
  110. package/lib/esm/openai/openai_format.js +127 -0
  111. package/lib/esm/openai/openai_format.js.map +1 -0
  112. package/lib/esm/replicate.js +268 -0
  113. package/lib/esm/replicate.js.map +1 -0
  114. package/lib/esm/test-driver/TestErrorCompletionStream.js +16 -0
  115. package/lib/esm/test-driver/TestErrorCompletionStream.js.map +1 -0
  116. package/lib/esm/test-driver/TestValidationErrorCompletionStream.js +20 -0
  117. package/lib/esm/test-driver/TestValidationErrorCompletionStream.js.map +1 -0
  118. package/lib/esm/test-driver/index.js +91 -0
  119. package/lib/esm/test-driver/index.js.map +1 -0
  120. package/lib/esm/test-driver/utils.js +25 -0
  121. package/lib/esm/test-driver/utils.js.map +1 -0
  122. package/lib/esm/togetherai/index.js +122 -0
  123. package/lib/esm/togetherai/index.js.map +1 -0
  124. package/lib/esm/togetherai/interfaces.js +2 -0
  125. package/lib/esm/togetherai/interfaces.js.map +1 -0
  126. package/lib/esm/vertexai/debug.js +6 -0
  127. package/lib/esm/vertexai/debug.js.map +1 -0
  128. package/lib/esm/vertexai/embeddings/embeddings-image.js +24 -0
  129. package/lib/esm/vertexai/embeddings/embeddings-image.js.map +1 -0
  130. package/lib/esm/vertexai/embeddings/embeddings-text.js +20 -0
  131. package/lib/esm/vertexai/embeddings/embeddings-text.js.map +1 -0
  132. package/lib/esm/vertexai/index.js +630 -0
  133. package/lib/esm/vertexai/index.js.map +1 -0
  134. package/lib/esm/vertexai/models/claude.js +833 -0
  135. package/lib/esm/vertexai/models/claude.js.map +1 -0
  136. package/lib/esm/vertexai/models/gemini.js +1104 -0
  137. package/lib/esm/vertexai/models/gemini.js.map +1 -0
  138. package/lib/esm/vertexai/models/imagen.js +299 -0
  139. package/lib/esm/vertexai/models/imagen.js.map +1 -0
  140. package/lib/esm/vertexai/models/llama.js +179 -0
  141. package/lib/esm/vertexai/models/llama.js.map +1 -0
  142. package/lib/esm/vertexai/models.js +32 -0
  143. package/lib/esm/vertexai/models.js.map +1 -0
  144. package/lib/esm/watsonx/index.js +157 -0
  145. package/lib/esm/watsonx/index.js.map +1 -0
  146. package/lib/esm/watsonx/interfaces.js +2 -0
  147. package/lib/esm/watsonx/interfaces.js.map +1 -0
  148. package/lib/esm/xai/index.js +58 -0
  149. package/lib/esm/xai/index.js.map +1 -0
  150. package/lib/types/adobe/firefly.d.ts +30 -0
  151. package/lib/types/adobe/firefly.d.ts.map +1 -0
  152. package/lib/types/azure/azure_foundry.d.ts +52 -0
  153. package/lib/types/azure/azure_foundry.d.ts.map +1 -0
  154. package/lib/types/bedrock/converse.d.ts +8 -0
  155. package/lib/types/bedrock/converse.d.ts.map +1 -0
  156. package/lib/types/bedrock/index.d.ts +135 -0
  157. package/lib/types/bedrock/index.d.ts.map +1 -0
  158. package/lib/types/bedrock/nova-image-payload.d.ts +74 -0
  159. package/lib/types/bedrock/nova-image-payload.d.ts.map +1 -0
  160. package/lib/types/bedrock/payloads.d.ts +12 -0
  161. package/lib/types/bedrock/payloads.d.ts.map +1 -0
  162. package/lib/types/bedrock/s3.d.ts +23 -0
  163. package/lib/types/bedrock/s3.d.ts.map +1 -0
  164. package/lib/types/bedrock/twelvelabs.d.ts +50 -0
  165. package/lib/types/bedrock/twelvelabs.d.ts.map +1 -0
  166. package/lib/types/groq/index.d.ts +27 -0
  167. package/lib/types/groq/index.d.ts.map +1 -0
  168. package/lib/types/huggingface_ie.d.ts +35 -0
  169. package/lib/types/huggingface_ie.d.ts.map +1 -0
  170. package/lib/types/index.d.ts +15 -0
  171. package/lib/types/index.d.ts.map +1 -0
  172. package/lib/types/mistral/index.d.ts +25 -0
  173. package/lib/types/mistral/index.d.ts.map +1 -0
  174. package/lib/types/mistral/types.d.ts +127 -0
  175. package/lib/types/mistral/types.d.ts.map +1 -0
  176. package/lib/types/openai/azure_openai.d.ts +25 -0
  177. package/lib/types/openai/azure_openai.d.ts.map +1 -0
  178. package/lib/types/openai/index.d.ts +126 -0
  179. package/lib/types/openai/index.d.ts.map +1 -0
  180. package/lib/types/openai/openai.d.ts +15 -0
  181. package/lib/types/openai/openai.d.ts.map +1 -0
  182. package/lib/types/openai/openai_compatible.d.ts +31 -0
  183. package/lib/types/openai/openai_compatible.d.ts.map +1 -0
  184. package/lib/types/openai/openai_format.d.ts +21 -0
  185. package/lib/types/openai/openai_format.d.ts.map +1 -0
  186. package/lib/types/replicate.d.ts +48 -0
  187. package/lib/types/replicate.d.ts.map +1 -0
  188. package/lib/types/test-driver/TestErrorCompletionStream.d.ts +9 -0
  189. package/lib/types/test-driver/TestErrorCompletionStream.d.ts.map +1 -0
  190. package/lib/types/test-driver/TestValidationErrorCompletionStream.d.ts +9 -0
  191. package/lib/types/test-driver/TestValidationErrorCompletionStream.d.ts.map +1 -0
  192. package/lib/types/test-driver/index.d.ts +24 -0
  193. package/lib/types/test-driver/index.d.ts.map +1 -0
  194. package/lib/types/test-driver/utils.d.ts +5 -0
  195. package/lib/types/test-driver/utils.d.ts.map +1 -0
  196. package/lib/types/togetherai/index.d.ts +23 -0
  197. package/lib/types/togetherai/index.d.ts.map +1 -0
  198. package/lib/types/togetherai/interfaces.d.ts +96 -0
  199. package/lib/types/togetherai/interfaces.d.ts.map +1 -0
  200. package/lib/types/vertexai/debug.d.ts +2 -0
  201. package/lib/types/vertexai/debug.d.ts.map +1 -0
  202. package/lib/types/vertexai/embeddings/embeddings-image.d.ts +11 -0
  203. package/lib/types/vertexai/embeddings/embeddings-image.d.ts.map +1 -0
  204. package/lib/types/vertexai/embeddings/embeddings-text.d.ts +10 -0
  205. package/lib/types/vertexai/embeddings/embeddings-text.d.ts.map +1 -0
  206. package/lib/types/vertexai/index.d.ts +79 -0
  207. package/lib/types/vertexai/index.d.ts.map +1 -0
  208. package/lib/types/vertexai/models/claude.d.ts +103 -0
  209. package/lib/types/vertexai/models/claude.d.ts.map +1 -0
  210. package/lib/types/vertexai/models/gemini.d.ts +78 -0
  211. package/lib/types/vertexai/models/gemini.d.ts.map +1 -0
  212. package/lib/types/vertexai/models/imagen.d.ts +75 -0
  213. package/lib/types/vertexai/models/imagen.d.ts.map +1 -0
  214. package/lib/types/vertexai/models/llama.d.ts +20 -0
  215. package/lib/types/vertexai/models/llama.d.ts.map +1 -0
  216. package/lib/types/vertexai/models.d.ts +20 -0
  217. package/lib/types/vertexai/models.d.ts.map +1 -0
  218. package/lib/types/watsonx/index.d.ts +27 -0
  219. package/lib/types/watsonx/index.d.ts.map +1 -0
  220. package/lib/types/watsonx/interfaces.d.ts +65 -0
  221. package/lib/types/watsonx/interfaces.d.ts.map +1 -0
  222. package/lib/types/xai/index.d.ts +18 -0
  223. package/lib/types/xai/index.d.ts.map +1 -0
  224. package/package.json +18 -18
  225. package/src/bedrock/converse.ts +85 -10
  226. package/src/bedrock/error-handling.test.ts +352 -0
  227. package/src/bedrock/index.ts +293 -16
  228. package/src/groq/index.ts +9 -4
  229. package/src/mistral/index.ts +25 -22
  230. package/src/mistral/types.ts +0 -5
  231. package/src/openai/error-handling.test.ts +567 -0
  232. package/src/openai/index.ts +513 -33
  233. package/src/openai/openai_compatible.ts +7 -0
  234. package/src/openai/openai_format.ts +1 -1
  235. package/src/vertexai/index.ts +61 -13
  236. package/src/vertexai/models/claude-error-handling.test.ts +432 -0
  237. package/src/vertexai/models/claude.ts +287 -10
  238. package/src/vertexai/models/gemini-error-handling.test.ts +353 -0
  239. package/src/vertexai/models/gemini.ts +329 -52
  240. package/src/vertexai/models.ts +7 -2
@@ -0,0 +1,1110 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GeminiModelDefinition = void 0;
4
+ exports.mergeConsecutiveRole = mergeConsecutiveRole;
5
+ exports.convertGeminiFunctionPartsToText = convertGeminiFunctionPartsToText;
6
+ const genai_1 = require("@google/genai");
7
+ const core_1 = require("@llumiverse/core");
8
+ const async_1 = require("@llumiverse/core/async");
9
+ function supportsStructuredOutput(options) {
10
+ // Gemini 1.0 Ultra does not support JSON output, 1.0 Pro does.
11
+ return !!options.result_schema && !options.model.includes("ultra");
12
+ }
13
+ const geminiSafetySettings = [
14
+ {
15
+ category: genai_1.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
16
+ threshold: genai_1.HarmBlockThreshold.BLOCK_ONLY_HIGH
17
+ },
18
+ {
19
+ category: genai_1.HarmCategory.HARM_CATEGORY_HARASSMENT,
20
+ threshold: genai_1.HarmBlockThreshold.BLOCK_ONLY_HIGH
21
+ },
22
+ {
23
+ category: genai_1.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
24
+ threshold: genai_1.HarmBlockThreshold.BLOCK_ONLY_HIGH
25
+ },
26
+ {
27
+ category: genai_1.HarmCategory.HARM_CATEGORY_HATE_SPEECH,
28
+ threshold: genai_1.HarmBlockThreshold.BLOCK_ONLY_HIGH
29
+ },
30
+ {
31
+ category: genai_1.HarmCategory.HARM_CATEGORY_UNSPECIFIED,
32
+ threshold: genai_1.HarmBlockThreshold.BLOCK_ONLY_HIGH
33
+ },
34
+ {
35
+ category: genai_1.HarmCategory.HARM_CATEGORY_CIVIC_INTEGRITY,
36
+ threshold: genai_1.HarmBlockThreshold.BLOCK_ONLY_HIGH
37
+ }
38
+ ];
39
+ // We do the mapping here rather than in common to avoid bringing the SDK into the common package.
40
+ function getProminentPeopleOption(prominentPeople) {
41
+ switch (prominentPeople) {
42
+ case "ALLOW_PROMINENT_PEOPLE":
43
+ return genai_1.ProminentPeople.ALLOW_PROMINENT_PEOPLE;
44
+ case "BLOCK_PROMINENT_PEOPLE":
45
+ return genai_1.ProminentPeople.BLOCK_PROMINENT_PEOPLE;
46
+ case "PROMINENT_PEOPLE_UNSPECIFIED":
47
+ return genai_1.ProminentPeople.PROMINENT_PEOPLE_UNSPECIFIED;
48
+ default:
49
+ return undefined;
50
+ }
51
+ }
52
+ function getGeminiPayload(options, prompt) {
53
+ const model_options = options.model_options;
54
+ const tools = getToolDefinitions(options.tools);
55
+ // When no tools are provided but conversation contains functionCall/functionResponse parts
56
+ // (e.g. checkpoint summary calls), convert them to text to avoid API errors
57
+ if (!tools && prompt.contents) {
58
+ const hasToolParts = prompt.contents.some(c => c.parts?.some(p => p.functionCall || p.functionResponse));
59
+ if (hasToolParts) {
60
+ prompt.contents = convertGeminiFunctionPartsToText(prompt.contents);
61
+ }
62
+ }
63
+ const useStructuredOutput = supportsStructuredOutput(options) && !tools;
64
+ const configNanoBanana = {
65
+ systemInstruction: prompt.system,
66
+ safetySettings: geminiSafetySettings,
67
+ responseModalities: [genai_1.Modality.TEXT, genai_1.Modality.IMAGE], // This is an error if only Text, and Only Image just gets blank responses.
68
+ candidateCount: 1,
69
+ //Model options
70
+ temperature: model_options?.temperature,
71
+ topP: model_options?.top_p,
72
+ maxOutputTokens: model_options?.max_tokens,
73
+ stopSequences: model_options?.stop_sequence,
74
+ thinkingConfig: geminiThinkingConfig(options),
75
+ imageConfig: {
76
+ imageSize: model_options?.image_size,
77
+ aspectRatio: model_options?.image_aspect_ratio,
78
+ personGeneration: model_options?.person_generation,
79
+ prominentPeople: getProminentPeopleOption(model_options?.prominent_people),
80
+ outputMimeType: model_options?.output_mime_type,
81
+ outputCompressionQuality: model_options?.output_compression_quality,
82
+ }
83
+ };
84
+ const config = {
85
+ systemInstruction: prompt.system,
86
+ safetySettings: geminiSafetySettings,
87
+ tools: tools ? [tools] : undefined,
88
+ toolConfig: tools ? {
89
+ functionCallingConfig: {
90
+ mode: genai_1.FunctionCallingConfigMode.AUTO,
91
+ }
92
+ } : undefined,
93
+ candidateCount: 1,
94
+ //JSON/Structured output
95
+ responseMimeType: useStructuredOutput ? "application/json" : undefined,
96
+ responseSchema: useStructuredOutput ? parseJSONtoSchema(options.result_schema, true) : undefined,
97
+ //Model options
98
+ temperature: model_options?.temperature,
99
+ topP: model_options?.top_p,
100
+ topK: model_options?.top_k,
101
+ maxOutputTokens: model_options?.max_tokens,
102
+ stopSequences: model_options?.stop_sequence,
103
+ presencePenalty: model_options?.presence_penalty,
104
+ frequencyPenalty: model_options?.frequency_penalty,
105
+ seed: model_options?.seed,
106
+ thinkingConfig: geminiThinkingConfig(options),
107
+ };
108
+ return {
109
+ model: options.model,
110
+ contents: prompt.contents,
111
+ config: options.model.toLowerCase().includes("image") ? configNanoBanana : config,
112
+ };
113
+ }
114
+ /**
115
+ * Convert JSONSchema to Gemini Schema,
116
+ * Make all properties required by default
117
+ * Properties previously marked as optional will be marked as nullable.
118
+ */
119
+ function parseJSONtoSchema(schema, requiredAll = false) {
120
+ if (!schema) {
121
+ return {};
122
+ }
123
+ return convertSchema(schema, 0, requiredAll);
124
+ }
125
+ /**
126
+ * Convert JSONSchema type to Gemini Schema Type
127
+ */
128
+ function convertType(type) {
129
+ if (!type)
130
+ return undefined;
131
+ // Handle single type
132
+ if (typeof type === 'string') {
133
+ switch (type) {
134
+ case 'string': return genai_1.Type.STRING;
135
+ case 'number': return genai_1.Type.NUMBER;
136
+ case 'integer': return genai_1.Type.INTEGER;
137
+ case 'boolean': return genai_1.Type.BOOLEAN;
138
+ case 'object': return genai_1.Type.OBJECT;
139
+ case 'array': return genai_1.Type.ARRAY;
140
+ default: return type; // For unsupported types, return as is
141
+ }
142
+ }
143
+ // For array of types, take the first valid one as the primary type
144
+ // The full set of types will be handled with anyOf
145
+ for (const t of type) {
146
+ const converted = convertType(t);
147
+ if (converted)
148
+ return converted;
149
+ }
150
+ return undefined;
151
+ }
152
+ /**
153
+ * Deep clone and convert the schema from JSONSchema to Gemini Schema
154
+ * @throws {Error} If circular references are detected (max depth exceeded)
155
+ */
156
+ function convertSchema(jsSchema, depth = 0, requiredAll = false) {
157
+ // Prevent circular references
158
+ if (depth > 20) {
159
+ throw new Error("Maximum schema depth (20) exceeded. Possible circular reference detected.");
160
+ }
161
+ if (!jsSchema)
162
+ return {};
163
+ // Create new schema object rather than mutating
164
+ const result = {};
165
+ // Handle types
166
+ result.type = convertSchemaType(jsSchema);
167
+ // Handle description
168
+ if (jsSchema.description) {
169
+ result.description = jsSchema.description;
170
+ }
171
+ // Handle properties and required fields
172
+ if (jsSchema.properties) {
173
+ const propertyResult = convertSchemaProperties(jsSchema, depth + 1, requiredAll);
174
+ Object.assign(result, propertyResult);
175
+ }
176
+ // Handle items for arrays
177
+ if (jsSchema.items) {
178
+ result.items = convertSchema(jsSchema.items, depth + 1);
179
+ }
180
+ // Handle enum values
181
+ if (jsSchema.enum) {
182
+ result.enum = [...jsSchema.enum]; // Create a copy instead of reference
183
+ }
184
+ // Copy constraints
185
+ Object.assign(result, extractConstraints(jsSchema));
186
+ return result;
187
+ }
188
+ /**
189
+ * Convert schema type information, handling anyOf for multiple types
190
+ */
191
+ function convertSchemaType(jsSchema) {
192
+ // Handle multiple types using anyOf
193
+ if (jsSchema.type && Array.isArray(jsSchema.type) && jsSchema.type.length > 1) {
194
+ // Since anyOf is an advanced type, we'll return the first valid type
195
+ // and handle the multi-type case separately in the schema
196
+ return convertType(jsSchema.type[0]);
197
+ }
198
+ // Handle single type
199
+ else if (jsSchema.type) {
200
+ return convertType(jsSchema.type);
201
+ }
202
+ return undefined;
203
+ }
204
+ /**
205
+ * Handle properties conversion and required fields
206
+ */
207
+ function convertSchemaProperties(jsSchema, depth, requiredAll) {
208
+ const result = { properties: {} };
209
+ if (jsSchema.required) {
210
+ result.required = [...jsSchema.required]; // Create a copy
211
+ }
212
+ // Extract property ordering from the object keys
213
+ const propertyNames = Object.keys(jsSchema.properties || {});
214
+ // Set property ordering based on the existing order in the schema
215
+ if (propertyNames.length > 0) {
216
+ result.propertyOrdering = propertyNames;
217
+ if (requiredAll) {
218
+ // Mark all properties as required by default
219
+ // This ensures the model fills all fields
220
+ result.required = propertyNames;
221
+ // Get the original required properties
222
+ const originalRequired = jsSchema.required || [];
223
+ // Make previously optional properties nullable since we're marking them as required
224
+ for (const key of propertyNames) {
225
+ const propSchema = jsSchema.properties?.[key];
226
+ if (propSchema && !originalRequired.includes(key)) {
227
+ // Initialize the property if needed
228
+ if (!result.properties[key]) {
229
+ result.properties[key] = {};
230
+ }
231
+ // Mark as nullable
232
+ result.properties[key].nullable = true;
233
+ }
234
+ }
235
+ }
236
+ }
237
+ // Convert each property schema
238
+ for (const [key, value] of Object.entries(jsSchema.properties || {})) {
239
+ if (!result.properties[key]) {
240
+ result.properties[key] = {};
241
+ }
242
+ // Merge with converted schema
243
+ result.properties[key] = {
244
+ ...result.properties[key],
245
+ ...convertSchema(value, depth)
246
+ };
247
+ }
248
+ // Override with explicit propertyOrdering if present
249
+ if (jsSchema.propertyOrdering) {
250
+ result.propertyOrdering = [...jsSchema.propertyOrdering]; // Create a copy
251
+ }
252
+ return result;
253
+ }
254
+ /**
255
+ * Extract schema constraints (min/max values, formats, etc.)
256
+ */
257
+ function extractConstraints(jsSchema) {
258
+ const constraints = {};
259
+ if (jsSchema.minimum !== undefined)
260
+ constraints.minimum = jsSchema.minimum;
261
+ if (jsSchema.maximum !== undefined)
262
+ constraints.maximum = jsSchema.maximum;
263
+ if (jsSchema.minLength !== undefined)
264
+ constraints.minLength = jsSchema.minLength;
265
+ if (jsSchema.maxLength !== undefined)
266
+ constraints.maxLength = jsSchema.maxLength;
267
+ if (jsSchema.minItems !== undefined)
268
+ constraints.minItems = jsSchema.minItems;
269
+ if (jsSchema.maxItems !== undefined)
270
+ constraints.maxItems = jsSchema.maxItems;
271
+ if (jsSchema.nullable !== undefined)
272
+ constraints.nullable = jsSchema.nullable;
273
+ if (jsSchema.pattern)
274
+ constraints.pattern = jsSchema.pattern;
275
+ if (jsSchema.format)
276
+ constraints.format = jsSchema.format;
277
+ if (jsSchema.default !== undefined)
278
+ constraints.default = jsSchema.default;
279
+ if (jsSchema.example !== undefined)
280
+ constraints.example = jsSchema.example;
281
+ return constraints;
282
+ }
283
+ /**
284
+ * Check if a value is empty (null, undefined, empty string, empty array, empty object)
285
+ * @param value The value to check
286
+ * @returns True if the value is considered empty
287
+ */
288
+ function isEmpty(value) {
289
+ if (value === null || value === undefined) {
290
+ return true;
291
+ }
292
+ if (typeof value === 'string' && value.trim() === '') {
293
+ return true;
294
+ }
295
+ if (Array.isArray(value) && value.length === 0) {
296
+ return true;
297
+ }
298
+ // Check for empty object (no own enumerable properties)
299
+ if (typeof value === 'object' && Object.keys(value).length === 0) {
300
+ return true;
301
+ }
302
+ // Check for array of empty objects
303
+ if (Array.isArray(value) && value.every(item => isEmpty(item))) {
304
+ return true;
305
+ }
306
+ return false;
307
+ }
308
+ // No array cleaning function needed as we're only working with JSONObjects
309
+ /**
310
+ * Clean up the JSON result by removing empty values for optional fields
311
+ * Uses immutable patterns to create a new Content object rather than modifying the original
312
+ * @param content The original content from Gemini
313
+ * @param result_schema The JSON schema to use for cleaning
314
+ * @returns A new Content object with cleaned JSON text
315
+ */
316
+ function cleanEmptyFieldsContent(content, result_schema) {
317
+ // If no schema provided, return original content
318
+ if (!result_schema) {
319
+ return content;
320
+ }
321
+ // Create a new content object (shallow copy)
322
+ const cleanedContent = { ...content };
323
+ // Create a new parts array if it exists
324
+ if (cleanedContent.parts) {
325
+ cleanedContent.parts = cleanedContent.parts.map(part => {
326
+ // Only process parts with text
327
+ if (!part.text) {
328
+ return part; // Return unchanged if no text
329
+ }
330
+ // Create a new part object
331
+ const newPart = { ...part };
332
+ try {
333
+ // Parse JSON, clean it based on schema, then stringify
334
+ const jsonText = JSON.parse(part.text);
335
+ // Skip cleaning if not an object
336
+ if (typeof jsonText === 'object' && jsonText !== null && !Array.isArray(jsonText)) {
337
+ const cleanedJson = removeEmptyFields(jsonText, result_schema);
338
+ newPart.text = JSON.stringify(cleanedJson);
339
+ }
340
+ else {
341
+ // Keep original if not an object (string, number, array, etc.)
342
+ newPart.text = part.text;
343
+ }
344
+ }
345
+ catch (e) {
346
+ // On error, keep the original text
347
+ console.warn("Error parsing Gemini output to JSON in part:", e);
348
+ }
349
+ return newPart;
350
+ });
351
+ }
352
+ return cleanedContent;
353
+ }
354
+ /**
355
+ * Removes empty optional fields from the JSON result based on the provided schema
356
+ * @param object The object to clean
357
+ * @param schema The JSON schema to use for cleaning
358
+ * @returns A new object with empty optional fields removed
359
+ */
360
+ function removeEmptyFields(object, schema) {
361
+ if (!object) {
362
+ return object;
363
+ }
364
+ if (Array.isArray(object)) {
365
+ return removeEmptyJSONArray(object, schema);
366
+ }
367
+ if (typeof object == 'object' || object === null) {
368
+ return removeEmptyJSONObject(object, schema);
369
+ }
370
+ return object;
371
+ }
372
+ function removeEmptyJSONObject(object, schema) {
373
+ // Get the original required properties from schema
374
+ const requiredProps = schema.required || [];
375
+ const cleanedResult = { ...object };
376
+ // Process each property
377
+ for (const [key, value] of Object.entries(object)) {
378
+ const isRequired = requiredProps.includes(key);
379
+ const propSchema = schema.properties?.[key];
380
+ // Recursively clean nested objects based on their schema
381
+ cleanedResult[key] = removeEmptyFields(value, propSchema ?? {});
382
+ if (isEmpty(value)) {
383
+ if (isRequired) {
384
+ continue; // Keep required fields even if empty
385
+ }
386
+ else {
387
+ delete cleanedResult[key]; // Remove empty optional fields
388
+ }
389
+ }
390
+ }
391
+ return cleanedResult;
392
+ }
393
+ function removeEmptyJSONArray(array, schema) {
394
+ const cleanedArray = array.map(item => {
395
+ return removeEmptyFields(item, schema);
396
+ });
397
+ // Filter out empty objects from the array
398
+ return cleanedArray.filter(item => !isEmpty(item));
399
+ }
400
+ /**
401
+ * Collect all parts (text and images) from content in order.
402
+ * This preserves the original ordering of text and image parts.
403
+ */
404
+ function extractCompletionResults(content) {
405
+ const results = [];
406
+ const parts = content.parts;
407
+ if (parts) {
408
+ for (const part of parts) {
409
+ if (part.text) {
410
+ results.push({
411
+ type: "text",
412
+ value: part.text
413
+ });
414
+ }
415
+ else if (part.inlineData) {
416
+ const base64ImageBytes = part.inlineData.data ?? "";
417
+ const mimeType = part.inlineData.mimeType ?? "image/png";
418
+ const imageUrl = `data:${mimeType};base64,${base64ImageBytes}`;
419
+ results.push({
420
+ type: "image",
421
+ value: imageUrl
422
+ });
423
+ }
424
+ }
425
+ }
426
+ return results;
427
+ }
428
+ function collectToolUseParts(content) {
429
+ const out = [];
430
+ const parts = content.parts ?? [];
431
+ for (const part of parts) {
432
+ if (part.functionCall) {
433
+ const toolUse = {
434
+ id: part.functionCall.name ?? '',
435
+ tool_name: part.functionCall.name ?? '',
436
+ tool_input: part.functionCall.args,
437
+ };
438
+ // Capture thought_signature for Gemini thinking models (2.5+/3.0+)
439
+ // This must be passed back with the function response
440
+ if (part.thoughtSignature) {
441
+ toolUse.thought_signature = part.thoughtSignature;
442
+ }
443
+ out.push(toolUse);
444
+ }
445
+ }
446
+ return out.length > 0 ? out : undefined;
447
+ }
448
+ function mergeConsecutiveRole(contents) {
449
+ if (!contents || contents.length === 0)
450
+ return [];
451
+ const needsMerging = contents.some((content, i) => i < contents.length - 1 && content.role === contents[i + 1].role);
452
+ // If no merging needed, return original array
453
+ if (!needsMerging) {
454
+ return contents;
455
+ }
456
+ const result = [];
457
+ let currentContent = { ...contents[0], parts: [...(contents[0].parts || [])] };
458
+ for (let i = 1; i < contents.length; i++) {
459
+ if (currentContent.role === contents[i].role) {
460
+ // Same role - concatenate parts (without merging individual parts)
461
+ currentContent.parts = (currentContent.parts || []).concat(...(contents[i].parts || []));
462
+ }
463
+ else {
464
+ // Different role - push current and start new
465
+ result.push(currentContent);
466
+ currentContent = { ...contents[i], parts: [...(contents[i].parts || [])] };
467
+ }
468
+ }
469
+ result.push(currentContent);
470
+ return result;
471
+ }
472
+ const supportedFinishReasons = [
473
+ genai_1.FinishReason.MAX_TOKENS,
474
+ genai_1.FinishReason.STOP,
475
+ genai_1.FinishReason.FINISH_REASON_UNSPECIFIED,
476
+ ];
477
+ // Finish reasons that indicate tool call issues but should be recovered gracefully
478
+ // instead of throwing an error. The tool_use is still extracted and returned
479
+ // so the workflow can generate a proper toolError response.
480
+ const recoverableToolCallReasons = [
481
+ 'UNEXPECTED_TOOL_CALL', // Model called an undeclared tool
482
+ ];
483
+ function geminiThinkingBudget(option) {
484
+ const model_options = option.model_options;
485
+ // If thinking_budget_tokens is explicitly set in model options, use it directly
486
+ if (model_options?.thinking_budget_tokens) {
487
+ return model_options.thinking_budget_tokens;
488
+ }
489
+ // Set minimum thinking level by default.
490
+ // Docs: https://ai.google.dev/gemini-api/docs/thinking#set-budget
491
+ if ((0, core_1.getGeminiModelVersion)(option.model) == '2.5') {
492
+ if (option.model.includes("pro")) {
493
+ return 128;
494
+ }
495
+ return 0;
496
+ }
497
+ return undefined;
498
+ }
499
+ function geminiThinkingConfig(option) {
500
+ const model_options = option.model_options;
501
+ // If thinking options are explicitly set in model options, use them directly
502
+ const include_thoughts = model_options?.include_thoughts ?? false;
503
+ if (model_options?.thinking_budget_tokens || model_options?.thinking_level) {
504
+ return {
505
+ includeThoughts: include_thoughts,
506
+ thinkingBudget: model_options.thinking_budget_tokens,
507
+ thinkingLevel: model_options.thinking_level,
508
+ };
509
+ }
510
+ // Set a low thinking level by default.
511
+ // Docs: https://ai.google.dev/gemini-api/docs/thinking#set-budget
512
+ // https://docs.cloud.google.com/vertex-ai/generative-ai/docs/thinking
513
+ if ((0, core_1.isGeminiModelVersionGte)(option.model, '3.0')) {
514
+ return {
515
+ includeThoughts: include_thoughts,
516
+ thinkingLevel: genai_1.ThinkingLevel.LOW
517
+ };
518
+ }
519
+ if ((0, core_1.isGeminiModelVersionGte)(option.model, '2.5')) {
520
+ const thinking_budget_tokens = geminiThinkingBudget(option) ?? 0;
521
+ return {
522
+ includeThoughts: include_thoughts,
523
+ thinkingBudget: thinking_budget_tokens
524
+ };
525
+ }
526
+ }
527
+ class GeminiModelDefinition {
528
+ model;
529
+ constructor(modelId) {
530
+ this.model = {
531
+ id: modelId,
532
+ name: modelId,
533
+ provider: 'vertexai',
534
+ type: core_1.ModelType.Text,
535
+ can_stream: true
536
+ };
537
+ }
538
+ preValidationProcessing(result, options) {
539
+ // Guard clause, if no result_schema, error, or tool use, skip processing
540
+ if (!options.result_schema || !result.result || result.tool_use || result.error) {
541
+ return { result, options };
542
+ }
543
+ try {
544
+ // Extract text content for JSON processing - only process first text result
545
+ const textResult = result.result.find(r => r.type === 'text')?.value;
546
+ if (textResult) {
547
+ const jsonResult = JSON.parse(textResult);
548
+ const cleanedJson = JSON.stringify(removeEmptyFields(jsonResult, options.result_schema));
549
+ // Replace the text result with cleaned version
550
+ result.result = result.result.map(r => r.type === 'text' ? { ...r, value: cleanedJson } : r);
551
+ }
552
+ return { result, options };
553
+ }
554
+ catch (error) {
555
+ // Log error during processing but don't fail the completion
556
+ console.warn('Error during Gemini JSON pre-validation: ', error);
557
+ // Return original result if cleanup fails
558
+ return { result, options };
559
+ }
560
+ }
561
+ async createPrompt(_driver, segments, options) {
562
+ const splits = options.model.split("/");
563
+ const modelName = splits[splits.length - 1];
564
+ options = { ...options, model: modelName };
565
+ const schema = options.result_schema;
566
+ let contents = [];
567
+ let system = { role: "user", parts: [] }; // Single content block for system messages
568
+ const safety = [];
569
+ for (const msg of segments) {
570
+ // Role specific handling
571
+ if (msg.role === core_1.PromptRole.system) {
572
+ // Text only for system messages
573
+ if (msg.files && msg.files.length > 0) {
574
+ throw new Error("Gemini does not support files/images etc. in system messages. Only text content is allowed.");
575
+ }
576
+ if (msg.content) {
577
+ system.parts?.push({
578
+ text: msg.content
579
+ });
580
+ }
581
+ }
582
+ else if (msg.role === core_1.PromptRole.tool) {
583
+ if (!msg.tool_use_id) {
584
+ throw new Error("Tool response missing tool_use_id");
585
+ }
586
+ // Build functionResponse part with optional thought_signature for Gemini thinking models
587
+ const functionResponsePart = {
588
+ functionResponse: {
589
+ name: msg.tool_use_id,
590
+ response: formatFunctionResponse(msg.content || ''),
591
+ },
592
+ // Include thought_signature if provided (required for Gemini 2.5+/3.0+ thinking models)
593
+ thoughtSignature: msg.thought_signature,
594
+ };
595
+ contents.push({
596
+ role: 'user',
597
+ parts: [functionResponsePart]
598
+ });
599
+ }
600
+ else { // PromptRole.user, PromptRole.assistant, PromptRole.safety
601
+ const parts = [];
602
+ // Text content handling
603
+ if (msg.content) {
604
+ parts.push({
605
+ text: msg.content,
606
+ });
607
+ }
608
+ // File content handling
609
+ if (msg.files) {
610
+ for (const f of msg.files) {
611
+ const fileUrl = await f.getURL();
612
+ const isGsUrl = fileUrl.startsWith('gs://') || fileUrl.startsWith('https://storage.googleapis.com/');
613
+ if (isGsUrl) {
614
+ parts.push({
615
+ fileData: {
616
+ fileUri: fileUrl,
617
+ mimeType: f.mime_type
618
+ }
619
+ });
620
+ }
621
+ else {
622
+ // Inline data handling
623
+ const stream = await f.getStream();
624
+ const data = await (0, core_1.readStreamAsBase64)(stream);
625
+ parts.push({
626
+ inlineData: {
627
+ data,
628
+ mimeType: f.mime_type
629
+ }
630
+ });
631
+ }
632
+ }
633
+ }
634
+ if (parts.length > 0) {
635
+ if (msg.role === core_1.PromptRole.safety) {
636
+ safety.push({
637
+ role: 'user',
638
+ parts,
639
+ });
640
+ }
641
+ else {
642
+ contents.push({
643
+ role: msg.role === core_1.PromptRole.assistant ? 'model' : 'user',
644
+ parts,
645
+ });
646
+ }
647
+ }
648
+ }
649
+ }
650
+ // Adding JSON Schema to system message
651
+ if (schema) {
652
+ if (supportsStructuredOutput(options) && !options.tools) {
653
+ // Gemini structured output is unnecessarily sparse. Adding encouragement to fill the fields.
654
+ // Putting JSON in prompt is not recommended by Google, when using structured output.
655
+ system.parts?.push({ text: "Fill all appropriate fields in the JSON output." });
656
+ }
657
+ else {
658
+ // Fallback to putting the schema in the system instructions, if not using structured output.
659
+ if (options.tools) {
660
+ system.parts?.push({
661
+ text: "When not calling tools, the output must be a JSON object using the following JSON Schema:\n" + JSON.stringify(schema)
662
+ });
663
+ }
664
+ else {
665
+ system.parts?.push({ text: "The output must be a JSON object using the following JSON Schema:\n" + JSON.stringify(schema) });
666
+ }
667
+ }
668
+ }
669
+ // If no system messages, set system to undefined.
670
+ if (!system.parts || system.parts.length === 0) {
671
+ system = undefined;
672
+ }
673
+ // Add safety messages to the end of contents. They are in effect user messages that come at the end.
674
+ if (safety.length > 0) {
675
+ contents = contents.concat(safety);
676
+ }
677
+ // Merge consecutive messages with the same role. Note: this may not be necessary, works without it, keeping to match previous behavior.
678
+ contents = mergeConsecutiveRole(contents);
679
+ return { contents, system };
680
+ }
681
+ usageMetadataToTokenUsage(usageMetadata) {
682
+ if (!usageMetadata || !usageMetadata.totalTokenCount) {
683
+ return {};
684
+ }
685
+ const tokenUsage = { total: usageMetadata.totalTokenCount, prompt: usageMetadata.promptTokenCount };
686
+ //Output/Response side
687
+ tokenUsage.result = (usageMetadata.candidatesTokenCount ?? 0)
688
+ + (usageMetadata.thoughtsTokenCount ?? 0)
689
+ + (usageMetadata.toolUsePromptTokenCount ?? 0);
690
+ if ((tokenUsage.total ?? 0) != (tokenUsage.prompt ?? 0) + tokenUsage.result) {
691
+ console.warn("[VertexAI] Gemini token usage mismatch: total does not equal prompt + result", {
692
+ total: tokenUsage.total,
693
+ prompt: tokenUsage.prompt,
694
+ result: tokenUsage.result
695
+ });
696
+ }
697
+ if (!tokenUsage.result) {
698
+ tokenUsage.result = undefined; // If no result, mark as undefined
699
+ }
700
+ return tokenUsage;
701
+ }
702
+ async requestTextCompletion(driver, prompt, options) {
703
+ const splits = options.model.split("/");
704
+ let region = undefined;
705
+ if (splits[0] === "locations" && splits.length >= 2) {
706
+ region = splits[1];
707
+ }
708
+ const modelName = splits[splits.length - 1];
709
+ options = { ...options, model: modelName };
710
+ // Restore system instruction from stored conversation on resume.
711
+ // The stored _llumiverse_system contains the complete system (interaction prompt + schema)
712
+ // from the initial call. Always prefer it over the prompt's system, which on resume only
713
+ // contains the schema instruction (no interaction system segments are present on resume).
714
+ const existingSystem = extractSystemFromConversation(options.conversation);
715
+ if (existingSystem) {
716
+ prompt.system = existingSystem;
717
+ }
718
+ let conversation = updateConversation(options.conversation, prompt.contents);
719
+ prompt.contents = conversation;
720
+ // TODO: Remove hack, use global endpoint manually if needed.
721
+ if (options.model.includes("gemini-2.5-flash-image")) {
722
+ region = "global"; // Gemini Flash Image only available in global region, this is for nano-banana model
723
+ }
724
+ const client = driver.getGoogleGenAIClient(region);
725
+ const payload = getGeminiPayload(options, prompt);
726
+ const response = await client.models.generateContent(payload);
727
+ const token_usage = this.usageMetadataToTokenUsage(response.usageMetadata);
728
+ let tool_use;
729
+ let finish_reason, result;
730
+ const candidate = response.candidates && response.candidates[0];
731
+ if (candidate) {
732
+ switch (candidate.finishReason) {
733
+ case genai_1.FinishReason.MAX_TOKENS:
734
+ finish_reason = "length";
735
+ break;
736
+ case genai_1.FinishReason.STOP:
737
+ finish_reason = "stop";
738
+ break;
739
+ default: finish_reason = candidate.finishReason;
740
+ }
741
+ const content = candidate.content;
742
+ // Check for unsupported finish reasons, but allow recoverable tool call issues
743
+ const isRecoverableToolCall = recoverableToolCallReasons.includes(candidate.finishReason);
744
+ if (candidate.finishReason && !supportedFinishReasons.includes(candidate.finishReason) && !isRecoverableToolCall) {
745
+ throw new Error(`Unsupported finish reason: ${candidate.finishReason}, `
746
+ + `finish message: ${candidate.finishMessage}, `
747
+ + `content: ${JSON.stringify(content, null, 2)}, safety: ${JSON.stringify(candidate.safetyRatings, null, 2)}`);
748
+ }
749
+ if (content) {
750
+ tool_use = collectToolUseParts(content);
751
+ // For recoverable tool call issues, log warning but continue processing
752
+ // The workflow will handle the invalid tool call gracefully
753
+ if (isRecoverableToolCall && tool_use && tool_use.length > 0) {
754
+ console.warn(`[Gemini] Recoverable tool call issue (${candidate.finishReason}): ` +
755
+ `Model tried to call undeclared tool(s): ${tool_use.map(t => t.tool_name).join(', ')}`);
756
+ }
757
+ // We clean the content before validation, so we can update the conversation.
758
+ const cleanedContent = cleanEmptyFieldsContent(content, options.result_schema);
759
+ // Collect all parts in order (text and images)
760
+ result = extractCompletionResults(cleanedContent);
761
+ conversation = updateConversation(conversation, [cleanedContent]);
762
+ }
763
+ }
764
+ if (tool_use) {
765
+ finish_reason = "tool_use";
766
+ }
767
+ // Increment turn counter for deferred stripping
768
+ conversation = (0, core_1.incrementConversationTurn)(conversation);
769
+ // Strip large base64 image data based on options.stripImagesAfterTurns
770
+ const currentTurn = (0, core_1.getConversationMeta)(conversation).turnNumber;
771
+ const stripOptions = {
772
+ keepForTurns: options.stripImagesAfterTurns ?? Infinity,
773
+ currentTurn,
774
+ textMaxTokens: options.stripTextMaxTokens
775
+ };
776
+ let processedConversation = (0, core_1.stripBase64ImagesFromConversation)(conversation, stripOptions);
777
+ // Truncate large text content if configured
778
+ processedConversation = (0, core_1.truncateLargeTextInConversation)(processedConversation, stripOptions);
779
+ // Strip old heartbeat status messages
780
+ processedConversation = (0, core_1.stripHeartbeatsFromConversation)(processedConversation, {
781
+ keepForTurns: options.stripHeartbeatsAfterTurns ?? 1,
782
+ currentTurn,
783
+ });
784
+ // Preserve system instruction in conversation for multi-turn support
785
+ const finalConversation = storeSystemInConversation(processedConversation, prompt.system);
786
+ return {
787
+ result: result && result.length > 0 ? result : [{ type: "text", value: '' }],
788
+ token_usage: token_usage,
789
+ finish_reason: finish_reason,
790
+ original_response: options.include_original_response ? response : undefined,
791
+ conversation: finalConversation,
792
+ tool_use
793
+ };
794
+ }
795
+ async requestTextCompletionStream(driver, prompt, options) {
796
+ const splits = options.model.split("/");
797
+ let region = undefined;
798
+ if (splits[0] === "locations" && splits.length >= 2) {
799
+ region = splits[1];
800
+ }
801
+ const modelName = splits[splits.length - 1];
802
+ options = { ...options, model: modelName };
803
+ // Restore system instruction from stored conversation on resume.
804
+ // The stored _llumiverse_system contains the complete system (interaction prompt + schema)
805
+ // from the initial call. Always prefer it over the prompt's system, which on resume only
806
+ // contains the schema instruction (no interaction system segments are present on resume).
807
+ const existingSystem = extractSystemFromConversation(options.conversation);
808
+ if (existingSystem) {
809
+ prompt.system = existingSystem;
810
+ }
811
+ // Include conversation history in prompt contents (same as non-streaming)
812
+ const conversation = updateConversation(options.conversation, prompt.contents);
813
+ prompt.contents = conversation;
814
+ if (options.model.includes("gemini-2.5-flash-image")) {
815
+ region = "global"; // Gemini Flash Image only available in global region, this is for nano-banana model
816
+ }
817
+ const client = driver.getGoogleGenAIClient(region);
818
+ const payload = getGeminiPayload(options, prompt);
819
+ const response = await client.models.generateContentStream(payload);
820
+ const stream = (0, async_1.asyncMap)(response, async (item) => {
821
+ const token_usage = this.usageMetadataToTokenUsage(item.usageMetadata);
822
+ if (item.candidates && item.candidates.length > 0) {
823
+ for (const candidate of item.candidates) {
824
+ let tool_use;
825
+ let finish_reason;
826
+ switch (candidate.finishReason) {
827
+ case genai_1.FinishReason.MAX_TOKENS:
828
+ finish_reason = "length";
829
+ break;
830
+ case genai_1.FinishReason.STOP:
831
+ finish_reason = "stop";
832
+ break;
833
+ default: finish_reason = candidate.finishReason;
834
+ }
835
+ // Check for unsupported finish reasons, but allow recoverable tool call issues
836
+ const isRecoverableToolCall = recoverableToolCallReasons.includes(candidate.finishReason);
837
+ if (candidate.finishReason && !supportedFinishReasons.includes(candidate.finishReason) && !isRecoverableToolCall) {
838
+ throw new Error(`Unsupported finish reason: ${candidate.finishReason}, `
839
+ + `finish message: ${candidate.finishMessage}, `
840
+ + `content: ${JSON.stringify(candidate.content, null, 2)}, safety: ${JSON.stringify(candidate.safetyRatings, null, 2)}`);
841
+ }
842
+ if (candidate.content?.role === 'model') {
843
+ // Collect all parts in order (text and images)
844
+ const combinedResults = extractCompletionResults(candidate.content);
845
+ tool_use = collectToolUseParts(candidate.content);
846
+ if (tool_use) {
847
+ finish_reason = "tool_use";
848
+ // Log warning for recoverable tool call issues
849
+ if (isRecoverableToolCall) {
850
+ console.warn(`[Gemini] Recoverable tool call issue (${candidate.finishReason}): ` +
851
+ `Model tried to call undeclared tool(s): ${tool_use.map(t => t.tool_name).join(', ')}`);
852
+ }
853
+ }
854
+ return {
855
+ result: combinedResults.length > 0 ? combinedResults : [],
856
+ token_usage: token_usage,
857
+ finish_reason: finish_reason,
858
+ tool_use,
859
+ };
860
+ }
861
+ }
862
+ }
863
+ //No normal output, returning block reason if it exists.
864
+ return {
865
+ result: item.promptFeedback?.blockReasonMessage ? [{ type: "text", value: item.promptFeedback.blockReasonMessage }] : [],
866
+ finish_reason: item.promptFeedback?.blockReason ?? "",
867
+ token_usage: token_usage,
868
+ };
869
+ });
870
+ return stream;
871
+ }
872
+ /**
873
+ * Format Google API errors into LlumiverseError with proper status codes and retryability.
874
+ *
875
+ * Google API errors follow AIP-193 standard:
876
+ * - ApiError.status: HTTP status code
877
+ * - ApiError.message: Error message
878
+ *
879
+ * Common error codes:
880
+ * - 400 (INVALID_ARGUMENT): Invalid request parameters
881
+ * - 401 (UNAUTHENTICATED): Authentication required
882
+ * - 403 (PERMISSION_DENIED): Insufficient permissions
883
+ * - 404 (NOT_FOUND): Resource not found
884
+ * - 429 (RESOURCE_EXHAUSTED): Rate limit/quota exceeded
885
+ * - 500 (INTERNAL): Internal server error
886
+ * - 503 (UNAVAILABLE): Service temporarily unavailable
887
+ * - 504 (DEADLINE_EXCEEDED): Request timeout
888
+ *
889
+ * @see https://google.aip.dev/193
890
+ * @see https://docs.cloud.google.com/vertex-ai/generative-ai/docs/model-reference/api-errors
891
+ */
892
+ formatLlumiverseError(_driver, error, context) {
893
+ // Check if it's a Google API error with status code
894
+ const isApiError = this.isGoogleApiError(error);
895
+ if (!isApiError) {
896
+ // Not a Google API error, use default handling
897
+ // This will be called by the driver's default formatLlumiverseError
898
+ throw error;
899
+ }
900
+ const apiError = error;
901
+ const httpStatusCode = apiError.status;
902
+ // Extract error message
903
+ const message = apiError.message || String(error);
904
+ // Build user-facing message with status code
905
+ let userMessage = message;
906
+ // Include status code in message (for end-user visibility)
907
+ if (httpStatusCode) {
908
+ userMessage = `[${httpStatusCode}] ${userMessage}`;
909
+ }
910
+ // Determine retryability based on Google error codes
911
+ const retryable = this.isGeminiErrorRetryable(httpStatusCode);
912
+ // Extract error name/type from message if present
913
+ const errorName = this.extractErrorName(message);
914
+ return new core_1.LlumiverseError(`[${context.provider}] ${userMessage}`, retryable, context, error, httpStatusCode, errorName);
915
+ }
916
+ /**
917
+ * Type guard to check if error is a Google API error.
918
+ */
919
+ isGoogleApiError(error) {
920
+ return (error !== null &&
921
+ typeof error === 'object' &&
922
+ 'status' in error &&
923
+ typeof error.status === 'number' &&
924
+ 'message' in error);
925
+ }
926
+ /**
927
+ * Determine if a Google API error is retryable based on HTTP status code.
928
+ *
929
+ * Retryable errors (per Google AIP-194):
930
+ * - 408 (REQUEST_TIMEOUT): Request timeout
931
+ * - 429 (RESOURCE_EXHAUSTED): Rate limit exceeded, quota exhausted
932
+ * - 500 (INTERNAL): Internal server error
933
+ * - 502 (BAD_GATEWAY): Bad gateway
934
+ * - 503 (UNAVAILABLE): Service temporarily unavailable
935
+ * - 504 (DEADLINE_EXCEEDED): Gateway timeout
936
+ *
937
+ * Non-retryable errors:
938
+ * - 400 (INVALID_ARGUMENT): Invalid request parameters
939
+ * - 401 (UNAUTHENTICATED): Authentication required
940
+ * - 403 (PERMISSION_DENIED): Insufficient permissions
941
+ * - 404 (NOT_FOUND): Resource not found
942
+ * - 409 (CONFLICT): Resource conflict
943
+ * - Other 4xx client errors
944
+ *
945
+ * @param httpStatusCode - The HTTP status code from the API error
946
+ * @returns True if retryable, false if not retryable, undefined if unknown
947
+ */
948
+ isGeminiErrorRetryable(httpStatusCode) {
949
+ // Retryable status codes
950
+ if (httpStatusCode === 408)
951
+ return true; // Request timeout
952
+ if (httpStatusCode === 429)
953
+ return true; // Rate limit/quota
954
+ if (httpStatusCode === 502)
955
+ return true; // Bad gateway
956
+ if (httpStatusCode === 503)
957
+ return true; // Service unavailable
958
+ if (httpStatusCode === 504)
959
+ return true; // Gateway timeout
960
+ if (httpStatusCode >= 500 && httpStatusCode < 600)
961
+ return true; // Other 5xx server errors
962
+ // Non-retryable 4xx client errors
963
+ if (httpStatusCode >= 400 && httpStatusCode < 500)
964
+ return false;
965
+ // Unknown status codes - let consumer decide retry strategy
966
+ return undefined;
967
+ }
968
+ /**
969
+ * Extract error type name from error message.
970
+ * Google errors often include the error type in the message.
971
+ * Examples: "INVALID_ARGUMENT", "RESOURCE_EXHAUSTED", "PERMISSION_DENIED"
972
+ */
973
+ extractErrorName(message) {
974
+ // Common Google error patterns
975
+ const patterns = [
976
+ /^([A-Z_]+):/, // "ERROR_NAME: message"
977
+ /\[([A-Z_]+)\]/, // "[ERROR_NAME] message"
978
+ /^(\w+Error):/, // "ErrorTypeError: message"
979
+ ];
980
+ for (const pattern of patterns) {
981
+ const match = message.match(pattern);
982
+ if (match) {
983
+ return match[1];
984
+ }
985
+ }
986
+ return undefined;
987
+ }
988
+ }
989
+ exports.GeminiModelDefinition = GeminiModelDefinition;
990
+ /**
991
+ * Converts functionCall and functionResponse parts to text parts in Gemini Content[].
992
+ * Preserves tool call information while removing structured parts that require
993
+ * tools/toolConfig to be defined in the API request.
994
+ */
995
+ function convertGeminiFunctionPartsToText(contents) {
996
+ return contents.map(content => {
997
+ if (!content.parts)
998
+ return content;
999
+ const hasFunctionParts = content.parts.some(p => p.functionCall || p.functionResponse);
1000
+ if (!hasFunctionParts)
1001
+ return content;
1002
+ const newParts = content.parts.map(part => {
1003
+ if (part.functionCall) {
1004
+ const argsStr = part.functionCall.args ? JSON.stringify(part.functionCall.args) : '';
1005
+ const truncated = argsStr.length > 500 ? argsStr.substring(0, 500) + '...' : argsStr;
1006
+ return { text: `[Tool call: ${part.functionCall.name}(${truncated})]` };
1007
+ }
1008
+ if (part.functionResponse) {
1009
+ const respStr = part.functionResponse.response
1010
+ ? JSON.stringify(part.functionResponse.response) : 'No response';
1011
+ const truncated = respStr.length > 500 ? respStr.substring(0, 500) + '...' : respStr;
1012
+ return { text: `[Tool result for ${part.functionResponse.name}: ${truncated}]` };
1013
+ }
1014
+ return part;
1015
+ });
1016
+ return { ...content, parts: newParts };
1017
+ });
1018
+ }
1019
+ function getToolDefinitions(tools) {
1020
+ if (!tools || tools.length === 0) {
1021
+ return undefined;
1022
+ }
1023
+ // VertexAI Gemini only supports one tool at a time.
1024
+ // For multiple tools, we have multiple functions in one tool.
1025
+ return {
1026
+ functionDeclarations: tools.map(getToolFunction),
1027
+ };
1028
+ }
1029
+ function getToolFunction(tool) {
1030
+ // If input_schema is a string, parse it; if it's already an object, use it directly
1031
+ let toolSchema;
1032
+ // Using a try-catch for safety, as the input_schema might not be a valid JSONSchema
1033
+ try {
1034
+ toolSchema = parseJSONtoSchema(tool.input_schema, false);
1035
+ }
1036
+ catch (e) {
1037
+ toolSchema = { ...tool.input_schema, type: genai_1.Type.OBJECT };
1038
+ }
1039
+ return {
1040
+ name: tool.name,
1041
+ description: tool.description,
1042
+ parameters: toolSchema,
1043
+ };
1044
+ }
1045
+ /**
1046
+ * Update the conversation messages
1047
+ * @param prompt
1048
+ * @param response
1049
+ * @returns
1050
+ */
1051
+ function updateConversation(conversation, prompt) {
1052
+ // Unwrap array if wrapped, otherwise treat as array
1053
+ const unwrapped = (0, core_1.unwrapConversationArray)(conversation);
1054
+ const convArray = unwrapped ?? (conversation || []);
1055
+ return convArray.concat(prompt);
1056
+ }
1057
+ const SYSTEM_KEY = '_llumiverse_system';
1058
+ /**
1059
+ * Extract the stored system instruction from a Gemini conversation object.
1060
+ * Returns undefined if no system was stored.
1061
+ */
1062
+ function extractSystemFromConversation(conversation) {
1063
+ if (typeof conversation === 'object' && conversation !== null) {
1064
+ const c = conversation;
1065
+ if (c[SYSTEM_KEY] && typeof c[SYSTEM_KEY] === 'object') {
1066
+ return c[SYSTEM_KEY];
1067
+ }
1068
+ }
1069
+ return undefined;
1070
+ }
1071
+ /**
1072
+ * Store the system instruction in the Gemini conversation wrapper object.
1073
+ * The conversation is already wrapped by incrementConversationTurn into
1074
+ * { _arrayConversation: Content[], _llumiverse_meta: {...} }.
1075
+ * We add _llumiverse_system alongside these fields.
1076
+ */
1077
+ function storeSystemInConversation(conversation, system) {
1078
+ if (!system)
1079
+ return conversation;
1080
+ if (typeof conversation === 'object' && conversation !== null) {
1081
+ return { ...conversation, [SYSTEM_KEY]: system };
1082
+ }
1083
+ return conversation;
1084
+ }
1085
+ /**
1086
+ *
1087
+ * Gemini supports JSON output in the response. so we test if the response is a valid JSON object. otherwise we treat the response as a string.
1088
+ *
1089
+ * This is an excerpt from googleapis.github.io/python-genai:
1090
+ *
1091
+ * The function response in JSON object format.
1092
+ * Use “output” key to specify function output and “error” key to specify error details (if any).
1093
+ * If “output” and “error” keys are not specified, then whole “response” is treated as function output.
1094
+ * @see https://googleapis.github.io/python-genai/genai.html#genai.types.FunctionResponse
1095
+ */
1096
+ function formatFunctionResponse(response) {
1097
+ response = response.trim();
1098
+ if (response.startsWith("{") && response.endsWith("}")) {
1099
+ try {
1100
+ return JSON.parse(response);
1101
+ }
1102
+ catch (e) {
1103
+ return { output: response };
1104
+ }
1105
+ }
1106
+ else {
1107
+ return { output: response };
1108
+ }
1109
+ }
1110
+ //# sourceMappingURL=gemini.js.map