@console-agent/agent 1.1.1 → 1.2.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/dist/index.js CHANGED
@@ -1,8 +1,17 @@
1
1
  import { createGoogleGenerativeAI } from '@ai-sdk/google';
2
- import { jsonSchema, Output, ToolLoopAgent } from 'ai';
2
+ import { jsonSchema, generateText, stepCountIs, Output, ToolLoopAgent } from 'ai';
3
+ import { existsSync, readFileSync } from 'fs';
4
+ import { extname, resolve, basename } from 'path';
3
5
  import chalk from 'chalk';
4
6
  import ora from 'ora';
5
7
 
8
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
9
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
10
+ }) : x)(function(x) {
11
+ if (typeof require !== "undefined") return require.apply(this, arguments);
12
+ throw Error('Dynamic require of "' + x + '" is not supported');
13
+ });
14
+
6
15
  // src/personas/debugger.ts
7
16
  var debuggerPersona = {
8
17
  name: "debugger",
@@ -203,6 +212,168 @@ function detectPersona(prompt, defaultPersona) {
203
212
  function getPersona(name) {
204
213
  return personas[name];
205
214
  }
215
+
216
+ // src/tools/file-analysis.ts
217
+ function prepareFileContent(fileData, mimeType) {
218
+ const base64 = Buffer.from(fileData).toString("base64");
219
+ return {
220
+ type: "file",
221
+ data: base64,
222
+ mimeType
223
+ };
224
+ }
225
+
226
+ // src/tools/index.ts
227
+ var TOOLS_MIN_TIMEOUT = 3e4;
228
+ function resolveTools(tools, google) {
229
+ const resolved = {};
230
+ for (const tool of tools) {
231
+ const name = typeof tool === "string" ? tool : tool.type;
232
+ const config2 = typeof tool === "object" ? tool.config : void 0;
233
+ switch (name) {
234
+ case "google_search":
235
+ resolved["google_search"] = google.tools.googleSearch(config2 ?? {});
236
+ break;
237
+ case "code_execution":
238
+ resolved["code_execution"] = google.tools.codeExecution(config2 ?? {});
239
+ break;
240
+ case "url_context":
241
+ resolved["url_context"] = google.tools.urlContext({});
242
+ break;
243
+ }
244
+ }
245
+ return resolved;
246
+ }
247
+ function hasExplicitTools(options) {
248
+ return !!(options?.tools && options.tools.length > 0);
249
+ }
250
+ var MAX_FILE_SIZE = 100 * 1024;
251
+ var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
252
+ ".ts",
253
+ ".tsx",
254
+ ".js",
255
+ ".jsx",
256
+ ".mjs",
257
+ ".cjs",
258
+ ".py",
259
+ ".rb",
260
+ ".go",
261
+ ".rs",
262
+ ".java",
263
+ ".kt",
264
+ ".c",
265
+ ".cpp",
266
+ ".h",
267
+ ".hpp",
268
+ ".cs",
269
+ ".swift",
270
+ ".vue",
271
+ ".svelte",
272
+ ".astro",
273
+ ".json",
274
+ ".yaml",
275
+ ".yml",
276
+ ".toml",
277
+ ".env",
278
+ ".sql",
279
+ ".graphql",
280
+ ".gql",
281
+ ".md",
282
+ ".txt",
283
+ ".html",
284
+ ".css",
285
+ ".scss"
286
+ ]);
287
+ var INTERNAL_PATTERNS = [
288
+ "/console-agent/",
289
+ "/console_agent/",
290
+ "@console-agent/",
291
+ "/node_modules/",
292
+ "node:internal/",
293
+ "node:async_hooks",
294
+ "<anonymous>"
295
+ ];
296
+ function parseStackFrames(stack) {
297
+ const frames = [];
298
+ const lines = stack.split("\n");
299
+ for (const line of lines) {
300
+ const match = line.match(/at\s+(?:.*?\s+)?\(?([^()]+):(\d+):(\d+)\)?/);
301
+ if (match) {
302
+ frames.push({
303
+ file: match[1],
304
+ line: parseInt(match[2], 10),
305
+ column: parseInt(match[3], 10)
306
+ });
307
+ }
308
+ }
309
+ return frames;
310
+ }
311
+ function isInternalFrame(filePath) {
312
+ return INTERNAL_PATTERNS.some((pattern) => filePath.includes(pattern));
313
+ }
314
+ function isSourceFile(filePath) {
315
+ const ext = extname(filePath).toLowerCase();
316
+ return SOURCE_EXTENSIONS.has(ext);
317
+ }
318
+ function getCallerFile(skipFrames = 0) {
319
+ const err = new Error();
320
+ if (!err.stack) return null;
321
+ const frames = parseStackFrames(err.stack);
322
+ for (const frame of frames) {
323
+ if (isInternalFrame(frame.file)) continue;
324
+ if (!isSourceFile(frame.file)) continue;
325
+ return readSourceFile(frame.file, frame.line, frame.column);
326
+ }
327
+ return null;
328
+ }
329
+ function getErrorSourceFile(error) {
330
+ if (!error.stack) return null;
331
+ const frames = parseStackFrames(error.stack);
332
+ for (const frame of frames) {
333
+ if (frame.file.includes("node:internal/") || frame.file.includes("<anonymous>")) continue;
334
+ if (!isSourceFile(frame.file)) continue;
335
+ return readSourceFile(frame.file, frame.line, frame.column);
336
+ }
337
+ return null;
338
+ }
339
+ function readSourceFile(filePath, line, column) {
340
+ try {
341
+ const resolvedPath = resolve(filePath);
342
+ if (!existsSync(resolvedPath)) return null;
343
+ const { statSync } = __require("fs");
344
+ const stats = statSync(resolvedPath);
345
+ if (stats.size > MAX_FILE_SIZE) {
346
+ const content2 = readFileSync(resolvedPath, "utf-8").substring(0, MAX_FILE_SIZE);
347
+ return {
348
+ filePath: resolvedPath,
349
+ fileName: basename(resolvedPath),
350
+ line,
351
+ column,
352
+ content: content2 + "\n\n// [TRUNCATED \u2014 file exceeds 100KB limit]"
353
+ };
354
+ }
355
+ const content = readFileSync(resolvedPath, "utf-8");
356
+ return {
357
+ filePath: resolvedPath,
358
+ fileName: basename(resolvedPath),
359
+ line,
360
+ column,
361
+ content
362
+ };
363
+ } catch {
364
+ return null;
365
+ }
366
+ }
367
+ function formatSourceForContext(source) {
368
+ const lines = source.content.split("\n");
369
+ const numbered = lines.map((line, i) => {
370
+ const lineNum = i + 1;
371
+ const marker = lineNum === source.line ? " \u2192 " : " ";
372
+ return `${marker}${String(lineNum).padStart(4)} | ${line}`;
373
+ });
374
+ return `--- Source File: ${source.fileName} (line ${source.line}) ---
375
+ ${numbered.join("\n")}`;
376
+ }
206
377
  var currentLogLevel = "info";
