@llumiverse/drivers 0.24.0 → 0.25.0-dev.20260204.110528Z

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 (224) hide show
  1. package/package.json +3 -3
  2. package/lib/cjs/adobe/firefly.js +0 -120
  3. package/lib/cjs/adobe/firefly.js.map +0 -1
  4. package/lib/cjs/azure/azure_foundry.js +0 -432
  5. package/lib/cjs/azure/azure_foundry.js.map +0 -1
  6. package/lib/cjs/bedrock/converse.js +0 -285
  7. package/lib/cjs/bedrock/converse.js.map +0 -1
  8. package/lib/cjs/bedrock/index.js +0 -1091
  9. package/lib/cjs/bedrock/index.js.map +0 -1
  10. package/lib/cjs/bedrock/nova-image-payload.js +0 -207
  11. package/lib/cjs/bedrock/nova-image-payload.js.map +0 -1
  12. package/lib/cjs/bedrock/payloads.js +0 -3
  13. package/lib/cjs/bedrock/payloads.js.map +0 -1
  14. package/lib/cjs/bedrock/s3.js +0 -107
  15. package/lib/cjs/bedrock/s3.js.map +0 -1
  16. package/lib/cjs/bedrock/twelvelabs.js +0 -87
  17. package/lib/cjs/bedrock/twelvelabs.js.map +0 -1
  18. package/lib/cjs/groq/index.js +0 -323
  19. package/lib/cjs/groq/index.js.map +0 -1
  20. package/lib/cjs/huggingface_ie.js +0 -201
  21. package/lib/cjs/huggingface_ie.js.map +0 -1
  22. package/lib/cjs/index.js +0 -31
  23. package/lib/cjs/index.js.map +0 -1
  24. package/lib/cjs/mistral/index.js +0 -173
  25. package/lib/cjs/mistral/index.js.map +0 -1
  26. package/lib/cjs/mistral/types.js +0 -83
  27. package/lib/cjs/mistral/types.js.map +0 -1
  28. package/lib/cjs/openai/azure_openai.js +0 -72
  29. package/lib/cjs/openai/azure_openai.js.map +0 -1
  30. package/lib/cjs/openai/index.js +0 -665
  31. package/lib/cjs/openai/index.js.map +0 -1
  32. package/lib/cjs/openai/openai.js +0 -21
  33. package/lib/cjs/openai/openai.js.map +0 -1
  34. package/lib/cjs/openai/openai_compatible.js +0 -62
  35. package/lib/cjs/openai/openai_compatible.js.map +0 -1
  36. package/lib/cjs/openai/openai_format.js +0 -131
  37. package/lib/cjs/openai/openai_format.js.map +0 -1
  38. package/lib/cjs/package.json +0 -3
  39. package/lib/cjs/replicate.js +0 -275
  40. package/lib/cjs/replicate.js.map +0 -1
  41. package/lib/cjs/test-driver/TestErrorCompletionStream.js +0 -20
  42. package/lib/cjs/test-driver/TestErrorCompletionStream.js.map +0 -1
  43. package/lib/cjs/test-driver/TestValidationErrorCompletionStream.js +0 -24
  44. package/lib/cjs/test-driver/TestValidationErrorCompletionStream.js.map +0 -1
  45. package/lib/cjs/test-driver/index.js +0 -109
  46. package/lib/cjs/test-driver/index.js.map +0 -1
  47. package/lib/cjs/test-driver/utils.js +0 -30
  48. package/lib/cjs/test-driver/utils.js.map +0 -1
  49. package/lib/cjs/togetherai/index.js +0 -126
  50. package/lib/cjs/togetherai/index.js.map +0 -1
  51. package/lib/cjs/togetherai/interfaces.js +0 -3
  52. package/lib/cjs/togetherai/interfaces.js.map +0 -1
  53. package/lib/cjs/vertexai/debug.js +0 -12
  54. package/lib/cjs/vertexai/debug.js.map +0 -1
  55. package/lib/cjs/vertexai/embeddings/embeddings-image.js +0 -27
  56. package/lib/cjs/vertexai/embeddings/embeddings-image.js.map +0 -1
  57. package/lib/cjs/vertexai/embeddings/embeddings-text.js +0 -23
  58. package/lib/cjs/vertexai/embeddings/embeddings-text.js.map +0 -1
  59. package/lib/cjs/vertexai/index.js +0 -576
  60. package/lib/cjs/vertexai/index.js.map +0 -1
  61. package/lib/cjs/vertexai/models/claude.js +0 -485
  62. package/lib/cjs/vertexai/models/claude.js.map +0 -1
  63. package/lib/cjs/vertexai/models/gemini.js +0 -871
  64. package/lib/cjs/vertexai/models/gemini.js.map +0 -1
  65. package/lib/cjs/vertexai/models/imagen.js +0 -303
  66. package/lib/cjs/vertexai/models/imagen.js.map +0 -1
  67. package/lib/cjs/vertexai/models/llama.js +0 -183
  68. package/lib/cjs/vertexai/models/llama.js.map +0 -1
  69. package/lib/cjs/vertexai/models.js +0 -35
  70. package/lib/cjs/vertexai/models.js.map +0 -1
  71. package/lib/cjs/watsonx/index.js +0 -161
  72. package/lib/cjs/watsonx/index.js.map +0 -1
  73. package/lib/cjs/watsonx/interfaces.js +0 -3
  74. package/lib/cjs/watsonx/interfaces.js.map +0 -1
  75. package/lib/cjs/xai/index.js +0 -65
  76. package/lib/cjs/xai/index.js.map +0 -1
  77. package/lib/esm/adobe/firefly.js +0 -116
  78. package/lib/esm/adobe/firefly.js.map +0 -1
  79. package/lib/esm/azure/azure_foundry.js +0 -426
  80. package/lib/esm/azure/azure_foundry.js.map +0 -1
  81. package/lib/esm/bedrock/converse.js +0 -278
  82. package/lib/esm/bedrock/converse.js.map +0 -1
  83. package/lib/esm/bedrock/index.js +0 -1087
  84. package/lib/esm/bedrock/index.js.map +0 -1
  85. package/lib/esm/bedrock/nova-image-payload.js +0 -203
  86. package/lib/esm/bedrock/nova-image-payload.js.map +0 -1
  87. package/lib/esm/bedrock/payloads.js +0 -2
  88. package/lib/esm/bedrock/payloads.js.map +0 -1
  89. package/lib/esm/bedrock/s3.js +0 -99
  90. package/lib/esm/bedrock/s3.js.map +0 -1
  91. package/lib/esm/bedrock/twelvelabs.js +0 -84
  92. package/lib/esm/bedrock/twelvelabs.js.map +0 -1
  93. package/lib/esm/groq/index.js +0 -316
  94. package/lib/esm/groq/index.js.map +0 -1
  95. package/lib/esm/huggingface_ie.js +0 -197
  96. package/lib/esm/huggingface_ie.js.map +0 -1
  97. package/lib/esm/index.js +0 -15
  98. package/lib/esm/index.js.map +0 -1
  99. package/lib/esm/mistral/index.js +0 -169
  100. package/lib/esm/mistral/index.js.map +0 -1
  101. package/lib/esm/mistral/types.js +0 -80
  102. package/lib/esm/mistral/types.js.map +0 -1
  103. package/lib/esm/openai/azure_openai.js +0 -68
  104. package/lib/esm/openai/azure_openai.js.map +0 -1
  105. package/lib/esm/openai/index.js +0 -660
  106. package/lib/esm/openai/index.js.map +0 -1
  107. package/lib/esm/openai/openai.js +0 -14
  108. package/lib/esm/openai/openai.js.map +0 -1
  109. package/lib/esm/openai/openai_compatible.js +0 -55
  110. package/lib/esm/openai/openai_compatible.js.map +0 -1
  111. package/lib/esm/openai/openai_format.js +0 -127
  112. package/lib/esm/openai/openai_format.js.map +0 -1
  113. package/lib/esm/replicate.js +0 -268
  114. package/lib/esm/replicate.js.map +0 -1
  115. package/lib/esm/test-driver/TestErrorCompletionStream.js +0 -16
  116. package/lib/esm/test-driver/TestErrorCompletionStream.js.map +0 -1
  117. package/lib/esm/test-driver/TestValidationErrorCompletionStream.js +0 -20
  118. package/lib/esm/test-driver/TestValidationErrorCompletionStream.js.map +0 -1
  119. package/lib/esm/test-driver/index.js +0 -91
  120. package/lib/esm/test-driver/index.js.map +0 -1
  121. package/lib/esm/test-driver/utils.js +0 -25
  122. package/lib/esm/test-driver/utils.js.map +0 -1
  123. package/lib/esm/togetherai/index.js +0 -122
  124. package/lib/esm/togetherai/index.js.map +0 -1
  125. package/lib/esm/togetherai/interfaces.js +0 -2
  126. package/lib/esm/togetherai/interfaces.js.map +0 -1
  127. package/lib/esm/vertexai/debug.js +0 -6
  128. package/lib/esm/vertexai/debug.js.map +0 -1
  129. package/lib/esm/vertexai/embeddings/embeddings-image.js +0 -24
  130. package/lib/esm/vertexai/embeddings/embeddings-image.js.map +0 -1
  131. package/lib/esm/vertexai/embeddings/embeddings-text.js +0 -20
  132. package/lib/esm/vertexai/embeddings/embeddings-text.js.map +0 -1
  133. package/lib/esm/vertexai/index.js +0 -571
  134. package/lib/esm/vertexai/index.js.map +0 -1
  135. package/lib/esm/vertexai/models/claude.js +0 -479
  136. package/lib/esm/vertexai/models/claude.js.map +0 -1
  137. package/lib/esm/vertexai/models/gemini.js +0 -866
  138. package/lib/esm/vertexai/models/gemini.js.map +0 -1
  139. package/lib/esm/vertexai/models/imagen.js +0 -299
  140. package/lib/esm/vertexai/models/imagen.js.map +0 -1
  141. package/lib/esm/vertexai/models/llama.js +0 -179
  142. package/lib/esm/vertexai/models/llama.js.map +0 -1
  143. package/lib/esm/vertexai/models.js +0 -32
  144. package/lib/esm/vertexai/models.js.map +0 -1
  145. package/lib/esm/watsonx/index.js +0 -157
  146. package/lib/esm/watsonx/index.js.map +0 -1
  147. package/lib/esm/watsonx/interfaces.js +0 -2
  148. package/lib/esm/watsonx/interfaces.js.map +0 -1
  149. package/lib/esm/xai/index.js +0 -58
  150. package/lib/esm/xai/index.js.map +0 -1
  151. package/lib/types/adobe/firefly.d.ts +0 -30
  152. package/lib/types/adobe/firefly.d.ts.map +0 -1
  153. package/lib/types/azure/azure_foundry.d.ts +0 -52
  154. package/lib/types/azure/azure_foundry.d.ts.map +0 -1
  155. package/lib/types/bedrock/converse.d.ts +0 -9
  156. package/lib/types/bedrock/converse.d.ts.map +0 -1
  157. package/lib/types/bedrock/index.d.ts +0 -68
  158. package/lib/types/bedrock/index.d.ts.map +0 -1
  159. package/lib/types/bedrock/nova-image-payload.d.ts +0 -74
  160. package/lib/types/bedrock/nova-image-payload.d.ts.map +0 -1
  161. package/lib/types/bedrock/payloads.d.ts +0 -12
  162. package/lib/types/bedrock/payloads.d.ts.map +0 -1
  163. package/lib/types/bedrock/s3.d.ts +0 -23
  164. package/lib/types/bedrock/s3.d.ts.map +0 -1
  165. package/lib/types/bedrock/twelvelabs.d.ts +0 -50
  166. package/lib/types/bedrock/twelvelabs.d.ts.map +0 -1
  167. package/lib/types/groq/index.d.ts +0 -27
  168. package/lib/types/groq/index.d.ts.map +0 -1
  169. package/lib/types/huggingface_ie.d.ts +0 -35
  170. package/lib/types/huggingface_ie.d.ts.map +0 -1
  171. package/lib/types/index.d.ts +0 -15
  172. package/lib/types/index.d.ts.map +0 -1
  173. package/lib/types/mistral/index.d.ts +0 -25
  174. package/lib/types/mistral/index.d.ts.map +0 -1
  175. package/lib/types/mistral/types.d.ts +0 -132
  176. package/lib/types/mistral/types.d.ts.map +0 -1
  177. package/lib/types/openai/azure_openai.d.ts +0 -25
  178. package/lib/types/openai/azure_openai.d.ts.map +0 -1
  179. package/lib/types/openai/index.d.ts +0 -31
  180. package/lib/types/openai/index.d.ts.map +0 -1
  181. package/lib/types/openai/openai.d.ts +0 -15
  182. package/lib/types/openai/openai.d.ts.map +0 -1
  183. package/lib/types/openai/openai_compatible.d.ts +0 -26
  184. package/lib/types/openai/openai_compatible.d.ts.map +0 -1
  185. package/lib/types/openai/openai_format.d.ts +0 -21
  186. package/lib/types/openai/openai_format.d.ts.map +0 -1
  187. package/lib/types/replicate.d.ts +0 -48
  188. package/lib/types/replicate.d.ts.map +0 -1
  189. package/lib/types/test-driver/TestErrorCompletionStream.d.ts +0 -9
  190. package/lib/types/test-driver/TestErrorCompletionStream.d.ts.map +0 -1
  191. package/lib/types/test-driver/TestValidationErrorCompletionStream.d.ts +0 -9
  192. package/lib/types/test-driver/TestValidationErrorCompletionStream.d.ts.map +0 -1
  193. package/lib/types/test-driver/index.d.ts +0 -24
  194. package/lib/types/test-driver/index.d.ts.map +0 -1
  195. package/lib/types/test-driver/utils.d.ts +0 -5
  196. package/lib/types/test-driver/utils.d.ts.map +0 -1
  197. package/lib/types/togetherai/index.d.ts +0 -23
  198. package/lib/types/togetherai/index.d.ts.map +0 -1
  199. package/lib/types/togetherai/interfaces.d.ts +0 -96
  200. package/lib/types/togetherai/interfaces.d.ts.map +0 -1
  201. package/lib/types/vertexai/debug.d.ts +0 -2
  202. package/lib/types/vertexai/debug.d.ts.map +0 -1
  203. package/lib/types/vertexai/embeddings/embeddings-image.d.ts +0 -11
  204. package/lib/types/vertexai/embeddings/embeddings-image.d.ts.map +0 -1
  205. package/lib/types/vertexai/embeddings/embeddings-text.d.ts +0 -10
  206. package/lib/types/vertexai/embeddings/embeddings-text.d.ts.map +0 -1
  207. package/lib/types/vertexai/index.d.ts +0 -65
  208. package/lib/types/vertexai/index.d.ts.map +0 -1
  209. package/lib/types/vertexai/models/claude.d.ts +0 -28
  210. package/lib/types/vertexai/models/claude.d.ts.map +0 -1
  211. package/lib/types/vertexai/models/gemini.d.ts +0 -18
  212. package/lib/types/vertexai/models/gemini.d.ts.map +0 -1
  213. package/lib/types/vertexai/models/imagen.d.ts +0 -75
  214. package/lib/types/vertexai/models/imagen.d.ts.map +0 -1
  215. package/lib/types/vertexai/models/llama.d.ts +0 -20
  216. package/lib/types/vertexai/models/llama.d.ts.map +0 -1
  217. package/lib/types/vertexai/models.d.ts +0 -15
  218. package/lib/types/vertexai/models.d.ts.map +0 -1
  219. package/lib/types/watsonx/index.d.ts +0 -27
  220. package/lib/types/watsonx/index.d.ts.map +0 -1
  221. package/lib/types/watsonx/interfaces.d.ts +0 -65
  222. package/lib/types/watsonx/interfaces.d.ts.map +0 -1
  223. package/lib/types/xai/index.d.ts +0 -18
  224. package/lib/types/xai/index.d.ts.map +0 -1
