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