ak-gemini 1.0.51 → 1.0.53

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 (4) hide show
  1. package/index.cjs +230 -79
  2. package/index.js +345 -114
  3. package/package.json +3 -3
  4. package/types.ts +0 -68
package/index.cjs CHANGED
@@ -29,8 +29,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
29
29
  // index.js
30
30
  var index_exports = {};
31
31
  __export(index_exports, {
32
- AITransformer: () => AITransformer,
33
- default: () => AITransformer,
32
+ default: () => index_default,
34
33
  log: () => logger_default
35
34
  });
36
35
  module.exports = __toCommonJS(index_exports);
@@ -76,7 +75,7 @@ When presented with new Source JSON, apply the learned transformation rules to p
76
75
 
77
76
  Always respond ONLY with a valid JSON object that strictly adheres to the expected output format.
78
77
 
79
- Do not include any additional text, explanations, or formatting before or after the JSON object.
78
+ Do not include any additional text, explanations, or formatting before or after the JSON object.
80
79
  `;
81
80
  var DEFAULT_CHAT_CONFIG = {
82
81
  responseMimeType: "application/json",
@@ -96,26 +95,34 @@ var AITransformer = class {
96
95
  this.promptKey = "";
97
96
  this.answerKey = "";
98
97
  this.contextKey = "";
98
+ this.explanationKey = "";
99
+ this.systemInstructionKey = "";
99
100
  this.maxRetries = 3;
100
101
  this.retryDelay = 1e3;
101
102
  this.systemInstructions = "";
102
103
  this.chatConfig = {};
103
104
  this.apiKey = GEMINI_API_KEY;
105
+ this.onlyJSON = true;
106
+ this.asyncValidator = null;
104
107
  AITransformFactory.call(this, options);
105
108
  this.init = initChat.bind(this);
106
109
  this.seed = seedWithExamples.bind(this);
107
- this.message = transformJSON.bind(this);
110
+ this.rawMessage = rawMessage.bind(this);
111
+ this.message = (payload, opts = {}, validatorFn = null) => {
112
+ return prepareAndValidateMessage.call(this, payload, opts, validatorFn || this.asyncValidator);
113
+ };
108
114
  this.rebuild = rebuildPayload.bind(this);
109
115
  this.reset = resetChat.bind(this);
110
116
  this.getHistory = getChatHistory.bind(this);
111
- this.transformWithValidation = transformWithValidation.bind(this);
117
+ this.messageAndValidate = prepareAndValidateMessage.bind(this);
112
118
  this.estimate = estimateTokenUsage.bind(this);
113
119
  }
114
120
  };
121
+ var index_default = AITransformer;
115
122
  function AITransformFactory(options = {}) {
116
123
  this.modelName = options.modelName || "gemini-2.0-flash";
117
124
  this.systemInstructions = options.systemInstructions || DEFAULT_SYSTEM_INSTRUCTIONS;
118
- this.apiKey = options.apiKey || GEMINI_API_KEY;
125
+ this.apiKey = options.apiKey !== void 0 && options.apiKey !== null ? options.apiKey : GEMINI_API_KEY;
119
126
  if (!this.apiKey) throw new Error("Missing Gemini API key. Provide via options.apiKey or GEMINI_API_KEY env var.");
120
127
  this.chatConfig = {
121
128
  ...DEFAULT_CHAT_CONFIG,
@@ -127,11 +134,15 @@ function AITransformFactory(options = {}) {
127
134
  }
128
135
  this.examplesFile = options.examplesFile || null;
129
136
  this.exampleData = options.exampleData || null;
130
- this.promptKey = options.sourceKey || "PROMPT";
131
- this.answerKey = options.targetKey || "ANSWER";
137
+ this.promptKey = options.promptKey || "PROMPT";
138
+ this.answerKey = options.answerKey || "ANSWER";
132
139
  this.contextKey = options.contextKey || "CONTEXT";
140
+ this.explanationKey = options.explanationKey || "EXPLANATION";
141
+ this.systemInstructionsKey = options.systemInstructionsKey || "SYSTEM";
133
142
  this.maxRetries = options.maxRetries || 3;
134
143
  this.retryDelay = options.retryDelay || 1e3;
144
+ this.asyncValidator = options.asyncValidator || null;
145
+ this.onlyJSON = options.onlyJSON !== void 0 ? options.onlyJSON : true;
135
146
  if (this.promptKey === this.answerKey) {
136
147
  throw new Error("Source and target keys cannot be the same. Please provide distinct keys.");
137
148
  }
@@ -141,8 +152,8 @@ function AITransformFactory(options = {}) {
141
152
  this.genAIClient = ai;
142
153
  this.chat = null;
143
154
  }
144
- async function initChat() {
145
- if (this.chat) return;
155
+ async function initChat(force = false) {
156
+ if (this.chat && !force) return;
146
157
  logger_default.debug(`Initializing Gemini chat session with model: ${this.modelName}...`);
147
158
  this.chat = await this.genAIClient.chats.create({
148
159
  model: this.modelName,
@@ -157,36 +168,48 @@ async function seedWithExamples(examples) {
157
168
  if (!examples || !Array.isArray(examples) || examples.length === 0) {
158
169
  if (this.examplesFile) {
159
170
  logger_default.debug(`No examples provided, loading from file: ${this.examplesFile}`);
160
- examples = await import_ak_tools.default.load(import_path.default.resolve(this.examplesFile), true);
171
+ try {
172
+ examples = await import_ak_tools.default.load(import_path.default.resolve(this.examplesFile), true);
173
+ } catch (err) {
174
+ throw new Error(`Could not load examples from file: ${this.examplesFile}. Please check the file path and format.`);
175
+ }
161
176
  } else {
162
177
  logger_default.debug("No examples provided and no examples file specified. Skipping seeding.");
163
178
  return;
164
179
  }
165
180
  }
181
+ if (examples?.slice().pop()[this.systemInstructionsKey]) {
182
+ logger_default.debug(`Found system instructions in examples; reinitializing chat with new instructions.`);
183
+ this.systemInstructions = examples.slice().pop()[this.systemInstructionsKey];
184
+ this.chatConfig.systemInstruction = this.systemInstructions;
185
+ await this.init(true);
186
+ }
166
187
  logger_default.debug(`Seeding chat with ${examples.length} transformation examples...`);
167
188
  const historyToAdd = [];
168
189
  for (const example of examples) {
169
190
  const contextValue = example[this.contextKey] || "";
170
191
  const promptValue = example[this.promptKey] || "";
171
192
  const answerValue = example[this.answerKey] || "";
193
+ const explanationValue = example[this.explanationKey] || "";
194
+ let userText = "";
195
+ let modelResponse = {};
172
196
  if (contextValue) {
173
- let contextText = import_ak_tools.default.isJSON(contextValue) ? JSON.stringify(contextValue, null, 2) : contextValue;
174
- historyToAdd.push({
175
- role: "user",
176
- parts: [{ text: `Context: ${contextText}` }]
177
- });
178
- historyToAdd.push({
179
- role: "model",
180
- parts: [{ text: "I understand the context." }]
181
- });
197
+ let contextText = isJSON(contextValue) ? JSON.stringify(contextValue, null, 2) : contextValue;
198
+ userText += `CONTEXT:
199
+ ${contextText}
200
+
201
+ `;
182
202
  }
183
203
  if (promptValue) {
184
- let promptText = import_ak_tools.default.isJSON(promptValue) ? JSON.stringify(promptValue, null, 2) : promptValue;
185
- historyToAdd.push({ role: "user", parts: [{ text: promptText }] });
204
+ let promptText = isJSON(promptValue) ? JSON.stringify(promptValue, null, 2) : promptValue;
205
+ userText += promptText;
186
206
  }
187
- if (answerValue) {
188
- let answerText = import_ak_tools.default.isJSON(answerValue) ? JSON.stringify(answerValue, null, 2) : answerValue;
189
- historyToAdd.push({ role: "model", parts: [{ text: answerText }] });
207
+ if (answerValue) modelResponse.data = answerValue;
208
+ if (explanationValue) modelResponse.explanation = explanationValue;
209
+ const modelText = JSON.stringify(modelResponse, null, 2);
210
+ if (userText.trim().length && modelText.trim().length > 0) {
211
+ historyToAdd.push({ role: "user", parts: [{ text: userText.trim() }] });
212
+ historyToAdd.push({ role: "model", parts: [{ text: modelText.trim() }] });
190
213
  }
191
214
  }
192
215
  const currentHistory = this?.chat?.getHistory() || [];
@@ -197,77 +220,64 @@ async function seedWithExamples(examples) {
197
220
  history: [...currentHistory, ...historyToAdd]
198
221
  });
199
222
  logger_default.debug("Transformation examples seeded successfully.");
223
+ return this.chat.getHistory();
200
224
  }
201
- async function transformJSON(sourcePayload) {
225
+ async function rawMessage(sourcePayload) {
202
226
  if (!this.chat) {
203
- throw new Error("Chat session not initialized. Call initChat() or seedWithExamples() first.");
227
+ throw new Error("Chat session not initialized.");
204
228
  }
205
- let result;
206
- let actualPayload;
207
- if (sourcePayload && import_ak_tools.default.isJSON(sourcePayload)) actualPayload = JSON.stringify(sourcePayload, null, 2);
208
- else if (typeof sourcePayload === "string") actualPayload = sourcePayload;
209
- else throw new Error("Invalid source payload. Must be a JSON object or a valid JSON string.");
229
+ const actualPayload = typeof sourcePayload === "string" ? sourcePayload : JSON.stringify(sourcePayload, null, 2);
210
230
  try {
211
- result = await this.chat.sendMessage({ message: actualPayload });
231
+ const result = await this.chat.sendMessage({ message: actualPayload });
232
+ const modelResponse = result.text;
233
+ const extractedJSON = extractJSON(modelResponse);
234
+ if (extractedJSON?.data) {
235
+ return extractedJSON.data;
236
+ }
237
+ return extractedJSON;
212
238
  } catch (error) {
213
- logger_default.error("Error with Gemini API:", error);
239
+ if (this.onlyJSON && error.message.includes("Could not extract valid JSON")) {
240
+ throw new Error(`Invalid JSON response from Gemini: ${error.message}`);
241
+ }
214
242
  throw new Error(`Transformation failed: ${error.message}`);
215
243
  }
216
- try {
217
- const modelResponse = result.text;
218
- const parsedResponse = JSON.parse(modelResponse);
219
- return parsedResponse;
220
- } catch (parseError) {
221
- logger_default.error("Error parsing Gemini response:", parseError);
222
- throw new Error(`Invalid JSON response from Gemini: ${parseError.message}`);
223
- }
224
244
  }
225
- async function transformWithValidation(sourcePayload, validatorFn, options = {}) {
245
+ async function prepareAndValidateMessage(sourcePayload, options = {}, validatorFn = null) {
246
+ if (!this.chat) {
247
+ throw new Error("Chat session not initialized. Please call init() first.");
248
+ }
226
249
  const maxRetries = options.maxRetries ?? this.maxRetries;
227
250
  const retryDelay = options.retryDelay ?? this.retryDelay;
228
- let lastPayload = null;
229
251
  let lastError = null;
252
+ let lastPayload = null;
253
+ if (sourcePayload && isJSON(sourcePayload)) {
254
+ lastPayload = JSON.stringify(sourcePayload, null, 2);
255
+ } else if (typeof sourcePayload === "string") {
256
+ lastPayload = sourcePayload;
257
+ } else {
258
+ throw new Error("Invalid source payload. Must be a JSON object or string.");
259
+ }
230
260
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
231
261
  try {
232
- const transformedPayload = attempt === 0 ? await this.message(sourcePayload) : await this.rebuild(lastPayload, lastError.message);
233
- const validatedPayload = await validatorFn(transformedPayload);
234
- logger_default.debug(`Transformation and validation succeeded on attempt ${attempt + 1}`);
235
- return validatedPayload;
262
+ const transformedPayload = attempt === 0 ? await this.rawMessage(lastPayload) : await this.rebuild(lastPayload, lastError.message);
263
+ lastPayload = transformedPayload;
264
+ if (validatorFn) {
265
+ await validatorFn(transformedPayload);
266
+ }
267
+ logger_default.debug(`Transformation succeeded on attempt ${attempt + 1}`);
268
+ return transformedPayload;
236
269
  } catch (error) {
237
270
  lastError = error;
238
- if (attempt === 0) {
239
- lastPayload = await this.message(sourcePayload).catch(() => null);
240
- }
241
- if (attempt < maxRetries) {
242
- const delay = retryDelay * Math.pow(2, attempt);
243
- logger_default.warn(`Attempt ${attempt + 1} failed, retrying in ${delay}ms...`, error.message);
244
- await new Promise((res) => setTimeout(res, delay));
245
- } else {
246
- logger_default.error(`All ${maxRetries + 1} attempts failed`);
247
- throw new Error(`Transformation with validation failed after ${maxRetries + 1} attempts. Last error: ${error.message}`);
271
+ logger_default.warn(`Attempt ${attempt + 1} failed: ${error.message}`);
272
+ if (attempt >= maxRetries) {
273
+ logger_default.error(`All ${maxRetries + 1} attempts failed.`);
274
+ throw new Error(`Transformation failed after ${maxRetries + 1} attempts. Last error: ${error.message}`);
248
275
  }
276
+ const delay = retryDelay * Math.pow(2, attempt);
277
+ await new Promise((res) => setTimeout(res, delay));
249
278
  }
250
279
  }
251
280
  }
252
- async function estimateTokenUsage(nextPayload) {
253
- const contents = [];
254
- if (this.systemInstructions) {
255
- contents.push({ parts: [{ text: this.systemInstructions }] });
256
- }
257
- if (this.chat && typeof this.chat.getHistory === "function") {
258
- const history = this.chat.getHistory();
259
- if (Array.isArray(history) && history.length > 0) {
260
- contents.push(...history);
261
- }
262
- }
263
- const nextMessage = typeof nextPayload === "string" ? nextPayload : JSON.stringify(nextPayload, null, 2);
264
- contents.push({ parts: [{ text: nextMessage }] });
265
- const resp = await this.genAIClient.models.countTokens({
266
- model: this.modelName,
267
- contents
268
- });
269
- return resp;
270
- }
271
281
  async function rebuildPayload(lastPayload, serverError) {
272
282
  await this.init();
273
283
  const prompt = `
