ak-gemini 1.0.10 → 1.0.12

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 (5) hide show
  1. package/README.md +51 -1
  2. package/index.cjs +177 -10
  3. package/index.js +262 -11
  4. package/package.json +1 -1
  5. package/types.d.ts +76 -11
package/README.md CHANGED
@@ -11,6 +11,8 @@ Use this to power LLM-driven data pipelines, JSON mapping, or any automated AI t
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.
14
+ * **Conversation Management:** Clear conversation history while preserving examples, or send stateless one-off messages that don't affect history
15
+ * **Response Metadata:** Access actual model version and token counts from API responses for billing verification and debugging
14
16
  * **Strong TypeScript/JSDoc Typings:** All public APIs fully typed (see `/types`)
15
17
  * **Minimal API Surface:** Dead simple, no ceremony—init, seed, transform, validate.
16
18
  * **Robust Logging:** Pluggable logger for all steps, easy debugging
@@ -106,6 +108,25 @@ console.log(validPayload);
106
108
 
107
109
  ---
108
110
 
111
+ ### 5. **Conversation Management**
112
+
113
+ Manage chat history to control costs and isolate requests:
114
+
115
+ ```js
116
+ // Clear conversation history while preserving seeded examples
117
+ await transformer.clearConversation();
118
+
119
+ // Send a stateless message that doesn't affect chat history
120
+ const result = await transformer.message({ query: "one-off question" }, { stateless: true });
121
+
122
+ // Check actual model and token usage from last API call
123
+ console.log(transformer.lastResponseMetadata);
124
+ // → { modelVersion: 'gemini-2.5-flash-001', requestedModel: 'gemini-2.5-flash',
125
+ // promptTokens: 150, responseTokens: 42, totalTokens: 192, timestamp: 1703... }
126
+ ```
127
+
128
+ ---
129
+
109
130
  ## API
110
131
 
111
132
  ### Constructor
@@ -142,10 +163,14 @@ Initializes Gemini chat session (idempotent).
142
163
  Seeds the model with example transformations (uses keys from constructor).
143
164
  You can omit `examples` to use the `examplesFile` (if provided).
144
165
 
145
- #### `await transformer.message(sourcePayload)`
166
+ #### `await transformer.message(sourcePayload, options?)`
146
167
 
147
168
  Transforms input JSON to output JSON using the seeded examples and system instructions. Throws if estimated token window would be exceeded.
148
169
 
170
+ **Options:**
171
+ - `stateless: true` — Send a one-off message without affecting chat history (uses `generateContent` instead of chat)
172
+ - `labels: {}` — Per-message billing labels
173
+
149
174
  #### `await transformer.estimateTokenUsage(sourcePayload)`
150
175
 
151
176
  Returns `{ totalTokens, breakdown }` for the *full request* that would be sent to Gemini (system instructions + all examples + your sourcePayload as the new prompt).
@@ -168,6 +193,31 @@ Resets the Gemini chat session, clearing all history/examples.
168
193
 
169
194
  Returns the current chat history (for debugging).
170
195
 
196
+ #### `await transformer.clearConversation()`
197
+
198
+ Clears conversation history while preserving seeded examples. Useful for starting fresh user sessions without re-seeding.
199
+
200
+ ---
201
+
202
+ ### Properties
203
+
204
+ #### `transformer.lastResponseMetadata`
205
+
206
+ After each API call, contains metadata from the response:
207
+
208
+ ```js
209
+ {
210
+ modelVersion: string | null, // Actual model version that responded (e.g., 'gemini-2.5-flash-001')
211
+ requestedModel: string, // Model you requested (e.g., 'gemini-2.5-flash')
212
+ promptTokens: number, // Tokens in the prompt
213
+ responseTokens: number, // Tokens in the response
214
+ totalTokens: number, // Total tokens used
215
+ timestamp: number // When response was received
216
+ }
217
+ ```
218
+
219
+ Useful for verifying billing, debugging model behavior, and tracking token usage.
220
+
171
221
  ---
172
222
 
173
223
  ## Examples
package/index.cjs CHANGED
@@ -82,7 +82,7 @@ var DEFAULT_THINKING_CONFIG = {
82
82
  thinkingBudget: 0,
83
83
  thinkingLevel: import_genai.ThinkingLevel.MINIMAL
84
84
  };
