@peakinfer/cli 1.0.133
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/.claude/settings.local.json +8 -0
- package/.env.example +6 -0
- package/.github/workflows/peakinfer.yml +64 -0
- package/CHANGELOG.md +31 -0
- package/LICENSE +190 -0
- package/README.md +335 -0
- package/data/inferencemax.json +274 -0
- package/dist/agent-analyzer.d.ts +45 -0
- package/dist/agent-analyzer.d.ts.map +1 -0
- package/dist/agent-analyzer.js +374 -0
- package/dist/agent-analyzer.js.map +1 -0
- package/dist/agent.d.ts +76 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +965 -0
- package/dist/agent.js.map +1 -0
- package/dist/agents/correlation-analyzer.d.ts +34 -0
- package/dist/agents/correlation-analyzer.d.ts.map +1 -0
- package/dist/agents/correlation-analyzer.js +261 -0
- package/dist/agents/correlation-analyzer.js.map +1 -0
- package/dist/agents/index.d.ts +91 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +111 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/runtime-analyzer.d.ts +38 -0
- package/dist/agents/runtime-analyzer.d.ts.map +1 -0
- package/dist/agents/runtime-analyzer.js +244 -0
- package/dist/agents/runtime-analyzer.js.map +1 -0
- package/dist/analysis-types.d.ts +500 -0
- package/dist/analysis-types.d.ts.map +1 -0
- package/dist/analysis-types.js +11 -0
- package/dist/analysis-types.js.map +1 -0
- package/dist/analytics.d.ts +25 -0
- package/dist/analytics.d.ts.map +1 -0
- package/dist/analytics.js +94 -0
- package/dist/analytics.js.map +1 -0
- package/dist/analyzer.d.ts +48 -0
- package/dist/analyzer.d.ts.map +1 -0
- package/dist/analyzer.js +547 -0
- package/dist/analyzer.js.map +1 -0
- package/dist/artifacts.d.ts +44 -0
- package/dist/artifacts.d.ts.map +1 -0
- package/dist/artifacts.js +165 -0
- package/dist/artifacts.js.map +1 -0
- package/dist/benchmarks/index.d.ts +88 -0
- package/dist/benchmarks/index.d.ts.map +1 -0
- package/dist/benchmarks/index.js +205 -0
- package/dist/benchmarks/index.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +427 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/ci.d.ts +19 -0
- package/dist/commands/ci.d.ts.map +1 -0
- package/dist/commands/ci.js +253 -0
- package/dist/commands/ci.js.map +1 -0
- package/dist/commands/config.d.ts +16 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +249 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/demo.d.ts +15 -0
- package/dist/commands/demo.d.ts.map +1 -0
- package/dist/commands/demo.js +106 -0
- package/dist/commands/demo.js.map +1 -0
- package/dist/commands/export.d.ts +14 -0
- package/dist/commands/export.d.ts.map +1 -0
- package/dist/commands/export.js +209 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/history.d.ts +15 -0
- package/dist/commands/history.d.ts.map +1 -0
- package/dist/commands/history.js +389 -0
- package/dist/commands/history.js.map +1 -0
- package/dist/commands/template.d.ts +14 -0
- package/dist/commands/template.d.ts.map +1 -0
- package/dist/commands/template.js +341 -0
- package/dist/commands/template.js.map +1 -0
- package/dist/commands/validate-map.d.ts +12 -0
- package/dist/commands/validate-map.d.ts.map +1 -0
- package/dist/commands/validate-map.js +274 -0
- package/dist/commands/validate-map.js.map +1 -0
- package/dist/commands/whatif.d.ts +17 -0
- package/dist/commands/whatif.d.ts.map +1 -0
- package/dist/commands/whatif.js +206 -0
- package/dist/commands/whatif.js.map +1 -0
- package/dist/comparison.d.ts +38 -0
- package/dist/comparison.d.ts.map +1 -0
- package/dist/comparison.js +223 -0
- package/dist/comparison.js.map +1 -0
- package/dist/config.d.ts +42 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +158 -0
- package/dist/config.js.map +1 -0
- package/dist/connectors/helicone.d.ts +9 -0
- package/dist/connectors/helicone.d.ts.map +1 -0
- package/dist/connectors/helicone.js +106 -0
- package/dist/connectors/helicone.js.map +1 -0
- package/dist/connectors/index.d.ts +37 -0
- package/dist/connectors/index.d.ts.map +1 -0
- package/dist/connectors/index.js +65 -0
- package/dist/connectors/index.js.map +1 -0
- package/dist/connectors/langsmith.d.ts +9 -0
- package/dist/connectors/langsmith.d.ts.map +1 -0
- package/dist/connectors/langsmith.js +122 -0
- package/dist/connectors/langsmith.js.map +1 -0
- package/dist/connectors/types.d.ts +83 -0
- package/dist/connectors/types.d.ts.map +1 -0
- package/dist/connectors/types.js +98 -0
- package/dist/connectors/types.js.map +1 -0
- package/dist/cost-estimator.d.ts +46 -0
- package/dist/cost-estimator.d.ts.map +1 -0
- package/dist/cost-estimator.js +104 -0
- package/dist/cost-estimator.js.map +1 -0
- package/dist/costs.d.ts +57 -0
- package/dist/costs.d.ts.map +1 -0
- package/dist/costs.js +251 -0
- package/dist/costs.js.map +1 -0
- package/dist/counterfactuals.d.ts +29 -0
- package/dist/counterfactuals.d.ts.map +1 -0
- package/dist/counterfactuals.js +448 -0
- package/dist/counterfactuals.js.map +1 -0
- package/dist/enhancement-prompts.d.ts +41 -0
- package/dist/enhancement-prompts.d.ts.map +1 -0
- package/dist/enhancement-prompts.js +88 -0
- package/dist/enhancement-prompts.js.map +1 -0
- package/dist/envelopes.d.ts +20 -0
- package/dist/envelopes.d.ts.map +1 -0
- package/dist/envelopes.js +790 -0
- package/dist/envelopes.js.map +1 -0
- package/dist/format-normalizer.d.ts +71 -0
- package/dist/format-normalizer.d.ts.map +1 -0
- package/dist/format-normalizer.js +1331 -0
- package/dist/format-normalizer.js.map +1 -0
- package/dist/history.d.ts +79 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +313 -0
- package/dist/history.js.map +1 -0
- package/dist/html.d.ts +11 -0
- package/dist/html.d.ts.map +1 -0
- package/dist/html.js +463 -0
- package/dist/html.js.map +1 -0
- package/dist/impact.d.ts +42 -0
- package/dist/impact.d.ts.map +1 -0
- package/dist/impact.js +443 -0
- package/dist/impact.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/insights.d.ts +5 -0
- package/dist/insights.d.ts.map +1 -0
- package/dist/insights.js +271 -0
- package/dist/insights.js.map +1 -0
- package/dist/joiner.d.ts +9 -0
- package/dist/joiner.d.ts.map +1 -0
- package/dist/joiner.js +247 -0
- package/dist/joiner.js.map +1 -0
- package/dist/orchestrator.d.ts +34 -0
- package/dist/orchestrator.d.ts.map +1 -0
- package/dist/orchestrator.js +827 -0
- package/dist/orchestrator.js.map +1 -0
- package/dist/pdf.d.ts +26 -0
- package/dist/pdf.d.ts.map +1 -0
- package/dist/pdf.js +84 -0
- package/dist/pdf.js.map +1 -0
- package/dist/prediction.d.ts +33 -0
- package/dist/prediction.d.ts.map +1 -0
- package/dist/prediction.js +316 -0
- package/dist/prediction.js.map +1 -0
- package/dist/prompts/loader.d.ts +38 -0
- package/dist/prompts/loader.d.ts.map +1 -0
- package/dist/prompts/loader.js +60 -0
- package/dist/prompts/loader.js.map +1 -0
- package/dist/renderer.d.ts +64 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +923 -0
- package/dist/renderer.js.map +1 -0
- package/dist/runid.d.ts +57 -0
- package/dist/runid.d.ts.map +1 -0
- package/dist/runid.js +199 -0
- package/dist/runid.js.map +1 -0
- package/dist/runtime.d.ts +29 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +366 -0
- package/dist/runtime.js.map +1 -0
- package/dist/scanner.d.ts +11 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +426 -0
- package/dist/scanner.js.map +1 -0
- package/dist/templates.d.ts +120 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/templates.js +429 -0
- package/dist/templates.js.map +1 -0
- package/dist/tools/index.d.ts +153 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +177 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/types.d.ts +3647 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +703 -0
- package/dist/types.js.map +1 -0
- package/dist/version.d.ts +7 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +23 -0
- package/dist/version.js.map +1 -0
- package/docs/demo-guide.md +423 -0
- package/docs/events-format.md +295 -0
- package/docs/inferencemap-spec.md +344 -0
- package/docs/migration-v2.md +293 -0
- package/fixtures/demo/precomputed.json +142 -0
- package/fixtures/demo-project/README.md +52 -0
- package/fixtures/demo-project/ai-service.ts +65 -0
- package/fixtures/demo-project/sample-events.jsonl +15 -0
- package/fixtures/demo-project/src/ai-service.ts +128 -0
- package/fixtures/demo-project/src/llm-client.ts +155 -0
- package/package.json +65 -0
- package/prompts/agent-analyzer.yaml +47 -0
- package/prompts/ci-gate.yaml +98 -0
- package/prompts/correlation-analyzer.yaml +178 -0
- package/prompts/format-normalizer.yaml +46 -0
- package/prompts/peak-performance.yaml +180 -0
- package/prompts/pr-comment.yaml +111 -0
- package/prompts/runtime-analyzer.yaml +189 -0
- package/prompts/unified-analyzer.yaml +241 -0
- package/schemas/inference-map.v0.1.json +215 -0
- package/scripts/benchmark.ts +394 -0
- package/scripts/demo-v1.5.sh +158 -0
- package/scripts/sync-from-site.sh +197 -0
- package/scripts/validate-sync.sh +178 -0
- package/src/agent-analyzer.ts +481 -0
- package/src/agent.ts +1232 -0
- package/src/agents/correlation-analyzer.ts +353 -0
- package/src/agents/index.ts +235 -0
- package/src/agents/runtime-analyzer.ts +343 -0
- package/src/analysis-types.ts +558 -0
- package/src/analytics.ts +100 -0
- package/src/analyzer.ts +692 -0
- package/src/artifacts.ts +218 -0
- package/src/benchmarks/index.ts +309 -0
- package/src/cli.ts +503 -0
- package/src/commands/ci.ts +336 -0
- package/src/commands/config.ts +288 -0
- package/src/commands/demo.ts +175 -0
- package/src/commands/export.ts +297 -0
- package/src/commands/history.ts +425 -0
- package/src/commands/template.ts +385 -0
- package/src/commands/validate-map.ts +324 -0
- package/src/commands/whatif.ts +272 -0
- package/src/comparison.ts +283 -0
- package/src/config.ts +188 -0
- package/src/connectors/helicone.ts +164 -0
- package/src/connectors/index.ts +93 -0
- package/src/connectors/langsmith.ts +179 -0
- package/src/connectors/types.ts +180 -0
- package/src/cost-estimator.ts +146 -0
- package/src/costs.ts +347 -0
- package/src/counterfactuals.ts +516 -0
- package/src/enhancement-prompts.ts +118 -0
- package/src/envelopes.ts +814 -0
- package/src/format-normalizer.ts +1486 -0
- package/src/history.ts +400 -0
- package/src/html.ts +512 -0
- package/src/impact.ts +522 -0
- package/src/index.ts +83 -0
- package/src/insights.ts +341 -0
- package/src/joiner.ts +289 -0
- package/src/orchestrator.ts +1015 -0
- package/src/pdf.ts +110 -0
- package/src/prediction.ts +392 -0
- package/src/prompts/loader.ts +88 -0
- package/src/renderer.ts +1045 -0
- package/src/runid.ts +261 -0
- package/src/runtime.ts +450 -0
- package/src/scanner.ts +508 -0
- package/src/templates.ts +561 -0
- package/src/tools/index.ts +214 -0
- package/src/types.ts +873 -0
- package/src/version.ts +24 -0
- package/templates/context-accumulation.yaml +23 -0
- package/templates/cost-concentration.yaml +20 -0
- package/templates/dead-code.yaml +20 -0
- package/templates/latency-explainer.yaml +23 -0
- package/templates/optimizations/ab-testing-framework.yaml +74 -0
- package/templates/optimizations/api-gateway-optimization.yaml +81 -0
- package/templates/optimizations/api-model-routing-strategy.yaml +126 -0
- package/templates/optimizations/auto-scaling-optimization.yaml +85 -0
- package/templates/optimizations/batch-utilization-diagnostic.yaml +142 -0
- package/templates/optimizations/comprehensive-apm.yaml +76 -0
- package/templates/optimizations/context-window-optimization.yaml +91 -0
- package/templates/optimizations/cost-sensitive-batch-processing.yaml +77 -0
- package/templates/optimizations/distributed-training-optimization.yaml +77 -0
- package/templates/optimizations/document-analysis-edge.yaml +77 -0
- package/templates/optimizations/document-pipeline-optimization.yaml +78 -0
- package/templates/optimizations/domain-specific-distillation.yaml +78 -0
- package/templates/optimizations/error-handling-optimization.yaml +76 -0
- package/templates/optimizations/gptq-4bit-quantization.yaml +96 -0
- package/templates/optimizations/long-context-memory-management.yaml +78 -0
- package/templates/optimizations/max-tokens-optimization.yaml +76 -0
- package/templates/optimizations/memory-bandwidth-optimization.yaml +73 -0
- package/templates/optimizations/multi-framework-resilience.yaml +75 -0
- package/templates/optimizations/multi-tenant-optimization.yaml +75 -0
- package/templates/optimizations/prompt-caching-optimization.yaml +143 -0
- package/templates/optimizations/pytorch-to-onnx-migration.yaml +109 -0
- package/templates/optimizations/quality-monitoring.yaml +74 -0
- package/templates/optimizations/realtime-budget-controls.yaml +74 -0
- package/templates/optimizations/realtime-latency-optimization.yaml +74 -0
- package/templates/optimizations/sglang-concurrency-optimization.yaml +78 -0
- package/templates/optimizations/smart-model-routing.yaml +96 -0
- package/templates/optimizations/streaming-batch-selection.yaml +167 -0
- package/templates/optimizations/system-prompt-optimization.yaml +75 -0
- package/templates/optimizations/tensorrt-llm-performance.yaml +77 -0
- package/templates/optimizations/vllm-high-throughput-optimization.yaml +93 -0
- package/templates/optimizations/vllm-migration-memory-bound.yaml +78 -0
- package/templates/overpowered-extraction.yaml +32 -0
- package/templates/overpowered-model.yaml +31 -0
- package/templates/prompt-bloat.yaml +24 -0
- package/templates/retry-explosion.yaml +28 -0
- package/templates/schema/insight.schema.json +113 -0
- package/templates/schema/optimization.schema.json +180 -0
- package/templates/streaming-drift.yaml +30 -0
- package/templates/throughput-gap.yaml +21 -0
- package/templates/token-underutilization.yaml +28 -0
- package/templates/untested-fallback.yaml +21 -0
- package/tests/accuracy/drift-detection.test.ts +184 -0
- package/tests/accuracy/false-positives.test.ts +166 -0
- package/tests/accuracy/templates.test.ts +205 -0
- package/tests/action/commands.test.ts +125 -0
- package/tests/action/comments.test.ts +347 -0
- package/tests/cli.test.ts +203 -0
- package/tests/comparison.test.ts +309 -0
- package/tests/correlation-analyzer.test.ts +534 -0
- package/tests/counterfactuals.test.ts +347 -0
- package/tests/fixtures/events/missing-id.jsonl +1 -0
- package/tests/fixtures/events/missing-input.jsonl +1 -0
- package/tests/fixtures/events/missing-latency.jsonl +1 -0
- package/tests/fixtures/events/missing-model.jsonl +1 -0
- package/tests/fixtures/events/missing-output.jsonl +1 -0
- package/tests/fixtures/events/missing-provider.jsonl +1 -0
- package/tests/fixtures/events/missing-ts.jsonl +1 -0
- package/tests/fixtures/events/valid.csv +3 -0
- package/tests/fixtures/events/valid.json +1 -0
- package/tests/fixtures/events/valid.jsonl +2 -0
- package/tests/fixtures/events/with-callsite.jsonl +1 -0
- package/tests/fixtures/events/with-intent.jsonl +1 -0
- package/tests/fixtures/events/wrong-type.jsonl +1 -0
- package/tests/fixtures/repos/empty/.gitkeep +0 -0
- package/tests/fixtures/repos/hybrid-router/router.py +35 -0
- package/tests/fixtures/repos/saas-anthropic/agent.ts +27 -0
- package/tests/fixtures/repos/saas-openai/assistant.js +33 -0
- package/tests/fixtures/repos/saas-openai/client.py +26 -0
- package/tests/fixtures/repos/self-hosted-vllm/inference.py +22 -0
- package/tests/github-action.test.ts +292 -0
- package/tests/insights.test.ts +878 -0
- package/tests/joiner.test.ts +168 -0
- package/tests/performance/action-latency.test.ts +132 -0
- package/tests/performance/benchmark.test.ts +189 -0
- package/tests/performance/cli-latency.test.ts +102 -0
- package/tests/pr-comment.test.ts +313 -0
- package/tests/prediction.test.ts +296 -0
- package/tests/runtime-analyzer.test.ts +375 -0
- package/tests/runtime.test.ts +205 -0
- package/tests/scanner.test.ts +122 -0
- package/tests/template-conformance.test.ts +526 -0
- package/tests/unit/cost-calculator.test.ts +303 -0
- package/tests/unit/credits.test.ts +180 -0
- package/tests/unit/inference-map.test.ts +276 -0
- package/tests/unit/schema.test.ts +300 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +14 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent-based Semantic Analyzer for PeakInfer
|
|
3
|
+
*
|
|
4
|
+
* Uses Claude Agent SDK (per TDD v1.9.3) for multi-step code analysis:
|
|
5
|
+
* 1. Read source files
|
|
6
|
+
* 2. Extract patterns and variable assignments
|
|
7
|
+
* 3. Trace variable definitions to resolve model names
|
|
8
|
+
* 4. Identify actual LLM callsites (not client initialization)
|
|
9
|
+
*
|
|
10
|
+
* Architecture: Claude Agent SDK = Engine, TypeScript = Glue (per TDD §1)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import 'dotenv/config';
|
|
14
|
+
import { query, tool, createSdkMcpServer } from '@anthropic-ai/claude-agent-sdk';
|
|
15
|
+
import type { SDKMessage } from '@anthropic-ai/claude-agent-sdk';
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import { readFileSync, existsSync } from 'fs';
|
|
18
|
+
import { join } from 'path';
|
|
19
|
+
import type { ScanResult, Callsite, Provider, Patterns } from './types.js';
|
|
20
|
+
import { createHash } from 'crypto';
|
|
21
|
+
import { loadPrompt, loadConfig, getConfiguredModel } from './templates.js';
|
|
22
|
+
|
|
23
|
+
// Type for MCP tool result
|
|
24
|
+
type ToolResult = { content: Array<{ type: 'text'; text: string }> };
|
|
25
|
+
|
|
26
|
+
// Load agent system prompt from YAML (with hardcoded fallback)
|
|
27
|
+
function getAgentSystemPrompt(): string {
|
|
28
|
+
const prompt = loadPrompt('agent-analyzer');
|
|
29
|
+
if (prompt) {
|
|
30
|
+
return prompt.prompt;
|
|
31
|
+
}
|
|
32
|
+
// Fallback to hardcoded prompt if YAML not available
|
|
33
|
+
return `You are an expert code analyst specializing in identifying LLM/AI inference points in source code.
|
|
34
|
+
|
|
35
|
+
Your task is to analyze code and find ALL actual LLM inference points with accurate provider and model information.
|
|
36
|
+
|
|
37
|
+
## CRITICAL RULES
|
|
38
|
+
|
|
39
|
+
### What IS an inference point (DO report these):
|
|
40
|
+
- client.chat.completions.create() - OpenAI API call
|
|
41
|
+
- client.messages.create() - Anthropic API call
|
|
42
|
+
- client.embeddings.create() - OpenAI embeddings call
|
|
43
|
+
- predictor(question=...) - DSPy module invocation (after dspy.Predict/ChainOfThought)
|
|
44
|
+
- chain.invoke() - LangChain invocation
|
|
45
|
+
- llm.generate() - Direct generation calls
|
|
46
|
+
|
|
47
|
+
### What is NOT an inference point (DO NOT report these):
|
|
48
|
+
- Client initialization: openai.OpenAI(), anthropic.Anthropic()
|
|
49
|
+
- Import statements
|
|
50
|
+
- Variable assignments: model = "gpt-4o"
|
|
51
|
+
- Class/function definitions
|
|
52
|
+
- DSPy Predict/ChainOfThought creation (only report the invocation)
|
|
53
|
+
|
|
54
|
+
### Model Extraction Rules:
|
|
55
|
+
1. Look at the model= parameter in the function call
|
|
56
|
+
2. Trace variables back to their definitions
|
|
57
|
+
3. For DSPy: find dspy.LM("provider/model") and extract the model part
|
|
58
|
+
4. Return the FULL exact model name (e.g., "gpt-4o-mini" not "gpt-4")
|
|
59
|
+
|
|
60
|
+
### Framework Detection:
|
|
61
|
+
- DSPy: look for dspy imports, dspy.Predict, dspy.ChainOfThought
|
|
62
|
+
- LangChain: look for langchain imports, ChatOpenAI, LLMChain
|
|
63
|
+
- LlamaIndex: look for llama_index imports
|
|
64
|
+
|
|
65
|
+
## WORKFLOW
|
|
66
|
+
|
|
67
|
+
1. Use search_pattern to find potential inference point locations
|
|
68
|
+
2. Use read_file to examine the code in detail
|
|
69
|
+
3. Use trace_variable to find where models/clients are defined
|
|
70
|
+
4. Use report_callsites to report your findings
|
|
71
|
+
|
|
72
|
+
Be thorough but precise. Only report actual inference points, not initialization or configuration.`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// =============================================================================
|
|
76
|
+
// TYPES
|
|
77
|
+
// =============================================================================
|
|
78
|
+
|
|
79
|
+
interface AgentCallsite {
|
|
80
|
+
file: string;
|
|
81
|
+
line: number;
|
|
82
|
+
provider: string | null;
|
|
83
|
+
model: string | null;
|
|
84
|
+
framework: string | null;
|
|
85
|
+
patterns: Partial<Patterns>;
|
|
86
|
+
confidence: number;
|
|
87
|
+
reasoning: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
interface AgentInsight {
|
|
91
|
+
severity: 'critical' | 'warning' | 'info';
|
|
92
|
+
category: string;
|
|
93
|
+
headline: string;
|
|
94
|
+
evidence: string;
|
|
95
|
+
location: string;
|
|
96
|
+
recommendation?: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
interface AgentAnalysisResult {
|
|
100
|
+
callsites: AgentCallsite[];
|
|
101
|
+
insights: AgentInsight[];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// =============================================================================
|
|
105
|
+
// TOOL CONTEXT (shared state for tool execution)
|
|
106
|
+
// =============================================================================
|
|
107
|
+
|
|
108
|
+
interface ToolContext {
|
|
109
|
+
projectRoot: string;
|
|
110
|
+
fileContents: Map<string, string>;
|
|
111
|
+
reportedCallsites: AgentCallsite[];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// =============================================================================
|
|
115
|
+
// MCP TOOLS USING CLAUDE AGENT SDK
|
|
116
|
+
// =============================================================================
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Helper to create ToolResult from string
|
|
120
|
+
*/
|
|
121
|
+
function makeToolResult(text: string): ToolResult {
|
|
122
|
+
return {
|
|
123
|
+
content: [{ type: 'text', text }],
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Create MCP server with analysis tools using Claude Agent SDK
|
|
129
|
+
*/
|
|
130
|
+
function createAnalysisMcpServer(ctx: ToolContext) {
|
|
131
|
+
// Tool: read_file - Read source code file contents
|
|
132
|
+
const readFileTool = tool(
|
|
133
|
+
'read_file',
|
|
134
|
+
'Read the contents of a source code file. Use this to examine code in detail.',
|
|
135
|
+
{
|
|
136
|
+
file_path: z.string().describe('Relative path to the file from project root'),
|
|
137
|
+
},
|
|
138
|
+
async ({ file_path }): Promise<ToolResult> => {
|
|
139
|
+
// Try from cache first
|
|
140
|
+
if (ctx.fileContents.has(file_path)) {
|
|
141
|
+
const content = ctx.fileContents.get(file_path)!;
|
|
142
|
+
const numbered = content.split('\n')
|
|
143
|
+
.map((line, i) => `${i + 1}: ${line}`)
|
|
144
|
+
.join('\n');
|
|
145
|
+
return makeToolResult(numbered.slice(0, 8000)); // Limit size
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Try reading from disk
|
|
149
|
+
const absPath = join(ctx.projectRoot, file_path);
|
|
150
|
+
if (existsSync(absPath)) {
|
|
151
|
+
try {
|
|
152
|
+
const content = readFileSync(absPath, 'utf-8');
|
|
153
|
+
const numbered = content.split('\n')
|
|
154
|
+
.map((line, i) => `${i + 1}: ${line}`)
|
|
155
|
+
.join('\n');
|
|
156
|
+
return makeToolResult(numbered.slice(0, 8000));
|
|
157
|
+
} catch (e) {
|
|
158
|
+
return makeToolResult(`Error reading file: ${e}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return makeToolResult(`File not found: ${file_path}`);
|
|
163
|
+
}
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
// Tool: search_pattern - Search for regex patterns across files
|
|
167
|
+
const searchPatternTool = tool(
|
|
168
|
+
'search_pattern',
|
|
169
|
+
'Search for a regex pattern across all source files. Returns matching lines with file and line number.',
|
|
170
|
+
{
|
|
171
|
+
pattern: z.string().describe('Regex pattern to search for (e.g., "dspy\\.LM\\(" or "model\\s*=")'),
|
|
172
|
+
file_filter: z.string().optional().describe('Optional glob pattern to filter files (e.g., "*.py" or "*.ts")'),
|
|
173
|
+
},
|
|
174
|
+
async ({ pattern, file_filter }): Promise<ToolResult> => {
|
|
175
|
+
const results: string[] = [];
|
|
176
|
+
const regex = new RegExp(pattern, 'gi');
|
|
177
|
+
|
|
178
|
+
for (const [filePath, content] of ctx.fileContents) {
|
|
179
|
+
// Apply file filter if provided
|
|
180
|
+
if (file_filter && !filePath.match(new RegExp(file_filter.replace('*', '.*')))) {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const lines = content.split('\n');
|
|
185
|
+
for (let i = 0; i < lines.length; i++) {
|
|
186
|
+
if (regex.test(lines[i])) {
|
|
187
|
+
results.push(`${filePath}:${i + 1}: ${lines[i].trim().slice(0, 100)}`);
|
|
188
|
+
if (results.length >= 20) break;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (results.length >= 20) break;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return makeToolResult(results.length > 0 ? results.join('\n') : 'No matches found');
|
|
195
|
+
}
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
// Tool: trace_variable - Find variable definitions and assignments
|
|
199
|
+
const traceVariableTool = tool(
|
|
200
|
+
'trace_variable',
|
|
201
|
+
'Find where a variable is defined or assigned in a file. Useful for tracing model names.',
|
|
202
|
+
{
|
|
203
|
+
file_path: z.string().describe('File to search in'),
|
|
204
|
+
variable_name: z.string().describe('Variable name to trace (e.g., "model", "lm", "client")'),
|
|
205
|
+
},
|
|
206
|
+
async ({ file_path, variable_name }): Promise<ToolResult> => {
|
|
207
|
+
const content = ctx.fileContents.get(file_path);
|
|
208
|
+
if (!content) {
|
|
209
|
+
return makeToolResult(`File not found: ${file_path}`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const results: string[] = [];
|
|
213
|
+
const lines = content.split('\n');
|
|
214
|
+
|
|
215
|
+
// Look for assignments and definitions
|
|
216
|
+
const patterns = [
|
|
217
|
+
new RegExp(`\\b${variable_name}\\s*=\\s*(.+)`, 'g'),
|
|
218
|
+
new RegExp(`\\bconst\\s+${variable_name}\\s*=\\s*(.+)`, 'g'),
|
|
219
|
+
new RegExp(`\\blet\\s+${variable_name}\\s*=\\s*(.+)`, 'g'),
|
|
220
|
+
new RegExp(`\\bvar\\s+${variable_name}\\s*=\\s*(.+)`, 'g'),
|
|
221
|
+
new RegExp(`\\bdef\\s+.*${variable_name}.*:`, 'g'),
|
|
222
|
+
new RegExp(`${variable_name}\\s*:\\s*(.+)`, 'g'),
|
|
223
|
+
];
|
|
224
|
+
|
|
225
|
+
for (let i = 0; i < lines.length; i++) {
|
|
226
|
+
for (const p of patterns) {
|
|
227
|
+
if (p.test(lines[i])) {
|
|
228
|
+
results.push(`Line ${i + 1}: ${lines[i].trim()}`);
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return makeToolResult(
|
|
235
|
+
results.length > 0
|
|
236
|
+
? `Found ${results.length} references to "${variable_name}":\n${results.join('\n')}`
|
|
237
|
+
: `No definitions found for "${variable_name}"`
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
// Tool: report_callsites - Report discovered LLM callsites
|
|
243
|
+
const reportCallsitesTool = tool(
|
|
244
|
+
'report_callsites',
|
|
245
|
+
'Report discovered LLM callsites. Call this when you have identified callsites with their details.',
|
|
246
|
+
{
|
|
247
|
+
callsites: z.array(z.object({
|
|
248
|
+
file: z.string().describe('File path'),
|
|
249
|
+
line: z.number().describe('Line number of the actual inference call'),
|
|
250
|
+
provider: z.string().describe('Provider: openai, anthropic, google, etc.'),
|
|
251
|
+
model: z.string().optional().describe('Exact model name as found in code'),
|
|
252
|
+
framework: z.string().optional().describe('Framework: dspy, langchain, llamaindex, or null'),
|
|
253
|
+
reasoning: z.string().describe('Brief explanation of how you identified this'),
|
|
254
|
+
})).describe('Array of identified callsites'),
|
|
255
|
+
},
|
|
256
|
+
async ({ callsites }): Promise<ToolResult> => {
|
|
257
|
+
for (const cs of callsites) {
|
|
258
|
+
ctx.reportedCallsites.push({
|
|
259
|
+
file: cs.file,
|
|
260
|
+
line: cs.line,
|
|
261
|
+
provider: cs.provider || null,
|
|
262
|
+
model: cs.model || null,
|
|
263
|
+
framework: cs.framework || null,
|
|
264
|
+
patterns: {},
|
|
265
|
+
confidence: 0.9,
|
|
266
|
+
reasoning: cs.reasoning,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
return makeToolResult(`Recorded ${callsites.length} callsites. Total: ${ctx.reportedCallsites.length}`);
|
|
270
|
+
}
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
// Create MCP server with all tools
|
|
274
|
+
return createSdkMcpServer({
|
|
275
|
+
name: 'peakinfer-analyzer',
|
|
276
|
+
version: '1.0.0',
|
|
277
|
+
tools: [readFileTool, searchPatternTool, traceVariableTool, reportCallsitesTool],
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// =============================================================================
|
|
282
|
+
// AGENT LOOP USING CLAUDE AGENT SDK
|
|
283
|
+
// =============================================================================
|
|
284
|
+
|
|
285
|
+
// AGENT_SYSTEM_PROMPT is now loaded from prompts/agent-analyzer.yaml via getAgentSystemPrompt()
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Extract text content from SDK messages
|
|
289
|
+
*/
|
|
290
|
+
function extractTextFromMessages(messages: SDKMessage[]): string {
|
|
291
|
+
let text = '';
|
|
292
|
+
for (const msg of messages) {
|
|
293
|
+
if (msg.type === 'assistant' && msg.message?.content) {
|
|
294
|
+
for (const block of msg.message.content) {
|
|
295
|
+
if (block.type === 'text') {
|
|
296
|
+
text += block.text;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return text;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export async function analyzeWithAgent(
|
|
305
|
+
scanResult: ScanResult,
|
|
306
|
+
options: { verbose?: boolean; maxIterations?: number } = {}
|
|
307
|
+
): Promise<AgentAnalysisResult> {
|
|
308
|
+
// Load configuration
|
|
309
|
+
const config = loadConfig();
|
|
310
|
+
const { verbose = config.agent.verbose } = options;
|
|
311
|
+
|
|
312
|
+
// Check for API key
|
|
313
|
+
if (!process.env.ANTHROPIC_API_KEY) {
|
|
314
|
+
throw new Error('ANTHROPIC_API_KEY required for agent analysis');
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Build file contents map
|
|
318
|
+
const fileContents = new Map<string, string>();
|
|
319
|
+
for (const file of scanResult.files) {
|
|
320
|
+
try {
|
|
321
|
+
const absPath = join(scanResult.root, file.path);
|
|
322
|
+
fileContents.set(file.path, readFileSync(absPath, 'utf-8'));
|
|
323
|
+
} catch {
|
|
324
|
+
// Skip unreadable files
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Create tool context for shared state
|
|
329
|
+
const ctx: ToolContext = {
|
|
330
|
+
projectRoot: scanResult.root,
|
|
331
|
+
fileContents,
|
|
332
|
+
reportedCallsites: [],
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
// Create MCP server with analysis tools
|
|
336
|
+
const mcpServer = createAnalysisMcpServer(ctx);
|
|
337
|
+
|
|
338
|
+
// Build initial task with candidate info
|
|
339
|
+
const candidateInfo = scanResult.candidates
|
|
340
|
+
.map(c => `- ${c.file}:${c.line}: ${c.snippet}`)
|
|
341
|
+
.join('\n');
|
|
342
|
+
|
|
343
|
+
const fileList = scanResult.files.map(f => f.path).join('\n');
|
|
344
|
+
|
|
345
|
+
const prompt = `Analyze this codebase to identify all LLM inference callsites.
|
|
346
|
+
|
|
347
|
+
## Files in project:
|
|
348
|
+
${fileList}
|
|
349
|
+
|
|
350
|
+
## Candidate locations (from regex scan):
|
|
351
|
+
${candidateInfo}
|
|
352
|
+
|
|
353
|
+
## Instructions:
|
|
354
|
+
1. Start by examining the candidate files
|
|
355
|
+
2. For each candidate, determine if it's a real callsite or false positive
|
|
356
|
+
3. Look for callsites that the regex might have missed (especially framework calls)
|
|
357
|
+
4. Trace variable assignments to find exact model names
|
|
358
|
+
5. Report all confirmed callsites using the report_callsites tool
|
|
359
|
+
|
|
360
|
+
Begin your analysis.`;
|
|
361
|
+
|
|
362
|
+
if (verbose) {
|
|
363
|
+
console.log('[agent] Starting Claude Agent SDK analysis...');
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Get model from config
|
|
367
|
+
const model = getConfiguredModel('agent', false);
|
|
368
|
+
|
|
369
|
+
try {
|
|
370
|
+
// Use Claude Agent SDK query() function with MCP server
|
|
371
|
+
const agentQuery = query({
|
|
372
|
+
prompt,
|
|
373
|
+
options: {
|
|
374
|
+
systemPrompt: getAgentSystemPrompt(),
|
|
375
|
+
model,
|
|
376
|
+
mcpServers: {
|
|
377
|
+
'peakinfer-analyzer': mcpServer,
|
|
378
|
+
},
|
|
379
|
+
permissionMode: 'default',
|
|
380
|
+
cwd: scanResult.root,
|
|
381
|
+
},
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
// Collect all messages from the agent
|
|
385
|
+
const messages: SDKMessage[] = [];
|
|
386
|
+
for await (const message of agentQuery) {
|
|
387
|
+
messages.push(message);
|
|
388
|
+
|
|
389
|
+
if (verbose && message.type === 'assistant') {
|
|
390
|
+
// Log tool usage for debugging
|
|
391
|
+
if (message.message?.content) {
|
|
392
|
+
for (const block of message.message.content) {
|
|
393
|
+
if (block.type === 'tool_use') {
|
|
394
|
+
console.log(`[agent] Tool: ${block.name}`);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (verbose) {
|
|
402
|
+
console.log('[agent] Analysis complete');
|
|
403
|
+
console.log(`[agent] Found ${ctx.reportedCallsites.length} callsites`);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return {
|
|
407
|
+
callsites: ctx.reportedCallsites,
|
|
408
|
+
insights: [],
|
|
409
|
+
};
|
|
410
|
+
} catch (error) {
|
|
411
|
+
// If primary model fails, try fallback
|
|
412
|
+
const fallbackModel = getConfiguredModel('agent', true);
|
|
413
|
+
|
|
414
|
+
if (verbose) {
|
|
415
|
+
console.log(`[agent] ${model} failed, trying ${fallbackModel}`);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const agentQuery = query({
|
|
419
|
+
prompt,
|
|
420
|
+
options: {
|
|
421
|
+
systemPrompt: getAgentSystemPrompt(),
|
|
422
|
+
model: fallbackModel,
|
|
423
|
+
mcpServers: {
|
|
424
|
+
'peakinfer-analyzer': mcpServer,
|
|
425
|
+
},
|
|
426
|
+
permissionMode: 'default',
|
|
427
|
+
cwd: scanResult.root,
|
|
428
|
+
},
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
for await (const message of agentQuery) {
|
|
432
|
+
if (verbose && message.type === 'assistant') {
|
|
433
|
+
if (message.message?.content) {
|
|
434
|
+
for (const block of message.message.content) {
|
|
435
|
+
if (block.type === 'tool_use') {
|
|
436
|
+
console.log(`[agent] Tool: ${block.name}`);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (verbose) {
|
|
444
|
+
console.log('[agent] Analysis complete (fallback)');
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return {
|
|
448
|
+
callsites: ctx.reportedCallsites,
|
|
449
|
+
insights: [],
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// =============================================================================
|
|
455
|
+
// INTEGRATION HELPER
|
|
456
|
+
// =============================================================================
|
|
457
|
+
|
|
458
|
+
function generateCallsiteId(file: string, line: number): string {
|
|
459
|
+
const hash = createHash('sha256')
|
|
460
|
+
.update(`${file}:${line}`)
|
|
461
|
+
.digest('hex')
|
|
462
|
+
.slice(0, 8);
|
|
463
|
+
return `cs_${hash}`;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Convert agent results to standard Callsite format
|
|
468
|
+
*/
|
|
469
|
+
export function convertAgentCallsites(agentCallsites: AgentCallsite[]): Callsite[] {
|
|
470
|
+
return agentCallsites.map(ac => ({
|
|
471
|
+
id: generateCallsiteId(ac.file, ac.line),
|
|
472
|
+
file: ac.file,
|
|
473
|
+
line: ac.line,
|
|
474
|
+
provider: ac.provider as Provider | null,
|
|
475
|
+
model: ac.model,
|
|
476
|
+
framework: ac.framework,
|
|
477
|
+
runtime: null,
|
|
478
|
+
patterns: ac.patterns as Patterns,
|
|
479
|
+
confidence: ac.confidence,
|
|
480
|
+
}));
|
|
481
|
+
}
|