ak-gemini 1.0.5 → 1.0.7

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 (6) hide show
  1. package/README.md +4 -3
  2. package/index.cjs +316 -86
  3. package/index.js +458 -126
  4. package/package.json +23 -17
  5. package/types.d.ts +138 -0
  6. package/types.ts +0 -65
package/README.md CHANGED
@@ -7,7 +7,7 @@ Use this to power LLM-driven data pipelines, JSON mapping, or any automated AI t
7
7
 
8
8
  ## Features
9
9
 
10
- * **Model-Agnostic:** Use any Gemini model (`gemini-2.0-flash` by default)
10
+ * **Model-Agnostic:** Use any Gemini model (`gemini-2.5-flash` by default)
11
11
  * **Declarative Few-shot Examples:** Seed transformations using example mappings, with support for custom keys (`PROMPT`, `ANSWER`, `CONTEXT`, or your own)
12
12
  * **Automatic Validation & Repair:** Validate outputs with your own async function; auto-repair failed payloads with LLM feedback loop (exponential backoff, fully configurable)
13
13
  * **Token Counting & Safety:** Preview the *exact* Gemini token consumption for any operation—including all examples, instructions, and your input—before sending, so you can avoid window errors and manage costs.
@@ -47,7 +47,7 @@ or pass it directly in the constructor options.
47
47
  import AITransformer from 'ak-gemini';
48
48
 
