ak-gemini 2.1.1 → 2.1.5

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/chat.js CHANGED
@@ -9,6 +9,7 @@ import log from './logger.js';
9
9
  /**
10
10
  * @typedef {import('./types').ChatOptions} ChatOptions
11
11
  * @typedef {import('./types').ChatResponse} ChatResponse
12
+ * @typedef {import('./types').ChatStreamEvent} ChatStreamEvent
12
13
  */
13
14
 
14
15
  /**
@@ -82,6 +83,34 @@ class Chat extends BaseGemini {
82
83
  usage: this.getLastUsage()
83
84
  };
84
85
  }
86
+
87
+ /**
88
+ * Send a message and stream the response as events.
89
+ *
90
+ * @param {string} message - The user's message
91
+ * @param {Object} [opts={}] - Per-message options
92
+ * @yields {ChatStreamEvent}
93
+ */
94
+ async *stream(message, opts = {}) {
95
+ if (!this.chatSession) await this.init();
96
+
97
+ let fullText = '';
98
+ const streamResponse = await this._withRetry(() => this.chatSession.sendMessageStream({ message }));
99
+
100
+ for await (const chunk of streamResponse) {
101
+ if (chunk.candidates?.[0]?.content?.parts?.[0]?.text) {
102
+ const text = chunk.candidates[0].content.parts[0].text;
103
+ fullText += text;
104
+ yield { type: 'text', text };
105
+ }
106
+ }
107
+
108
+ yield {
109
+ type: 'done',
110
+ fullText,
111
+ usage: this.getLastUsage()
112
+ };
113
+ }
85
114
  }
86
115
 
87
116
  export default Chat;
package/code-agent.js CHANGED
@@ -8,7 +8,7 @@ import BaseGemini from './base.js';
8
8
  import log from './logger.js';
9
9
  import { execFile } from 'node:child_process';
10
10
  import { writeFile, unlink, readdir, readFile, mkdir } from 'node:fs/promises';
11
- import { join, sep, basename } from 'node:path';
11
+ import { join, sep, basename, isAbsolute } from 'node:path';
12
12
  import { randomUUID } from 'node:crypto';
13
13
 
14
14
  /**
@@ -49,6 +49,17 @@ class CodeAgent extends BaseGemini {
49
49
  this.skills = options.skills || [];
50
50
  this.envOverview = options.envOverview || '';
51
51
 
52
+ // ── Custom tools ──
53
+ this.customTools = (options.tools || []).map(t => ({
54
+ name: t.name,
55
+ description: t.description,
56
+ parametersJsonSchema: t.parametersJsonSchema || t.parameters || t.input_schema || t.inputSchema
57
+ }));
58
+ this.toolExecutor = options.toolExecutor || null;
59
+ if (this.customTools.length > 0 && !this.toolExecutor) {
60
+ throw new Error('CodeAgent: tools provided without a toolExecutor.');
61
+ }
62
+
52
63
  // ── Internal state ──
53
64
  this._codebaseContext = null;
54
65
  this._contextGathered = false;
@@ -156,6 +167,11 @@ class CodeAgent extends BaseGemini {
156
167
  });
157
168
  }
158
169
 
170
+ // Append custom tools
171
+ for (const t of this.customTools) {
172
+ declarations.push({ name: t.name, description: t.description, parametersJsonSchema: t.parametersJsonSchema });
173
+ }
174
+
159
175
  return { functionDeclarations: declarations };
160
176
  }
161
177
 
@@ -252,7 +268,7 @@ class CodeAgent extends BaseGemini {
252
268
  continue;
253
269
  }
254
270
  try {
255
- const fullPath = join(this.workingDirectory, resolved);
271
+ const fullPath = isAbsolute(resolved) ? resolved : join(this.workingDirectory, resolved);
256
272
  const content = await readFile(fullPath, 'utf-8');
257
273
  importantFileContents.push({ path: resolved, content });
258
274
  } catch (e) {
@@ -269,6 +285,8 @@ class CodeAgent extends BaseGemini {
269
285
  * @private
270
286
  */