@@ -277,6 +287,7 @@ The server's error message is quoted afterward.
277
287
  ---------------- BAD PAYLOAD ----------------
278
288
  ${JSON.stringify(lastPayload, null, 2)}
279
289
 
290
+
280
291
  ---------------- SERVER ERROR ----------------
281
292
  ${serverError}
282
293
 
@@ -296,6 +307,25 @@ Respond with JSON only \u2013 no comments or explanations.
296
307
  throw new Error(`Gemini returned non-JSON while repairing payload: ${parseErr.message}`);
297
308
  }
298
309
  }
310
+ async function estimateTokenUsage(nextPayload) {
311
+ const contents = [];
312
+ if (this.systemInstructions) {
313
+ contents.push({ parts: [{ text: this.systemInstructions }] });
314
+ }
315
+ if (this.chat && typeof this.chat.getHistory === "function") {
316
+ const history = this.chat.getHistory();
317
+ if (Array.isArray(history) && history.length > 0) {
318
+ contents.push(...history);
319
+ }
320
+ }
321
+ const nextMessage = typeof nextPayload === "string" ? nextPayload : JSON.stringify(nextPayload, null, 2);
322
+ contents.push({ parts: [{ text: nextMessage }] });
323
+ const resp = await this.genAIClient.models.countTokens({
324
+ model: this.modelName,
325
+ contents
326
+ });
327
+ return resp;
328
+ }
299
329
  async function resetChat() {
300
330
  if (this.chat) {
301
331
  logger_default.debug("Resetting Gemini chat session...");
@@ -317,6 +347,128 @@ function getChatHistory() {
317
347
  }
318
348
  return this.chat.getHistory();
319
349
  }
350
+ function isJSON(data) {
351
+ try {
352
+ const attempt = JSON.stringify(data);
353
+ if (attempt?.startsWith("{") || attempt?.startsWith("[")) {
354
+ if (attempt?.endsWith("}") || attempt?.endsWith("]")) {
355
+ return true;
356
+ }
357
+ }
358
+ return false;
359
+ } catch (e) {
360
+ return false;
361
+ }
362
+ }
363
+ function isJSONStr(string) {
364
+ if (typeof string !== "string") return false;
365
+ try {
366
+ const result = JSON.parse(string);
367
+ const type = Object.prototype.toString.call(result);
368
+ return type === "[object Object]" || type === "[object Array]";
369
+ } catch (err) {
370
+ return false;
371
+ }
372
+ }
373
+ function extractJSON(text) {
374
+ if (!text || typeof text !== "string") {
375
+ throw new Error("No text provided for JSON extraction");
376
+ }
377
+ if (isJSONStr(text.trim())) {
378
+ return JSON.parse(text.trim());
379
+ }
380
+ const codeBlockPatterns = [
381
+ /```json\s*\n?([\s\S]*?)\n?\s*```/gi,
382
+ /```\s*\n?([\s\S]*?)\n?\s*```/gi
383
+ ];
384
+ for (const pattern of codeBlockPatterns) {
385
+ const matches = text.match(pattern);
386
+ if (matches) {
387
+ for (const match of matches) {
388
+ const jsonContent = match.replace(/```json\s*\n?/gi, "").replace(/```\s*\n?/gi, "").trim();
389
+ if (isJSONStr(jsonContent)) {
390
+ return JSON.parse(jsonContent);
391
+ }
392
+ }
393
+ }
394
+ }
395
+ const jsonPatterns = [
396
+ // Match complete JSON objects
397
+ /\{[\s\S]*\}/g,
398
+ // Match complete JSON arrays
399
+ /\[[\s\S]*\]/g
400
+ ];
401
+ for (const pattern of jsonPatterns) {
402
+ const matches = text.match(pattern);
403
+ if (matches) {
404
+ for (const match of matches) {
405
+ const candidate = match.trim();
406
+ if (isJSONStr(candidate)) {
407
+ return JSON.parse(candidate);
408
+ }
409
+ }
410
+ }
411
+ }
412
+ const advancedExtract = findCompleteJSONStructures(text);
413
+ if (advancedExtract.length > 0) {
414
+ for (const candidate of advancedExtract) {
415
+ if (isJSONStr(candidate)) {
416
+ return JSON.parse(candidate);
417
+ }
418
+ }
419
+ }
420
+ 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();
421
+ if (isJSONStr(cleanedText)) {
422
+ return JSON.parse(cleanedText);
423
+ }
424
+ throw new Error(`Could not extract valid JSON from model response. Response preview: ${text.substring(0, 200)}...`);
425
+ }
426
+ function findCompleteJSONStructures(text) {
427
+ const results = [];
428
+ const startChars = ["{", "["];
429
+ for (let i = 0; i < text.length; i++) {
430
+ if (startChars.includes(text[i])) {
431
+ const extracted = extractCompleteStructure(text, i);
432
+ if (extracted) {
433
+ results.push(extracted);
434
+ }
435
+ }
436
+ }
437
+ return results;
438
+ }
439
+ function extractCompleteStructure(text, startPos) {
440
+ const startChar = text[startPos];
441
+ const endChar = startChar === "{" ? "}" : "]";
442
+ let depth = 0;
443
+ let inString = false;
444
+ let escaped = false;
445
+ for (let i = startPos; i < text.length; i++) {
446
+ const char = text[i];
447
+ if (escaped) {
448
+ escaped = false;
449
+ continue;
450
+ }
451
+ if (char === "\\" && inString) {
452
+ escaped = true;
453
+ continue;
454
+ }
455
+ if (char === '"' && !escaped) {
456
+ inString = !inString;
457
+ continue;
458
+ }
459
+ if (!inString) {
460
+ if (char === startChar) {
461
+ depth++;
462
+ } else if (char === endChar) {
463
+ depth--;
464
+ if (depth === 0) {
465
+ return text.substring(startPos, i + 1);
466
+ }
467
+ }
468
+ }
469
+ }
470
+ return null;
471
+ }
320
472
  if (import_meta.url === new URL(`file://${process.argv[1]}`).href) {
321
473
  logger_default.info("RUNNING AI Transformer as standalone script...");
322
474
  (async () => {
@@ -361,7 +513,7 @@ if (import_meta.url === new URL(`file://${process.argv[1]}`).href) {
361
513
  }
362
514
  return payload;
363
515
  };
364
- const validatedResponse = await transformer.transformWithValidation(
516
+ const validatedResponse = await transformer.messageAndValidate(
365
517
  { "name": "Lynn" },
366
518
  mockValidator
367
519
  );
@@ -375,6 +527,5 @@ if (import_meta.url === new URL(`file://${process.argv[1]}`).href) {
375
527
  }
376
528
  // Annotate the CommonJS export names for ESM import in node:
377
529
  0 && (module.exports = {
378
- AITransformer,
379
530
  log
380
531
  });
package/index.js CHANGED
@@ -52,7 +52,7 @@ When presented with new Source JSON, apply the learned transformation rules to p
52
52
 
53
53
  Always respond ONLY with a valid JSON object that strictly adheres to the expected output format.
54
54
 
55
- Do not include any additional text, explanations, or formatting before or after the JSON object.
55
+ Do not include any additional text, explanations, or formatting before or after the JSON object.
56
56
  `;
57
57
 
58
58
  const DEFAULT_CHAT_CONFIG = {
@@ -64,14 +64,20 @@ const DEFAULT_CHAT_CONFIG = {
64
64
  safetySettings: DEFAULT_SAFETY_SETTINGS
65
65
  };
66
66
 
67
+ /**
68
+ * @typedef {import('./types').AITransformer} AITransformerUtility
69
+ */
70
+
71
+
72
+
67
73
  /**
68
74
  * main export class for AI Transformer
69
75
  * @class AITransformer
76
+ * @type {AITransformerUtility}
70
77
  * @description A class that provides methods to initialize, seed, transform, and manage AI-based transformations using Google Gemini API.
71
78
  * @implements {ExportedAPI}
72
79
  */
73
- // @ts-ignore
74
- export default class AITransformer {
80
+ class AITransformer {
75
81
  /**
76
82
  * @param {AITransformerOptions} [options={}] - Configuration options for the transformer
77
83
  *
@@ -81,25 +87,40 @@ export default class AITransformer {
81
87
  this.promptKey = "";
82
88
  this.answerKey = "";
83
89
  this.contextKey = "";
90
+ this.explanationKey = "";
91
+ this.systemInstructionKey = "";
84
92
  this.maxRetries = 3;
85
93
  this.retryDelay = 1000;
86
94
  this.systemInstructions = "";
87
95
  this.chatConfig = {};
88
96
  this.apiKey = GEMINI_API_KEY;
97
+ this.onlyJSON = true; // always return JSON
98
+ this.asyncValidator = null; // for transformWithValidation
89
99
  AITransformFactory.call(this, options);
90
100
 
91
101
  //external API
92
102
  this.init = initChat.bind(this);
93
103
  this.seed = seedWithExamples.bind(this);
94
- this.message = transformJSON.bind(this);
104
+
105
+ // Internal "raw" message sender
106
+ this.rawMessage = rawMessage.bind(this);
107
+
108
+ // The public `.message()` method uses the GLOBAL validator
109
+ this.message = (payload, opts = {}, validatorFn = null) => {
110
+
111
+ return prepareAndValidateMessage.call(this, payload, opts, validatorFn || this.asyncValidator);
112
+ };
113
+
95
114
  this.rebuild = rebuildPayload.bind(this);
96
115
  this.reset = resetChat.bind(this);
97
116
  this.getHistory = getChatHistory.bind(this);
98
- this.transformWithValidation = transformWithValidation.bind(this);
117
+ this.messageAndValidate = prepareAndValidateMessage.bind(this);
99
118
  this.estimate = estimateTokenUsage.bind(this);
100
119
  }
101
120
  }
102
- export { AITransformer };
121
+
122
+ export default AITransformer;
123
+
103
124
  /**
104
125
  * factory function to create an AI Transformer instance
105
126
  * @param {AITransformerOptions} [options={}] - Configuration options for the transformer
@@ -110,7 +131,7 @@ function AITransformFactory(options = {}) {
110
131
  this.modelName = options.modelName || 'gemini-2.0-flash';
111
132
  this.systemInstructions = options.systemInstructions || DEFAULT_SYSTEM_INSTRUCTIONS;
112
133
 
113
- this.apiKey = options.apiKey || GEMINI_API_KEY;
134
+ this.apiKey = options.apiKey !== undefined && options.apiKey !== null ? options.apiKey : GEMINI_API_KEY;
114
135
  if (!this.apiKey) throw new Error("Missing Gemini API key. Provide via options.apiKey or GEMINI_API_KEY env var.");
115
136
  // Build chat config, making sure systemInstruction uses the custom instructions
116
137
  this.chatConfig = {
@@ -129,14 +150,22 @@ function AITransformFactory(options = {}) {
129
150
  this.exampleData = options.exampleData || null; // can be used instead of examplesFile
130
151
 
131
152
  // Use configurable keys with fallbacks
132
- this.promptKey = options.sourceKey || 'PROMPT';
133
- this.answerKey = options.targetKey || 'ANSWER';
134
- this.contextKey = options.contextKey || 'CONTEXT'; // Now configurable
153
+ this.promptKey = options.promptKey || 'PROMPT';
154
+ this.answerKey = options.answerKey || 'ANSWER';
155
+ this.contextKey = options.contextKey || 'CONTEXT'; // Optional key for context
156
+ this.explanationKey = options.explanationKey || 'EXPLANATION'; // Optional key for explanations
157
+ this.systemInstructionsKey = options.systemInstructionsKey || 'SYSTEM'; // Optional key for system instructions
135
158
 
136
159
  // Retry configuration
137
160
  this.maxRetries = options.maxRetries || 3;
138
161
  this.retryDelay = options.retryDelay || 1000;
139
162
 
163
+ //allow async validation function
164
+ this.asyncValidator = options.asyncValidator || null; // Function to validate transformed payloads
165
+
166
+ //are we forcing json responses only?
167
+ this.onlyJSON = options.onlyJSON !== undefined ? options.onlyJSON : true; // If true, only return JSON responses
168
+
140
169
  if (this.promptKey === this.answerKey) {
141
170
  throw new Error("Source and target keys cannot be the same. Please provide distinct keys.");
142
171
  }
@@ -151,11 +180,12 @@ function AITransformFactory(options = {}) {
151
180
 
152
181
  /**
153
182
  * Initializes the chat session with the specified model and configurations.
183
+ * @param {boolean} [force=false] - If true, forces reinitialization of the chat session.
154
184
  * @this {ExportedAPI}
155
185
  * @returns {Promise<void>}
156
186
  */
157
- async function initChat() {
158
- if (this.chat) return;
187
+ async function initChat(force = false) {
188
+ if (this.chat && !force) return;
159
189
 
160
190
  log.debug(`Initializing Gemini chat session with model: ${this.modelName}...`);
161
191
 
@@ -173,6 +203,7 @@ async function initChat() {
173
203
  * Seeds the chat session with example transformations.
174
204
  * @this {ExportedAPI}
175
205
  * @param {TransformationExample[]} [examples] - An array of transformation examples.
206
+ * @this {ExportedAPI}
176
207
  * @returns {Promise<void>}
177
208
  */
178
209
  async function seedWithExamples(examples) {
@@ -181,13 +212,25 @@ async function seedWithExamples(examples) {
181
212
  if (!examples || !Array.isArray(examples) || examples.length === 0) {
182
213
  if (this.examplesFile) {
183
214
  log.debug(`No examples provided, loading from file: ${this.examplesFile}`);
184
- examples = await u.load(path.resolve(this.examplesFile), true);
215
+ try {
216
+ examples = await u.load(path.resolve(this.examplesFile), true);
217
+ }
218
+ catch (err) {
219
+ throw new Error(`Could not load examples from file: ${this.examplesFile}. Please check the file path and format.`);
220
+ }
185
221
  } else {
186
222
  log.debug("No examples provided and no examples file specified. Skipping seeding.");
187
223
  return;
188
224
  }
189
225
  }
190
226
 
227
+ if (examples?.slice().pop()[this.systemInstructionsKey]) {
228
+ log.debug(`Found system instructions in examples; reinitializing chat with new instructions.`);
229
+ this.systemInstructions = examples.slice().pop()[this.systemInstructionsKey];
230
+ this.chatConfig.systemInstruction = this.systemInstructions;
231
+ await this.init(true); // Reinitialize chat with new system instructions
232
+ }
233
+
191
234
  log.debug(`Seeding chat with ${examples.length} transformation examples...`);
192
235
  const historyToAdd = [];
193
236
 
@@ -196,31 +239,31 @@ async function seedWithExamples(examples) {
196
239
  const contextValue = example[this.contextKey] || "";
197
240
  const promptValue = example[this.promptKey] || "";
198
241
  const answerValue = example[this.answerKey] || "";
242
+ const explanationValue = example[this.explanationKey] || "";
243
+ let userText = "";
244
+ let modelResponse = {};
199
245
 
200
246
  // Add context as user message with special formatting to make it part of the example flow
201
247
  if (contextValue) {
202
- let contextText = u.isJSON(contextValue) ? JSON.stringify(contextValue, null, 2) : contextValue;
248
+ let contextText = isJSON(contextValue) ? JSON.stringify(contextValue, null, 2) : contextValue;
203
249
  // Prefix context to make it clear it's contextual information
204
- historyToAdd.push({
205
- role: 'user',
206
- parts: [{ text: `Context: ${contextText}` }]
207
- });
208
- // Add a brief model acknowledgment
209
- historyToAdd.push({
210
- role: 'model',
211
- parts: [{ text: "I understand the context." }]
212
- });
250
+ userText += `CONTEXT:\n${contextText}\n\n`;
213
251
  }
214
252
 
215
253
  if (promptValue) {
216
- let promptText = u.isJSON(promptValue) ? JSON.stringify(promptValue, null, 2) : promptValue;
217
- historyToAdd.push({ role: 'user', parts: [{ text: promptText }] });
254
+ let promptText = isJSON(promptValue) ? JSON.stringify(promptValue, null, 2) : promptValue;
255
+ userText += promptText;
218
256
  }
219
257
 
220
- if (answerValue) {
221
- let answerText = u.isJSON(answerValue) ? JSON.stringify(answerValue, null, 2) : answerValue;
222
- historyToAdd.push({ role: 'model', parts: [{ text: answerText }] });
258
+ if (answerValue) modelResponse.data = answerValue;
259
+ if (explanationValue) modelResponse.explanation = explanationValue;
260
+ const modelText = JSON.stringify(modelResponse, null, 2);
261
+
262
+ if (userText.trim().length && modelText.trim().length > 0) {
263
+ historyToAdd.push({ role: 'user', parts: [{ text: userText.trim() }] });
264
+ historyToAdd.push({ role: 'model', parts: [{ text: modelText.trim() }] });
223
265
  }
266
+
224
267
  }
225
268
 
226
269
  const currentHistory = this?.chat?.getHistory() || [];
@@ -233,6 +276,8 @@ async function seedWithExamples(examples) {
233
276
  });
234
277
 
235
278
  log.debug("Transformation examples seeded successfully.");
279
+
280
+ return this.chat.getHistory(); // Return the updated chat history for reference
236
281
  }
237
282
 
238
283
  /**
@@ -241,90 +286,150 @@ async function seedWithExamples(examples) {
241
286
  * @returns {Promise<Object>} - The transformed target payload (as a JavaScript object).
242
287
  * @throws {Error} If the transformation fails or returns invalid JSON.
243
288
  */
244
- async function transformJSON(sourcePayload) {
289
+ /**
290
+ * (Internal) Sends a single prompt to the model and parses the response.
291
+ * No validation or retry logic.
292
+ * @this {ExportedAPI}
293
+ * @param {Object|string} sourcePayload - The source payload.
294
+ * @returns {Promise<Object>} - The transformed payload.
295
+ */
296
+ async function rawMessage(sourcePayload) {
245
297
  if (!this.chat) {
246
- throw new Error("Chat session not initialized. Call initChat() or seedWithExamples() first.");
298
+ throw new Error("Chat session not initialized.");
247
299
  }
248
300
 
249
- let result;
250
- let actualPayload;
251
- if (sourcePayload && u.isJSON(sourcePayload)) actualPayload = JSON.stringify(sourcePayload, null, 2);
252
- else if (typeof sourcePayload === 'string') actualPayload = sourcePayload;
253
- else throw new Error("Invalid source payload. Must be a JSON object or a valid JSON string.");
301
+ const actualPayload = typeof sourcePayload === 'string'
302
+ ? sourcePayload
303
+ : JSON.stringify(sourcePayload, null, 2);
254
304
 
255
305
  try {
256
- result = await this.chat.sendMessage({ message: actualPayload });
306
+ const result = await this.chat.sendMessage({ message: actualPayload });
307
+ const modelResponse = result.text;
308
+ const extractedJSON = extractJSON(modelResponse); // Assuming extractJSON is defined
309
+
310
+ // Unwrap the 'data' property if it exists
311
+ if (extractedJSON?.data) {
312
+ return extractedJSON.data;
313
+ }
314
+ return extractedJSON;
315
+
257
316
  } catch (error) {
258
- log.error("Error with Gemini API:", error);
317
+ if (this.onlyJSON && error.message.includes("Could not extract valid JSON")) {
318
+ throw new Error(`Invalid JSON response from Gemini: ${error.message}`);
319
+ }
320
+ // For other API errors, just re-throw
259
321
  throw new Error(`Transformation failed: ${error.message}`);
260
322
  }
261
-
262
- try {
263
- const modelResponse = result.text;
264
- const parsedResponse = JSON.parse(modelResponse);
265
- return parsedResponse;
266
- } catch (parseError) {
267
- log.error("Error parsing Gemini response:", parseError);
268
- throw new Error(`Invalid JSON response from Gemini: ${parseError.message}`);
269
- }
270
323
  }
271
324
 
272
325
  /**
273
- * Transforms payload with automatic validation and retry logic
274
- * @param {Object} sourcePayload - The source payload to transform
275
- * @param {AsyncValidatorFunction} validatorFn - Async function that validates the transformed payload
276
- * @param {Object} [options] - Options for the validation process
277
- * @param {number} [options.maxRetries] - Override default max retries
278
- * @param {number} [options.retryDelay] - Override default retry delay
279
- * @returns {Promise<Object>} - The validated transformed payload
280
- * @throws {Error} If transformation or validation fails after all retries
326
+ * (Engine) Transforms a payload with validation and automatic retry logic.
327
+ * @this {ExportedAPI}
328
+ * @param {Object} sourcePayload - The source payload to transform.
329
+ * @param {Object} [options] - Options for the validation process.
330
+ * @param {AsyncValidatorFunction | null} validatorFn - The specific validator to use for this run.
331
+ * @returns {Promise<Object>} - The validated transformed payload.
281
332
  */
282
- async function transformWithValidation(sourcePayload, validatorFn, options = {}) {
333
+ async function prepareAndValidateMessage(sourcePayload, options = {}, validatorFn = null) {
334
+ if (!this.chat) {
335
+ throw new Error("Chat session not initialized. Please call init() first.");
336
+ }
283
337
  const maxRetries = options.maxRetries ?? this.maxRetries;
284
338
  const retryDelay = options.retryDelay ?? this.retryDelay;
285
339
 
286
- let lastPayload = null;
287
340
  let lastError = null;
341
+ let lastPayload = null; // Store the payload that caused the validation error
342
+
343
+ // Prepare the payload
344
+ if (sourcePayload && isJSON(sourcePayload)) {
345
+ lastPayload = JSON.stringify(sourcePayload, null, 2);
346
+ } else if (typeof sourcePayload === 'string') {
347
+ lastPayload = sourcePayload;
348
+ } else {
349
+ throw new Error("Invalid source payload. Must be a JSON object or string.");
350
+ }
288
351
 
289
352
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
290
353
  try {
291
- // First attempt uses normal transformation, subsequent attempts use rebuild
292
- const transformedPayload = attempt === 0
293
- ? await this.message(sourcePayload)
354
+ // Step 1: Get the transformed payload
355
+ const transformedPayload = (attempt === 0)
356
+ ? await this.rawMessage(lastPayload) // Use the new raw method
294
357
  : await this.rebuild(lastPayload, lastError.message);
295
358
 
296
- // Validate the transformed payload
297
- const validatedPayload = await validatorFn(transformedPayload);
359
+ lastPayload = transformedPayload; // Always update lastPayload *before* validation
298
360
 
299
- log.debug(`Transformation and validation succeeded on attempt ${attempt + 1}`);
300
- return validatedPayload;
361
+ // Step 2: Validate if a validator is provided
362
+ if (validatorFn) {
363
+ await validatorFn(transformedPayload); // Validator throws on failure
364
+ }
365
+
366
+ // Step 3: Success!
367
+ log.debug(`Transformation succeeded on attempt ${attempt + 1}`);
368
+ return transformedPayload;
301
369
 
302
370
  } catch (error) {
303
371
  lastError = error;
372
+ log.warn(`Attempt ${attempt + 1} failed: ${error.message}`);
304
373
 
305
- if (attempt === 0) {
306
- // First attempt failed - could be transformation or validation error
307
- lastPayload = await this.message(sourcePayload).catch(() => null);
374
+ if (attempt >= maxRetries) {
375
+ log.error(`All ${maxRetries + 1} attempts failed.`);
376
+ throw new Error(`Transformation failed after ${maxRetries + 1} attempts. Last error: ${error.message}`);
308
377
  }
309
378
 
310
- if (attempt < maxRetries) {
311
- const delay = retryDelay * Math.pow(2, attempt); // Exponential backoff
312
- log.warn(`Attempt ${attempt + 1} failed, retrying in ${delay}ms...`, error.message);
313
- await new Promise(res => setTimeout(res, delay));
314
- } else {
315
- log.error(`All ${maxRetries + 1} attempts failed`);
316
- throw new Error(`Transformation with validation failed after ${maxRetries + 1} attempts. Last error: ${error.message}`);
317
- }
379
+ // Wait before retrying
380
+ const delay = retryDelay * Math.pow(2, attempt);
381
+ await new Promise(res => setTimeout(res, delay));
318
382
  }
319
383
  }
320
384
  }
321
385
 
386
+ /**
387
+ * Rebuilds a payload based on server error feedback
388
+ * @param {Object} lastPayload - The payload that failed validation
389
+ * @param {string} serverError - The error message from the server
390
+ * @returns {Promise<Object>} - A new corrected payload
391
+ * @throws {Error} If the rebuild process fails.
392
+ */
393
+ async function rebuildPayload(lastPayload, serverError) {
394
+ await this.init(); // Ensure chat is initialized
395
+ const prompt = `
396
+ The previous JSON payload (below) failed validation.
397
+ The server's error message is quoted afterward.
398
+
399
+ ---------------- BAD PAYLOAD ----------------
400
+ ${JSON.stringify(lastPayload, null, 2)}
401
+
402
+
403
+ ---------------- SERVER ERROR ----------------
404
+ ${serverError}
405
+
406
+ Please return a NEW JSON payload that corrects the issue.
407
+ Respond with JSON only – no comments or explanations.
408
+ `;
409
+
410
+ let result;
411
+ try {
412
+ result = await this.chat.sendMessage({ message: prompt });
413
+ } catch (err) {
414
+ throw new Error(`Gemini call failed while repairing payload: ${err.message}`);
415
+ }
416
+
417
+ try {
418
+ const text = result.text ?? result.response ?? '';
419
+ return typeof text === 'object' ? text : JSON.parse(text);
420
+ } catch (parseErr) {
421
+ throw new Error(`Gemini returned non-JSON while repairing payload: ${parseErr.message}`);
422
+ }
423
+ }
424
+
425
+
426
+
322
427
 
323
428
  /**
324
429
  * Estimate total token usage if you were to send a new payload as the next message.
325
430
  * Considers system instructions, current chat history (including examples), and the new message.
326
431
  * @param {object|string} nextPayload - The next user message to be sent (object or string)
327
- * @returns {Promise<{ totalTokens: number, ... }>} - The result of Gemini's countTokens API
432
+ * @returns {Promise<{ totalTokens: number }>} - The result of Gemini's countTokens API
328
433
  */
329
434
  async function estimateTokenUsage(nextPayload) {
330
435
  // Compose the conversation contents, Gemini-style
@@ -360,44 +465,6 @@ async function estimateTokenUsage(nextPayload) {
360
465
  return resp; // includes totalTokens, possibly breakdown
361
466
  }
362
467
 
363
- /**
364
- * Rebuilds a payload based on server error feedback
365
- * @param {Object} lastPayload - The payload that failed validation
366
- * @param {string} serverError - The error message from the server
367
- * @returns {Promise<Object>} - A new corrected payload
368
- * @throws {Error} If the rebuild process fails.
369
- */
370
- async function rebuildPayload(lastPayload, serverError) {
371
- await this.init();
372
-
373
- const prompt = `
374
- The previous JSON payload (below) failed validation.
375
- The server's error message is quoted afterward.
376
-
377
- ---------------- BAD PAYLOAD ----------------
378
- ${JSON.stringify(lastPayload, null, 2)}
379
-
380
- ---------------- SERVER ERROR ----------------
381
- ${serverError}
382
-
383
- Please return a NEW JSON payload that corrects the issue.
384
- Respond with JSON only – no comments or explanations.
385
- `;
386
-
387
- let result;
388
- try {
389
- result = await this.chat.sendMessage({ message: prompt });
390
- } catch (err) {
391
- throw new Error(`Gemini call failed while repairing payload: ${err.message}`);
392
- }
393
-
394
- try {
395
- const text = result.text ?? result.response ?? '';
396
- return typeof text === 'object' ? text : JSON.parse(text);
397
- } catch (parseErr) {
398
- throw new Error(`Gemini returned non-JSON while repairing payload: ${parseErr.message}`);
399
- }
400
- }
401
468
 
402
469
  /**
403
470
  * Resets the current chat session, clearing all history and examples
@@ -432,6 +499,170 @@ function getChatHistory() {
432
499
  }
433
500
 
434
501
 
502
+ /*
503
+ ----
504
+ HELPERS
505
+ ----
506
+ */
507
+
508
+ function isJSON(data) {
509
+ try {
510
+ const attempt = JSON.stringify(data);
511
+ if (attempt?.startsWith('{') || attempt?.startsWith('[')) {
512
+ if (attempt?.endsWith('}') || attempt?.endsWith(']')) {
513
+ return true;
514
+ }
515
+ }
516
+ return false;
517
+ } catch (e) {
518
+ return false;
519
+ }
520
+ }
521
+
522
+ function isJSONStr(string) {
523
+ if (typeof string !== 'string') return false;
524
+ try {
525
+ const result = JSON.parse(string);
526
+ const type = Object.prototype.toString.call(result);
527
+ return type === '[object Object]' || type === '[object Array]';
528
+ } catch (err) {
529
+ return false;
530
+ }
531
+ }
532
+
533
+ function extractJSON(text) {
534
+ if (!text || typeof text !== 'string') {
535
+ throw new Error('No text provided for JSON extraction');
536
+ }
537
+
538
+ // Strategy 1: Try parsing the entire response as JSON
539
+ if (isJSONStr(text.trim())) {
540
+ return JSON.parse(text.trim());
541
+ }
542
+
543
+ // Strategy 2: Look for JSON code blocks (```json...``` or ```...```)
544
+ const codeBlockPatterns = [
545
+ /```json\s*\n?([\s\S]*?)\n?\s*```/gi,
546
+ /```\s*\n?([\s\S]*?)\n?\s*```/gi
547
+ ];
548
+
549
+ for (const pattern of codeBlockPatterns) {
550
+ const matches = text.match(pattern);
551
+ if (matches) {
552
+ for (const match of matches) {
553
+ const jsonContent = match.replace(/```json\s*\n?/gi, '').replace(/```\s*\n?/gi, '').trim();
554
+ if (isJSONStr(jsonContent)) {
555
+ return JSON.parse(jsonContent);
556
+ }
557
+ }
558
+ }
559
+ }
560
+
561
+ // Strategy 3: Look for JSON objects/arrays using bracket matching
562
+ const jsonPatterns = [
563
+ // Match complete JSON objects
564
+ /\{[\s\S]*\}/g,
565
+ // Match complete JSON arrays
566
+ /\[[\s\S]*\]/g
567
+ ];
568
+
569
+ for (const pattern of jsonPatterns) {
570
+ const matches = text.match(pattern);
571
+ if (matches) {
572
+ for (const match of matches) {
573
+ const candidate = match.trim();
574
+ if (isJSONStr(candidate)) {
575
+ return JSON.parse(candidate);
576
+ }
577
+ }
578
+ }
579
+ }
580
+
581
+ // Strategy 4: Advanced bracket matching for nested structures
582
+ const advancedExtract = findCompleteJSONStructures(text);
583
+ if (advancedExtract.length > 0) {
584
+ // Return the first valid JSON structure found
585
+ for (const candidate of advancedExtract) {
586
+ if (isJSONStr(candidate)) {
587
+ return JSON.parse(candidate);
588
+ }
589
+ }
590
+ }
591
+
592
+ // Strategy 5: Clean up common formatting issues and retry
593
+ const cleanedText = text
594
+ .replace(/^\s*Sure,?\s*here\s+is\s+your?\s+.*?[:\n]/gi, '') // Remove conversational intros
595
+ .replace(/^\s*Here\s+is\s+the\s+.*?[:\n]/gi, '')
596
+ .replace(/^\s*The\s+.*?is\s*[:\n]/gi, '')
597
+ .replace(/\/\*[\s\S]*?\*\//g, '') // Remove /* comments */
598
+ .replace(/\/\/.*$/gm, '') // Remove // comments
599
+ .trim();
600
+
601
+ if (isJSONStr(cleanedText)) {
602
+ return JSON.parse(cleanedText);
603
+ }
604
+
605
+ // If all else fails, throw an error with helpful information
606
+ throw new Error(`Could not extract valid JSON from model response. Response preview: ${text.substring(0, 200)}...`);
607
+ }
608
+
609
+ function findCompleteJSONStructures(text) {
610
+ const results = [];
611
+ const startChars = ['{', '['];
612
+
613
+ for (let i = 0; i < text.length; i++) {
614
+ if (startChars.includes(text[i])) {
615
+ const extracted = extractCompleteStructure(text, i);
616
+ if (extracted) {
617
+ results.push(extracted);
618
+ }
619
+ }
620
+ }
621
+
622
+ return results;
623
+ }
624
+
625
+
626
+ function extractCompleteStructure(text, startPos) {
627
+ const startChar = text[startPos];
628
+ const endChar = startChar === '{' ? '}' : ']';
629
+ let depth = 0;
630
+ let inString = false;
631
+ let escaped = false;
632
+
633
+ for (let i = startPos; i < text.length; i++) {
634
+ const char = text[i];
635
+
636
+ if (escaped) {
637
+ escaped = false;
638
+ continue;
639
+ }
640
+
641
+ if (char === '\\' && inString) {
642
+ escaped = true;
643
+ continue;
644
+ }
645
+
646
+ if (char === '"' && !escaped) {
647
+ inString = !inString;
648
+ continue;
649
+ }
650
+
651
+ if (!inString) {
652
+ if (char === startChar) {
653
+ depth++;
654
+ } else if (char === endChar) {
655
+ depth--;
656
+ if (depth === 0) {
657
+ return text.substring(startPos, i + 1);
658
+ }
659
+ }
660
+ }
661
+ }
662
+
663
+ return null; // Incomplete structure
664
+ }
665
+
435
666
  if (import.meta.url === new URL(`file://${process.argv[1]}`).href) {
436
667
  log.info("RUNNING AI Transformer as standalone script...");
437
668
  (
@@ -483,7 +714,7 @@ if (import.meta.url === new URL(`file://${process.argv[1]}`).href) {
483
714
  return payload; // Return the payload if validation passes
484
715
  };
485
716
 
486
- const validatedResponse = await transformer.transformWithValidation(
717
+ const validatedResponse = await transformer.messageAndValidate(
487
718
  { "name": "Lynn" },
488
719
  mockValidator
489
720
  );
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "ak-gemini",
3
3
  "author": "ak@mixpanel.com",
4
4
  "description": "AK's Generative AI Helper for doing... transforms",
5
- "version": "1.0.51",
5
+ "version": "1.0.53",
6
6
  "main": "index.js",
7
7
  "files": [
8
8
  "index.js",
@@ -10,7 +10,7 @@
10
10
  "types.ts",
11
11
  "logger.js"
12
12
  ],
13
- "types": "types.ts",
13
+ "types": "types.d.ts",
14
14
  "exports": {
15
15
  ".": {
16
16
  "import": "./index.js",
@@ -59,4 +59,4 @@
59
59
  "jest": "^29.7.0",
60
60
  "nodemon": "^3.1.10"
61
61
  }
62
- }
62
+ }
package/types.ts DELETED
@@ -1,68 +0,0 @@
1
- import type { GoogleGenAI } from '@google/genai';
2
-
3
- export interface SafetySetting {
4
- category: string; // The harm category
5
- threshold: string; // The blocking threshold
6
- }
7
-
8
- export interface ChatConfig {
9
- responseMimeType?: string; // MIME type for responses
10
- temperature?: number; // Controls randomness (0.0 to 1.0)
11
- topP?: number; // Controls diversity via nucleus sampling
12
- topK?: number; // Controls diversity by limiting top-k tokens
13
- systemInstruction?: string; // System instruction for the model
14
- safetySettings?: SafetySetting[]; // Safety settings array
15
- responseSchema?: Object; // Schema for validating model responses
16
- }
17
-
18
- export interface AITransformerContext {
19
- modelName?: string;
20
- systemInstructions?: string;
21
- chatConfig?: ChatConfig;
22
- genAI?: any;
23
- chat?: any;
24
- examplesFile?: string | null;
25
- exampleData?: TransformationExample[] | null;
26
- promptKey?: string;
27
- answerKey?: string;
28
- contextKey?: string;
29
- maxRetries?: number;
30
- retryDelay?: number;
31
- init?: () => Promise<void>; // Initialization function
32
- seed?: () => Promise<void>; // Function to seed the transformer with examples
33
- message?: (payload: Record<string, unknown>) => Promise<Record<string, unknown>>; // Function to send messages to the model
34
- genAIClient?: GoogleGenAI; // Google GenAI client instance
35
-
36
- }
37
-
38
- export interface TransformationExample {
39
- CONTEXT?: Record<string, unknown>; // optional context for the transformation
40
- PROMPT?: Record<string, unknown>; // what the user provides as input
41
- ANSWER?: Record<string, unknown>; // what the model should return as output
42
- }
43
-
44
- export interface ExampleFileContent {
45
- examples: TransformationExample[];
46
- }
47
-
48
- export interface AITransformerOptions {
49
- modelName?: string; // The Gemini model to use
50
- systemInstructions?: string; // Custom system instructions for the model
51
- chatConfig?: ChatConfig; // Configuration object for the chat session
52
- examplesFile?: string; // Path to JSON file containing transformation examples
53
- exampleData?: TransformationExample[]; // Inline examples to seed the transformer
54
- sourceKey?: string; // Key name for source data in examples
55
- targetKey?: string; // Key name for target data in examples
56
- contextKey?: string; // Key name for context data in examples
57
- maxRetries?: number; // Maximum retry attempts for auto-retry functionality
58
- retryDelay?: number; // Initial retry delay in milliseconds
59
- // ? https://ai.google.dev/gemini-api/docs/structured-output
60
- responseSchema?: Object; // Schema for validating model responses
61
- apiKey?: string; // API key for Google GenAI
62
- }
63
-
64
- // Async validator function type
65
- export type AsyncValidatorFunction = (payload: Record<string, unknown>) => Promise<Record<string, unknown>>;
66
-
67
-
68
- export declare class AITransformer implements AITransformerContext {}