ak-gemini 1.1.13 → 2.0.0
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 +259 -294
- package/base.js +485 -0
- package/chat.js +87 -0
- package/code-agent.js +563 -0
- package/index.cjs +1596 -789
- package/index.js +38 -1500
- package/json-helpers.js +352 -0
- package/message.js +170 -0
- package/package.json +23 -14
- package/tool-agent.js +311 -0
- package/transformer.js +502 -0
- package/types.d.ts +376 -189
package/json-helpers.js
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Pure utility functions for JSON extraction and recovery.
|
|
3
|
+
* Used by Transformer and Message classes to parse AI model responses.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import log from './logger.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Checks if a JavaScript value is a JSON-serializable object or array.
|
|
10
|
+
* @param {*} data - The value to check
|
|
11
|
+
* @returns {boolean}
|
|
12
|
+
*/
|
|
13
|
+
export function isJSON(data) {
|
|
14
|
+
try {
|
|
15
|
+
const attempt = JSON.stringify(data);
|
|
16
|
+
if (attempt?.startsWith('{') || attempt?.startsWith('[')) {
|
|
17
|
+
if (attempt?.endsWith('}') || attempt?.endsWith(']')) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return false;
|
|
22
|
+
} catch (e) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Checks if a string is valid JSON that parses to an object or array.
|
|
29
|
+
* @param {string} string - The string to check
|
|
30
|
+
* @returns {boolean}
|
|
31
|
+
*/
|
|
32
|
+
export function isJSONStr(string) {
|
|
33
|
+
if (typeof string !== 'string') return false;
|
|
34
|
+
try {
|
|
35
|
+
const result = JSON.parse(string);
|
|
36
|
+
const type = Object.prototype.toString.call(result);
|
|
37
|
+
return type === '[object Object]' || type === '[object Array]';
|
|
38
|
+
} catch (err) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Attempts to recover truncated JSON by progressively removing characters from the end
|
|
45
|
+
* until valid JSON is found or recovery fails.
|
|
46
|
+
* @param {string} text - The potentially truncated JSON string
|
|
47
|
+
* @param {number} [maxAttempts=100] - Maximum number of characters to remove
|
|
48
|
+
* @returns {Object|null} - Parsed JSON object or null if recovery fails
|
|
49
|
+
*/
|
|
50
|
+
export function attemptJSONRecovery(text, maxAttempts = 100) {
|
|
51
|
+
if (!text || typeof text !== 'string') return null;
|
|
52
|
+
|
|
53
|
+
// First, try parsing as-is
|
|
54
|
+
try {
|
|
55
|
+
return JSON.parse(text);
|
|
56
|
+
} catch (e) {
|
|
57
|
+
// Continue with recovery
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let workingText = text.trim();
|
|
61
|
+
|
|
62
|
+
// First attempt: try to close unclosed structures without removing characters
|
|
63
|
+
let braces = 0;
|
|
64
|
+
let brackets = 0;
|
|
65
|
+
let inString = false;
|
|
66
|
+
let escapeNext = false;
|
|
67
|
+
|
|
68
|
+
for (let j = 0; j < workingText.length; j++) {
|
|
69
|
+
const char = workingText[j];
|
|
70
|
+
|
|
71
|
+
if (escapeNext) {
|
|
72
|
+
escapeNext = false;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (char === '\\') {
|
|
77
|
+
escapeNext = true;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (char === '"') {
|
|
82
|
+
inString = !inString;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!inString) {
|
|
87
|
+
if (char === '{') braces++;
|
|
88
|
+
else if (char === '}') braces--;
|
|
89
|
+
else if (char === '[') brackets++;
|
|
90
|
+
else if (char === ']') brackets--;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Try to fix by just adding closing characters
|
|
95
|
+
if ((braces > 0 || brackets > 0 || inString) && workingText.length > 2) {
|
|
96
|
+
let fixedText = workingText;
|
|
97
|
+
|
|
98
|
+
if (inString) {
|
|
99
|
+
fixedText += '"';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
while (braces > 0) {
|
|
103
|
+
fixedText += '}';
|
|
104
|
+
braces--;
|
|
105
|
+
}
|
|
106
|
+
while (brackets > 0) {
|
|
107
|
+
fixedText += ']';
|
|
108
|
+
brackets--;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const result = JSON.parse(fixedText);
|
|
113
|
+
if (log.level !== 'silent') {
|
|
114
|
+
log.warn(`JSON response appears truncated (possibly hit maxOutputTokens limit). Recovered by adding closing characters.`);
|
|
115
|
+
}
|
|
116
|
+
return result;
|
|
117
|
+
} catch (e) {
|
|
118
|
+
// Simple fix didn't work, continue with more aggressive recovery
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Second attempt: progressively remove characters from the end
|
|
123
|
+
for (let i = 0; i < maxAttempts && workingText.length > 2; i++) {
|
|
124
|
+
workingText = workingText.slice(0, -1);
|
|
125
|
+
|
|
126
|
+
let braces = 0;
|
|
127
|
+
let brackets = 0;
|
|
128
|
+
let inString = false;
|
|
129
|
+
let escapeNext = false;
|
|
130
|
+
|
|
131
|
+
for (let j = 0; j < workingText.length; j++) {
|
|
132
|
+
const char = workingText[j];
|
|
133
|
+
|
|
134
|
+
if (escapeNext) {
|
|
135
|
+
escapeNext = false;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (char === '\\') {
|
|
140
|
+
escapeNext = true;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (char === '"') {
|
|
145
|
+
inString = !inString;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!inString) {
|
|
150
|
+
if (char === '{') braces++;
|
|
151
|
+
else if (char === '}') braces--;
|
|
152
|
+
else if (char === '[') brackets++;
|
|
153
|
+
else if (char === ']') brackets--;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// If we have balanced braces/brackets, try parsing
|
|
158
|
+
if (braces === 0 && brackets === 0 && !inString) {
|
|
159
|
+
try {
|
|
160
|
+
const result = JSON.parse(workingText);
|
|
161
|
+
if (log.level !== 'silent') {
|
|
162
|
+
log.warn(`JSON response appears truncated (possibly hit maxOutputTokens limit). Recovered by removing ${i + 1} characters from the end.`);
|
|
163
|
+
}
|
|
164
|
+
return result;
|
|
165
|
+
} catch (e) {
|
|
166
|
+
// Continue trying
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// After a few attempts, try adding closing characters
|
|
171
|
+
if (i > 5) {
|
|
172
|
+
let fixedText = workingText;
|
|
173
|
+
|
|
174
|
+
if (inString) {
|
|
175
|
+
fixedText += '"';
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
while (braces > 0) {
|
|
179
|
+
fixedText += '}';
|
|
180
|
+
braces--;
|
|
181
|
+
}
|
|
182
|
+
while (brackets > 0) {
|
|
183
|
+
fixedText += ']';
|
|
184
|
+
brackets--;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const result = JSON.parse(fixedText);
|
|
189
|
+
if (log.level !== 'silent') {
|
|
190
|
+
log.warn(`JSON response appears truncated (possibly hit maxOutputTokens limit). Recovered by adding closing characters.`);
|
|
191
|
+
}
|
|
192
|
+
return result;
|
|
193
|
+
} catch (e) {
|
|
194
|
+
// Recovery failed, continue trying
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Extracts a complete JSON structure from text starting at a given position
|
|
204
|
+
* using bracket/brace matching.
|
|
205
|
+
* @param {string} text - The text containing JSON
|
|
206
|
+
* @param {number} startPos - Position of the opening bracket/brace
|
|
207
|
+
* @returns {string|null} - The complete JSON structure or null
|
|
208
|
+
*/
|
|
209
|
+
function extractCompleteStructure(text, startPos) {
|
|
210
|
+
const startChar = text[startPos];
|
|
211
|
+
const endChar = startChar === '{' ? '}' : ']';
|
|
212
|
+
let depth = 0;
|
|
213
|
+
let inString = false;
|
|
214
|
+
let escaped = false;
|
|
215
|
+
|
|
216
|
+
for (let i = startPos; i < text.length; i++) {
|
|
217
|
+
const char = text[i];
|
|
218
|
+
|
|
219
|
+
if (escaped) {
|
|
220
|
+
escaped = false;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (char === '\\' && inString) {
|
|
225
|
+
escaped = true;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (char === '"' && !escaped) {
|
|
230
|
+
inString = !inString;
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (!inString) {
|
|
235
|
+
if (char === startChar) {
|
|
236
|
+
depth++;
|
|
237
|
+
} else if (char === endChar) {
|
|
238
|
+
depth--;
|
|
239
|
+
if (depth === 0) {
|
|
240
|
+
return text.substring(startPos, i + 1);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Finds all complete JSON structures (objects and arrays) in text.
|
|
251
|
+
* @param {string} text - The text to search
|
|
252
|
+
* @returns {string[]} - Array of JSON structure strings
|
|
253
|
+
*/
|
|
254
|
+
function findCompleteJSONStructures(text) {
|
|
255
|
+
const results = [];
|
|
256
|
+
const startChars = ['{', '['];
|
|
257
|
+
|
|
258
|
+
for (let i = 0; i < text.length; i++) {
|
|
259
|
+
if (startChars.includes(text[i])) {
|
|
260
|
+
const extracted = extractCompleteStructure(text, i);
|
|
261
|
+
if (extracted) {
|
|
262
|
+
results.push(extracted);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return results;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Extracts valid JSON from model response text using multiple strategies.
|
|
272
|
+
* @param {string} text - The model response text
|
|
273
|
+
* @returns {Object} - Parsed JSON object
|
|
274
|
+
* @throws {Error} If no valid JSON can be extracted
|
|
275
|
+
*/
|
|
276
|
+
export function extractJSON(text) {
|
|
277
|
+
if (!text || typeof text !== 'string') {
|
|
278
|
+
throw new Error('No text provided for JSON extraction');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Strategy 1: Try parsing the entire response as JSON
|
|
282
|
+
if (isJSONStr(text.trim())) {
|
|
283
|
+
return JSON.parse(text.trim());
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Strategy 2: Look for JSON code blocks (```json...``` or ```...```)
|
|
287
|
+
const codeBlockPatterns = [
|
|
288
|
+
/```json\s*\n?([\s\S]*?)\n?\s*```/gi,
|
|
289
|
+
/```\s*\n?([\s\S]*?)\n?\s*```/gi
|
|
290
|
+
];
|
|
291
|
+
|
|
292
|
+
for (const pattern of codeBlockPatterns) {
|
|
293
|
+
const matches = text.match(pattern);
|
|
294
|
+
if (matches) {
|
|
295
|
+
for (const match of matches) {
|
|
296
|
+
const jsonContent = match.replace(/```json\s*\n?/gi, '').replace(/```\s*\n?/gi, '').trim();
|
|
297
|
+
if (isJSONStr(jsonContent)) {
|
|
298
|
+
return JSON.parse(jsonContent);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Strategy 3: Look for JSON objects/arrays using bracket matching
|
|
305
|
+
const jsonPatterns = [
|
|
306
|
+
/\{[\s\S]*\}/g,
|
|
307
|
+
/\[[\s\S]*\]/g
|
|
308
|
+
];
|
|
309
|
+
|
|
310
|
+
for (const pattern of jsonPatterns) {
|
|
311
|
+
const matches = text.match(pattern);
|
|
312
|
+
if (matches) {
|
|
313
|
+
for (const match of matches) {
|
|
314
|
+
const candidate = match.trim();
|
|
315
|
+
if (isJSONStr(candidate)) {
|
|
316
|
+
return JSON.parse(candidate);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Strategy 4: Advanced bracket matching for nested structures
|
|
323
|
+
const advancedExtract = findCompleteJSONStructures(text);
|
|
324
|
+
if (advancedExtract.length > 0) {
|
|
325
|
+
for (const candidate of advancedExtract) {
|
|
326
|
+
if (isJSONStr(candidate)) {
|
|
327
|
+
return JSON.parse(candidate);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Strategy 5: Clean up common formatting issues and retry
|
|
333
|
+
const cleanedText = text
|
|
334
|
+
.replace(/^\s*Sure,?\s*here\s+is\s+your?\s+.*?[:\n]/gi, '')
|
|
335
|
+
.replace(/^\s*Here\s+is\s+the\s+.*?[:\n]/gi, '')
|
|
336
|
+
.replace(/^\s*The\s+.*?is\s*[:\n]/gi, '')
|
|
337
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
338
|
+
.replace(/\/\/.*$/gm, '')
|
|
339
|
+
.trim();
|
|
340
|
+
|
|
341
|
+
if (isJSONStr(cleanedText)) {
|
|
342
|
+
return JSON.parse(cleanedText);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Strategy 6: Last resort - attempt recovery for potentially truncated JSON
|
|
346
|
+
const recoveredJSON = attemptJSONRecovery(text);
|
|
347
|
+
if (recoveredJSON !== null) {
|
|
348
|
+
return recoveredJSON;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
throw new Error(`Could not extract valid JSON from model response. Response preview: ${text.substring(0, 200)}...`);
|
|
352
|
+
}
|
package/message.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Message class — stateless one-off messages to AI.
|
|
3
|
+
* Uses generateContent() instead of chat sessions. No conversation history.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import BaseGemini from './base.js';
|
|
7
|
+
import { extractJSON } from './json-helpers.js';
|
|
8
|
+
import log from './logger.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {import('./types').MessageOptions} MessageOptions
|
|
12
|
+
* @typedef {import('./types').MessageResponse} MessageResponse
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Stateless one-off messages to AI.
|
|
17
|
+
* Each send() call is independent — no conversation history is maintained.
|
|
18
|
+
* Uses generateContent() directly instead of chat sessions.
|
|
19
|
+
*
|
|
20
|
+
* Optionally returns structured data when responseSchema or
|
|
21
|
+
* responseMimeType: 'application/json' is configured.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```javascript
|
|
25
|
+
* import { Message } from 'ak-gemini';
|
|
26
|
+
*
|
|
27
|
+
* // Simple text response
|
|
28
|
+
* const msg = new Message({
|
|
29
|
+
* systemPrompt: 'You are a helpful assistant.'
|
|
30
|
+
* });
|
|
31
|
+
* const r = await msg.send('What is the capital of France?');
|
|
32
|
+
* console.log(r.text); // "The capital of France is Paris."
|
|
33
|
+
*
|
|
34
|
+
* // Structured JSON response
|
|
35
|
+
* const jsonMsg = new Message({
|
|
36
|
+
* systemPrompt: 'Extract entities from text.',
|
|
37
|
+
* responseMimeType: 'application/json'
|
|
38
|
+
* });
|
|
39
|
+
* const r2 = await jsonMsg.send('Alice works at Acme Corp in New York.');
|
|
40
|
+
* console.log(r2.data); // { entities: [...] }
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
class Message extends BaseGemini {
|
|
44
|
+
/**
|
|
45
|
+
* @param {MessageOptions} [options={}]
|
|
46
|
+
*/
|
|
47
|
+
constructor(options = {}) {
|
|
48
|
+
super(options);
|
|
49
|
+
|
|
50
|
+
// ── Structured output config ──
|
|
51
|
+
if (options.responseSchema) {
|
|
52
|
+
this.chatConfig.responseSchema = options.responseSchema;
|
|
53
|
+
}
|
|
54
|
+
if (options.responseMimeType) {
|
|
55
|
+
this.chatConfig.responseMimeType = options.responseMimeType;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
this._isStructured = !!(options.responseSchema || options.responseMimeType === 'application/json');
|
|
59
|
+
|
|
60
|
+
log.debug(`Message created (structured=${this._isStructured})`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Initialize the Message client.
|
|
65
|
+
* Override: creates genAIClient only, NO chat session (stateless).
|
|
66
|
+
* @param {boolean} [force=false]
|
|
67
|
+
* @returns {Promise<void>}
|
|
68
|
+
*/
|
|
69
|
+
async init(force = false) {
|
|
70
|
+
if (this._initialized && !force) return;
|
|
71
|
+
|
|
72
|
+
log.debug(`Initializing ${this.constructor.name} with model: ${this.modelName}...`);
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
await this.genAIClient.models.list();
|
|
76
|
+
log.debug(`${this.constructor.name}: API connection successful.`);
|
|
77
|
+
} catch (e) {
|
|
78
|
+
throw new Error(`${this.constructor.name} initialization failed: ${e.message}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this._initialized = true;
|
|
82
|
+
log.debug(`${this.constructor.name}: Initialized (stateless mode).`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Send a stateless message and get a response.
|
|
87
|
+
* Each call is independent — no history is maintained.
|
|
88
|
+
*
|
|
89
|
+
* @param {Object|string} payload - The message or data to send
|
|
90
|
+
* @param {Object} [opts={}] - Per-message options
|
|
91
|
+
* @param {Record<string, string>} [opts.labels] - Per-message billing labels
|
|
92
|
+
* @returns {Promise<MessageResponse>} Response with text, optional data, and usage
|
|
93
|
+
*/
|
|
94
|
+
async send(payload, opts = {}) {
|
|
95
|
+
if (!this._initialized) await this.init();
|
|
96
|
+
|
|
97
|
+
const payloadStr = typeof payload === 'string'
|
|
98
|
+
? payload
|
|
99
|
+
: JSON.stringify(payload, null, 2);
|
|
100
|
+
|
|
101
|
+
const contents = [{ role: 'user', parts: [{ text: payloadStr }] }];
|
|
102
|
+
|
|
103
|
+
const mergedLabels = { ...this.labels, ...(opts.labels || {}) };
|
|
104
|
+
|
|
105
|
+
const result = await this.genAIClient.models.generateContent({
|
|
106
|
+
model: this.modelName,
|
|
107
|
+
contents: contents,
|
|
108
|
+
config: {
|
|
109
|
+
...this.chatConfig,
|
|
110
|
+
...(this.vertexai && Object.keys(mergedLabels).length > 0 && { labels: mergedLabels })
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
this._captureMetadata(result);
|
|
115
|
+
|
|
116
|
+
this._cumulativeUsage = {
|
|
117
|
+
promptTokens: this.lastResponseMetadata.promptTokens,
|
|
118
|
+
responseTokens: this.lastResponseMetadata.responseTokens,
|
|
119
|
+
totalTokens: this.lastResponseMetadata.totalTokens,
|
|
120
|
+
attempts: 1
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
if (result.usageMetadata && log.level !== 'silent') {
|
|
124
|
+
log.debug(`Message response: model=${result.modelVersion || 'unknown'}, tokens=${result.usageMetadata.totalTokenCount}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const text = result.text || '';
|
|
128
|
+
const response = {
|
|
129
|
+
text,
|
|
130
|
+
usage: this.getLastUsage()
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// Parse structured data if configured
|
|
134
|
+
if (this._isStructured) {
|
|
135
|
+
try {
|
|
136
|
+
response.data = extractJSON(text);
|
|
137
|
+
} catch (e) {
|
|
138
|
+
log.warn(`Could not parse structured response: ${e.message}`);
|
|
139
|
+
response.data = null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return response;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ── No-ops for stateless class ──
|
|
147
|
+
|
|
148
|
+
/** @returns {Array} Always returns empty array (stateless). */
|
|
149
|
+
getHistory() { return []; }
|
|
150
|
+
|
|
151
|
+
/** No-op (stateless). */
|
|
152
|
+
async clearHistory() { }
|
|
153
|
+
|
|
154
|
+
/** Not supported on Message (stateless). */
|
|
155
|
+
async seed() {
|
|
156
|
+
log.warn("Message is stateless — seed() has no effect. Use Transformer or Chat for few-shot learning.");
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Not supported on Message (stateless).
|
|
162
|
+
* @param {any} [_nextPayload]
|
|
163
|
+
* @returns {Promise<{inputTokens: number}>}
|
|
164
|
+
*/
|
|
165
|
+
async estimate(_nextPayload) {
|
|
166
|
+
throw new Error("Message is stateless — use estimate() on Chat or Transformer which have conversation context.");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export default Message;
|
package/package.json
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ak-gemini",
|
|
3
3
|
"author": "ak@mixpanel.com",
|
|
4
|
-
"description": "AK's Generative AI Helper for doing...
|
|
5
|
-
"version": "
|
|
4
|
+
"description": "AK's Generative AI Helper for doing... everything",
|
|
5
|
+
"version": "2.0.0",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"files": [
|
|
8
8
|
"index.js",
|
|
9
9
|
"index.cjs",
|
|
10
|
+
"base.js",
|
|
11
|
+
"transformer.js",
|
|
12
|
+
"chat.js",
|
|
13
|
+
"message.js",
|
|
14
|
+
"tool-agent.js",
|
|
15
|
+
"code-agent.js",
|
|
16
|
+
"json-helpers.js",
|
|
10
17
|
"types.d.ts",
|
|
11
18
|
"logger.js"
|
|
12
19
|
],
|
|
@@ -38,7 +45,7 @@
|
|
|
38
45
|
"update-deps": "npx npm-check-updates -u && npm install",
|
|
39
46
|
"prune": "rm -rf tmp/*",
|
|
40
47
|
"test": "node --no-warnings --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
41
|
-
"build:cjs": "esbuild index.js --bundle --platform=node --format=cjs --outfile=index.cjs --external:@google/genai --external:
|
|
48
|
+
"build:cjs": "esbuild index.js --bundle --platform=node --format=cjs --outfile=index.cjs --external:@google/genai --external:dotenv --external:pino-pretty --external:pino",
|
|
42
49
|
"coverage": "node --no-warnings --experimental-vm-modules node_modules/jest/bin/jest.js --coverage",
|
|
43
50
|
"typecheck": "tsc --noEmit",
|
|
44
51
|
"update:gemini": "npm install @google/genai@latest"
|
|
@@ -47,21 +54,23 @@
|
|
|
47
54
|
"keywords": [
|
|
48
55
|
"gemini",
|
|
49
56
|
"ai wrapper",
|
|
50
|
-
"json transform"
|
|
57
|
+
"json transform",
|
|
58
|
+
"chat",
|
|
59
|
+
"agent",
|
|
60
|
+
"tool agent"
|
|
51
61
|
],
|
|
52
62
|
"license": "ISC",
|
|
53
63
|
"dependencies": {
|
|
54
|
-
"@google/genai": "^1.
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"pino": "^
|
|
58
|
-
"pino-pretty": "^13.0.0"
|
|
64
|
+
"@google/genai": "^1.44.0",
|
|
65
|
+
"dotenv": "^17.3.1",
|
|
66
|
+
"pino": "^10.3.1",
|
|
67
|
+
"pino-pretty": "^13.1.3"
|
|
59
68
|
},
|
|
60
69
|
"devDependencies": {
|
|
61
|
-
"@types/jest": "^
|
|
62
|
-
"esbuild": "^0.
|
|
63
|
-
"jest": "^
|
|
64
|
-
"nodemon": "^3.1.
|
|
65
|
-
"typescript": "^5.9.
|
|
70
|
+
"@types/jest": "^30.0.0",
|
|
71
|
+
"esbuild": "^0.27.4",
|
|
72
|
+
"jest": "^30.3.0",
|
|
73
|
+
"nodemon": "^3.1.14",
|
|
74
|
+
"typescript": "^5.9.3"
|
|
66
75
|
}
|
|
67
76
|
}
|