271
287
  _resolveImportantFile(filename, fileTreeLines) {
288
+ if (isAbsolute(filename)) return filename;
289
+
272
290
  const exact = fileTreeLines.find(line => line === filename);
273
291
  if (exact) return exact;
274
292
 
@@ -651,12 +669,30 @@ These rules apply when using execute_code, write_and_run_code, or fix_code (with
651
669
  data: { tool: 'use_skill', skillName: skill.name, content: skill.content, found: true }
652
670
  };
653
671
  }
654
- default:
672
+ default: {
673
+ if (this.toolExecutor) {
674
+ try {
675
+ const result = await this.toolExecutor(name, input);
676
+ const resultStr = typeof result === 'string' ? result : JSON.stringify(result);
677
+ return {
678
+ output: resultStr,
679
+ type: 'tool',
680
+ data: { tool: name, args: input, result }
681
+ };
682
+ } catch (err) {
683
+ return {
684
+ output: `Tool "${name}" failed: ${err.message}`,
685
+ type: 'tool',
686
+ data: { tool: name, args: input, error: err.message }
687
+ };
688
+ }
689
+ }
655
690
  return {
656
691
  output: `Unknown tool: ${name}`,
657
692
  type: 'unknown',
658
693
  data: { tool: name }
659
694
  };
695
+ }
660
696
  }
661
697
  }
662
698
 
@@ -859,6 +895,11 @@ These rules apply when using execute_code, write_and_run_code, or fix_code (with
859
895
  yield { type: 'skill', skillName: data.skillName, content: data.content, found: data.found };
860
896
  }
861
897
 
898
+ // Emit custom tool event
899
+ if (type === 'tool') {
900
+ yield { type: 'tool', toolName, args: data.args, result: data.result, error: data.error };
901
+ }
902
+
862
903
  // Track consecutive failures
863
904
  const isExecutingTool = EXECUTING_TOOLS.has(toolName) || (toolName === 'fix_code' && toolInput.execute);
864
905
  if (isExecutingTool) {
package/index.cjs CHANGED
@@ -1216,6 +1216,30 @@ var Chat = class extends base_default {
1216
1216
  usage: this.getLastUsage()
1217
1217
  };
1218
1218
  }
1219
+ /**
1220
+ * Send a message and stream the response as events.
1221
+ *
1222
+ * @param {string} message - The user's message
1223
+ * @param {Object} [opts={}] - Per-message options
1224
+ * @yields {ChatStreamEvent}
1225
+ */
1226
+ async *stream(message, opts = {}) {
1227
+ if (!this.chatSession) await this.init();
1228
+ let fullText = "";
1229
+ const streamResponse = await this._withRetry(() => this.chatSession.sendMessageStream({ message }));
1230
+ for await (const chunk of streamResponse) {
1231
+ if (chunk.candidates?.[0]?.content?.parts?.[0]?.text) {
1232
+ const text = chunk.candidates[0].content.parts[0].text;
1233
+ fullText += text;
1234
+ yield { type: "text", text };
1235
+ }
1236
+ }
1237
+ yield {
1238
+ type: "done",
1239
+ fullText,
1240
+ usage: this.getLastUsage()
1241
+ };
1242
+ }
1219
1243
  };
1220
1244
  var chat_default = Chat;
1221
1245
 
@@ -1625,6 +1649,15 @@ var CodeAgent = class extends base_default {
1625
1649
  this.maxRetries = options.maxRetries ?? 3;
1626
1650
  this.skills = options.skills || [];
1627
1651
  this.envOverview = options.envOverview || "";
1652
+ this.customTools = (options.tools || []).map((t) => ({
1653
+ name: t.name,
1654
+ description: t.description,
1655
+ parametersJsonSchema: t.parametersJsonSchema || t.parameters || t.input_schema || t.inputSchema
1656
+ }));
1657
+ this.toolExecutor = options.toolExecutor || null;
1658
+ if (this.customTools.length > 0 && !this.toolExecutor) {
1659
+ throw new Error("CodeAgent: tools provided without a toolExecutor.");
1660
+ }
1628
1661
  this._codebaseContext = null;
1629
1662
  this._contextGathered = false;
1630
1663
  this._stopped = false;
@@ -1722,6 +1755,9 @@ var CodeAgent = class extends base_default {
1722
1755
  }
1723
1756
  });