85
- var DEFAULT_MAX_OUTPUT_TOKENS = 1e5;
85
+ var DEFAULT_MAX_OUTPUT_TOKENS = 5e4;
86
86
  var THINKING_SUPPORTED_MODELS = [
87
87
  /^gemini-3-flash(-preview)?$/,
88
88
  /^gemini-3-pro(-preview|-image-preview)?$/,
@@ -120,6 +120,8 @@ var AITransformer = class {
120
120
  this.onlyJSON = true;
121
121
  this.asyncValidator = null;
122
122
  this.logLevel = "info";
123
+ this.lastResponseMetadata = null;
124
+ this.exampleCount = 0;
123
125
  AITransformFactory.call(this, options);
124
126
  this.init = initChat.bind(this);
125
127
  this.seed = seedWithExamples.bind(this);
@@ -135,6 +137,8 @@ var AITransformer = class {
135
137
  this.estimate = estimateTokenUsage.bind(this);
136
138
  this.estimateTokenUsage = estimateTokenUsage.bind(this);
137
139
  this.updateSystemInstructions = updateSystemInstructions.bind(this);
140
+ this.estimateCost = estimateCost.bind(this);
141
+ this.clearConversation = clearConversation.bind(this);
138
142
  }
139
143
  };
140
144
  var index_default = AITransformer;
@@ -164,8 +168,17 @@ function AITransformFactory(options = {}) {
164
168
  this.logLevel = "info";
165
169
  logger_default.level = "info";
166
170
  }
171
+ this.vertexai = options.vertexai || false;
172
+ this.project = options.project || process.env.GOOGLE_CLOUD_PROJECT || null;
173
+ this.location = options.location || process.env.GOOGLE_CLOUD_LOCATION || "us-central1";
174
+ this.googleAuthOptions = options.googleAuthOptions || null;
167
175
  this.apiKey = options.apiKey !== void 0 && options.apiKey !== null ? options.apiKey : GEMINI_API_KEY;
168
- if (!this.apiKey) throw new Error("Missing Gemini API key. Provide via options.apiKey or GEMINI_API_KEY env var.");
176
+ if (!this.vertexai && !this.apiKey) {
177
+ throw new Error("Missing Gemini API key. Provide via options.apiKey or GEMINI_API_KEY env var. For Vertex AI, set vertexai: true with project and location.");
178
+ }
179
+ if (this.vertexai && !this.project) {
180
+ throw new Error("Vertex AI requires a project ID. Provide via options.project or GOOGLE_CLOUD_PROJECT env var.");
181
+ }
169
182
  this.chatConfig = {
170
183
  ...DEFAULT_CHAT_CONFIG,
171
184
  ...options.chatConfig,
@@ -226,6 +239,10 @@ function AITransformFactory(options = {}) {
226
239
  this.onlyJSON = options.onlyJSON !== void 0 ? options.onlyJSON : true;
227
240
  this.enableGrounding = options.enableGrounding || false;
228
241
  this.groundingConfig = options.groundingConfig || {};
242
+ this.labels = options.labels || {};
243
+ if (Object.keys(this.labels).length > 0 && logger_default.level !== "silent") {
244
+ logger_default.debug(`Billing labels configured: ${JSON.stringify(this.labels)}`);
245
+ }
229
246
  if (this.promptKey === this.answerKey) {
230
247
  throw new Error("Source and target keys cannot be the same. Please provide distinct keys.");
231
248
  }
@@ -233,10 +250,27 @@ function AITransformFactory(options = {}) {
233
250
  logger_default.debug(`Creating AI Transformer with model: ${this.modelName}`);
234
251
  logger_default.debug(`Using keys - Source: "${this.promptKey}", Target: "${this.answerKey}", Context: "${this.contextKey}"`);
235
252
  logger_default.debug(`Max output tokens set to: ${this.chatConfig.maxOutputTokens}`);
236
- logger_default.debug(`Using API key: ${this.apiKey.substring(0, 10)}...`);
253
+ if (this.vertexai) {
254
+ logger_default.debug(`Using Vertex AI - Project: ${this.project}, Location: ${this.location}`);
255
+ if (this.googleAuthOptions?.keyFilename) {
256
+ logger_default.debug(`Auth: Service account key file: ${this.googleAuthOptions.keyFilename}`);
257
+ } else if (this.googleAuthOptions?.credentials) {
258
+ logger_default.debug(`Auth: Inline credentials provided`);
259
+ } else {
260
+ logger_default.debug(`Auth: Application Default Credentials (ADC)`);
261
+ }
262
+ } else {
263
+ logger_default.debug(`Using Gemini API with key: ${this.apiKey.substring(0, 10)}...`);
264
+ }
237
265
  logger_default.debug(`Grounding ${this.enableGrounding ? "ENABLED" : "DISABLED"} (costs $35/1k queries)`);
238
266
  }
239
- const ai = new import_genai.GoogleGenAI({ apiKey: this.apiKey });
267
+ const clientOptions = this.vertexai ? {
268
+ vertexai: true,
269
+ project: this.project,
270
+ location: this.location,
271
+ ...this.googleAuthOptions && { googleAuthOptions: this.googleAuthOptions }
272
+ } : { apiKey: this.apiKey };
273
+ const ai = new import_genai.GoogleGenAI(clientOptions);
240
274
  this.genAIClient = ai;
241
275
  this.chat = null;
242
276
  }
@@ -246,7 +280,10 @@ async function initChat(force = false) {
246
280
  const chatOptions = {
247
281
  model: this.modelName,
248
282
  // @ts-ignore
249
- config: this.chatConfig,
283
+ config: {
284
+ ...this.chatConfig,
285
+ ...Object.keys(this.labels).length > 0 && { labels: this.labels }
286
+ },
250
287
  history: []
251
288
  };
252
289
  if (this.enableGrounding) {
@@ -326,20 +363,47 @@ ${contextText}
326
363
  this.chat = await this.genAIClient.chats.create({
327
364
  model: this.modelName,
328
365
  // @ts-ignore
329
- config: this.chatConfig,
366
+ config: {
367
+ ...this.chatConfig,
368
+ ...Object.keys(this.labels).length > 0 && { labels: this.labels }
369
+ },
330
370
  history: [...currentHistory, ...historyToAdd]
331
371
  });
372
+ this.exampleCount = currentHistory.length + historyToAdd.length;
332
373
  const newHistory = this.chat.getHistory();
333
374
  logger_default.debug(`Created new chat session with ${newHistory.length} examples.`);
334
375
  return newHistory;
335
376
  }
336
- async function rawMessage(sourcePayload) {
377
+ async function rawMessage(sourcePayload, messageOptions = {}) {
337
378
  if (!this.chat) {
338
379
  throw new Error("Chat session not initialized.");
339
380
  }
340
381
  const actualPayload = typeof sourcePayload === "string" ? sourcePayload : JSON.stringify(sourcePayload, null, 2);
382
+ const mergedLabels = { ...this.labels, ...messageOptions.labels || {} };
383
+ const hasLabels = Object.keys(mergedLabels).length > 0;
341
384
  try {
342
- const result = await this.chat.sendMessage({ message: actualPayload });
385
+ const sendParams = { message: actualPayload };
386
+ if (hasLabels) {
387
+ sendParams.config = { labels: mergedLabels };
388
+ }
389
+ const result = await this.chat.sendMessage(sendParams);
390
+ this.lastResponseMetadata = {
391
+ modelVersion: result.modelVersion || null,
392
+ requestedModel: this.modelName,
393
+ promptTokens: result.usageMetadata?.promptTokenCount || 0,
394
+ responseTokens: result.usageMetadata?.candidatesTokenCount || 0,
395
+ totalTokens: result.usageMetadata?.totalTokenCount || 0,
396
+ timestamp: Date.now()
397
+ };
398
+ if (result.usageMetadata && logger_default.level !== "silent") {
399
+ logger_default.debug(`API response metadata:`, {
400
+ modelVersion: result.modelVersion || "not-provided",
401
+ requestedModel: this.modelName,
402
+ promptTokens: result.usageMetadata.promptTokenCount,
403
+ responseTokens: result.usageMetadata.candidatesTokenCount,
404
+ totalTokens: result.usageMetadata.totalTokenCount
405
+ });
406
+ }
343
407
  const modelResponse = result.text;
344
408
  const extractedJSON = extractJSON(modelResponse);
345
409
  if (extractedJSON?.data) {
@@ -357,6 +421,9 @@ async function prepareAndValidateMessage(sourcePayload, options = {}, validatorF
357
421
  if (!this.chat) {
358
422
  throw new Error("Chat session not initialized. Please call init() first.");
359
423
  }
424
+ if (options.stateless) {
425
+ return await statelessMessage.call(this, sourcePayload, options, validatorFn);
426
+ }
360
427
  const maxRetries = options.maxRetries ?? this.maxRetries;
361
428
  const retryDelay = options.retryDelay ?? this.retryDelay;
362
429
  const enableGroundingForMessage = options.enableGrounding ?? this.enableGrounding;
@@ -398,9 +465,13 @@ async function prepareAndValidateMessage(sourcePayload, options = {}, validatorF
398
465
  } else {
399
466
  throw new Error("Invalid source payload. Must be a JSON object or string.");
400
467
  }
468
+ const messageOptions = {};
469
+ if (options.labels) {
470
+ messageOptions.labels = options.labels;
471
+ }
401
472
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
402
473
  try {
403
- const transformedPayload = attempt === 0 ? await this.rawMessage(lastPayload) : await this.rebuild(lastPayload, lastError.message);
474
+ const transformedPayload = attempt === 0 ? await this.rawMessage(lastPayload, messageOptions) : await this.rebuild(lastPayload, lastError.message);
404
475
  lastPayload = transformedPayload;
405
476
  if (validatorFn) {
406
477
  await validatorFn(transformedPayload);
@@ -444,6 +515,17 @@ Respond with JSON only \u2013 no comments or explanations.
444
515
  let result;
445
516
  try {
446
517
  result = await this.chat.sendMessage({ message: prompt });
518
+ this.lastResponseMetadata = {
519
+ modelVersion: result.modelVersion || null,
520
+ requestedModel: this.modelName,
521
+ promptTokens: result.usageMetadata?.promptTokenCount || 0,
522
+ responseTokens: result.usageMetadata?.candidatesTokenCount || 0,
523
+ totalTokens: result.usageMetadata?.totalTokenCount || 0,
524
+ timestamp: Date.now()
525
+ };
526
+ if (result.usageMetadata && logger_default.level !== "silent") {
527
+ logger_default.debug(`Rebuild response metadata - tokens used:`, result.usageMetadata.totalTokenCount);
528
+ }
447
529
  } catch (err) {
448
530
  throw new Error(`Gemini call failed while repairing payload: ${err.message}`);
449
531
  }
@@ -473,13 +555,36 @@ async function estimateTokenUsage(nextPayload) {
473
555
  });
474
556
  return resp;
475
557
  }
558
+ var MODEL_PRICING = {
559
+ "gemini-2.5-flash": { input: 0.15, output: 0.6 },
560
+ "gemini-2.5-flash-lite": { input: 0.02, output: 0.1 },
561
+ "gemini-2.5-pro": { input: 2.5, output: 10 },
562
+ "gemini-3-pro": { input: 2, output: 12 },
563
+ "gemini-3-pro-preview": { input: 2, output: 12 },
564
+ "gemini-2.0-flash": { input: 0.1, output: 0.4 },
565
+ "gemini-2.0-flash-lite": { input: 0.02, output: 0.1 }
566
+ };
567
+ async function estimateCost(nextPayload) {
568
+ const tokenInfo = await this.estimateTokenUsage(nextPayload);
569
+ const pricing = MODEL_PRICING[this.modelName] || { input: 0, output: 0 };
570
+ return {
571
+ totalTokens: tokenInfo.totalTokens,
572
+ model: this.modelName,
573
+ pricing,
574
+ estimatedInputCost: tokenInfo.totalTokens / 1e6 * pricing.input,
575
+ note: "Cost is for input tokens only; output cost depends on response length"
576
+ };
577
+ }
476
578
  async function resetChat() {
477
579
  if (this.chat) {
478
580
  logger_default.debug("Resetting Gemini chat session...");
479
581
  const chatOptions = {
480
582
  model: this.modelName,
481
583
  // @ts-ignore
482
- config: this.chatConfig,
584
+ config: {
585
+ ...this.chatConfig,
586
+ ...Object.keys(this.labels).length > 0 && { labels: this.labels }
587
+ },
483
588
  history: []
484
589
  };
485
590
  if (this.enableGrounding) {
@@ -510,6 +615,68 @@ async function updateSystemInstructions(newInstructions) {
510
615
  logger_default.debug("Updating system instructions and reinitializing chat...");
511
616
  await this.init(true);
512
617
  }
618
+ async function clearConversation() {
619
+ if (!this.chat) {
620
+ logger_default.warn("Cannot clear conversation: chat not initialized.");
621
+ return;
622
+ }
623
+ const history = this.chat.getHistory();
624
+ const exampleHistory = history.slice(0, this.exampleCount || 0);
625
+ this.chat = await this.genAIClient.chats.create({
626
+ model: this.modelName,
627
+ // @ts-ignore
628
+ config: {
629
+ ...this.chatConfig,
630
+ ...Object.keys(this.labels).length > 0 && { labels: this.labels }
631
+ },
632
+ history: exampleHistory
633
+ });
634
+ logger_default.debug(`Conversation cleared. Preserved ${exampleHistory.length} example items.`);
635
+ }
636
+ async function statelessMessage(sourcePayload, options = {}, validatorFn = null) {
637
+ if (!this.chat) {
638
+ throw new Error("Chat session not initialized. Please call init() first.");
639
+ }
640
+ const payloadStr = typeof sourcePayload === "string" ? sourcePayload : JSON.stringify(sourcePayload, null, 2);
641
+ const contents = [];
642
+ if (this.exampleCount > 0) {
643
+ const history = this.chat.getHistory();
644
+ const exampleHistory = history.slice(0, this.exampleCount);
645
+ contents.push(...exampleHistory);
646
+ }
647
+ contents.push({ role: "user", parts: [{ text: payloadStr }] });
648
+ const mergedLabels = { ...this.labels, ...options.labels || {} };
649
+ const result = await this.genAIClient.models.generateContent({
650
+ model: this.modelName,
651
+ contents,
652
+ config: {
653
+ ...this.chatConfig,
654
+ ...Object.keys(mergedLabels).length > 0 && { labels: mergedLabels }
655
+ }
656
+ });
657
+ this.lastResponseMetadata = {
658
+ modelVersion: result.modelVersion || null,
659
+ requestedModel: this.modelName,
660
+ promptTokens: result.usageMetadata?.promptTokenCount || 0,
661
+ responseTokens: result.usageMetadata?.candidatesTokenCount || 0,
662
+ totalTokens: result.usageMetadata?.totalTokenCount || 0,
663
+ timestamp: Date.now()
664
+ };
665
+ if (result.usageMetadata && logger_default.level !== "silent") {
666
+ logger_default.debug(`Stateless message metadata:`, {
667
+ modelVersion: result.modelVersion || "not-provided",
668
+ promptTokens: result.usageMetadata.promptTokenCount,
669
+ responseTokens: result.usageMetadata.candidatesTokenCount
670
+ });
671
+ }
672
+ const modelResponse = result.text;
673
+ const extractedJSON = extractJSON(modelResponse);
674
+ let transformedPayload = extractedJSON?.data ? extractedJSON.data : extractedJSON;
675
+ if (validatorFn) {
676
+ await validatorFn(transformedPayload);
677
+ }
678
+ return transformedPayload;
679
+ }
513
680
  function attemptJSONRecovery(text, maxAttempts = 100) {
514
681
  if (!text || typeof text !== "string") return null;
515
682
  try {
package/index.js CHANGED
@@ -57,7 +57,7 @@ const DEFAULT_THINKING_CONFIG = {
57
57
  thinkingLevel: ThinkingLevel.MINIMAL
58
58
  };
59
59
 
60
- const DEFAULT_MAX_OUTPUT_TOKENS = 100000; // Default ceiling for output tokens
60
+ const DEFAULT_MAX_OUTPUT_TOKENS = 50_000; // Default ceiling for output tokens
61
61
 
62
62
  // Models that support thinking features (as of Dec 2024)
63
63
  // Using regex patterns for more precise matching
@@ -112,6 +112,8 @@ class AITransformer {
112
112
  this.onlyJSON = true; // always return JSON
113
113
  this.asyncValidator = null; // for transformWithValidation
114
114
  this.logLevel = 'info'; // default log level
115
+ this.lastResponseMetadata = null; // stores metadata from last API response
116
+ this.exampleCount = 0; // tracks number of example history items from seed()
115
117
  AITransformFactory.call(this, options);
116
118
 
117
119
  //external API
@@ -135,6 +137,8 @@ class AITransformer {
135
137
  this.estimate = estimateTokenUsage.bind(this);
136
138
  this.estimateTokenUsage = estimateTokenUsage.bind(this);
137
139
  this.updateSystemInstructions = updateSystemInstructions.bind(this);
140
+ this.estimateCost = estimateCost.bind(this);
141
+ this.clearConversation = clearConversation.bind(this);
138
142
  }
139
143
  }
140
144
 
@@ -180,8 +184,22 @@ function AITransformFactory(options = {}) {
180
184
  log.level = 'info';
181
185
  }
182
186
 
187
+ // Vertex AI configuration
188
+ this.vertexai = options.vertexai || false;
189
+ this.project = options.project || process.env.GOOGLE_CLOUD_PROJECT || null;
190
+ this.location = options.location || process.env.GOOGLE_CLOUD_LOCATION || 'us-central1';
191
+ this.googleAuthOptions = options.googleAuthOptions || null;
192
+
193
+ // API Key (for Gemini API, not Vertex AI)
183
194
  this.apiKey = options.apiKey !== undefined && options.apiKey !== null ? options.apiKey : GEMINI_API_KEY;
184
- if (!this.apiKey) throw new Error("Missing Gemini API key. Provide via options.apiKey or GEMINI_API_KEY env var.");
195
+
196
+ // Validate authentication - need either API key (for Gemini API) or Vertex AI config
197
+ if (!this.vertexai && !this.apiKey) {
198
+ throw new Error("Missing Gemini API key. Provide via options.apiKey or GEMINI_API_KEY env var. For Vertex AI, set vertexai: true with project and location.");
199
+ }
200
+ if (this.vertexai && !this.project) {
201
+ throw new Error("Vertex AI requires a project ID. Provide via options.project or GOOGLE_CLOUD_PROJECT env var.");
202
+ }
185
203
 
186
204
  // Build chat config, making sure systemInstruction uses the custom instructions
187
205
  this.chatConfig = {
@@ -270,6 +288,12 @@ function AITransformFactory(options = {}) {
270
288
  this.enableGrounding = options.enableGrounding || false;
271
289
  this.groundingConfig = options.groundingConfig || {};
272
290
 
291
+ // Billing labels for cost segmentation
292
+ this.labels = options.labels || {};
293
+ if (Object.keys(this.labels).length > 0 && log.level !== 'silent') {
294
+ log.debug(`Billing labels configured: ${JSON.stringify(this.labels)}`);
295
+ }
296
+
273
297
  if (this.promptKey === this.answerKey) {
274
298
  throw new Error("Source and target keys cannot be the same. Please provide distinct keys.");
275
299
  }
@@ -278,12 +302,33 @@ function AITransformFactory(options = {}) {
278
302
  log.debug(`Creating AI Transformer with model: ${this.modelName}`);
279
303
  log.debug(`Using keys - Source: "${this.promptKey}", Target: "${this.answerKey}", Context: "${this.contextKey}"`);
280
304
  log.debug(`Max output tokens set to: ${this.chatConfig.maxOutputTokens}`);
281
- // Log API key prefix for tracking (first 10 chars only for security)
282
- log.debug(`Using API key: ${this.apiKey.substring(0, 10)}...`);
305
+ // Log authentication method
306
+ if (this.vertexai) {
307
+ log.debug(`Using Vertex AI - Project: ${this.project}, Location: ${this.location}`);
308
+ if (this.googleAuthOptions?.keyFilename) {
309
+ log.debug(`Auth: Service account key file: ${this.googleAuthOptions.keyFilename}`);
310
+ } else if (this.googleAuthOptions?.credentials) {
311
+ log.debug(`Auth: Inline credentials provided`);
312
+ } else {
313
+ log.debug(`Auth: Application Default Credentials (ADC)`);
314
+ }
315
+ } else {
316
+ log.debug(`Using Gemini API with key: ${this.apiKey.substring(0, 10)}...`);
317
+ }
283
318
  log.debug(`Grounding ${this.enableGrounding ? 'ENABLED' : 'DISABLED'} (costs $35/1k queries)`);
284
319
  }
285
320
 
286
- const ai = new GoogleGenAI({ apiKey: this.apiKey });
321
+ // Initialize Google GenAI client with appropriate configuration
322
+ const clientOptions = this.vertexai
323
+ ? {
324
+ vertexai: true,
325
+ project: this.project,
326
+ location: this.location,
327
+ ...(this.googleAuthOptions && { googleAuthOptions: this.googleAuthOptions })
328
+ }
329
+ : { apiKey: this.apiKey };
330
+
331
+ const ai = new GoogleGenAI(clientOptions);
287
332
  this.genAIClient = ai;
288
333
  this.chat = null;
289
334
  }
@@ -303,7 +348,10 @@ async function initChat(force = false) {
303
348
  const chatOptions = {
304
349
  model: this.modelName,
305
350
  // @ts-ignore
306
- config: this.chatConfig,
351
+ config: {
352
+ ...this.chatConfig,
353
+ ...(Object.keys(this.labels).length > 0 && { labels: this.labels })
354
+ },
307
355
  history: [],
308
356
  };
309
357
 
@@ -415,10 +463,15 @@ async function seedWithExamples(examples) {
415
463
  this.chat = await this.genAIClient.chats.create({
416
464
  model: this.modelName,
417
465
  // @ts-ignore
418
- config: this.chatConfig,
466
+ config: {
467
+ ...this.chatConfig,
468
+ ...(Object.keys(this.labels).length > 0 && { labels: this.labels })
469
+ },
419
470
  history: [...currentHistory, ...historyToAdd],
420
471
  });
421
472
 
473
+ // Track example count for clearConversation() and stateless messages
474
+ this.exampleCount = currentHistory.length + historyToAdd.length;
422
475
 
423
476
  const newHistory = this.chat.getHistory();
424
477
  log.debug(`Created new chat session with ${newHistory.length} examples.`);
@@ -436,9 +489,10 @@ async function seedWithExamples(examples) {
436
489
  * No validation or retry logic.
437
490
  * @this {ExportedAPI}
438
491
  * @param {Object|string} sourcePayload - The source payload.
492
+ * @param {Object} [messageOptions] - Optional per-message options (e.g., labels).
439
493
  * @returns {Promise<Object>} - The transformed payload.
440
494
  */
441
- async function rawMessage(sourcePayload) {
495
+ async function rawMessage(sourcePayload, messageOptions = {}) {
442
496
  if (!this.chat) {
443
497
  throw new Error("Chat session not initialized.");
444
498
  }
@@ -447,8 +501,40 @@ async function rawMessage(sourcePayload) {
447
501
  ? sourcePayload
448
502
  : JSON.stringify(sourcePayload, null, 2);
449
503
 
504
+ // Merge instance labels with per-message labels (per-message takes precedence)
505
+ const mergedLabels = { ...this.labels, ...(messageOptions.labels || {}) };
506
+ const hasLabels = Object.keys(mergedLabels).length > 0;
507
+
450
508
  try {
451
- const result = await this.chat.sendMessage({ message: actualPayload });
509
+ const sendParams = { message: actualPayload };
510
+
511
+ // Add config with labels if we have any
512
+ if (hasLabels) {
513
+ sendParams.config = { labels: mergedLabels };
514
+ }
515
+
516
+ const result = await this.chat.sendMessage(sendParams);
517
+
518
+ // Capture and log response metadata for model verification and debugging
519
+ this.lastResponseMetadata = {
520
+ modelVersion: result.modelVersion || null,
521
+ requestedModel: this.modelName,
522
+ promptTokens: result.usageMetadata?.promptTokenCount || 0,
523
+ responseTokens: result.usageMetadata?.candidatesTokenCount || 0,
524
+ totalTokens: result.usageMetadata?.totalTokenCount || 0,
525
+ timestamp: Date.now()
526
+ };
527
+
528
+ if (result.usageMetadata && log.level !== 'silent') {
529
+ log.debug(`API response metadata:`, {
530
+ modelVersion: result.modelVersion || 'not-provided',
531
+ requestedModel: this.modelName,
532
+ promptTokens: result.usageMetadata.promptTokenCount,
533
+ responseTokens: result.usageMetadata.candidatesTokenCount,
534
+ totalTokens: result.usageMetadata.totalTokenCount
535
+ });
536
+ }
537
+
452
538
  const modelResponse = result.text;
453
539
  const extractedJSON = extractJSON(modelResponse); // Assuming extractJSON is defined
454
540
 
@@ -479,6 +565,12 @@ async function prepareAndValidateMessage(sourcePayload, options = {}, validatorF
479
565
  if (!this.chat) {
480
566
  throw new Error("Chat session not initialized. Please call init() first.");
481
567
  }
568
+
569
+ // Handle stateless messages separately - they don't add to chat history
570
+ if (options.stateless) {
571
+ return await statelessMessage.call(this, sourcePayload, options, validatorFn);
572
+ }
573
+
482
574
  const maxRetries = options.maxRetries ?? this.maxRetries;
483
575
  const retryDelay = options.retryDelay ?? this.retryDelay;
484
576
 
@@ -542,11 +634,17 @@ async function prepareAndValidateMessage(sourcePayload, options = {}, validatorF
542
634
  throw new Error("Invalid source payload. Must be a JSON object or string.");
543
635
  }
544
636
 
637
+ // Extract per-message labels for passing to rawMessage
638
+ const messageOptions = {};
639
+ if (options.labels) {
640
+ messageOptions.labels = options.labels;
641
+ }
642
+
545
643
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
546
644
  try {
547
645
  // Step 1: Get the transformed payload
548
646
  const transformedPayload = (attempt === 0)
549
- ? await this.rawMessage(lastPayload) // Use the new raw method
647
+ ? await this.rawMessage(lastPayload, messageOptions) // Use the new raw method with per-message options
550
648
  : await this.rebuild(lastPayload, lastError.message);
551
649
 
552
650
  lastPayload = transformedPayload; // Always update lastPayload *before* validation
@@ -590,6 +688,7 @@ async function prepareAndValidateMessage(sourcePayload, options = {}, validatorF
590
688
 
591
689
  /**
592
690
  * Rebuilds a payload based on server error feedback
691
+ * @this {ExportedAPI}
593
692
  * @param {Object} lastPayload - The payload that failed validation
594
693
  * @param {string} serverError - The error message from the server
595
694
  * @returns {Promise<Object>} - A new corrected payload
@@ -615,6 +714,20 @@ Respond with JSON only – no comments or explanations.
615
714
  let result;
616
715
  try {
617
716
  result = await this.chat.sendMessage({ message: prompt });
717
+
718
+ // Capture and log response metadata for rebuild calls too
719
+ this.lastResponseMetadata = {
720
+ modelVersion: result.modelVersion || null,
721
+ requestedModel: this.modelName,
722
+ promptTokens: result.usageMetadata?.promptTokenCount || 0,
723
+ responseTokens: result.usageMetadata?.candidatesTokenCount || 0,
724
+ totalTokens: result.usageMetadata?.totalTokenCount || 0,
725
+ timestamp: Date.now()
726
+ };
727
+
728
+ if (result.usageMetadata && log.level !== 'silent') {
729
+ log.debug(`Rebuild response metadata - tokens used:`, result.usageMetadata.totalTokenCount);
730
+ }
618
731
  } catch (err) {
619
732
  throw new Error(`Gemini call failed while repairing payload: ${err.message}`);
620
733
  }
@@ -670,6 +783,37 @@ async function estimateTokenUsage(nextPayload) {
670
783
  return resp; // includes totalTokens, possibly breakdown
671
784
  }
672
785
 
786
+ // Model pricing per million tokens (as of Dec 2025)
787
+ // https://ai.google.dev/gemini-api/docs/pricing
788
+ const MODEL_PRICING = {
789
+ 'gemini-2.5-flash': { input: 0.15, output: 0.60 },
790
+ 'gemini-2.5-flash-lite': { input: 0.02, output: 0.10 },
791
+ 'gemini-2.5-pro': { input: 2.50, output: 10.00 },
792
+ 'gemini-3-pro': { input: 2.00, output: 12.00 },
793
+ 'gemini-3-pro-preview': { input: 2.00, output: 12.00 },
794
+ 'gemini-2.0-flash': { input: 0.10, output: 0.40 },
795
+ 'gemini-2.0-flash-lite': { input: 0.02, output: 0.10 }
796
+ };
797
+
798
+ /**
799
+ * Estimates the cost of sending a payload based on token count and model pricing.
800
+ * @this {ExportedAPI}
801
+ * @param {object|string} nextPayload - The next user message to be sent (object or string)
802
+ * @returns {Promise<Object>} - Cost estimation including tokens, model, pricing, and estimated input cost
803
+ */
804
+ async function estimateCost(nextPayload) {
805
+ const tokenInfo = await this.estimateTokenUsage(nextPayload);
806
+ const pricing = MODEL_PRICING[this.modelName] || { input: 0, output: 0 };
807
+
808
+ return {
809
+ totalTokens: tokenInfo.totalTokens,
810
+ model: this.modelName,
811
+ pricing: pricing,
812
+ estimatedInputCost: (tokenInfo.totalTokens / 1_000_000) * pricing.input,
813
+ note: 'Cost is for input tokens only; output cost depends on response length'
814
+ };
815
+ }
816
+
673
817
 
674
818
  /**
675
819
  * Resets the current chat session, clearing all history and examples
@@ -684,7 +828,10 @@ async function resetChat() {
684
828
  const chatOptions = {
685
829
  model: this.modelName,
686
830
  // @ts-ignore
687
- config: this.chatConfig,
831
+ config: {
832
+ ...this.chatConfig,
833
+ ...(Object.keys(this.labels).length > 0 && { labels: this.labels })
834
+ },
688
835
  history: [],
689
836
  };
690
837
 
@@ -733,6 +880,110 @@ async function updateSystemInstructions(newInstructions) {
733
880
  await this.init(true); // Force reinitialize with new instructions
734
881
  }
735
882
 
883
+ /**
884
+ * Clears conversation history while preserving seeded examples.
885
+ * Useful for starting a fresh conversation within the same session
886
+ * without losing the few-shot learning examples.
887
+ * @this {ExportedAPI}
888
+ * @returns {Promise<void>}
889
+ */
890
+ async function clearConversation() {
891
+ if (!this.chat) {
892
+ log.warn("Cannot clear conversation: chat not initialized.");
893
+ return;
894
+ }
895
+
896
+ const history = this.chat.getHistory();
897
+ const exampleHistory = history.slice(0, this.exampleCount || 0);
898
+
899
+ this.chat = await this.genAIClient.chats.create({
900
+ model: this.modelName,
901
+ // @ts-ignore
902
+ config: {
903
+ ...this.chatConfig,
904
+ ...(Object.keys(this.labels).length > 0 && { labels: this.labels })
905
+ },
906
+ history: exampleHistory,
907
+ });
908
+
909
+ log.debug(`Conversation cleared. Preserved ${exampleHistory.length} example items.`);
910
+ }
911
+
912
+ /**
913
+ * Sends a one-off message using generateContent (not chat).
914
+ * Does NOT affect chat history - useful for isolated requests.
915
+ * @this {ExportedAPI}
916
+ * @param {Object|string} sourcePayload - The source payload.
917
+ * @param {Object} [options] - Options including labels.
918
+ * @param {AsyncValidatorFunction|null} [validatorFn] - Optional validator.
919
+ * @returns {Promise<Object>} - The transformed payload.
920
+ */
921
+ async function statelessMessage(sourcePayload, options = {}, validatorFn = null) {
922
+ if (!this.chat) {
923
+ throw new Error("Chat session not initialized. Please call init() first.");
924
+ }
925
+
926
+ const payloadStr = typeof sourcePayload === 'string'
927
+ ? sourcePayload
928
+ : JSON.stringify(sourcePayload, null, 2);
929
+
930
+ // Build contents including examples from current chat history
931
+ const contents = [];
932
+
933
+ // Include seeded examples if we have them
934
+ if (this.exampleCount > 0) {
935
+ const history = this.chat.getHistory();
936
+ const exampleHistory = history.slice(0, this.exampleCount);
937
+ contents.push(...exampleHistory);
938
+ }
939
+
940
+ // Add the user message
941
+ contents.push({ role: 'user', parts: [{ text: payloadStr }] });
942
+
943
+ // Merge labels
944
+ const mergedLabels = { ...this.labels, ...(options.labels || {}) };
945
+
946
+ // Use generateContent instead of chat.sendMessage
947
+ const result = await this.genAIClient.models.generateContent({
948
+ model: this.modelName,
949
+ contents: contents,
950
+ config: {
951
+ ...this.chatConfig,
952
+ ...(Object.keys(mergedLabels).length > 0 && { labels: mergedLabels })
953
+ }
954
+ });
955
+
956
+ // Capture and log response metadata
957
+ this.lastResponseMetadata = {
958
+ modelVersion: result.modelVersion || null,
959
+ requestedModel: this.modelName,
960
+ promptTokens: result.usageMetadata?.promptTokenCount || 0,
961
+ responseTokens: result.usageMetadata?.candidatesTokenCount || 0,
962
+ totalTokens: result.usageMetadata?.totalTokenCount || 0,
963
+ timestamp: Date.now()
964
+ };
965
+
966
+ if (result.usageMetadata && log.level !== 'silent') {
967
+ log.debug(`Stateless message metadata:`, {
968
+ modelVersion: result.modelVersion || 'not-provided',
969
+ promptTokens: result.usageMetadata.promptTokenCount,
970
+ responseTokens: result.usageMetadata.candidatesTokenCount
971
+ });
972
+ }
973
+
974
+ const modelResponse = result.text;
975
+ const extractedJSON = extractJSON(modelResponse);
976
+
977
+ let transformedPayload = extractedJSON?.data ? extractedJSON.data : extractedJSON;
978
+
979
+ // Validate if a validator is provided
980
+ if (validatorFn) {
981
+ await validatorFn(transformedPayload);
982
+ }
983
+
984
+ return transformedPayload;
985
+ }
986
+
736
987
 
737
988
  /*
738
989
  ----
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.10",
5
+ "version": "1.0.12",
6
6
  "main": "index.js",
7
7
  "files": [
8
8
  "index.js",
package/types.d.ts CHANGED
@@ -26,9 +26,31 @@ export interface ChatConfig {
26
26
  safetySettings?: SafetySetting[]; // Safety settings array
27
27
  responseSchema?: Object; // Schema for validating model responses
28
28
  thinkingConfig?: ThinkingConfig; // Thinking features configuration
29
+ labels?: Record<string, string>; // Labels for billing segmentation
30
+ tools?: any[]; // Tools configuration (e.g., grounding)
29
31
  [key: string]: any; // Additional properties for flexibility
30
32
  }
31
33
 
34
+ /** Metadata from the last API response, useful for debugging and cost tracking */
35
+ export interface ResponseMetadata {
36
+ modelVersion: string | null; // The actual model version that responded
37
+ requestedModel: string; // The model that was requested
38
+ promptTokens: number; // Number of tokens in the prompt
39
+ responseTokens: number; // Number of tokens in the response
40
+ totalTokens: number; // Total tokens used
41
+ timestamp: number; // Timestamp of when the response was received
42
+ }
43
+
44
+ /** Options for per-message configuration */
45
+ export interface MessageOptions {
46
+ labels?: Record<string, string>; // Per-message billing labels
47
+ stateless?: boolean; // If true, send message without affecting chat history
48
+ maxRetries?: number; // Override max retries for this message
49
+ retryDelay?: number; // Override retry delay for this message
50
+ enableGrounding?: boolean; // Override grounding setting for this message
51
+ groundingConfig?: Record<string, any>; // Override grounding config for this message
52
+ }
53
+
32
54
  export interface AITransformerContext {
33
55
  modelName?: string;
34
56
  systemInstructions?: string;
@@ -45,15 +67,19 @@ export interface AITransformerContext {
45
67
  maxRetries?: number;
46
68
  retryDelay?: number;
47
69
  init?: (force?: boolean) => Promise<void>; // Initialization function
48
- seed?: () => Promise<void>; // Function to seed the transformer with examples
49
- message?: (payload: Record<string, unknown>) => Promise<Record<string, unknown>>; // Function to send messages to the model
70
+ seed?: () => Promise<void>; // Function to seed the transformer with examples
71
+ message?: (payload: Record<string, unknown>, opts?: MessageOptions, validatorFn?: AsyncValidatorFunction | null) => Promise<Record<string, unknown>>; // Function to send messages to the model
50
72
  rebuild?: (lastPayload: Record<string, unknown>, serverError: string) => Promise<Record<string, unknown>>; // Function to rebuild the transformer
51
- rawMessage?: (payload: Record<string, unknown> | string) => Promise<Record<string, unknown>>; // Function to send raw messages to the model
73
+ rawMessage?: (payload: Record<string, unknown> | string, messageOptions?: { labels?: Record<string, string> }) => Promise<Record<string, unknown>>; // Function to send raw messages to the model
52
74
  genAIClient?: GoogleGenAI; // Google GenAI client instance
53
75
  onlyJSON?: boolean; // If true, only JSON responses are allowed
54
76
  enableGrounding?: boolean; // Enable Google Search grounding (default: false, WARNING: costs $35/1k queries)
55
77
  groundingConfig?: Record<string, any>; // Additional grounding configuration options
56
-
78
+ labels?: Record<string, string>; // Custom labels for billing segmentation (keys: 1-63 chars lowercase, values: max 63 chars)
79
+ estimateTokenUsage?: (nextPayload: Record<string, unknown> | string) => Promise<{ totalTokens: number; breakdown?: any }>;
80
+ lastResponseMetadata?: ResponseMetadata | null; // Metadata from the last API response
81
+ exampleCount?: number; // Number of example history items from seed()
82
+ clearConversation?: () => Promise<void>; // Clears conversation history while preserving examples
57
83
  }
58
84
 
59
85
  export interface TransformationExample {
@@ -71,13 +97,24 @@ export interface ExampleFileContent {
71
97
  examples: TransformationExample[];
72
98
  }
73
99
 
100
+ // Google Auth options for Vertex AI authentication
101
+ // See: https://github.com/googleapis/google-auth-library-nodejs/blob/main/src/auth/googleauth.ts
102
+ export interface GoogleAuthOptions {
103
+ keyFilename?: string; // Path to a .json, .pem, or .p12 key file
104
+ keyFile?: string; // Alias for keyFilename
105
+ credentials?: { client_email?: string; private_key?: string; [key: string]: any }; // Object containing client_email and private_key
106
+ scopes?: string | string[]; // Required scopes for the API request
107
+ projectId?: string; // Your project ID (alias for project)
108
+ universeDomain?: string; // The default service domain for a Cloud universe
109
+ }
110
+
74
111
  export interface AITransformerOptions {
75
112
  // ? https://ai.google.dev/gemini-api/docs/models
76
113
  modelName?: string; // The Gemini model to use
77
114
  systemInstructions?: string; // Custom system instructions for the model
78
115
  chatConfig?: ChatConfig; // Configuration object for the chat session
79
116
  thinkingConfig?: ThinkingConfig; // Thinking features configuration (defaults to thinkingBudget: 0, thinkingLevel: "MINIMAL")
80
- maxOutputTokens?: number; // Maximum number of tokens that can be generated in the response (defaults to 100000)
117
+ maxOutputTokens?: number; // Maximum number of tokens that can be generated in the response (defaults to 50000)
81
118
  examplesFile?: string; // Path to JSON file containing transformation examples
82
119
  exampleData?: TransformationExample[]; // Inline examples to seed the transformer
83
120
  sourceKey?: string; // Key name for source data in examples (alias for promptKey)
@@ -91,12 +128,20 @@ export interface AITransformerOptions {
91
128
  retryDelay?: number; // Initial retry delay in milliseconds
92
129
  // ? https://ai.google.dev/gemini-api/docs/structured-output
93
130
  responseSchema?: Object; // Schema for validating model responses
94
- apiKey?: string; // API key for Google GenAI
131
+ apiKey?: string; // API key for Google GenAI (Gemini API)
95
132
  onlyJSON?: boolean; // If true, only JSON responses are allowed
96
133
  asyncValidator?: AsyncValidatorFunction; // Optional async validator function for response validation
97
134
  logLevel?: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal' | 'none'; // Log level for the logger (defaults to 'info', 'none' disables logging)
98
135
  enableGrounding?: boolean; // Enable Google Search grounding (default: false, WARNING: costs $35/1k queries)
99
136
  groundingConfig?: Record<string, any>; // Additional grounding configuration options
137
+ labels?: Record<string, string>; // Custom labels for billing segmentation
138
+
139
+ // Vertex AI Authentication Options
140
+ // Use these instead of apiKey for Vertex AI with service account authentication
141
+ vertexai?: boolean; // Set to true to use Vertex AI instead of Gemini API
142
+ project?: string; // Google Cloud project ID (required for Vertex AI)
143
+ location?: string; // Google Cloud location/region (e.g., 'us-central1') - required for Vertex AI
144
+ googleAuthOptions?: GoogleAuthOptions; // Authentication options for Vertex AI (keyFilename, credentials, etc.)
100
145
  }
101
146
 
102
147
  // Async validator function type
@@ -126,20 +171,40 @@ export declare class AITransformer {
126
171
  logLevel: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal' | 'none';
127
172
  enableGrounding: boolean;
128
173
  groundingConfig: Record<string, any>;
129
-
174
+ labels: Record<string, string>;
175
+ /** Metadata from the last API response (model version, token counts, etc.) */
176
+ lastResponseMetadata: ResponseMetadata | null;
177
+ /** Number of history items that are seeded examples (used by clearConversation) */
178
+ exampleCount: number;
179
+
130
180
  // Methods
131
181
  init(force?: boolean): Promise<void>;
132
182
  seed(examples?: TransformationExample[]): Promise<any>;
133
- message(payload: Record<string, unknown>, opts?: object, validatorFn?: AsyncValidatorFunction | null): Promise<Record<string, unknown>>;
134
- rawMessage(sourcePayload: Record<string, unknown> | string): Promise<Record<string, unknown> | any>;
135
- transformWithValidation(sourcePayload: Record<string, unknown>, validatorFn: AsyncValidatorFunction, options?: object): Promise<Record<string, unknown>>;
136
- messageAndValidate(sourcePayload: Record<string, unknown>, validatorFn: AsyncValidatorFunction, options?: object): Promise<Record<string, unknown>>;
183
+ /**
184
+ * Send a message to the model.
185
+ * @param payload - The payload to transform
186
+ * @param opts - Options including { stateless: true } to send without affecting history
187
+ * @param validatorFn - Optional validator function
188
+ */
189
+ message(payload: Record<string, unknown>, opts?: MessageOptions, validatorFn?: AsyncValidatorFunction | null): Promise<Record<string, unknown>>;
190
+ rawMessage(sourcePayload: Record<string, unknown> | string, messageOptions?: { labels?: Record<string, string> }): Promise<Record<string, unknown> | any>;
191
+ transformWithValidation(sourcePayload: Record<string, unknown>, validatorFn: AsyncValidatorFunction, options?: MessageOptions): Promise<Record<string, unknown>>;
192
+ messageAndValidate(sourcePayload: Record<string, unknown>, validatorFn: AsyncValidatorFunction, options?: MessageOptions): Promise<Record<string, unknown>>;
137
193
  rebuild(lastPayload: Record<string, unknown>, serverError: string): Promise<Record<string, unknown>>;
138
194
  reset(): Promise<void>;
139
195
  getHistory(): Array<any>;
140
196
  estimateTokenUsage(nextPayload: Record<string, unknown> | string): Promise<{ totalTokens: number; breakdown?: any }>;
141
197
  estimate(nextPayload: Record<string, unknown> | string): Promise<{ totalTokens: number; breakdown?: any }>;
142
198
  updateSystemInstructions(newInstructions: string): Promise<void>;
199
+ estimateCost(nextPayload: Record<string, unknown> | string): Promise<{
200
+ totalTokens: number;
201
+ model: string;
202
+ pricing: { input: number; output: number };
203
+ estimatedInputCost: number;
204
+ note: string;
205
+ }>;
206
+ /** Clears conversation history while preserving seeded examples */
207
+ clearConversation(): Promise<void>;
143
208
  }
144
209
 
145
210
  // Default export