207
378
  function setLogLevel(level) {
208
379
  currentLogLevel = level;
@@ -211,8 +382,9 @@ function shouldLog(level) {
211
382
  const levels = ["silent", "errors", "info", "debug"];
212
383
  return levels.indexOf(currentLogLevel) >= levels.indexOf(level);
213
384
  }
214
- function startSpinner(persona, prompt) {
385
+ function startSpinner(persona, prompt, verbose = false) {
215
386
  if (!shouldLog("info")) return null;
387
+ if (!verbose) return null;
216
388
  const truncated = prompt.length > 60 ? prompt.substring(0, 57) + "..." : prompt;
217
389
  const spinner = ora({
218
390
  text: chalk.cyan(`${persona.icon} ${persona.label}... `) + chalk.dim(truncated),
@@ -228,8 +400,29 @@ function stopSpinner(spinner, success) {
228
400
  spinner.fail();
229
401
  }
230
402
  }
231
- function formatResult(result, persona) {
403
+ function formatResult(result, persona, verbose = false) {
232
404
  if (!shouldLog("info")) return;
405
+ if (!verbose) {
406
+ formatResultQuiet(result);
407
+ return;
408
+ }
409
+ formatResultVerbose(result, persona);
410
+ }
411
+ function formatResultQuiet(result) {
412
+ console.log("");
413
+ console.log(result.summary);
414
+ const dataEntries = Object.entries(result.data);
415
+ const hasMeaningfulData = dataEntries.length > 0 && !(dataEntries.length === 1 && dataEntries[0][0] === "raw");
416
+ if (hasMeaningfulData) {
417
+ for (const [key, value] of dataEntries) {
418
+ if (key === "raw") continue;
419
+ const displayValue = typeof value === "string" ? value : JSON.stringify(value);
420
+ console.log(`${chalk.dim(key + ":")} ${displayValue}`);
421
+ }
422
+ }
423
+ console.log("");
424
+ }
425
+ function formatResultVerbose(result, persona) {
233
426
  const prefix = chalk.gray("[AGENT]");
234
427
  const confidenceColor = result.confidence >= 0.8 ? chalk.green : result.confidence >= 0.5 ? chalk.yellow : chalk.red;
235
428
  const statusIcon = result.success ? chalk.green("\u2713") : chalk.red("\u2717");
@@ -259,12 +452,20 @@ function formatResult(result, persona) {
259
452
  const confidence = confidenceColor(`confidence: ${result.confidence.toFixed(2)}`);
260
453
  const latency = chalk.dim(`${result.metadata.latencyMs}ms`);
261
454
  const tokens = chalk.dim(`${result.metadata.tokensUsed} tokens`);
455
+ const model = chalk.dim(`model: ${result.metadata.model}`);
262
456
  const cached = result.metadata.cached ? chalk.green(" (cached)") : "";
263
- console.log(`${prefix} \u2514\u2500 ${confidence} | ${latency} | ${tokens}${cached}`);
457
+ const toolNames = result.metadata.toolCalls.length > 0 ? chalk.dim(` | tools: ${result.metadata.toolCalls.map((t) => t.name).join(", ")}`) : "";
458
+ console.log(`${prefix} \u2514\u2500 ${confidence} | ${latency} | ${tokens} | ${model}${cached}${toolNames}`);
264
459
  console.log("");
265
460
  }
266
- function formatError(error, persona) {
461
+ function formatError(error, persona, verbose = false) {
267
462
  if (!shouldLog("errors")) return;
463
+ if (!verbose) {
464
+ console.log("");
465
+ console.log(chalk.red(`Error: ${error.message}`));
466
+ console.log("");
467
+ return;
468
+ }
268
469
  const prefix = chalk.gray("[AGENT]");
269
470
  console.log("");
270
471
  console.log(`${prefix} ${persona.icon} ${chalk.red("Error:")} ${error.message}`);
@@ -273,18 +474,32 @@ function formatError(error, persona) {
273
474
  }
274
475
  console.log("");
275
476
  }
276
- function formatBudgetWarning(reason) {
477
+ function formatBudgetWarning(reason, verbose = false) {
277
478
  if (!shouldLog("errors")) return;
479
+ if (!verbose) {
480
+ console.log(chalk.yellow(`Budget limit: ${reason}`));
481
+ return;
482
+ }
278
483
  const prefix = chalk.gray("[AGENT]");
279
484
  console.log(`${prefix} ${chalk.yellow("\u26A0 Budget limit:")} ${reason}`);
280
485
  }
281
- function formatRateLimitWarning() {
486
+ function formatRateLimitWarning(verbose = false) {
282
487
  if (!shouldLog("errors")) return;
488
+ if (!verbose) {
489
+ console.log(chalk.yellow("Rate limited: Too many calls. Try again later."));
490
+ return;
491
+ }
283
492
  const prefix = chalk.gray("[AGENT]");
284
493
  console.log(`${prefix} ${chalk.yellow("\u26A0 Rate limited:")} Too many calls. Try again later.`);
285
494
  }
286
- function formatDryRun(prompt, persona, context) {
495
+ function formatDryRun(prompt, persona, context, verbose = false) {
287
496
  if (!shouldLog("info")) return;
497
+ if (!verbose) {
498
+ console.log("");
499
+ console.log(chalk.magenta("[DRY RUN]") + ` Would execute with ${persona.name} persona`);
500
+ console.log("");
501
+ return;
502
+ }
288
503
  const prefix = chalk.gray("[AGENT]");
289
504
  console.log("");
290
505
  console.log(`${prefix} ${chalk.magenta("DRY RUN")} ${persona.icon} ${persona.label}`);
@@ -327,7 +542,37 @@ var agentOutputSchema = jsonSchema({
327
542
  required: ["success", "summary", "data", "actions", "confidence"],
328
543
  additionalProperties: false
329
544
  });
330
- async function callGoogle(prompt, context, persona, config2, options) {
545
+ var JSON_RESPONSE_INSTRUCTION = `
546
+
547
+ IMPORTANT: You MUST respond with ONLY a valid JSON object (no markdown, no code fences, no extra text).
548
+ Use this exact format:
549
+ {"success": true, "summary": "one-line conclusion", "reasoning": "your thought process", "data": {"result": "primary finding"}, "actions": ["tools/steps used"], "confidence": 0.95}`;
550
+ function buildMessages(prompt, context, sourceFile, files) {
551
+ const parts = [];
552
+ parts.push({ type: "text", text: prompt });
553
+ if (context) {
554
+ parts.push({ type: "text", text: `
555
+ --- Context ---
556
+ ${context}` });
557
+ }
558
+ if (sourceFile) {
559
+ const formatted = formatSourceForContext(sourceFile);
560
+ parts.push({ type: "text", text: `
561
+ ${formatted}` });
562
+ }
563
+ if (files && files.length > 0) {
564
+ for (const file of files) {
565
+ const prepared = prepareFileContent(file.data, file.mediaType);
566
+ if (file.fileName) {
567
+ parts.push({ type: "text", text: `
568
+ --- Attached File: ${file.fileName} ---` });
569
+ }
570
+ parts.push(prepared);
571
+ }
572
+ }
573
+ return [{ role: "user", content: parts }];
574
+ }
575
+ async function callGoogle(prompt, context, persona, config2, options, sourceFile, files) {
331
576
  const startTime = Date.now();
332
577
  const modelName = options?.model ?? config2.model;
333
578
  logDebug(`Using model: ${modelName}`);
@@ -348,14 +593,69 @@ async function callGoogle(prompt, context, persona, config2, options) {
348
593
  if (Object.keys(googleOpts).length > 0) {
349
594
  providerOptions["google"] = googleOpts;
350
595
  }
351
- if (!config2.localOnly) {
352
- const toolNames = options?.tools ?? persona.defaultTools;
353
- logDebug(`Persona tools (informational): ${toolNames.join(", ")}`);
596
+ const useTools = hasExplicitTools(options) && !config2.localOnly;
597
+ if (useTools) {
598
+ logDebug("Tools requested \u2014 using generateText path (no structured output)");
599
+ return callWithTools(prompt, context, persona, config2, options, google, modelName, startTime, providerOptions, sourceFile, files);
354
600
  }
355
- const userMessage = context ? `${prompt}
356
-
357
- --- Context ---
358
- ${context}` : prompt;
601
+ logDebug("No tools \u2014 using ToolLoopAgent with structured output");
602
+ return callWithStructuredOutput(prompt, context, persona, config2, options, google, modelName, startTime, providerOptions, sourceFile, files);
603
+ }
604
+ async function callWithTools(prompt, context, persona, config2, options, google, modelName, startTime, providerOptions, sourceFile, files) {
605
+ const resolvedTools = resolveTools(options.tools, google);
606
+ const toolNames = Object.keys(resolvedTools);
607
+ logDebug(`Tools enabled: ${toolNames.join(", ")}`);
608
+ const effectiveTimeout = Math.max(config2.timeout, TOOLS_MIN_TIMEOUT);
609
+ const messages = buildMessages(prompt, context, sourceFile, files);
610
+ const result = await generateText({
611
+ model: google(modelName),
612
+ system: persona.systemPrompt + JSON_RESPONSE_INSTRUCTION,
613
+ messages,
614
+ tools: resolvedTools,
615
+ stopWhen: stepCountIs(5),
616
+ // Allow multi-step: tool invocation → response
617
+ maxOutputTokens: config2.budget.maxTokensPerCall,
618
+ providerOptions: Object.keys(providerOptions).length > 0 ? providerOptions : void 0,
619
+ abortSignal: AbortSignal.timeout(effectiveTimeout)
620
+ });
621
+ const latencyMs = Date.now() - startTime;
622
+ const tokensUsed = result.usage?.totalTokens ?? 0;
623
+ logDebug(`Response received (tools path): ${latencyMs}ms, ${tokensUsed} tokens`);
624
+ const collectedToolCalls = [];
625
+ if (result.steps) {
626
+ for (const step of result.steps) {
627
+ if (step.toolCalls) {
628
+ for (const tc of step.toolCalls) {
629
+ collectedToolCalls.push({
630
+ name: tc.toolName,
631
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
632
+ args: tc.args ?? {},
633
+ result: tc.toolName
634
+ });
635
+ }
636
+ }
637
+ }
638
+ }
639
+ logDebug(`Tool calls collected: ${collectedToolCalls.length}`);
640
+ const parsed = parseResponse(result.text);
641
+ return {
642
+ success: parsed?.success ?? true,
643
+ summary: parsed?.summary ?? result.text.substring(0, 200),
644
+ reasoning: parsed?.reasoning,
645
+ data: parsed?.data ?? { raw: result.text },
646
+ actions: parsed?.actions ?? collectedToolCalls.map((tc) => tc.name),
647
+ confidence: parsed?.confidence ?? 0.5,
648
+ metadata: {
649
+ model: modelName,
650
+ tokensUsed,
651
+ latencyMs,
652
+ toolCalls: collectedToolCalls,
653
+ cached: false
654
+ }
655
+ };
656
+ }
657
+ async function callWithStructuredOutput(prompt, context, persona, config2, options, google, modelName, startTime, providerOptions, sourceFile, files) {
658
+ const messages = buildMessages(prompt, context, sourceFile, files);
359
659
  const collectedToolCalls = [];
360
660
  const useCustomSchema = !!(options?.schema || options?.responseFormat);
361
661
  let outputConfig;
@@ -394,7 +694,7 @@ IMPORTANT: You must respond with structured data matching the requested output s
394
694
  }
395
695
  });
396
696
  const result = await agent.generate({
397
- prompt: userMessage,
697
+ messages,
398
698
  timeout: config2.timeout
399
699
  });
400
700
  const latencyMs = Date.now() - startTime;
@@ -689,7 +989,9 @@ var DEFAULT_CONFIG = {
689
989
  localOnly: false,
690
990
  dryRun: false,
691
991
  logLevel: "info",
692
- safetySettings: []
992
+ verbose: false,
993
+ safetySettings: [],
994
+ includeCallerSource: true
693
995
  };
694
996
  var config = { ...DEFAULT_CONFIG };
695
997
  var rateLimiter = new RateLimiter(config.budget.maxCallsPerDay);
@@ -708,18 +1010,19 @@ function getConfig() {
708
1010
  async function executeAgent(prompt, context, options) {
709
1011
  const personaName = options?.persona ?? config.persona;
710
1012
  const persona = options?.persona ? getPersona(options.persona) : detectPersona(prompt, personaName);
1013
+ const verbose = options?.verbose ?? config.verbose;
711
1014
  logDebug(`Selected persona: ${persona.name} (${persona.icon})`);
712
1015
  if (config.dryRun) {
713
- formatDryRun(prompt, persona, context);
1016
+ formatDryRun(prompt, persona, context, verbose);
714
1017
  return createDryRunResult(persona.name);
715
1018
  }
716
1019
  if (!rateLimiter.tryConsume()) {
717
- formatRateLimitWarning();
1020
+ formatRateLimitWarning(verbose);
718
1021
  return createErrorResult("Rate limited \u2014 too many calls. Try again later.");
719
1022
  }
720
1023
  const budgetCheck = budgetTracker.canMakeCall();
721
1024
  if (!budgetCheck.allowed) {
722
- formatBudgetWarning(budgetCheck.reason);
1025
+ formatBudgetWarning(budgetCheck.reason, verbose);
723
1026
  return createErrorResult(budgetCheck.reason);
724
1027
  }
725
1028
  let contextStr = "";
@@ -741,10 +1044,27 @@ async function executeAgent(prompt, context, options) {
741
1044
  }
742
1045
  }
743
1046
  const processedPrompt = config.anonymize ? anonymizeValue(prompt) : prompt;
744
- const spinner = startSpinner(persona, processedPrompt);
1047
+ const shouldIncludeSource = options?.includeCallerSource ?? config.includeCallerSource;
1048
+ let sourceFile = null;
1049
+ if (shouldIncludeSource) {
1050
+ if (context instanceof Error) {
1051
+ sourceFile = getErrorSourceFile(context);
1052
+ if (sourceFile) {
1053
+ logDebug(`Auto-detected error source file: ${sourceFile.fileName} (line ${sourceFile.line})`);
1054
+ }
1055
+ }
1056
+ if (!sourceFile) {
1057
+ sourceFile = getCallerFile();
1058
+ if (sourceFile) {
1059
+ logDebug(`Auto-detected caller file: ${sourceFile.fileName} (line ${sourceFile.line})`);
1060
+ }
1061
+ }
1062
+ }
1063
+ const files = options?.files;
1064
+ const spinner = startSpinner(persona, processedPrompt, verbose);
745
1065
  try {
746
1066
  const result = await Promise.race([
747
- callGoogle(processedPrompt, contextStr, persona, config, options),
1067
+ callGoogle(processedPrompt, contextStr, persona, config, options, sourceFile, files),
748
1068
  createTimeout(config.timeout)
749
1069
  ]);
750
1070
  budgetTracker.recordUsage(
@@ -752,12 +1072,12 @@ async function executeAgent(prompt, context, options) {
752
1072
  estimateCost(result.metadata.tokensUsed, result.metadata.model)
753
1073
  );
754
1074
  stopSpinner(spinner, result.success);
755
- formatResult(result, persona);
1075
+ formatResult(result, persona, verbose);
756
1076
  return result;
757
1077
  } catch (error) {
758
1078
  stopSpinner(spinner, false);
759
1079
  const err = error instanceof Error ? error : new Error(String(error));
760
- formatError(err, persona);
1080
+ formatError(err, persona, verbose);
761
1081
  return createErrorResult(err.message);
762
1082
  }
763
1083
  }