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.
- package/README.md +4 -3
- package/index.cjs +316 -86
- package/index.js +458 -126
- package/package.json +23 -17
- package/types.d.ts +138 -0
- 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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
137
|
+
this.modelName = options.modelName || "gemini-2.5-flash";
|
|
116
138
|
this.systemInstructions = options.systemInstructions || DEFAULT_SYSTEM_INSTRUCTIONS;
|
|
117
|
-
|
|
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.
|
|
138
|
-
|
|
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
|
-
|
|
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 =
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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 =
|
|
184
|
-
|
|
274
|
+
let promptText = isJSON(promptValue) ? JSON.stringify(promptValue, null, 2) : promptValue;
|
|
275
|
+
userText += promptText;
|
|
185
276
|
}
|
|
186
|
-
if (answerValue)
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
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
|
|
297
|
+
async function rawMessage(sourcePayload) {
|
|
201
298
|
if (!this.chat) {
|
|
202
|
-
throw new Error("Chat session not initialized.
|
|
299
|
+
throw new Error("Chat session not initialized.");
|
|
203
300
|
}
|
|
204
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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.
|
|
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.
|
|
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
|
});
|