ak-gemini 2.0.7 → 2.1.1
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/GUIDE.md +16 -0
- package/README.md +1 -0
- package/code-agent.js +435 -173
- package/index.cjs +524 -178
- package/package.json +1 -1
- package/tool-agent.js +133 -59
- package/types.d.ts +47 -23
package/package.json
CHANGED
package/tool-agent.js
CHANGED
|
@@ -13,6 +13,31 @@ import log from './logger.js';
|
|
|
13
13
|
* @typedef {import('./types').AgentStreamEvent} AgentStreamEvent
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Execute async task factories with a concurrency limit.
|
|
18
|
+
* @param {Array<() => Promise<any>>} tasks
|
|
19
|
+
* @param {number} concurrency - Infinity for unlimited, 1 for sequential
|
|
20
|
+
* @returns {Promise<any[]>} Results in same order as tasks
|
|
21
|
+
*/
|
|
22
|
+
async function runWithConcurrency(tasks, concurrency) {
|
|
23
|
+
if (concurrency === Infinity) return Promise.all(tasks.map(t => t()));
|
|
24
|
+
if (concurrency === 1) {
|
|
25
|
+
const results = [];
|
|
26
|
+
for (const t of tasks) results.push(await t());
|
|
27
|
+
return results;
|
|
28
|
+
}
|
|
29
|
+
const results = new Array(tasks.length);
|
|
30
|
+
let next = 0;
|
|
31
|
+
async function worker() {
|
|
32
|
+
while (next < tasks.length) {
|
|
33
|
+
const i = next++;
|
|
34
|
+
results[i] = await tasks[i]();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, tasks.length) }, () => worker()));
|
|
38
|
+
return results;
|
|
39
|
+
}
|
|
40
|
+
|
|
16
41
|
/**
|
|
17
42
|
* AI agent that uses user-provided tools to accomplish tasks.
|
|
18
43
|
* Automatically manages the tool-use loop: when the model decides to call
|
|
@@ -75,6 +100,13 @@ class ToolAgent extends BaseGemini {
|
|
|
75
100
|
throw new Error("ToolAgent: toolExecutor provided without tools. Provide tool declarations so the model knows what tools are available.");
|
|
76
101
|
}
|
|
77
102
|
|
|
103
|
+
// ── Parallel execution ──
|
|
104
|
+
this.parallelToolCalls = options.parallelToolCalls ?? true;
|
|
105
|
+
/** @private */
|
|
106
|
+
this._concurrency = this.parallelToolCalls === true ? Infinity
|
|
107
|
+
: this.parallelToolCalls === false ? 1
|
|
108
|
+
: this.parallelToolCalls;
|
|
109
|
+
|
|
78
110
|
// ── Tool loop config ──
|
|
79
111
|
this.maxToolRounds = options.maxToolRounds || 10;
|
|
80
112
|
this.onToolCall = options.onToolCall || null;
|
|
@@ -116,41 +148,39 @@ class ToolAgent extends BaseGemini {
|
|
|
116
148
|
const functionCalls = response.functionCalls;
|
|
117
149
|
if (!functionCalls || functionCalls.length === 0) break;
|
|
118
150
|
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
151
|
+
const tasks = functionCalls.map(call => async () => {
|
|
152
|
+
// Fire onToolCall callback
|
|
153
|
+
if (this.onToolCall) {
|
|
154
|
+
try { this.onToolCall(call.name, call.args); }
|
|
155
|
+
catch (e) { log.warn(`onToolCall callback error: ${e.message}`); }
|
|
156
|
+
}
|
|
126
157
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
return { id: call.id, name: call.name, result };
|
|
135
|
-
}
|
|
136
|
-
} catch (e) {
|
|
137
|
-
log.warn(`onBeforeExecution callback error: ${e.message}`);
|
|
158
|
+
// Check onBeforeExecution gate
|
|
159
|
+
if (this.onBeforeExecution) {
|
|
160
|
+
try {
|
|
161
|
+
const allowed = await this.onBeforeExecution(call.name, call.args);
|
|
162
|
+
if (allowed === false) {
|
|
163
|
+
const result = { error: 'Execution denied by onBeforeExecution callback' };
|
|
164
|
+
return { id: call.id, name: call.name, args: call.args, result };
|
|
138
165
|
}
|
|
166
|
+
} catch (e) {
|
|
167
|
+
log.warn(`onBeforeExecution callback error: ${e.message}`);
|
|
139
168
|
}
|
|
169
|
+
}
|
|
140
170
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
171
|
+
let result;
|
|
172
|
+
try {
|
|
173
|
+
result = await this.toolExecutor(call.name, call.args);
|
|
174
|
+
} catch (err) {
|
|
175
|
+
log.warn(`Tool ${call.name} failed: ${err.message}`);
|
|
176
|
+
result = { error: err.message };
|
|
177
|
+
}
|
|
148
178
|
|
|
149
|
-
|
|
179
|
+
return { id: call.id, name: call.name, args: call.args, result };
|
|
180
|
+
});
|
|
150
181
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
);
|
|
182
|
+
const toolResults = await runWithConcurrency(tasks, this._concurrency);
|
|
183
|
+
for (const r of toolResults) allToolCalls.push({ name: r.name, args: r.args, result: r.result });
|
|
154
184
|
|
|
155
185
|
// Send function responses back to the model
|
|
156
186
|
response = await this._withRetry(() => this.chatSession.sendMessage({
|
|
@@ -234,46 +264,90 @@ class ToolAgent extends BaseGemini {
|
|
|
234
264
|
return;
|
|
235
265
|
}
|
|
236
266
|
|
|
237
|
-
// Execute tools
|
|
267
|
+
// Execute tools and yield events
|
|
238
268
|
const toolResults = [];
|
|
239
|
-
|
|
240
|
-
|
|
269
|
+
if (this._concurrency === 1) {
|
|
270
|
+
// Sequential: yield tool_call, execute, yield tool_result for each
|
|
271
|
+
for (const call of functionCalls) {
|
|
272
|
+
if (this._stopped) break;
|
|
241
273
|
|
|
242
|
-
|
|
274
|
+
yield { type: 'tool_call', toolName: call.name, args: call.args };
|
|
243
275
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
276
|
+
if (this.onToolCall) {
|
|
277
|
+
try { this.onToolCall(call.name, call.args); }
|
|
278
|
+
catch (e) { log.warn(`onToolCall callback error: ${e.message}`); }
|
|
279
|
+
}
|
|
249
280
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
281
|
+
let denied = false;
|
|
282
|
+
if (this.onBeforeExecution) {
|
|
283
|
+
try {
|
|
284
|
+
const allowed = await this.onBeforeExecution(call.name, call.args);
|
|
285
|
+
if (allowed === false) denied = true;
|
|
286
|
+
} catch (e) {
|
|
287
|
+
log.warn(`onBeforeExecution callback error: ${e.message}`);
|
|
288
|
+
}
|
|
258
289
|
}
|
|
259
|
-
}
|
|
260
290
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
291
|
+
let result;
|
|
292
|
+
if (denied) {
|
|
293
|
+
result = { error: 'Execution denied by onBeforeExecution callback' };
|
|
294
|
+
} else {
|
|
295
|
+
try {
|
|
296
|
+
result = await this.toolExecutor(call.name, call.args);
|
|
297
|
+
} catch (err) {
|
|
298
|
+
log.warn(`Tool ${call.name} failed: ${err.message}`);
|
|
299
|
+
result = { error: err.message };
|
|
300
|
+
}
|
|
270
301
|
}
|
|
302
|
+
|
|
303
|
+
allToolCalls.push({ name: call.name, args: call.args, result });
|
|
304
|
+
yield { type: 'tool_result', toolName: call.name, result };
|
|
305
|
+
|
|
306
|
+
toolResults.push({ id: call.id, name: call.name, result });
|
|
307
|
+
}
|
|
308
|
+
} else {
|
|
309
|
+
// Parallel: yield all tool_call events, execute all, yield all tool_result events
|
|
310
|
+
for (const call of functionCalls) {
|
|
311
|
+
yield { type: 'tool_call', toolName: call.name, args: call.args };
|
|
271
312
|
}
|
|
272
313
|
|
|
273
|
-
|
|
274
|
-
|
|
314
|
+
const tasks = functionCalls.map(call => async () => {
|
|
315
|
+
if (this.onToolCall) {
|
|
316
|
+
try { this.onToolCall(call.name, call.args); }
|
|
317
|
+
catch (e) { log.warn(`onToolCall callback error: ${e.message}`); }
|
|
318
|
+
}
|
|
275
319
|
|
|
276
|
-
|
|
320
|
+
let denied = false;
|
|
321
|
+
if (this.onBeforeExecution) {
|
|
322
|
+
try {
|
|
323
|
+
const allowed = await this.onBeforeExecution(call.name, call.args);
|
|
324
|
+
if (allowed === false) denied = true;
|
|
325
|
+
} catch (e) {
|
|
326
|
+
log.warn(`onBeforeExecution callback error: ${e.message}`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
let result;
|
|
331
|
+
if (denied) {
|
|
332
|
+
result = { error: 'Execution denied by onBeforeExecution callback' };
|
|
333
|
+
} else {
|
|
334
|
+
try {
|
|
335
|
+
result = await this.toolExecutor(call.name, call.args);
|
|
336
|
+
} catch (err) {
|
|
337
|
+
log.warn(`Tool ${call.name} failed: ${err.message}`);
|
|
338
|
+
result = { error: err.message };
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return { id: call.id, name: call.name, args: call.args, result };
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
const results = await runWithConcurrency(tasks, this._concurrency);
|
|
346
|
+
for (const r of results) {
|
|
347
|
+
allToolCalls.push({ name: r.name, args: r.args, result: r.result });
|
|
348
|
+
yield { type: 'tool_result', toolName: r.name, result: r.result };
|
|
349
|
+
toolResults.push({ id: r.id, name: r.name, result: r.result });
|
|
350
|
+
}
|
|
277
351
|
}
|
|
278
352
|
|
|
279
353
|
// Send function responses back and get next stream
|
package/types.d.ts
CHANGED
|
@@ -279,6 +279,8 @@ export interface ToolAgentOptions extends BaseGeminiOptions {
|
|
|
279
279
|
onBeforeExecution?: (toolName: string, args: Record<string, any>) => Promise<boolean>;
|
|
280
280
|
/** Directory for tool-written files (pass-through for toolExecutor use) */
|
|
281
281
|
writeDir?: string;
|
|
282
|
+
/** Parallel tool execution: false = sequential, true = unlimited parallel, number = concurrency limit (default: true) */
|
|
283
|
+
parallelToolCalls?: boolean | number;
|
|
282
284
|
}
|
|
283
285
|
|
|
284
286
|
export interface LocalDataEntry {
|
|
@@ -304,9 +306,9 @@ export interface CodeAgentOptions extends BaseGeminiOptions {
|
|
|
304
306
|
maxRounds?: number;
|
|
305
307
|
/** Per-execution timeout in milliseconds (default: 30000) */
|
|
306
308
|
timeout?: number;
|
|
307
|
-
/** Async callback before code execution; return false to deny */
|
|
308
|
-
onBeforeExecution?: (
|
|
309
|
-
/** Notification callback after code execution */
|
|
309
|
+
/** Async callback before code/bash execution; return false to deny. Receives (content, toolName). */
|
|
310
|
+
onBeforeExecution?: (content: string, toolName: string) => Promise<boolean> | boolean;
|
|
311
|
+
/** Notification callback after code/bash execution */
|
|
310
312
|
onCodeExecution?: (code: string, output: { stdout: string; stderr: string; exitCode: number }) => void;
|
|
311
313
|
/** Files whose contents are included in the system prompt for project context */
|
|
312
314
|
importantFiles?: string[];
|
|
@@ -318,6 +320,10 @@ export interface CodeAgentOptions extends BaseGeminiOptions {
|
|
|
318
320
|
comments?: boolean;
|
|
319
321
|
/** Max consecutive failed executions before stopping (default: 3) */
|
|
320
322
|
maxRetries?: number;
|
|
323
|
+
/** Paths to skill files (markdown) loaded dynamically via the use_skill tool */
|
|
324
|
+
skills?: string[];
|
|
325
|
+
/** Plain text environment overview appended to the system prompt — describe the project, stack, conventions, etc. */
|
|
326
|
+
envOverview?: string;
|
|
321
327
|
}
|
|
322
328
|
|
|
323
329
|
export interface CodeExecution {
|
|
@@ -333,35 +339,57 @@ export interface CodeExecution {
|
|
|
333
339
|
exitCode: number;
|
|
334
340
|
}
|
|
335
341
|
|
|
342
|
+
export interface ToolCallResult {
|
|
343
|
+
tool: 'write_code' | 'execute_code' | 'write_and_run_code' | 'fix_code' | 'run_bash' | 'use_skill';
|
|
344
|
+
code?: string;
|
|
345
|
+
purpose?: string;
|
|
346
|
+
language?: string;
|
|
347
|
+
originalCode?: string;
|
|
348
|
+
fixedCode?: string;
|
|
349
|
+
explanation?: string;
|
|
350
|
+
executed?: boolean;
|
|
351
|
+
command?: string;
|
|
352
|
+
skillName?: string;
|
|
353
|
+
content?: string;
|
|
354
|
+
found?: boolean;
|
|
355
|
+
stdout?: string;
|
|
356
|
+
stderr?: string;
|
|
357
|
+
exitCode?: number;
|
|
358
|
+
denied?: boolean;
|
|
359
|
+
}
|
|
360
|
+
|
|
336
361
|
export interface CodeAgentResponse {
|
|
337
362
|
/** The agent's final text response */
|
|
338
363
|
text: string;
|
|
339
|
-
/**
|
|
364
|
+
/** Backward-compatible: only code executions (execute_code, write_and_run_code, fix_code with execute) */
|
|
340
365
|
codeExecutions: CodeExecution[];
|
|
366
|
+
/** All tool calls made during this chat turn */
|
|
367
|
+
toolCalls: ToolCallResult[];
|
|
341
368
|
/** Token usage data */
|
|
342
369
|
usage: UsageData | null;
|
|
343
370
|
}
|
|
344
371
|
|
|
345
372
|
export interface CodeAgentStreamEvent {
|
|
346
|
-
type: 'text' | 'code' | 'output' | 'done';
|
|
347
|
-
/** For 'text' events: the text chunk */
|
|
373
|
+
type: 'text' | 'code' | 'output' | 'write' | 'fix' | 'bash' | 'skill' | 'done';
|
|
348
374
|
text?: string;
|
|
349
|
-
/** For 'code' events: the code about to be executed */
|
|
350
375
|
code?: string;
|
|
351
|
-
/** For 'output' events: stdout from execution */
|
|
352
376
|
stdout?: string;
|
|
353
|
-
/** For 'output' events: stderr from execution */
|
|
354
377
|
stderr?: string;
|
|
355
|
-
/** For 'output' events: process exit code */
|
|
356
378
|
exitCode?: number;
|
|
357
|
-
/** For 'done' events: complete accumulated text */
|
|
358
379
|
fullText?: string;
|
|
359
|
-
/** For 'done' events: all code executions */
|
|
360
380
|
codeExecutions?: CodeExecution[];
|
|
361
|
-
|
|
381
|
+
toolCalls?: ToolCallResult[];
|
|
362
382
|
usage?: UsageData | null;
|
|
363
|
-
/** For 'done' events: e.g. "Max tool rounds reached" or "Agent was stopped" */
|
|
364
383
|
warning?: string;
|
|
384
|
+
purpose?: string;
|
|
385
|
+
language?: string;
|
|
386
|
+
originalCode?: string;
|
|
387
|
+
fixedCode?: string;
|
|
388
|
+
explanation?: string;
|
|
389
|
+
command?: string;
|
|
390
|
+
skillName?: string;
|
|
391
|
+
content?: string;
|
|
392
|
+
found?: boolean;
|
|
365
393
|
}
|
|
366
394
|
|
|
367
395
|
// ── Per-Message Options ──────────────────────────────────────────────────────
|
|
@@ -539,6 +567,7 @@ export declare class ToolAgent extends BaseGemini {
|
|
|
539
567
|
onBeforeExecution: ((toolName: string, args: Record<string, any>) => Promise<boolean>) | null;
|
|
540
568
|
/** Directory for tool-written files (pass-through for toolExecutor use) */
|
|
541
569
|
writeDir: string | null;
|
|
570
|
+
parallelToolCalls: boolean | number;
|
|
542
571
|
|
|
543
572
|
chat(message: string, opts?: { labels?: Record<string, string> }): Promise<AgentResponse>;
|
|
544
573
|
stream(message: string, opts?: { labels?: Record<string, string> }): AsyncGenerator<AgentStreamEvent, void, unknown>;
|
|
@@ -579,25 +608,20 @@ export declare class CodeAgent extends BaseGemini {
|
|
|
579
608
|
workingDirectory: string;
|
|
580
609
|
maxRounds: number;
|
|
581
610
|
timeout: number;
|
|
582
|
-
onBeforeExecution: ((
|
|
611
|
+
onBeforeExecution: ((content: string, toolName: string) => Promise<boolean> | boolean) | null;
|
|
583
612
|
onCodeExecution: ((code: string, output: { stdout: string; stderr: string; exitCode: number }) => void) | null;
|
|
584
|
-
/** Files whose contents are included in the system prompt */
|
|
585
613
|
importantFiles: string[];
|
|
586
|
-
/** Directory for writing script files */
|
|
587
614
|
writeDir: string;
|
|
588
|
-
/** Keep script files on disk after execution */
|
|
589
615
|
keepArtifacts: boolean;
|
|
590
|
-
/** Whether the model writes comments in generated code */
|
|
591
616
|
comments: boolean;
|
|
592
|
-
/** Max consecutive failed executions before stopping */
|
|
593
617
|
maxRetries: number;
|
|
618
|
+
skills: string[];
|
|
619
|
+
envOverview: string;
|
|
594
620
|
|
|
595
621
|
init(force?: boolean): Promise<void>;
|
|
596
622
|
chat(message: string, opts?: { labels?: Record<string, string> }): Promise<CodeAgentResponse>;
|
|
597
623
|
stream(message: string, opts?: { labels?: Record<string, string> }): AsyncGenerator<CodeAgentStreamEvent, void, unknown>;
|
|
598
|
-
|
|
599
|
-
dump(): Array<{ fileName: string; purpose: string | null; script: string; filePath: string | null }>;
|
|
600
|
-
/** Stop the agent before the next code execution. Kills any running child process. */
|
|
624
|
+
dump(): Array<{ fileName: string; purpose: string | null; script: string; filePath: string | null; tool: string }>;
|
|
601
625
|
stop(): void;
|
|
602
626
|
}
|
|
603
627
|
|