agentaudit 3.12.4 → 3.12.6

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.
Files changed (2) hide show
  1. package/cli.mjs +65 -8
  2. package/package.json +1 -1
package/cli.mjs CHANGED
@@ -2587,10 +2587,43 @@ function loadAuditPrompt() {
2587
2587
  return null;
2588
2588
  }
2589
2589
 
2590
+ // Known context window sizes (input tokens) for common models
2591
+ const MODEL_CONTEXT_LIMITS = {
2592
+ 'claude-sonnet-4': 200000, 'claude-opus-4': 200000, 'claude-haiku-4': 200000,
2593
+ 'claude-3.5-sonnet': 200000, 'claude-3-haiku': 200000,
2594
+ 'gpt-4o': 128000, 'gpt-4o-mini': 128000, 'gpt-4-turbo': 128000, 'gpt-4': 8192,
2595
+ 'gemini-2.5-flash': 1048576, 'gemini-2.5-pro': 1048576, 'gemini-2.0-flash': 1048576,
2596
+ 'deepseek-chat': 64000, 'deepseek-reasoner': 64000,
2597
+ 'mistral-large': 128000, 'mistral-small': 32000,
2598
+ };
2599
+
2600
+ function estimateTokens(text) { return Math.ceil(text.length / 3.5); }
2601
+
2602
+ function checkContextLimit(model, systemPrompt, userMessage) {
2603
+ const modelKey = Object.keys(MODEL_CONTEXT_LIMITS).find(k => model.toLowerCase().includes(k.toLowerCase()));
2604
+ if (!modelKey) return null; // unknown model, skip check
2605
+ const limit = MODEL_CONTEXT_LIMITS[modelKey];
2606
+ const estimated = estimateTokens(systemPrompt) + estimateTokens(userMessage);
2607
+ if (estimated > limit * 0.9) {
2608
+ return { estimated, limit, pct: Math.round(estimated / limit * 100) };
2609
+ }
2610
+ return null;
2611
+ }
2612
+
2590
2613
  async function callLlm(llmConfig, systemPrompt, userMessage) {
2591
2614
  const apiKey = process.env[llmConfig.key];
2592
2615
  if (!apiKey) return { error: `Missing API key: ${llmConfig.key}` };
2593
2616
  const start = Date.now();
2617
+
2618
+ // Context window warning
2619
+ const ctxCheck = checkContextLimit(llmConfig.model, systemPrompt, userMessage);
2620
+ if (ctxCheck) {
2621
+ console.log(` ${c.yellow}⚠ Input ~${Math.round(ctxCheck.estimated/1000)}k tokens (${ctxCheck.pct}% of ${Math.round(ctxCheck.limit/1000)}k context window)${c.reset}`);
2622
+ if (ctxCheck.pct > 100) {
2623
+ return { error: `Input too large (~${Math.round(ctxCheck.estimated/1000)}k tokens) for ${llmConfig.model} (${Math.round(ctxCheck.limit/1000)}k context limit). Try a smaller package or a model with a larger context window.` };
2624
+ }
2625
+ }
2626
+
2594
2627
  let _text = '';
2595
2628
  try {
2596
2629
  let data;
@@ -2598,8 +2631,8 @@ async function callLlm(llmConfig, systemPrompt, userMessage) {
2598
2631
  const res = await fetch(llmConfig.url, {
2599
2632
  method: 'POST',
2600
2633
  headers: { 'x-api-key': apiKey, 'anthropic-version': '2023-06-01', 'content-type': 'application/json' },
2601
- body: JSON.stringify({ model: llmConfig.model, max_tokens: 8192, system: systemPrompt, messages: [{ role: 'user', content: userMessage }] }),
2602
- signal: AbortSignal.timeout(120_000),
2634
+ body: JSON.stringify({ model: llmConfig.model, max_tokens: 16384, system: systemPrompt, messages: [{ role: 'user', content: userMessage }] }),
2635
+ signal: AbortSignal.timeout(180_000),
2603
2636
  });
2604
2637
  data = await res.json();
2605
2638
  if (data.error) {
@@ -2607,14 +2640,19 @@ async function callLlm(llmConfig, systemPrompt, userMessage) {
2607
2640
  return { error: friendly?.text || data.error.message || JSON.stringify(data.error), hint: friendly?.hint, duration: Date.now() - start };
2608
2641
  }
2609
2642
  _text = data.content?.[0]?.text || '';
2643
+ if (data.stop_reason === 'max_tokens') {
2644
+ console.log(` ${c.red}✗ Output truncated — model hit max_tokens limit (${data.usage?.output_tokens || '?'} tokens). Results may be incomplete.${c.reset}`);
2645
+ console.log(` ${c.dim} Hint: Try a model with higher output capacity, or scan a smaller package.${c.reset}`);
2646
+ }
2610
2647
  const report = extractJSON(_text);
2611
2648
  if (report) {
2612
2649
  report.audit_model = data.model || llmConfig.model;
2613
2650
  report.audit_provider = llmConfig.provider;
2614
2651
  if (data.id) report.provider_msg_id = data.id;
2615
2652
  if (data.usage) { report.input_tokens = data.usage.input_tokens; report.output_tokens = data.usage.output_tokens; }
2653
+ if (data.stop_reason === 'max_tokens') report.output_truncated = true;
2616
2654
  }
2617
- return { report, text: _text, duration: Date.now() - start };
2655
+ return { report, text: _text, duration: Date.now() - start, truncated: data.stop_reason === 'max_tokens' };
2618
2656
  } else if (llmConfig.type === 'gemini') {
2619
2657
  const res = await fetch(`${llmConfig.url}/${llmConfig.model}:generateContent?key=${apiKey}`, {
2620
2658
  method: 'POST',
@@ -2632,21 +2670,27 @@ async function callLlm(llmConfig, systemPrompt, userMessage) {
2632
2670
  return { error: friendly?.text || data.error.message || JSON.stringify(data.error), hint: friendly?.hint, duration: Date.now() - start };
2633
2671
  }
2634
2672
  _text = data.candidates?.[0]?.content?.parts?.[0]?.text || '';
2673
+ const geminiFinish = data.candidates?.[0]?.finishReason;
2674
+ if (geminiFinish === 'MAX_TOKENS') {
2675
+ console.log(` ${c.red}✗ Output truncated — model hit maxOutputTokens limit (${data.usageMetadata?.candidatesTokenCount || '?'} tokens). Results may be incomplete.${c.reset}`);
2676
+ console.log(` ${c.dim} Hint: Try a model with higher output capacity, or scan a smaller package.${c.reset}`);
2677
+ }
2635
2678
  const report = extractJSON(_text);
2636
2679
  if (report) {
2637
2680
  report.audit_model = data.modelVersion || llmConfig.model;
2638
2681
  report.audit_provider = llmConfig.provider;
2639
2682
  if (data.usageMetadata) { report.input_tokens = data.usageMetadata.promptTokenCount; report.output_tokens = data.usageMetadata.candidatesTokenCount; }
2683
+ if (geminiFinish === 'MAX_TOKENS') report.output_truncated = true;
2640
2684
  }
2641
- return { report, text: _text, duration: Date.now() - start };
2685
+ return { report, text: _text, duration: Date.now() - start, truncated: geminiFinish === 'MAX_TOKENS' };
2642
2686
  } else {
2643
2687
  const headers = { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' };
2644
2688
  if (llmConfig.provider === 'openrouter') { headers['HTTP-Referer'] = 'https://agentaudit.dev'; headers['X-Title'] = 'AgentAudit CLI'; }
2645
2689
  const res = await fetch(llmConfig.url, {
2646
2690
  method: 'POST',
2647
2691
  headers,
2648
- body: JSON.stringify({ model: llmConfig.model, max_tokens: 8192, messages: [{ role: 'system', content: systemPrompt }, { role: 'user', content: userMessage }] }),
2649
- signal: AbortSignal.timeout(120_000),
2692
+ body: JSON.stringify({ model: llmConfig.model, max_tokens: 16384, messages: [{ role: 'system', content: systemPrompt }, { role: 'user', content: userMessage }] }),
2693
+ signal: AbortSignal.timeout(180_000),
2650
2694
  });
2651
2695
  data = await res.json();
2652
2696
  if (data.error) {
@@ -2654,6 +2698,11 @@ async function callLlm(llmConfig, systemPrompt, userMessage) {
2654
2698
  return { error: friendly?.text || data.error.message || JSON.stringify(data.error), hint: friendly?.hint, duration: Date.now() - start };
2655
2699
  }
2656
2700
  _text = data.choices?.[0]?.message?.content || '';
2701
+ const oaiFinish = data.choices?.[0]?.finish_reason;
2702
+ if (oaiFinish === 'length') {
2703
+ console.log(` ${c.red}✗ Output truncated — model hit max_tokens limit (${data.usage?.completion_tokens || '?'} tokens). Results may be incomplete.${c.reset}`);
2704
+ console.log(` ${c.dim} Hint: Try a model with higher output capacity, or scan a smaller package.${c.reset}`);
2705
+ }
2657
2706
  const report = extractJSON(_text);
2658
2707
  if (report) {
2659
2708
  report.audit_model = data.model || llmConfig.model;
@@ -2661,12 +2710,13 @@ async function callLlm(llmConfig, systemPrompt, userMessage) {
2661
2710
  if (data.id) report.provider_msg_id = data.id;
2662
2711
  if (data.system_fingerprint) report.provider_fingerprint = data.system_fingerprint;
2663
2712
  if (data.usage) { report.input_tokens = data.usage.prompt_tokens; report.output_tokens = data.usage.completion_tokens; }
2713
+ if (oaiFinish === 'length') report.output_truncated = true;
2664
2714
  }
2665
- return { report, text: _text, duration: Date.now() - start };
2715
+ return { report, text: _text, duration: Date.now() - start, truncated: oaiFinish === 'length' };
2666
2716
  }
2667
2717
  } catch (err) {
2668
2718
  const dur = Date.now() - start;
2669
- if (err.name === 'TimeoutError' || err.message?.includes('timeout')) return { error: 'Request timed out (120s)', hint: 'Try again or use a faster model', duration: dur };
2719
+ if (err.name === 'TimeoutError' || err.message?.includes('timeout')) return { error: 'Request timed out (180s)', hint: 'Try again or use a faster model', duration: dur };
2670
2720
  if (err.code === 'ENOTFOUND' || err.code === 'ECONNREFUSED' || err.message?.includes('fetch failed')) return { error: `Network error: could not reach ${llmConfig.provider}`, hint: 'Check your internet connection', duration: dur };
2671
2721
  return { error: err.message, duration: dur };
2672
2722
  }
@@ -3139,6 +3189,13 @@ async function auditRepo(url) {
3139
3189
 
3140
3190
  console.log(` ${c.green}done${c.reset} ${c.dim}(${elapsed(start)})${c.reset}`);
3141
3191
 
3192
+ if (llmResult.truncated) {
3193
+ console.log();
3194
+ console.log(` ${c.yellow}⚠ WARNING: The model's output was truncated (hit token limit).${c.reset}`);
3195
+ console.log(` ${c.yellow} Some findings may be missing from this scan.${c.reset}`);
3196
+ console.log(` ${c.dim} Tip: Try a model with more output capacity or scan a smaller package.${c.reset}`);
3197
+ }
3198
+
3142
3199
  const report = llmResult.report;
3143
3200
  if (!report) {
3144
3201
  console.log(` ${c.red}Could not parse LLM response as JSON${c.reset}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentaudit",
3
- "version": "3.12.4",
3
+ "version": "3.12.6",
4
4
  "description": "Security scanner for AI packages — MCP server + CLI",
5
5
  "type": "module",
6
6
  "bin": {