coder-agent 2.6.0 → 2.6.2
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/dist/agent.js +104 -7
- package/dist/index.js +24 -2
- package/dist/tools.js +19 -4
- package/package.json +1 -1
package/dist/agent.js
CHANGED
|
@@ -276,6 +276,104 @@ async function callGeminiAPIWithRotation(apiKey, params, maxRetries = 3, initial
|
|
|
276
276
|
err.status = res.status;
|
|
277
277
|
throw err;
|
|
278
278
|
}
|
|
279
|
+
if (params.stream) {
|
|
280
|
+
const decoder = new TextDecoder();
|
|
281
|
+
let buffer = "";
|
|
282
|
+
let accumulatedContent = "";
|
|
283
|
+
let accumulatedToolCalls = [];
|
|
284
|
+
if (!silent) {
|
|
285
|
+
stopSpinner();
|
|
286
|
+
}
|
|
287
|
+
const processChunk = (chunk) => {
|
|
288
|
+
buffer += decoder.decode(chunk, { stream: true });
|
|
289
|
+
const lines = buffer.split("\n");
|
|
290
|
+
buffer = lines.pop() || "";
|
|
291
|
+
for (const line of lines) {
|
|
292
|
+
const trimmed = line.trim();
|
|
293
|
+
if (!trimmed)
|
|
294
|
+
continue;
|
|
295
|
+
if (trimmed === "data: [DONE]")
|
|
296
|
+
break;
|
|
297
|
+
if (trimmed.startsWith("data: ")) {
|
|
298
|
+
try {
|
|
299
|
+
const parsed = JSON.parse(trimmed.slice(6));
|
|
300
|
+
const choice = parsed.choices?.[0];
|
|
301
|
+
if (!choice)
|
|
302
|
+
continue;
|
|
303
|
+
const content = choice.delta?.content;
|
|
304
|
+
if (content) {
|
|
305
|
+
accumulatedContent += content;
|
|
306
|
+
if (!silent) {
|
|
307
|
+
process.stdout.write(content);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
const toolCalls = choice.delta?.tool_calls;
|
|
311
|
+
if (toolCalls) {
|
|
312
|
+
for (const tc of toolCalls) {
|
|
313
|
+
const idx = tc.index ?? 0;
|
|
314
|
+
if (!accumulatedToolCalls[idx]) {
|
|
315
|
+
accumulatedToolCalls[idx] = {
|
|
316
|
+
id: tc.id || "",
|
|
317
|
+
type: "function",
|
|
318
|
+
function: { name: "", arguments: "" }
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
if (tc.id)
|
|
322
|
+
accumulatedToolCalls[idx].id = tc.id;
|
|
323
|
+
if (tc.function?.name) {
|
|
324
|
+
accumulatedToolCalls[idx].function.name += tc.function.name;
|
|
325
|
+
}
|
|
326
|
+
if (tc.function?.arguments) {
|
|
327
|
+
accumulatedToolCalls[idx].function.arguments += tc.function.arguments;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
catch (e) {
|
|
333
|
+
// Ignore partial JSON parse errors
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
const bodyStream = res.body;
|
|
339
|
+
if (bodyStream && typeof bodyStream[Symbol.asyncIterator] === "function") {
|
|
340
|
+
for await (const chunk of bodyStream) {
|
|
341
|
+
if (signal?.aborted) {
|
|
342
|
+
const abortErr = new Error("The user aborted a request.");
|
|
343
|
+
abortErr.name = "AbortError";
|
|
344
|
+
throw abortErr;
|
|
345
|
+
}
|
|
346
|
+
processChunk(chunk);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
else if (bodyStream && typeof bodyStream.getReader === "function") {
|
|
350
|
+
const reader = bodyStream.getReader();
|
|
351
|
+
while (true) {
|
|
352
|
+
if (signal?.aborted) {
|
|
353
|
+
const abortErr = new Error("The user aborted a request.");
|
|
354
|
+
abortErr.name = "AbortError";
|
|
355
|
+
throw abortErr;
|
|
356
|
+
}
|
|
357
|
+
const { done, value } = await reader.read();
|
|
358
|
+
if (done)
|
|
359
|
+
break;
|
|
360
|
+
if (value)
|
|
361
|
+
processChunk(value);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
const finalResponse = {
|
|
365
|
+
choices: [
|
|
366
|
+
{
|
|
367
|
+
message: {
|
|
368
|
+
role: "assistant",
|
|
369
|
+
content: accumulatedContent,
|
|
370
|
+
tool_calls: accumulatedToolCalls.length > 0 ? accumulatedToolCalls.filter(Boolean) : undefined
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
]
|
|
374
|
+
};
|
|
375
|
+
return { data: finalResponse, modelUsed: currentModel };
|
|
376
|
+
}
|
|
279
377
|
const data = await res.json();
|
|
280
378
|
return { data, modelUsed: currentModel };
|
|
281
379
|
}
|
|
@@ -331,11 +429,13 @@ export class Agent {
|
|
|
331
429
|
memory;
|
|
332
430
|
model;
|
|
333
431
|
memoryScope;
|
|
334
|
-
|
|
432
|
+
confirmHandler;
|
|
433
|
+
constructor(apiKey, model = "gemini-2.5-flash", memoryScope = "project", confirmHandler) {
|
|
335
434
|
this.apiKey = apiKey;
|
|
336
435
|
this.memory = new Memory();
|
|
337
436
|
this.model = model;
|
|
338
437
|
this.memoryScope = memoryScope;
|
|
438
|
+
this.confirmHandler = confirmHandler;
|
|
339
439
|
}
|
|
340
440
|
clearMemory() {
|
|
341
441
|
this.memory.clear();
|
|
@@ -449,6 +549,7 @@ export class Agent {
|
|
|
449
549
|
tools: TOOL_DEFINITIONS,
|
|
450
550
|
tool_choice: "auto",
|
|
451
551
|
temperature: 0.2,
|
|
552
|
+
stream: true,
|
|
452
553
|
}, 3, 1500, signal);
|
|
453
554
|
stopSpinner();
|
|
454
555
|
if (signal?.aborted) {
|
|
@@ -516,7 +617,7 @@ export class Agent {
|
|
|
516
617
|
modifiedFiles.add(path.normalize(args.file_path));
|
|
517
618
|
}
|
|
518
619
|
}
|
|
519
|
-
const result = await dispatchTool(name, args, signal);
|
|
620
|
+
const result = await dispatchTool(name, args, this.confirmHandler, signal);
|
|
520
621
|
if (signal?.aborted) {
|
|
521
622
|
const abortErr = new Error("The user aborted a request.");
|
|
522
623
|
abortErr.name = "AbortError";
|
|
@@ -558,11 +659,7 @@ export class Agent {
|
|
|
558
659
|
console.log(chalk.dim('─') + ' ' + chalk.dim(`${modifiedFiles.size} file(s) modified`));
|
|
559
660
|
}
|
|
560
661
|
// ── Phase 3: Final Agent Response ───────────────────────────────────────
|
|
561
|
-
|
|
562
|
-
if (cleanContent) {
|
|
563
|
-
console.log(formatResponseText(cleanContent));
|
|
564
|
-
console.log(""); // one trailing blank line
|
|
565
|
-
}
|
|
662
|
+
// Content has already been streamed in real-time.
|
|
566
663
|
if (iterations >= MAX_ITERATIONS) {
|
|
567
664
|
console.log(chalk.hex('#ff453a')('✕ error'));
|
|
568
665
|
console.log(chalk.dim(' Max tool iterations reached.'));
|
package/dist/index.js
CHANGED
|
@@ -114,6 +114,7 @@ async function promptApiKey() {
|
|
|
114
114
|
}
|
|
115
115
|
// ─── Main Execution ───────────────────────────────────────────────────────────
|
|
116
116
|
async function main() {
|
|
117
|
+
let rl;
|
|
117
118
|
const args = process.argv.slice(2);
|
|
118
119
|
let tempModel;
|
|
119
120
|
let tempMemoryScope;
|
|
@@ -182,7 +183,28 @@ async function main() {
|
|
|
182
183
|
if (!apiKey) {
|
|
183
184
|
apiKey = await promptApiKey();
|
|
184
185
|
}
|
|
185
|
-
const
|
|
186
|
+
const confirmHandler = async (question) => {
|
|
187
|
+
if (rl) {
|
|
188
|
+
return new Promise((resolve) => {
|
|
189
|
+
rl.question(question, (answer) => {
|
|
190
|
+
resolve(answer.trim().toLowerCase().startsWith("y"));
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
const rlConfirm = readline.createInterface({
|
|
196
|
+
input: process.stdin,
|
|
197
|
+
output: process.stdout,
|
|
198
|
+
});
|
|
199
|
+
return new Promise((resolve) => {
|
|
200
|
+
rlConfirm.question(question, (answer) => {
|
|
201
|
+
rlConfirm.close();
|
|
202
|
+
resolve(answer.trim().toLowerCase().startsWith("y"));
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
const agent = new Agent(apiKey, modelToUse, tempMemoryScope, confirmHandler);
|
|
186
208
|
// Single-Shot Mode
|
|
187
209
|
if (queryArgs.length > 0) {
|
|
188
210
|
const singleShotPrompt = queryArgs.join(" ").trim();
|
|
@@ -206,7 +228,7 @@ async function main() {
|
|
|
206
228
|
}
|
|
207
229
|
// Interactive REPL Mode
|
|
208
230
|
printBanner(modelToUse);
|
|
209
|
-
|
|
231
|
+
rl = readline.createInterface({
|
|
210
232
|
input: process.stdin,
|
|
211
233
|
output: process.stdout,
|
|
212
234
|
terminal: true,
|
package/dist/tools.js
CHANGED
|
@@ -212,7 +212,7 @@ export async function list_directory({ dir_path = "." }) {
|
|
|
212
212
|
return `ERROR: ${e.message}`;
|
|
213
213
|
}
|
|
214
214
|
}
|
|
215
|
-
export async function run_shell({ command, cwd }, signal) {
|
|
215
|
+
export async function run_shell({ command, cwd }, confirmHandler, signal) {
|
|
216
216
|
try {
|
|
217
217
|
let targetCwd = process.cwd();
|
|
218
218
|
if (cwd) {
|
|
@@ -234,7 +234,22 @@ export async function run_shell({ command, cwd }, signal) {
|
|
|
234
234
|
if (cwd) {
|
|
235
235
|
console.log(` in directory: ${chalk.gray(cwd)}`);
|
|
236
236
|
}
|
|
237
|
-
|
|
237
|
+
let allowed = false;
|
|
238
|
+
if (confirmHandler) {
|
|
239
|
+
allowed = await confirmHandler(` Allow execution? (y/N): `);
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
const rlConfirm = readline.createInterface({
|
|
243
|
+
input: process.stdin,
|
|
244
|
+
output: process.stdout,
|
|
245
|
+
});
|
|
246
|
+
allowed = await new Promise((resolve) => {
|
|
247
|
+
rlConfirm.question(` Allow execution? (y/N): `, (answer) => {
|
|
248
|
+
rlConfirm.close();
|
|
249
|
+
resolve(answer.trim().toLowerCase().startsWith("y"));
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
}
|
|
238
253
|
if (!allowed) {
|
|
239
254
|
return "ERROR: Command execution denied by user.";
|
|
240
255
|
}
|
|
@@ -392,12 +407,12 @@ export async function patch_file({ file_path, target_code, replacement_code }) {
|
|
|
392
407
|
}
|
|
393
408
|
}
|
|
394
409
|
// ─── Dispatcher ──────────────────────────────────────────────────────────────
|
|
395
|
-
export async function dispatchTool(name, args, signal) {
|
|
410
|
+
export async function dispatchTool(name, args, confirmHandler, signal) {
|
|
396
411
|
switch (name) {
|
|
397
412
|
case "read_file": return read_file(args);
|
|
398
413
|
case "write_file": return write_file(args);
|
|
399
414
|
case "list_directory": return list_directory(args);
|
|
400
|
-
case "run_shell": return run_shell(args, signal);
|
|
415
|
+
case "run_shell": return run_shell(args, confirmHandler, signal);
|
|
401
416
|
case "web_search": return web_search(args, signal);
|
|
402
417
|
case "find_files": return find_files(args);
|
|
403
418
|
case "read_file_lines": return read_file_lines(args);
|