@@ -1,866 +0,0 @@
1
- import { FinishReason, FunctionCallingConfigMode, HarmBlockThreshold, HarmCategory, Modality, Type } from "@google/genai";
2
- import { getConversationMeta, getMaxTokensLimitVertexAi, incrementConversationTurn, ModelType, PromptRole, readStreamAsBase64, stripBase64ImagesFromConversation, truncateLargeTextInConversation, unwrapConversationArray } from "@llumiverse/core";
3
- import { asyncMap } from "@llumiverse/core/async";
4
- function supportsStructuredOutput(options) {
5
- // Gemini 1.0 Ultra does not support JSON output, 1.0 Pro does.
6
- return !!options.result_schema && !options.model.includes("ultra");
7
- }
8
- const geminiSafetySettings = [
9
- {
10
- category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
11
- threshold: HarmBlockThreshold.BLOCK_ONLY_HIGH
12
- },
13
- {
14
- category: HarmCategory.HARM_CATEGORY_HARASSMENT,
15
- threshold: HarmBlockThreshold.BLOCK_ONLY_HIGH
16
- },
17
- {
18
- category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
19
- threshold: HarmBlockThreshold.BLOCK_ONLY_HIGH
20
- },
21
- {
22
- category: HarmCategory.HARM_CATEGORY_HATE_SPEECH,
23
- threshold: HarmBlockThreshold.BLOCK_ONLY_HIGH
24
- },
25
- {
26
- category: HarmCategory.HARM_CATEGORY_UNSPECIFIED,
27
- threshold: HarmBlockThreshold.BLOCK_ONLY_HIGH
28
- },
29
- {
30
- category: HarmCategory.HARM_CATEGORY_CIVIC_INTEGRITY,
31
- threshold: HarmBlockThreshold.BLOCK_ONLY_HIGH
32
- }
33
- ];
34
- function getGeminiPayload(options, prompt) {
35
- const model_options = options.model_options;
36
- const tools = getToolDefinitions(options.tools);
37
- const useStructuredOutput = supportsStructuredOutput(options) && !tools;
38
- const thinkingConfigNeeded = model_options?.include_thoughts
39
- || model_options?.thinking_budget_tokens
40
- || options.model.includes("gemini-2.5");
41
- const configNanoBanana = {
42
- systemInstruction: prompt.system,
43
- safetySettings: geminiSafetySettings,
44
- responseModalities: [Modality.TEXT, Modality.IMAGE], // This is an error if only Text, and Only Image just gets blank responses.
45
- candidateCount: 1,
46
- //Model options
47
- temperature: model_options?.temperature,
48
- topP: model_options?.top_p,
49
- maxOutputTokens: geminiMaxTokens(options),
50
- stopSequences: model_options?.stop_sequence,
51
- imageConfig: {
52
- aspectRatio: model_options?.image_aspect_ratio,
53
- }
54
- };
55
- const config = {
56
- systemInstruction: prompt.system,
57
- safetySettings: geminiSafetySettings,
58
- tools: tools ? [tools] : undefined,
59
- toolConfig: tools ? {
60
- functionCallingConfig: {
61
- mode: FunctionCallingConfigMode.AUTO,
62
- }
63
- } : undefined,
64
- candidateCount: 1,
65
- //JSON/Structured output
66
- responseMimeType: useStructuredOutput ? "application/json" : undefined,
67
- responseSchema: useStructuredOutput ? parseJSONtoSchema(options.result_schema, true) : undefined,
68
- //Model options
69
- temperature: model_options?.temperature,
70
- topP: model_options?.top_p,
71
- topK: model_options?.top_k,
72
- maxOutputTokens: geminiMaxTokens(options),
73
- stopSequences: model_options?.stop_sequence,
74
- presencePenalty: model_options?.presence_penalty,
75
- frequencyPenalty: model_options?.frequency_penalty,
76
- seed: model_options?.seed,
77
- thinkingConfig: thinkingConfigNeeded ? geminiThinkingConfig(options) : undefined,
78
- };
79
- return {
80
- model: options.model,
81
- contents: prompt.contents,
82
- config: options.model.toLowerCase().includes("image") ? configNanoBanana : config,
83
- };
84
- }
85
- /**
86
- * Convert JSONSchema to Gemini Schema,
87
- * Make all properties required by default
88
- * Properties previously marked as optional will be marked as nullable.
89
- */
90
- function parseJSONtoSchema(schema, requiredAll = false) {
91
- if (!schema) {
92
- return {};
93
- }
94
- return convertSchema(schema, 0, requiredAll);
95
- }
96
- /**
97
- * Convert JSONSchema type to Gemini Schema Type
98
- */
99
- function convertType(type) {
100
- if (!type)
101
- return undefined;
102
- // Handle single type
103
- if (typeof type === 'string') {
104
- switch (type) {
105
- case 'string': return Type.STRING;
106
- case 'number': return Type.NUMBER;
107
- case 'integer': return Type.INTEGER;
108
- case 'boolean': return Type.BOOLEAN;
109
- case 'object': return Type.OBJECT;
110
- case 'array': return Type.ARRAY;
111
- default: return type; // For unsupported types, return as is
112
- }
113
- }
114
- // For array of types, take the first valid one as the primary type
115
- // The full set of types will be handled with anyOf
116
- for (const t of type) {
117
- const converted = convertType(t);
118
- if (converted)
119
- return converted;
120
- }
121
- return undefined;
122
- }
123
- /**
124
- * Deep clone and convert the schema from JSONSchema to Gemini Schema
125
- * @throws {Error} If circular references are detected (max depth exceeded)
126
- */
127
- function convertSchema(jsSchema, depth = 0, requiredAll = false) {
128
- // Prevent circular references
129
- if (depth > 20) {
130
- throw new Error("Maximum schema depth (20) exceeded. Possible circular reference detected.");
131
- }
132
- if (!jsSchema)
133
- return {};
134
- // Create new schema object rather than mutating
135
- const result = {};
136
- // Handle types
137
- result.type = convertSchemaType(jsSchema);
138
- // Handle description
139
- if (jsSchema.description) {
140
- result.description = jsSchema.description;
141
- }
142
- // Handle properties and required fields
143
- if (jsSchema.properties) {
144
- const propertyResult = convertSchemaProperties(jsSchema, depth + 1, requiredAll);
145
- Object.assign(result, propertyResult);
146
- }
147
- // Handle items for arrays
148
- if (jsSchema.items) {
149
- result.items = convertSchema(jsSchema.items, depth + 1);
150
- }
151
- // Handle enum values
152
- if (jsSchema.enum) {
153
- result.enum = [...jsSchema.enum]; // Create a copy instead of reference
154
- }
155
- // Copy constraints
156
- Object.assign(result, extractConstraints(jsSchema));
157
- return result;
158
- }
159
- /**
160
- * Convert schema type information, handling anyOf for multiple types
161
- */
162
- function convertSchemaType(jsSchema) {
163
- // Handle multiple types using anyOf
164
- if (jsSchema.type && Array.isArray(jsSchema.type) && jsSchema.type.length > 1) {
165
- // Since anyOf is an advanced type, we'll return the first valid type
166
- // and handle the multi-type case separately in the schema
167
- return convertType(jsSchema.type[0]);
168
- }
169
- // Handle single type
170
- else if (jsSchema.type) {
171
- return convertType(jsSchema.type);
172
- }
173
- return undefined;
174
- }
175
- /**
176
- * Handle properties conversion and required fields
177
- */
178
- function convertSchemaProperties(jsSchema, depth, requiredAll) {
179
- const result = { properties: {} };
180
- if (jsSchema.required) {
181
- result.required = [...jsSchema.required]; // Create a copy
182
- }
183
- // Extract property ordering from the object keys
184
- const propertyNames = Object.keys(jsSchema.properties || {});
185
- // Set property ordering based on the existing order in the schema
186
- if (propertyNames.length > 0) {
187
- result.propertyOrdering = propertyNames;
188
- if (requiredAll) {
189
- // Mark all properties as required by default
190
- // This ensures the model fills all fields
191
- result.required = propertyNames;
192
- // Get the original required properties
193
- const originalRequired = jsSchema.required || [];
194
- // Make previously optional properties nullable since we're marking them as required
195
- for (const key of propertyNames) {
196
- const propSchema = jsSchema.properties?.[key];
197
- if (propSchema && !originalRequired.includes(key)) {
198
- // Initialize the property if needed
199
- if (!result.properties[key]) {
200
- result.properties[key] = {};
201
- }
202
- // Mark as nullable
203
- result.properties[key].nullable = true;
204
- }
205
- }
206
- }
207
- }
208
- // Convert each property schema
209
- for (const [key, value] of Object.entries(jsSchema.properties || {})) {
210
- if (!result.properties[key]) {
211
- result.properties[key] = {};
212
- }
213
- // Merge with converted schema
214
- result.properties[key] = {
215
- ...result.properties[key],
216
- ...convertSchema(value, depth)
217
- };
218
- }
219
- // Override with explicit propertyOrdering if present
220
- if (jsSchema.propertyOrdering) {
221
- result.propertyOrdering = [...jsSchema.propertyOrdering]; // Create a copy
222
- }
223
- return result;
224
- }
225
- /**
226
- * Extract schema constraints (min/max values, formats, etc.)
227
- */
228
- function extractConstraints(jsSchema) {
229
- const constraints = {};
230
- if (jsSchema.minimum !== undefined)
231
- constraints.minimum = jsSchema.minimum;
232
- if (jsSchema.maximum !== undefined)
233
- constraints.maximum = jsSchema.maximum;
234
- if (jsSchema.minLength !== undefined)
235
- constraints.minLength = jsSchema.minLength;
236
- if (jsSchema.maxLength !== undefined)
237
- constraints.maxLength = jsSchema.maxLength;
238
- if (jsSchema.minItems !== undefined)
239
- constraints.minItems = jsSchema.minItems;
240
- if (jsSchema.maxItems !== undefined)
241
- constraints.maxItems = jsSchema.maxItems;
242
- if (jsSchema.nullable !== undefined)
243
- constraints.nullable = jsSchema.nullable;
244
- if (jsSchema.pattern)
245
- constraints.pattern = jsSchema.pattern;
246
- if (jsSchema.format)
247
- constraints.format = jsSchema.format;
248
- if (jsSchema.default !== undefined)
249
- constraints.default = jsSchema.default;
250
- if (jsSchema.example !== undefined)
251
- constraints.example = jsSchema.example;
252
- return constraints;
253
- }
254
- /**
255
- * Check if a value is empty (null, undefined, empty string, empty array, empty object)
256
- * @param value The value to check
257
- * @returns True if the value is considered empty
258
- */
259
- function isEmpty(value) {
260
- if (value === null || value === undefined) {
261
- return true;
262
- }
263
- if (typeof value === 'string' && value.trim() === '') {
264
- return true;
265
- }
266
- if (Array.isArray(value) && value.length === 0) {
267
- return true;
268
- }
269
- // Check for empty object (no own enumerable properties)
270
- if (typeof value === 'object' && Object.keys(value).length === 0) {
271
- return true;
272
- }
273
- // Check for array of empty objects
274
- if (Array.isArray(value) && value.every(item => isEmpty(item))) {
275
- return true;
276
- }
277
- return false;
278
- }
279
- // No array cleaning function needed as we're only working with JSONObjects
280
- /**
281
- * Clean up the JSON result by removing empty values for optional fields
282
- * Uses immutable patterns to create a new Content object rather than modifying the original
283
- * @param content The original content from Gemini
284
- * @param result_schema The JSON schema to use for cleaning
285
- * @returns A new Content object with cleaned JSON text
286
- */
287
- function cleanEmptyFieldsContent(content, result_schema) {
288
- // If no schema provided, return original content
289
- if (!result_schema) {
290
- return content;
291
- }
292
- // Create a new content object (shallow copy)
293
- const cleanedContent = { ...content };
294
- // Create a new parts array if it exists
295
- if (cleanedContent.parts) {
296
- cleanedContent.parts = cleanedContent.parts.map(part => {
297
- // Only process parts with text
298
- if (!part.text) {
299
- return part; // Return unchanged if no text
300
- }
301
- // Create a new part object
302
- const newPart = { ...part };
303
- try {
304
- // Parse JSON, clean it based on schema, then stringify
305
- const jsonText = JSON.parse(part.text);
306
- // Skip cleaning if not an object
307
- if (typeof jsonText === 'object' && jsonText !== null && !Array.isArray(jsonText)) {
308
- const cleanedJson = removeEmptyFields(jsonText, result_schema);
309
- newPart.text = JSON.stringify(cleanedJson);
310
- }
311
- else {
312
- // Keep original if not an object (string, number, array, etc.)
313
- newPart.text = part.text;
314
- }
315
- }
316
- catch (e) {
317
- // On error, keep the original text
318
- console.warn("Error parsing Gemini output to JSON in part:", e);
319
- }
320
- return newPart;
321
- });
322
- }
323
- return cleanedContent;
324
- }
325
- /**
326
- * Removes empty optional fields from the JSON result based on the provided schema
327
- * @param object The object to clean
328
- * @param schema The JSON schema to use for cleaning
329
- * @returns A new object with empty optional fields removed
330
- */
331
- function removeEmptyFields(object, schema) {
332
- if (!object) {
333
- return object;
334
- }
335
- if (Array.isArray(object)) {
336
- return removeEmptyJSONArray(object, schema);
337
- }
338
- if (typeof object == 'object' || object === null) {
339
- return removeEmptyJSONObject(object, schema);
340
- }
341
- return object;
342
- }
343
- function removeEmptyJSONObject(object, schema) {
344
- // Get the original required properties from schema
345
- const requiredProps = schema.required || [];
346
- const cleanedResult = { ...object };
347
- // Process each property
348
- for (const [key, value] of Object.entries(object)) {
349
- const isRequired = requiredProps.includes(key);
350
- const propSchema = schema.properties?.[key];
351
- // Recursively clean nested objects based on their schema
352
- cleanedResult[key] = removeEmptyFields(value, propSchema ?? {});
353
- if (isEmpty(value)) {
354
- if (isRequired) {
355
- continue; // Keep required fields even if empty
356
- }
357
- else {
358
- delete cleanedResult[key]; // Remove empty optional fields
359
- }
360
- }
361
- }
362
- return cleanedResult;
363
- }
364
- function removeEmptyJSONArray(array, schema) {
365
- const cleanedArray = array.map(item => {
366
- return removeEmptyFields(item, schema);
367
- });
368
- // Filter out empty objects from the array
369
- return cleanedArray.filter(item => !isEmpty(item));
370
- }
371
- function collectTextParts(content) {
372
- const results = [];
373
- const parts = content.parts;
374
- if (parts) {
375
- for (const part of parts) {
376
- if (part.text) {
377
- results.push({
378
- type: "text",
379
- value: part.text
380
- });
381
- }
382
- }
383
- }
384
- return results;
385
- }
386
- function collectInlineDataParts(content) {
387
- const results = [];
388
- const parts = content.parts;
389
- if (parts) {
390
- for (const part of parts) {
391
- if (part.inlineData) {
392
- const base64ImageBytes = part.inlineData.data ?? "";
393
- const mimeType = part.inlineData.mimeType ?? "image/png";
394
- const imageUrl = `data:${mimeType};base64,${base64ImageBytes}`;
395
- results.push({
396
- type: "image",
397
- value: imageUrl
398
- });
399
- }
400
- }
401
- }
402
- return results;
403
- }
404
- function collectToolUseParts(content) {
405
- const out = [];
406
- const parts = content.parts ?? [];
407
- for (const part of parts) {
408
- if (part.functionCall) {
409
- const toolUse = {
410
- id: part.functionCall.name ?? '',
411
- tool_name: part.functionCall.name ?? '',
412
- tool_input: part.functionCall.args,
413
- };
414
- // Capture thought_signature for Gemini thinking models (2.5+/3.0+)
415
- // This must be passed back with the function response
416
- if (part.thoughtSignature) {
417
- toolUse.thought_signature = part.thoughtSignature;
418
- }
419
- out.push(toolUse);
420
- }
421
- }
422
- return out.length > 0 ? out : undefined;
423
- }
424
- export function mergeConsecutiveRole(contents) {
425
- if (!contents || contents.length === 0)
426
- return [];
427
- const needsMerging = contents.some((content, i) => i < contents.length - 1 && content.role === contents[i + 1].role);
428
- // If no merging needed, return original array
429
- if (!needsMerging) {
430
- return contents;
431
- }
432
- const result = [];
433
- let currentContent = { ...contents[0], parts: [...(contents[0].parts || [])] };
434
- for (let i = 1; i < contents.length; i++) {
435
- if (currentContent.role === contents[i].role) {
436
- // Same role - concatenate parts (without merging individual parts)
437
- currentContent.parts = (currentContent.parts || []).concat(...(contents[i].parts || []));
438
- }
439
- else {
440
- // Different role - push current and start new
441
- result.push(currentContent);
442
- currentContent = { ...contents[i], parts: [...(contents[i].parts || [])] };
443
- }
444
- }
445
- result.push(currentContent);
446
- return result;
447
- }
448
- const supportedFinishReasons = [
449
- FinishReason.MAX_TOKENS,
450
- FinishReason.STOP,
451
- FinishReason.FINISH_REASON_UNSPECIFIED,
452
- ];
453
- function geminiMaxTokens(option) {
454
- const model_options = option.model_options;
455
- if (model_options?.max_tokens) {
456
- return model_options.max_tokens;
457
- }
458
- if (option.model.includes("gemini-2.5")) {
459
- const maxSupportedTokens = getMaxTokensLimitVertexAi(option.model);
460
- const thinkingBudget = geminiThinkingBudget(option) ?? 0;
461
- return Math.min(maxSupportedTokens, 16000 + thinkingBudget);
462
- }
463
- return undefined;
464
- }
465
- function geminiThinkingBudget(option) {
466
- const model_options = option.model_options;
467
- if (model_options?.thinking_budget_tokens) {
468
- return model_options.thinking_budget_tokens;
469
- }
470
- // Set minimum thinking level by default.
471
- // Docs: https://ai.google.dev/gemini-api/docs/thinking#set-budget
472
- if (option.model.includes("gemini-2.5")) {
473
- if (option.model.includes("pro")) {
474
- return 128;
475
- }
476
- return 0;
477
- }
478
- return undefined;
479
- }
480
- function geminiThinkingConfig(option) {
481
- const model_options = option.model_options;
482
- const include_thoughts = model_options?.include_thoughts ?? false;
483
- if (model_options?.thinking_budget_tokens) {
484
- return { includeThoughts: include_thoughts, thinkingBudget: model_options.thinking_budget_tokens };
485
- }
486
- // Set minimum thinking level by default.
487
- // Docs: https://ai.google.dev/gemini-api/docs/thinking#set-budget
488
- if (option.model.includes("gemini-2.5") || option.model.includes("gemini-3")) {
489
- const thinking_budget_tokens = geminiThinkingBudget(option) ?? 0;
490
- return { includeThoughts: include_thoughts, thinkingBudget: thinking_budget_tokens };
491
- }
492
- }
493
- export class GeminiModelDefinition {
494
- model;
495
- constructor(modelId) {
496
- this.model = {
497
- id: modelId,
498
- name: modelId,
499
- provider: 'vertexai',
500
- type: ModelType.Text,
501
- can_stream: true
502
- };
503
- }
504
- preValidationProcessing(result, options) {
505
- // Guard clause, if no result_schema, error, or tool use, skip processing
506
- if (!options.result_schema || !result.result || result.tool_use || result.error) {
507
- return { result, options };
508
- }
509
- try {
510
- // Extract text content for JSON processing - only process first text result
511
- const textResult = result.result.find(r => r.type === 'text')?.value;
512
- if (textResult) {
513
- const jsonResult = JSON.parse(textResult);
514
- const cleanedJson = JSON.stringify(removeEmptyFields(jsonResult, options.result_schema));
515
- // Replace the text result with cleaned version
516
- result.result = result.result.map(r => r.type === 'text' ? { ...r, value: cleanedJson } : r);
517
- }
518
- return { result, options };
519
- }
520
- catch (error) {
521
- // Log error during processing but don't fail the completion
522
- console.warn('Error during Gemini JSON pre-validation: ', error);
523
- // Return original result if cleanup fails
524
- return { result, options };
525
- }
526
- }
527
- async createPrompt(_driver, segments, options) {
528
- const splits = options.model.split("/");
529
- const modelName = splits[splits.length - 1];
530
- options = { ...options, model: modelName };
531
- const schema = options.result_schema;
532
- let contents = [];
533
- let system = { role: "user", parts: [] }; // Single content block for system messages
534
- const safety = [];
535
- for (const msg of segments) {
536
- // Role specific handling
537
- if (msg.role === PromptRole.system) {
538
- // Text only for system messages
539
- if (msg.files && msg.files.length > 0) {
540
- throw new Error("Gemini does not support files/images etc. in system messages. Only text content is allowed.");
541
- }
542
- if (msg.content) {
543
- system.parts?.push({
544
- text: msg.content
545
- });
546
- }
547
- }
548
- else if (msg.role === PromptRole.tool) {
549
- if (!msg.tool_use_id) {
550
- throw new Error("Tool response missing tool_use_id");
551
- }
552
- // Build functionResponse part with optional thought_signature for Gemini thinking models
553
- const functionResponsePart = {
554
- functionResponse: {
555
- name: msg.tool_use_id,
556
- response: formatFunctionResponse(msg.content || ''),
557
- },
558
- // Include thought_signature if provided (required for Gemini 2.5+/3.0+ thinking models)
559
- thoughtSignature: msg.thought_signature,
560
- };
561
- contents.push({
562
- role: 'user',
563
- parts: [functionResponsePart]
564
- });
565
- }
566
- else { // PromptRole.user, PromptRole.assistant, PromptRole.safety
567
- const parts = [];
568
- // Text content handling
569
- if (msg.content) {
570
- parts.push({
571
- text: msg.content,
572
- });
573
- }
574
- // File content handling
575
- if (msg.files) {
576
- for (const f of msg.files) {
577
- let fileUrl = await f.getURL();
578
- const isGsUrl = fileUrl.startsWith('gs://') || fileUrl.startsWith('https://storage.googleapis.com/');
579
- if (isGsUrl) {
580
- parts.push({
581
- fileData: {
582
- fileUri: fileUrl,
583
- mimeType: f.mime_type
584
- }
585
- });
586
- }
587
- else {
588
- // Inline data handling
589
- const stream = await f.getStream();
590
- const data = await readStreamAsBase64(stream);
591
- parts.push({
592
- inlineData: {
593
- data,
594
- mimeType: f.mime_type
595
- }
596
- });
597
- }
598
- }
599
- }
600
- if (parts.length > 0) {
601
- if (msg.role === PromptRole.safety) {
602
- safety.push({
603
- role: 'user',
604
- parts,
605
- });
606
- }
607
- else {
608
- contents.push({
609
- role: msg.role === PromptRole.assistant ? 'model' : 'user',
610
- parts,
611
- });
612
- }
613
- }
614
- }
615
- }
616
- // Adding JSON Schema to system message
617
- if (schema) {
618
- if (supportsStructuredOutput(options) && !options.tools) {
619
- // Gemini structured output is unnecessarily sparse. Adding encouragement to fill the fields.
620
- // Putting JSON in prompt is not recommended by Google, when using structured output.
621
- system.parts?.push({ text: "Fill all appropriate fields in the JSON output." });
622
- }
623
- else {
624
- // Fallback to putting the schema in the system instructions, if not using structured output.
625
- if (options.tools) {
626
- system.parts?.push({
627
- text: "When not calling tools, the output must be a JSON object using the following JSON Schema:\n" + JSON.stringify(schema)
628
- });
629
- }
630
- else {
631
- system.parts?.push({ text: "The output must be a JSON object using the following JSON Schema:\n" + JSON.stringify(schema) });
632
- }
633
- }
634
- }
635
- // If no system messages, set system to undefined.
636
- if (!system.parts || system.parts.length === 0) {
637
- system = undefined;
638
- }
639
- // Add safety messages to the end of contents. They are in effect user messages that come at the end.
640
- if (safety.length > 0) {
641
- contents = contents.concat(safety);
642
- }
643
- // Merge consecutive messages with the same role. Note: this may not be necessary, works without it, keeping to match previous behavior.
644
- contents = mergeConsecutiveRole(contents);
645
- return { contents, system };
646
- }
647
- usageMetadataToTokenUsage(usageMetadata) {
648
- if (!usageMetadata || !usageMetadata.totalTokenCount) {
649
- return {};
650
- }
651
- const tokenUsage = { total: usageMetadata.totalTokenCount, prompt: usageMetadata.promptTokenCount };
652
- //Output/Response side
653
- tokenUsage.result = (usageMetadata.candidatesTokenCount ?? 0)
654
- + (usageMetadata.thoughtsTokenCount ?? 0)
655
- + (usageMetadata.toolUsePromptTokenCount ?? 0);
656
- if ((tokenUsage.total ?? 0) != (tokenUsage.prompt ?? 0) + tokenUsage.result) {
657
- console.warn("[VertexAI] Gemini token usage mismatch: total does not equal prompt + result", {
658
- total: tokenUsage.total,
659
- prompt: tokenUsage.prompt,
660
- result: tokenUsage.result
661
- });
662
- }
663
- if (!tokenUsage.result) {
664
- tokenUsage.result = undefined; // If no result, mark as undefined
665
- }
666
- return tokenUsage;
667
- }
668
- async requestTextCompletion(driver, prompt, options) {
669
- const splits = options.model.split("/");
670
- let region = undefined;
671
- if (splits[0] === "locations" && splits.length >= 2) {
672
- region = splits[1];
673
- }
674
- const modelName = splits[splits.length - 1];
675
- options = { ...options, model: modelName };
676
- let conversation = updateConversation(options.conversation, prompt.contents);
677
- prompt.contents = conversation;
678
- // TODO: Remove hack, use global endpoint manually if needed.
679
- if (options.model.includes("gemini-2.5-flash-image")) {
680
- region = "global"; // Gemini Flash Image only available in global region, this is for nano-banana model
681
- }
682
- const client = driver.getGoogleGenAIClient(region);
683
- const payload = getGeminiPayload(options, prompt);
684
- const response = await client.models.generateContent(payload);
685
- const token_usage = this.usageMetadataToTokenUsage(response.usageMetadata);
686
- let tool_use;
687
- let finish_reason, result;
688
- const candidate = response.candidates && response.candidates[0];
689
- if (candidate) {
690
- switch (candidate.finishReason) {
691
- case FinishReason.MAX_TOKENS:
692
- finish_reason = "length";
693
- break;
694
- case FinishReason.STOP:
695
- finish_reason = "stop";
696
- break;
697
- default: finish_reason = candidate.finishReason;
698
- }
699
- const content = candidate.content;
700
- if (candidate.finishReason && !supportedFinishReasons.includes(candidate.finishReason)) {
701
- throw new Error(`Unsupported finish reason: ${candidate.finishReason}, `
702
- + `finish message: ${candidate.finishMessage}, `
703
- + `content: ${JSON.stringify(content, null, 2)}, safety: ${JSON.stringify(candidate.safetyRatings, null, 2)}`);
704
- }
705
- if (content) {
706
- tool_use = collectToolUseParts(content);
707
- // We clean the content before validation, so we can update the conversation.
708
- const cleanedContent = cleanEmptyFieldsContent(content, options.result_schema);
709
- const textResults = collectTextParts(cleanedContent);
710
- const imageResults = collectInlineDataParts(cleanedContent);
711
- result = [...textResults, ...imageResults];
712
- conversation = updateConversation(conversation, [cleanedContent]);
713
- }
714
- }
715
- if (tool_use) {
716
- finish_reason = "tool_use";
717
- }
718
- // Increment turn counter for deferred stripping
719
- conversation = incrementConversationTurn(conversation);
720
- // Strip large base64 image data based on options.stripImagesAfterTurns
721
- const currentTurn = getConversationMeta(conversation).turnNumber;
722
- const stripOptions = {
723
- keepForTurns: options.stripImagesAfterTurns ?? Infinity,
724
- currentTurn,
725
- textMaxTokens: options.stripTextMaxTokens
726
- };
727
- let processedConversation = stripBase64ImagesFromConversation(conversation, stripOptions);
728
- // Truncate large text content if configured
729
- processedConversation = truncateLargeTextInConversation(processedConversation, stripOptions);
730
- return {
731
- result: result && result.length > 0 ? result : [{ type: "text", value: '' }],
732
- token_usage: token_usage,
733
- finish_reason: finish_reason,
734
- original_response: options.include_original_response ? response : undefined,
735
- conversation: processedConversation,
736
- tool_use
737
- };
738
- }
739
- async requestTextCompletionStream(driver, prompt, options) {
740
- const splits = options.model.split("/");
741
- let region = undefined;
742
- if (splits[0] === "locations" && splits.length >= 2) {
743
- region = splits[1];
744
- }
745
- const modelName = splits[splits.length - 1];
746
- options = { ...options, model: modelName };
747
- // Include conversation history in prompt contents (same as non-streaming)
748
- const conversation = updateConversation(options.conversation, prompt.contents);
749
- prompt.contents = conversation;
750
- if (options.model.includes("gemini-2.5-flash-image")) {
751
- region = "global"; // Gemini Flash Image only available in global region, this is for nano-banana model
752
- }
753
- const client = driver.getGoogleGenAIClient(region);
754
- const payload = getGeminiPayload(options, prompt);
755
- const response = await client.models.generateContentStream(payload);
756
- const stream = asyncMap(response, async (item) => {
757
- const token_usage = this.usageMetadataToTokenUsage(item.usageMetadata);
758
- if (item.candidates && item.candidates.length > 0) {
759
- for (const candidate of item.candidates) {
760
- let tool_use;
761
- let finish_reason;
762
- switch (candidate.finishReason) {
763
- case FinishReason.MAX_TOKENS:
764
- finish_reason = "length";
765
- break;
766
- case FinishReason.STOP:
767
- finish_reason = "stop";
768
- break;
769
- default: finish_reason = candidate.finishReason;
770
- }
771
- if (candidate.finishReason && !supportedFinishReasons.includes(candidate.finishReason)) {
772
- throw new Error(`Unsupported finish reason: ${candidate.finishReason}, `
773
- + `finish message: ${candidate.finishMessage}, `
774
- + `content: ${JSON.stringify(candidate.content, null, 2)}, safety: ${JSON.stringify(candidate.safetyRatings, null, 2)}`);
775
- }
776
- if (candidate.content?.role === 'model') {
777
- const textResults = collectTextParts(candidate.content);
778
- const imageResults = collectInlineDataParts(candidate.content);
779
- const combinedResults = [...textResults, ...imageResults];
780
- tool_use = collectToolUseParts(candidate.content);
781
- if (tool_use) {
782
- finish_reason = "tool_use";
783
- }
784
- return {
785
- result: combinedResults.length > 0 ? combinedResults : [],
786
- token_usage: token_usage,
787
- finish_reason: finish_reason,
788
- tool_use,
789
- };
790
- }
791
- }
792
- }
793
- //No normal output, returning block reason if it exists.
794
- return {
795
- result: item.promptFeedback?.blockReasonMessage ? [{ type: "text", value: item.promptFeedback.blockReasonMessage }] : [],
796
- finish_reason: item.promptFeedback?.blockReason ?? "",
797
- token_usage: token_usage,
798
- };
799
- });
800
- return stream;
801
- }
802
- }
803
- function getToolDefinitions(tools) {
804
- if (!tools || tools.length === 0) {
805
- return undefined;
806
- }
807
- // VertexAI Gemini only supports one tool at a time.
808
- // For multiple tools, we have multiple functions in one tool.
809
- return {
810
- functionDeclarations: tools.map(getToolFunction),
811
- };
812
- }
813
- function getToolFunction(tool) {
814
- // If input_schema is a string, parse it; if it's already an object, use it directly
815
- let toolSchema;
816
- // Using a try-catch for safety, as the input_schema might not be a valid JSONSchema
817
- try {
818
- toolSchema = parseJSONtoSchema(tool.input_schema, false);
819
- }
820
- catch (e) {
821
- toolSchema = { ...tool.input_schema, type: Type.OBJECT };
822
- }
823
- return {
824
- name: tool.name,
825
- description: tool.description,
826
- parameters: toolSchema,
827
- };
828
- }
829
- /**
830
- * Update the conversation messages
831
- * @param prompt
832
- * @param response
833
- * @returns
834
- */
835
- function updateConversation(conversation, prompt) {
836
- // Unwrap array if wrapped, otherwise treat as array
837
- const unwrapped = unwrapConversationArray(conversation);
838
- const convArray = unwrapped ?? (conversation || []);
839
- return convArray.concat(prompt);
840
- }
841
- /**
842
- *
843
- * 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.
844
- *
845
- * This is an excerpt from googleapis.github.io/python-genai:
846
- *
847
- * The function response in JSON object format.
848
- * Use “output” key to specify function output and “error” key to specify error details (if any).
849
- * If “output” and “error” keys are not specified, then whole “response” is treated as function output.
850
- * @see https://googleapis.github.io/python-genai/genai.html#genai.types.FunctionResponse
851
- */
852
- function formatFunctionResponse(response) {
853
- response = response.trim();
854
- if (response.startsWith("{") && response.endsWith("}")) {
855
- try {
856
- return JSON.parse(response);
857
- }
858
- catch (e) {
859
- return { output: response };
860
- }
861
- }
862
- else {
863
- return { output: response };
864
- }
865
- }
866
- //# sourceMappingURL=gemini.js.map