1724
1757
  }
1758
+ for (const t of this.customTools) {
1759
+ declarations.push({ name: t.name, description: t.description, parametersJsonSchema: t.parametersJsonSchema });
1760
+ }
1725
1761
  return { functionDeclarations: declarations };
1726
1762
  }
1727
1763
  // ── Init ─────────────────────────────────────────────────────────────────
@@ -1799,7 +1835,7 @@ var CodeAgent = class extends base_default {
1799
1835
  continue;
1800
1836
  }
1801
1837
  try {
1802
- const fullPath = (0, import_node_path.join)(this.workingDirectory, resolved);
1838
+ const fullPath = (0, import_node_path.isAbsolute)(resolved) ? resolved : (0, import_node_path.join)(this.workingDirectory, resolved);
1803
1839
  const content = await (0, import_promises2.readFile)(fullPath, "utf-8");
1804
1840
  importantFileContents.push({ path: resolved, content });
1805
1841
  } catch (e) {
@@ -1814,6 +1850,7 @@ var CodeAgent = class extends base_default {
1814
1850
  * @private
1815
1851
  */
1816
1852
  _resolveImportantFile(filename, fileTreeLines) {
1853
+ if ((0, import_node_path.isAbsolute)(filename)) return filename;
1817
1854
  const exact = fileTreeLines.find((line) => line === filename);
1818
1855
  if (exact) return exact;
1819
1856
  const partial = fileTreeLines.find(
@@ -2210,12 +2247,30 @@ ${this.envOverview}`;
2210
2247
  data: { tool: "use_skill", skillName: skill.name, content: skill.content, found: true }
2211
2248
  };
2212
2249
  }
2213
- default:
2250
+ default: {
2251
+ if (this.toolExecutor) {
2252
+ try {
2253
+ const result = await this.toolExecutor(name, input);
2254
+ const resultStr = typeof result === "string" ? result : JSON.stringify(result);
2255
+ return {
2256
+ output: resultStr,
2257
+ type: "tool",
2258
+ data: { tool: name, args: input, result }
2259
+ };
2260
+ } catch (err) {
2261
+ return {
2262
+ output: `Tool "${name}" failed: ${err.message}`,
2263
+ type: "tool",
2264
+ data: { tool: name, args: input, error: err.message }
2265
+ };
2266
+ }
2267
+ }
2214
2268
  return {
2215
2269
  output: `Unknown tool: ${name}`,
2216
2270
  type: "unknown",
2217
2271
  data: { tool: name }
2218
2272
  };
2273
+ }
2219
2274
  }
2220
2275
  }
2221
2276
  // ── Non-Streaming Chat ───────────────────────────────────────────────────
@@ -2372,6 +2427,9 @@ ${this.envOverview}`;
2372
2427
  if (toolName === "use_skill") {
2373
2428
  yield { type: "skill", skillName: data.skillName, content: data.content, found: data.found };
2374
2429
  }
2430
+ if (type === "tool") {
2431
+ yield { type: "tool", toolName, args: data.args, result: data.result, error: data.error };
2432
+ }
2375
2433
  const isExecutingTool = EXECUTING_TOOLS.has(toolName) || toolName === "fix_code" && toolInput.execute;
2376
2434
  if (isExecutingTool) {
2377
2435
  if (data.exitCode !== 0 && !data.denied) {
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... everything",
5
- "version": "2.1.1",
5
+ "version": "2.1.5",
6
6
  "main": "index.js",
7
7
  "files": [
8
8
  "index.js",
package/types.d.ts CHANGED
@@ -324,6 +324,17 @@ export interface CodeAgentOptions extends BaseGeminiOptions {
324
324
  skills?: string[];
325
325
  /** Plain text environment overview appended to the system prompt — describe the project, stack, conventions, etc. */
326
326
  envOverview?: string;
327
+ /** Custom tool declarations to add alongside built-in CodeAgent tools. Accepts Gemini, Claude, or OpenAI tool formats (auto-mapped). */
328
+ tools?: Array<{
329
+ name: string;
330
+ description: string;
331
+ parametersJsonSchema?: any;
332
+ parameters?: any;
333
+ input_schema?: any;
334
+ inputSchema?: any;
335
+ }>;
336
+ /** Function to execute custom tool calls: (toolName, args) => result */
337
+ toolExecutor?: (toolName: string, args: Record<string, any>) => Promise<any>;
327
338
  }
328
339
 
329
340
  export interface CodeExecution {
@@ -340,7 +351,7 @@ export interface CodeExecution {
340
351
  }
341
352
 
342
353
  export interface ToolCallResult {
343
- tool: 'write_code' | 'execute_code' | 'write_and_run_code' | 'fix_code' | 'run_bash' | 'use_skill';
354
+ tool: 'write_code' | 'execute_code' | 'write_and_run_code' | 'fix_code' | 'run_bash' | 'use_skill' | string;
344
355
  code?: string;
345
356
  purpose?: string;
346
357
  language?: string;
@@ -370,7 +381,7 @@ export interface CodeAgentResponse {
370
381
  }
371
382
 
372
383
  export interface CodeAgentStreamEvent {
373
- type: 'text' | 'code' | 'output' | 'write' | 'fix' | 'bash' | 'skill' | 'done';
384
+ type: 'text' | 'code' | 'output' | 'write' | 'fix' | 'bash' | 'skill' | 'tool' | 'done';
374
385
  text?: string;
375
386
  code?: string;
376
387
  stdout?: string;
@@ -390,6 +401,14 @@ export interface CodeAgentStreamEvent {
390
401
  skillName?: string;
391
402
  content?: string;
392
403
  found?: boolean;
404
+ /** custom tool: tool name */
405
+ toolName?: string;
406
+ /** custom tool: arguments passed */
407
+ args?: Record<string, any>;
408
+ /** custom tool: result returned */
409
+ result?: any;
410
+ /** custom tool: error message (if failed) */
411
+ error?: string;
393
412
  }
394
413
 
395
414
  // ── Per-Message Options ──────────────────────────────────────────────────────
@@ -421,6 +440,13 @@ export interface ChatResponse {
421
440
  usage: UsageData | null;
422
441
  }
423
442
 
443
+ export interface ChatStreamEvent {
444
+ type: 'text' | 'done';
445
+ text?: string;
446
+ fullText?: string;
447
+ usage?: UsageData | null;
448
+ }
449
+
424
450
  export interface MessageResponse {
425
451
  /** The model's text response */
426
452
  text: string;
@@ -548,6 +574,7 @@ export declare class Chat extends BaseGemini {
548
574
  constructor(options?: ChatOptions);
549
575
 
550
576
  send(message: string, opts?: { labels?: Record<string, string> }): Promise<ChatResponse>;
577
+ stream(message: string, opts?: { labels?: Record<string, string> }): AsyncGenerator<ChatStreamEvent, void, unknown>;
551
578
  }
552
579
 
553
580
  export declare class Message extends BaseGemini {
@@ -617,6 +644,8 @@ export declare class CodeAgent extends BaseGemini {
617
644
  maxRetries: number;
618
645
  skills: string[];
619
646
  envOverview: string;
647
+ customTools: Array<{ name: string; description: string; parametersJsonSchema: any }>;
648
+ toolExecutor: ((toolName: string, args: Record<string, any>) => Promise<any>) | null;
620
649
 
621
650
  init(force?: boolean): Promise<void>;
622
651
  chat(message: string, opts?: { labels?: Record<string, string> }): Promise<CodeAgentResponse>;