49
49
  const transformer = new AITransformer({
50
- modelName: 'gemini-2.0-flash', // or your preferred Gemini model
50
+ modelName: 'gemini-2.5-flash', // or your preferred Gemini model
51
51
  sourceKey: 'INPUT', // Custom prompt key (default: 'PROMPT')
52
52
  targetKey: 'OUTPUT', // Custom answer key (default: 'ANSWER')
53
53
  contextKey: 'CONTEXT', // Optional, for per-example context
@@ -116,7 +116,7 @@ new AITransformer(options)
116
116
 
117
117
  | Option | Type | Default | Description |
118
118
  | ------------------ | ------ | ------------------ | ------------------------------------------------- |
119
- | modelName | string | 'gemini-2.0-flash' | Gemini model to use |
119
+ | modelName | string | 'gemini-2.5-flash' | Gemini model to use |
120
120
  | sourceKey | string | 'PROMPT' | Key for prompt/example input |
121
121
  | targetKey | string | 'ANSWER' | Key for expected output in examples |
122
122
  | contextKey | string | 'CONTEXT' | Key for per-example context (optional) |
@@ -125,6 +125,7 @@ new AITransformer(options)
125
125
  | responseSchema | object | null | Optional JSON schema for strict output validation |
126
126
  | maxRetries | number | 3 | Retries for validation+rebuild loop |
127
127
  | retryDelay | number | 1000 | Initial retry delay in ms (exponential backoff) |
128
+ | logLevel | string | 'info' | Log level: 'trace', 'debug', 'info', 'warn', 'error', 'fatal', or 'none' |
128
129
  | chatConfig | object | ... | Gemini chat config overrides |
129
130
  | systemInstructions | string | ... | System prompt for Gemini |
130
131
 
package/index.cjs CHANGED
@@ -29,7 +29,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
29
29
  // index.js
30
30
  var index_exports = {};
31
31
  __export(index_exports, {
32
- default: () => AITransformer,
32
+ ThinkingLevel: () => import_genai.ThinkingLevel,
33
+ default: () => index_default,
33
34
  log: () => logger_default
34
35
  });
35
36
  module.exports = __toCommonJS(index_exports);
@@ -56,10 +57,7 @@ var logger_default = logger;
56
57
  // index.js
57
58
  var import_meta = {};
58
59
  import_dotenv.default.config();
59
- var { NODE_ENV = "unknown", GEMINI_API_KEY } = process.env;
60
- if (NODE_ENV === "dev") logger_default.level = "debug";
61
- if (NODE_ENV === "test") logger_default.level = "warn";
62
- if (NODE_ENV.startsWith("prod")) logger_default.level = "error";
60
+ var { NODE_ENV = "unknown", GEMINI_API_KEY, LOG_LEVEL = "" } = process.env;
63
61
  var DEFAULT_SAFETY_SETTINGS = [
64
62
  { category: import_genai.HarmCategory.HARM_CATEGORY_HARASSMENT, threshold: import_genai.HarmBlockThreshold.BLOCK_NONE },
65
63
  { category: import_genai.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, threshold: import_genai.HarmBlockThreshold.BLOCK_NONE }
@@ -67,7 +65,7 @@ var DEFAULT_SAFETY_SETTINGS = [
67
65
  var DEFAULT_SYSTEM_INSTRUCTIONS = `
68
66
  You are an expert JSON transformation engine. Your task is to accurately convert data payloads from one format to another.
69
67
 
70
- You will be provided with example transformations (Source JSON -> Target JSON).
68
+ You will be provided with example transformations (Source JSON -> Target JSON).
71
69
 
72
70
  Learn the mapping rules from these examples.
73
71
 
@@ -77,6 +75,19 @@ Always respond ONLY with a valid JSON object that strictly adheres to the expect
77
75
 
78
76
  Do not include any additional text, explanations, or formatting before or after the JSON object.
79
77
  `;
78
+ var DEFAULT_THINKING_CONFIG = {
79
+ thinkingBudget: 0,
80
+ thinkingLevel: import_genai.ThinkingLevel.MINIMAL
81
+ };
82
+ var THINKING_SUPPORTED_MODELS = [
83
+ /^gemini-3-flash(-preview)?$/,
84
+ /^gemini-3-pro(-preview|-image-preview)?$/,
85
+ /^gemini-2\.5-pro/,
86
+ /^gemini-2\.5-flash(-preview)?$/,
87
+ /^gemini-2\.5-flash-lite(-preview)?$/,
88
+ /^gemini-2\.0-flash$/
89
+ // Experimental support, exact match only
90
+ ];
80
91
  var DEFAULT_CHAT_CONFIG = {
81
92
  responseMimeType: "application/json",
82
93
  temperature: 0.2,
@@ -95,53 +106,110 @@ var AITransformer = class {
95
106
  this.promptKey = "";
96
107
  this.answerKey = "";
97
108
  this.contextKey = "";
109
+ this.explanationKey = "";
110
+ this.systemInstructionKey = "";
98
111
  this.maxRetries = 3;
99
112
  this.retryDelay = 1e3;
100
113
  this.systemInstructions = "";
101
114
  this.chatConfig = {};
102
115
  this.apiKey = GEMINI_API_KEY;
116
+ this.onlyJSON = true;
117
+ this.asyncValidator = null;
118
+ this.logLevel = "info";
103
119
  AITransformFactory.call(this, options);
104
120
  this.init = initChat.bind(this);
105
121
  this.seed = seedWithExamples.bind(this);
106
- this.message = transformJSON.bind(this);
122
+ this.rawMessage = rawMessage.bind(this);
123
+ this.message = (payload, opts = {}, validatorFn = null) => {
124
+ return prepareAndValidateMessage.call(this, payload, opts, validatorFn || this.asyncValidator);
125
+ };
107
126
  this.rebuild = rebuildPayload.bind(this);
108
127
  this.reset = resetChat.bind(this);
109
128
  this.getHistory = getChatHistory.bind(this);
110
- this.transformWithValidation = transformWithValidation.bind(this);
129
+ this.messageAndValidate = prepareAndValidateMessage.bind(this);
130
+ this.transformWithValidation = prepareAndValidateMessage.bind(this);
111
131
  this.estimate = estimateTokenUsage.bind(this);
132
+ this.estimateTokenUsage = estimateTokenUsage.bind(this);
112
133
  }
113
134
  };
135
+ var index_default = AITransformer;
114
136
  function AITransformFactory(options = {}) {
115
- this.modelName = options.modelName || "gemini-2.0-flash";
137
+ this.modelName = options.modelName || "gemini-2.5-flash";
116
138
  this.systemInstructions = options.systemInstructions || DEFAULT_SYSTEM_INSTRUCTIONS;
117
- this.apiKey = options.apiKey || GEMINI_API_KEY;
139
+ if (options.logLevel) {
140
+ this.logLevel = options.logLevel;
141
+ if (this.logLevel === "none") {
142
+ logger_default.level = "silent";
143
+ } else {
144
+ logger_default.level = this.logLevel;
145
+ }
146
+ } else if (LOG_LEVEL) {
147
+ this.logLevel = LOG_LEVEL;
148
+ logger_default.level = LOG_LEVEL;
149
+ } else if (NODE_ENV === "dev") {
150
+ this.logLevel = "debug";
151
+ logger_default.level = "debug";
152
+ } else if (NODE_ENV === "test") {
153
+ this.logLevel = "warn";
154
+ logger_default.level = "warn";
155
+ } else if (NODE_ENV.startsWith("prod")) {
156
+ this.logLevel = "error";
157
+ logger_default.level = "error";
158
+ } else {
159
+ this.logLevel = "info";
160
+ logger_default.level = "info";
161
+ }
162
+ this.apiKey = options.apiKey !== void 0 && options.apiKey !== null ? options.apiKey : GEMINI_API_KEY;
118
163
  if (!this.apiKey) throw new Error("Missing Gemini API key. Provide via options.apiKey or GEMINI_API_KEY env var.");
119
164
  this.chatConfig = {
120
165
  ...DEFAULT_CHAT_CONFIG,
121
166
  ...options.chatConfig,
122
167
  systemInstruction: this.systemInstructions
123
168
  };
169
+ const modelSupportsThinking = THINKING_SUPPORTED_MODELS.some(
170
+ (pattern) => pattern.test(this.modelName)
171
+ );
172
+ if (modelSupportsThinking && options.thinkingConfig) {
173
+ const thinkingConfig = {
174
+ ...DEFAULT_THINKING_CONFIG,
175
+ ...options.thinkingConfig
176
+ };
177
+ this.chatConfig.thinkingConfig = thinkingConfig;
178
+ if (logger_default.level !== "silent") {
179
+ logger_default.debug(`Model ${this.modelName} supports thinking. Applied thinkingConfig:`, thinkingConfig);
180
+ }
181
+ } else if (options.thinkingConfig && !modelSupportsThinking) {
182
+ if (logger_default.level !== "silent") {
183
+ logger_default.warn(`Model ${this.modelName} does not support thinking features. Ignoring thinkingConfig.`);
184
+ }
185
+ }
124
186
  if (options.responseSchema) {
125
187
  this.chatConfig.responseSchema = options.responseSchema;
126
188
  }
127
189
  this.examplesFile = options.examplesFile || null;
128
190
  this.exampleData = options.exampleData || null;
129
- this.promptKey = options.sourceKey || "PROMPT";
130
- this.answerKey = options.targetKey || "ANSWER";
191
+ this.promptKey = options.promptKey || options.sourceKey || "PROMPT";
192
+ this.answerKey = options.answerKey || options.targetKey || "ANSWER";
131
193
  this.contextKey = options.contextKey || "CONTEXT";
194
+ this.explanationKey = options.explanationKey || "EXPLANATION";
195
+ this.systemInstructionsKey = options.systemInstructionsKey || "SYSTEM";
132
196
  this.maxRetries = options.maxRetries || 3;
133
197
  this.retryDelay = options.retryDelay || 1e3;
198
+ this.asyncValidator = options.asyncValidator || null;
199
+ this.onlyJSON = options.onlyJSON !== void 0 ? options.onlyJSON : true;
134
200
  if (this.promptKey === this.answerKey) {
135
201
  throw new Error("Source and target keys cannot be the same. Please provide distinct keys.");
136
202
  }
137
- logger_default.debug(`Creating AI Transformer with model: ${this.modelName}`);
138
- logger_default.debug(`Using keys - Source: "${this.promptKey}", Target: "${this.answerKey}", Context: "${this.contextKey}"`);
203
+ if (logger_default.level !== "silent") {
204
+ logger_default.debug(`Creating AI Transformer with model: ${this.modelName}`);
205
+ logger_default.debug(`Using keys - Source: "${this.promptKey}", Target: "${this.answerKey}", Context: "${this.contextKey}"`);
206
+ }
139
207
  const ai = new import_genai.GoogleGenAI({ apiKey: this.apiKey });
140
208
  this.genAIClient = ai;
141
209
  this.chat = null;
142
210
  }
143
- async function initChat() {
144
- if (this.chat) return;
211
+ async function initChat(force = false) {
212
+ if (this.chat && !force) return;
145
213
  logger_default.debug(`Initializing Gemini chat session with model: ${this.modelName}...`);
146
214
  this.chat = await this.genAIClient.chats.create({
147
215
  model: this.modelName,
@@ -149,6 +217,12 @@ async function initChat() {
149
217
  config: this.chatConfig,
150
218
  history: []
151
219
  });
220
+ try {
221
+ await this.genAIClient.models.list();
222
+ logger_default.debug("Gemini API connection successful.");
223
+ } catch (e) {
224
+ throw new Error(`Gemini chat initialization failed: ${e.message}`);
225
+ }
152
226
  logger_default.debug("Gemini chat session initialized.");
153
227
  }
154
228
  async function seedWithExamples(examples) {
@@ -156,117 +230,130 @@ async function seedWithExamples(examples) {
156
230
  if (!examples || !Array.isArray(examples) || examples.length === 0) {
157
231
  if (this.examplesFile) {
158
232
  logger_default.debug(`No examples provided, loading from file: ${this.examplesFile}`);
159
- examples = await import_ak_tools.default.load(import_path.default.resolve(this.examplesFile), true);
233
+ try {
234
+ examples = await import_ak_tools.default.load(import_path.default.resolve(this.examplesFile), true);
235
+ } catch (err) {
236
+ throw new Error(`Could not load examples from file: ${this.examplesFile}. Please check the file path and format.`);
237
+ }
238
+ } else if (this.exampleData) {
239
+ logger_default.debug(`Using example data provided in options.`);
240
+ if (Array.isArray(this.exampleData)) {
241
+ examples = this.exampleData;
242
+ } else {
243
+ throw new Error(`Invalid example data provided. Expected an array of examples.`);
244
+ }
160
245
  } else {
161
246
  logger_default.debug("No examples provided and no examples file specified. Skipping seeding.");
162
247
  return;
163
248
  }
164
249
  }
250
+ const instructionExample = examples.find((ex) => ex[this.systemInstructionsKey]);
251
+ if (instructionExample) {
252
+ logger_default.debug(`Found system instructions in examples; reinitializing chat with new instructions.`);
253
+ this.systemInstructions = instructionExample[this.systemInstructionsKey];
254
+ this.chatConfig.systemInstruction = this.systemInstructions;
255
+ await this.init(true);
256
+ }
165
257
  logger_default.debug(`Seeding chat with ${examples.length} transformation examples...`);
166
258
  const historyToAdd = [];
167
259
  for (const example of examples) {
168
260
  const contextValue = example[this.contextKey] || "";
169
261
  const promptValue = example[this.promptKey] || "";
170
262
  const answerValue = example[this.answerKey] || "";
263
+ const explanationValue = example[this.explanationKey] || "";
264
+ let userText = "";
265
+ let modelResponse = {};
171
266
  if (contextValue) {
172
- let contextText = import_ak_tools.default.isJSON(contextValue) ? JSON.stringify(contextValue, null, 2) : contextValue;
173
- historyToAdd.push({
174
- role: "user",
175
- parts: [{ text: `Context: ${contextText}` }]
176
- });
177
- historyToAdd.push({
178
- role: "model",
179
- parts: [{ text: "I understand the context." }]
180
- });
267
+ let contextText = isJSON(contextValue) ? JSON.stringify(contextValue, null, 2) : contextValue;
268
+ userText += `CONTEXT:
269
+ ${contextText}
270
+
271
+ `;
181
272
  }
182
273
  if (promptValue) {
183
- let promptText = import_ak_tools.default.isJSON(promptValue) ? JSON.stringify(promptValue, null, 2) : promptValue;
184
- historyToAdd.push({ role: "user", parts: [{ text: promptText }] });
274
+ let promptText = isJSON(promptValue) ? JSON.stringify(promptValue, null, 2) : promptValue;
275
+ userText += promptText;
185
276
  }
186
- if (answerValue) {
187
- let answerText = import_ak_tools.default.isJSON(answerValue) ? JSON.stringify(answerValue, null, 2) : answerValue;
188
- historyToAdd.push({ role: "model", parts: [{ text: answerText }] });
277
+ if (answerValue) modelResponse.data = answerValue;
278
+ if (explanationValue) modelResponse.explanation = explanationValue;
279
+ const modelText = JSON.stringify(modelResponse, null, 2);
280
+ if (userText.trim().length && modelText.trim().length > 0) {
281
+ historyToAdd.push({ role: "user", parts: [{ text: userText.trim() }] });
282
+ historyToAdd.push({ role: "model", parts: [{ text: modelText.trim() }] });
189
283
  }
190
284
  }
191
285
  const currentHistory = this?.chat?.getHistory() || [];
286
+ logger_default.debug(`Adding ${historyToAdd.length} examples to chat history (${currentHistory.length} current examples)...`);
192
287
  this.chat = await this.genAIClient.chats.create({
193
288
  model: this.modelName,
194
289
  // @ts-ignore
195
290
  config: this.chatConfig,
196
291
  history: [...currentHistory, ...historyToAdd]
197
292
  });
198
- logger_default.debug("Transformation examples seeded successfully.");
293
+ const newHistory = this.chat.getHistory();
294
+ logger_default.debug(`Created new chat session with ${newHistory.length} examples.`);
295
+ return newHistory;
199
296
  }
200
- async function transformJSON(sourcePayload) {
297
+ async function rawMessage(sourcePayload) {
201
298
  if (!this.chat) {
202
- throw new Error("Chat session not initialized. Call initChat() or seedWithExamples() first.");
299
+ throw new Error("Chat session not initialized.");
203
300
  }
204
- let result;
205
- let actualPayload;
206
- if (sourcePayload && import_ak_tools.default.isJSON(sourcePayload)) actualPayload = JSON.stringify(sourcePayload, null, 2);
207
- else if (typeof sourcePayload === "string") actualPayload = sourcePayload;
208
- else throw new Error("Invalid source payload. Must be a JSON object or a valid JSON string.");
301
+ const actualPayload = typeof sourcePayload === "string" ? sourcePayload : JSON.stringify(sourcePayload, null, 2);
209
302
  try {
210
- result = await this.chat.sendMessage({ message: actualPayload });
303
+ const result = await this.chat.sendMessage({ message: actualPayload });
304
+ const modelResponse = result.text;
305
+ const extractedJSON = extractJSON(modelResponse);
306
+ if (extractedJSON?.data) {
307
+ return extractedJSON.data;
308
+ }
309
+ return extractedJSON;
211
310
  } catch (error) {
212
- logger_default.error("Error with Gemini API:", error);
311
+ if (this.onlyJSON && error.message.includes("Could not extract valid JSON")) {
312
+ throw new Error(`Invalid JSON response from Gemini: ${error.message}`);
313
+ }
213
314
  throw new Error(`Transformation failed: ${error.message}`);
214
315
  }
215
- try {
216
- const modelResponse = result.text;
217
- const parsedResponse = JSON.parse(modelResponse);
218
- return parsedResponse;
219
- } catch (parseError) {
220
- logger_default.error("Error parsing Gemini response:", parseError);
221
- throw new Error(`Invalid JSON response from Gemini: ${parseError.message}`);
222
- }
223
316
  }
224
- async function transformWithValidation(sourcePayload, validatorFn, options = {}) {
317
+ async function prepareAndValidateMessage(sourcePayload, options = {}, validatorFn = null) {
318
+ if (!this.chat) {
319
+ throw new Error("Chat session not initialized. Please call init() first.");
320
+ }
225
321
  const maxRetries = options.maxRetries ?? this.maxRetries;
226
322
  const retryDelay = options.retryDelay ?? this.retryDelay;
227
- let lastPayload = null;
228
323
  let lastError = null;
324
+ let lastPayload = null;
325
+ if (sourcePayload && isJSON(sourcePayload)) {
326
+ lastPayload = JSON.stringify(sourcePayload, null, 2);
327
+ } else if (typeof sourcePayload === "string") {
328
+ lastPayload = sourcePayload;
329
+ } else if (typeof sourcePayload === "boolean" || typeof sourcePayload === "number") {
330
+ lastPayload = sourcePayload.toString();
331
+ } else if (sourcePayload === null || sourcePayload === void 0) {
332
+ lastPayload = JSON.stringify({});
333
+ } else {
334
+ throw new Error("Invalid source payload. Must be a JSON object or string.");
335
+ }
229
336
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
230
337
  try {
231
- const transformedPayload = attempt === 0 ? await this.message(sourcePayload) : await this.rebuild(lastPayload, lastError.message);
232
- const validatedPayload = await validatorFn(transformedPayload);
233
- logger_default.debug(`Transformation and validation succeeded on attempt ${attempt + 1}`);
234
- return validatedPayload;
338
+ const transformedPayload = attempt === 0 ? await this.rawMessage(lastPayload) : await this.rebuild(lastPayload, lastError.message);
339
+ lastPayload = transformedPayload;
340
+ if (validatorFn) {
341
+ await validatorFn(transformedPayload);
342
+ }
343
+ logger_default.debug(`Transformation succeeded on attempt ${attempt + 1}`);
344
+ return transformedPayload;
235
345
  } catch (error) {
236
346
  lastError = error;
237
- if (attempt === 0) {
238
- lastPayload = await this.message(sourcePayload).catch(() => null);
239
- }
240
- if (attempt < maxRetries) {
241
- const delay = retryDelay * Math.pow(2, attempt);
242
- logger_default.warn(`Attempt ${attempt + 1} failed, retrying in ${delay}ms...`, error.message);
243
- await new Promise((res) => setTimeout(res, delay));
244
- } else {
245
- logger_default.error(`All ${maxRetries + 1} attempts failed`);
246
- throw new Error(`Transformation with validation failed after ${maxRetries + 1} attempts. Last error: ${error.message}`);
347
+ logger_default.warn(`Attempt ${attempt + 1} failed: ${error.message}`);
348
+ if (attempt >= maxRetries) {
349
+ logger_default.error(`All ${maxRetries + 1} attempts failed.`);
350
+ throw new Error(`Transformation failed after ${maxRetries + 1} attempts. Last error: ${error.message}`);
247
351
  }
352
+ const delay = retryDelay * Math.pow(2, attempt);
353
+ await new Promise((res) => setTimeout(res, delay));
248
354
  }
249
355
  }
250
356
  }
251
- async function estimateTokenUsage(nextPayload) {
252
- const contents = [];
253
- if (this.systemInstructions) {
254
- contents.push({ parts: [{ text: this.systemInstructions }] });
255
- }
256
- if (this.chat && typeof this.chat.getHistory === "function") {
257
- const history = this.chat.getHistory();
258
- if (Array.isArray(history) && history.length > 0) {
259
- contents.push(...history);
260
- }
261
- }
262
- const nextMessage = typeof nextPayload === "string" ? nextPayload : JSON.stringify(nextPayload, null, 2);
263
- contents.push({ parts: [{ text: nextMessage }] });
264
- const resp = await this.genAIClient.models.countTokens({
265
- model: this.modelName,
266
- contents
267
- });
268
- return resp;
269
- }
270
357
  async function rebuildPayload(lastPayload, serverError) {
271
358
  await this.init();
272
359
  const prompt = `
@@ -276,6 +363,7 @@ The server's error message is quoted afterward.
276
363
  ---------------- BAD PAYLOAD ----------------
277
364
  ${JSON.stringify(lastPayload, null, 2)}
278
365
 
366
+
279
367
  ---------------- SERVER ERROR ----------------
280
368
  ${serverError}
281
369
 
@@ -295,6 +383,25 @@ Respond with JSON only \u2013 no comments or explanations.
295
383
  throw new Error(`Gemini returned non-JSON while repairing payload: ${parseErr.message}`);
296
384
  }
297
385
  }
386
+ async function estimateTokenUsage(nextPayload) {
387
+ const contents = [];
388
+ if (this.systemInstructions) {
389
+ contents.push({ parts: [{ text: this.systemInstructions }] });
390
+ }
391
+ if (this.chat && typeof this.chat.getHistory === "function") {
392
+ const history = this.chat.getHistory();
393
+ if (Array.isArray(history) && history.length > 0) {
394
+ contents.push(...history);
395
+ }
396
+ }
397
+ const nextMessage = typeof nextPayload === "string" ? nextPayload : JSON.stringify(nextPayload, null, 2);
398
+ contents.push({ parts: [{ text: nextMessage }] });
399
+ const resp = await this.genAIClient.models.countTokens({
400
+ model: this.modelName,
401
+ contents
402
+ });
403
+ return resp;
404
+ }
298
405
  async function resetChat() {
299
406
  if (this.chat) {
300
407
  logger_default.debug("Resetting Gemini chat session...");
@@ -316,13 +423,135 @@ function getChatHistory() {
316
423
  }
317
424
  return this.chat.getHistory();
318
425
  }
426
+ function isJSON(data) {
427
+ try {
428
+ const attempt = JSON.stringify(data);
429
+ if (attempt?.startsWith("{") || attempt?.startsWith("[")) {
430
+ if (attempt?.endsWith("}") || attempt?.endsWith("]")) {
431
+ return true;
432
+ }
433
+ }
434
+ return false;
435
+ } catch (e) {
436
+ return false;
437
+ }
438
+ }
439
+ function isJSONStr(string) {
440
+ if (typeof string !== "string") return false;
441
+ try {
442
+ const result = JSON.parse(string);
443
+ const type = Object.prototype.toString.call(result);
444
+ return type === "[object Object]" || type === "[object Array]";
445
+ } catch (err) {
446
+ return false;
447
+ }
448
+ }
449
+ function extractJSON(text) {
450
+ if (!text || typeof text !== "string") {
451
+ throw new Error("No text provided for JSON extraction");
452
+ }
453
+ if (isJSONStr(text.trim())) {
454
+ return JSON.parse(text.trim());
455
+ }
456
+ const codeBlockPatterns = [
457
+ /```json\s*\n?([\s\S]*?)\n?\s*```/gi,
458
+ /```\s*\n?([\s\S]*?)\n?\s*```/gi
459
+ ];
460
+ for (const pattern of codeBlockPatterns) {
461
+ const matches = text.match(pattern);
462
+ if (matches) {
463
+ for (const match of matches) {
464
+ const jsonContent = match.replace(/```json\s*\n?/gi, "").replace(/```\s*\n?/gi, "").trim();
465
+ if (isJSONStr(jsonContent)) {
466
+ return JSON.parse(jsonContent);
467
+ }
468
+ }
469
+ }
470
+ }
471
+ const jsonPatterns = [
472
+ // Match complete JSON objects
473
+ /\{[\s\S]*\}/g,
474
+ // Match complete JSON arrays
475
+ /\[[\s\S]*\]/g
476
+ ];
477
+ for (const pattern of jsonPatterns) {
478
+ const matches = text.match(pattern);
479
+ if (matches) {
480
+ for (const match of matches) {
481
+ const candidate = match.trim();
482
+ if (isJSONStr(candidate)) {
483
+ return JSON.parse(candidate);
484
+ }
485
+ }
486
+ }
487
+ }
488
+ const advancedExtract = findCompleteJSONStructures(text);
489
+ if (advancedExtract.length > 0) {
490
+ for (const candidate of advancedExtract) {
491
+ if (isJSONStr(candidate)) {
492
+ return JSON.parse(candidate);
493
+ }
494
+ }
495
+ }
496
+ const cleanedText = text.replace(/^\s*Sure,?\s*here\s+is\s+your?\s+.*?[:\n]/gi, "").replace(/^\s*Here\s+is\s+the\s+.*?[:\n]/gi, "").replace(/^\s*The\s+.*?is\s*[:\n]/gi, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "").trim();
497
+ if (isJSONStr(cleanedText)) {
498
+ return JSON.parse(cleanedText);
499
+ }
500
+ throw new Error(`Could not extract valid JSON from model response. Response preview: ${text.substring(0, 200)}...`);
501
+ }
502
+ function findCompleteJSONStructures(text) {
503
+ const results = [];
504
+ const startChars = ["{", "["];
505
+ for (let i = 0; i < text.length; i++) {
506
+ if (startChars.includes(text[i])) {
507
+ const extracted = extractCompleteStructure(text, i);
508
+ if (extracted) {
509
+ results.push(extracted);
510
+ }
511
+ }
512
+ }
513
+ return results;
514
+ }
515
+ function extractCompleteStructure(text, startPos) {
516
+ const startChar = text[startPos];
517
+ const endChar = startChar === "{" ? "}" : "]";
518
+ let depth = 0;
519
+ let inString = false;
520
+ let escaped = false;
521
+ for (let i = startPos; i < text.length; i++) {
522
+ const char = text[i];
523
+ if (escaped) {
524
+ escaped = false;
525
+ continue;
526
+ }
527
+ if (char === "\\" && inString) {
528
+ escaped = true;
529
+ continue;
530
+ }
531
+ if (char === '"' && !escaped) {
532
+ inString = !inString;
533
+ continue;
534
+ }
535
+ if (!inString) {
536
+ if (char === startChar) {
537
+ depth++;
538
+ } else if (char === endChar) {
539
+ depth--;
540
+ if (depth === 0) {
541
+ return text.substring(startPos, i + 1);
542
+ }
543
+ }
544
+ }
545
+ }
546
+ return null;
547
+ }
319
548
  if (import_meta.url === new URL(`file://${process.argv[1]}`).href) {
320
549
  logger_default.info("RUNNING AI Transformer as standalone script...");
321
550
  (async () => {
322
551
  try {
323
552
  logger_default.info("Initializing AI Transformer...");
324
553
  const transformer = new AITransformer({
325
- modelName: "gemini-2.0-flash",
554
+ modelName: "gemini-2.5-flash",
326
555
  sourceKey: "INPUT",
327
556
  // Custom source key
328
557
  targetKey: "OUTPUT",
@@ -360,7 +589,7 @@ if (import_meta.url === new URL(`file://${process.argv[1]}`).href) {
360
589
  }
361
590
  return payload;
362
591
  };
363
- const validatedResponse = await transformer.transformWithValidation(
592
+ const validatedResponse = await transformer.messageAndValidate(
364
593
  { "name": "Lynn" },
365
594
  mockValidator
366
595
  );
@@ -374,5 +603,6 @@ if (import_meta.url === new URL(`file://${process.argv[1]}`).href) {
374
603
  }
375
604
  // Annotate the CommonJS export names for ESM import in node:
376
605
  0 && (module.exports = {
606
+ ThinkingLevel,
377
607
  log
378
608
  });