@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.
Files changed (367) hide show
  1. package/.claude/settings.local.json +8 -0
  2. package/.env.example +6 -0
  3. package/.github/workflows/peakinfer.yml +64 -0
  4. package/CHANGELOG.md +31 -0
  5. package/LICENSE +190 -0
  6. package/README.md +335 -0
  7. package/data/inferencemax.json +274 -0
  8. package/dist/agent-analyzer.d.ts +45 -0
  9. package/dist/agent-analyzer.d.ts.map +1 -0
  10. package/dist/agent-analyzer.js +374 -0
  11. package/dist/agent-analyzer.js.map +1 -0
  12. package/dist/agent.d.ts +76 -0
  13. package/dist/agent.d.ts.map +1 -0
  14. package/dist/agent.js +965 -0
  15. package/dist/agent.js.map +1 -0
  16. package/dist/agents/correlation-analyzer.d.ts +34 -0
  17. package/dist/agents/correlation-analyzer.d.ts.map +1 -0
  18. package/dist/agents/correlation-analyzer.js +261 -0
  19. package/dist/agents/correlation-analyzer.js.map +1 -0
  20. package/dist/agents/index.d.ts +91 -0
  21. package/dist/agents/index.d.ts.map +1 -0
  22. package/dist/agents/index.js +111 -0
  23. package/dist/agents/index.js.map +1 -0
  24. package/dist/agents/runtime-analyzer.d.ts +38 -0
  25. package/dist/agents/runtime-analyzer.d.ts.map +1 -0
  26. package/dist/agents/runtime-analyzer.js +244 -0
  27. package/dist/agents/runtime-analyzer.js.map +1 -0
  28. package/dist/analysis-types.d.ts +500 -0
  29. package/dist/analysis-types.d.ts.map +1 -0
  30. package/dist/analysis-types.js +11 -0
  31. package/dist/analysis-types.js.map +1 -0
  32. package/dist/analytics.d.ts +25 -0
  33. package/dist/analytics.d.ts.map +1 -0
  34. package/dist/analytics.js +94 -0
  35. package/dist/analytics.js.map +1 -0
  36. package/dist/analyzer.d.ts +48 -0
  37. package/dist/analyzer.d.ts.map +1 -0
  38. package/dist/analyzer.js +547 -0
  39. package/dist/analyzer.js.map +1 -0
  40. package/dist/artifacts.d.ts +44 -0
  41. package/dist/artifacts.d.ts.map +1 -0
  42. package/dist/artifacts.js +165 -0
  43. package/dist/artifacts.js.map +1 -0
  44. package/dist/benchmarks/index.d.ts +88 -0
  45. package/dist/benchmarks/index.d.ts.map +1 -0
  46. package/dist/benchmarks/index.js +205 -0
  47. package/dist/benchmarks/index.js.map +1 -0
  48. package/dist/cli.d.ts +3 -0
  49. package/dist/cli.d.ts.map +1 -0
  50. package/dist/cli.js +427 -0
  51. package/dist/cli.js.map +1 -0
  52. package/dist/commands/ci.d.ts +19 -0
  53. package/dist/commands/ci.d.ts.map +1 -0
  54. package/dist/commands/ci.js +253 -0
  55. package/dist/commands/ci.js.map +1 -0
  56. package/dist/commands/config.d.ts +16 -0
  57. package/dist/commands/config.d.ts.map +1 -0
  58. package/dist/commands/config.js +249 -0
  59. package/dist/commands/config.js.map +1 -0
  60. package/dist/commands/demo.d.ts +15 -0
  61. package/dist/commands/demo.d.ts.map +1 -0
  62. package/dist/commands/demo.js +106 -0
  63. package/dist/commands/demo.js.map +1 -0
  64. package/dist/commands/export.d.ts +14 -0
  65. package/dist/commands/export.d.ts.map +1 -0
  66. package/dist/commands/export.js +209 -0
  67. package/dist/commands/export.js.map +1 -0
  68. package/dist/commands/history.d.ts +15 -0
  69. package/dist/commands/history.d.ts.map +1 -0
  70. package/dist/commands/history.js +389 -0
  71. package/dist/commands/history.js.map +1 -0
  72. package/dist/commands/template.d.ts +14 -0
  73. package/dist/commands/template.d.ts.map +1 -0
  74. package/dist/commands/template.js +341 -0
  75. package/dist/commands/template.js.map +1 -0
  76. package/dist/commands/validate-map.d.ts +12 -0
  77. package/dist/commands/validate-map.d.ts.map +1 -0
  78. package/dist/commands/validate-map.js +274 -0
  79. package/dist/commands/validate-map.js.map +1 -0
  80. package/dist/commands/whatif.d.ts +17 -0
  81. package/dist/commands/whatif.d.ts.map +1 -0
  82. package/dist/commands/whatif.js +206 -0
  83. package/dist/commands/whatif.js.map +1 -0
  84. package/dist/comparison.d.ts +38 -0
  85. package/dist/comparison.d.ts.map +1 -0
  86. package/dist/comparison.js +223 -0
  87. package/dist/comparison.js.map +1 -0
  88. package/dist/config.d.ts +42 -0
  89. package/dist/config.d.ts.map +1 -0
  90. package/dist/config.js +158 -0
  91. package/dist/config.js.map +1 -0
  92. package/dist/connectors/helicone.d.ts +9 -0
  93. package/dist/connectors/helicone.d.ts.map +1 -0
  94. package/dist/connectors/helicone.js +106 -0
  95. package/dist/connectors/helicone.js.map +1 -0
  96. package/dist/connectors/index.d.ts +37 -0
  97. package/dist/connectors/index.d.ts.map +1 -0
  98. package/dist/connectors/index.js +65 -0
  99. package/dist/connectors/index.js.map +1 -0
  100. package/dist/connectors/langsmith.d.ts +9 -0
  101. package/dist/connectors/langsmith.d.ts.map +1 -0
  102. package/dist/connectors/langsmith.js +122 -0
  103. package/dist/connectors/langsmith.js.map +1 -0
  104. package/dist/connectors/types.d.ts +83 -0
  105. package/dist/connectors/types.d.ts.map +1 -0
  106. package/dist/connectors/types.js +98 -0
  107. package/dist/connectors/types.js.map +1 -0
  108. package/dist/cost-estimator.d.ts +46 -0
  109. package/dist/cost-estimator.d.ts.map +1 -0
  110. package/dist/cost-estimator.js +104 -0
  111. package/dist/cost-estimator.js.map +1 -0
  112. package/dist/costs.d.ts +57 -0
  113. package/dist/costs.d.ts.map +1 -0
  114. package/dist/costs.js +251 -0
  115. package/dist/costs.js.map +1 -0
  116. package/dist/counterfactuals.d.ts +29 -0
  117. package/dist/counterfactuals.d.ts.map +1 -0
  118. package/dist/counterfactuals.js +448 -0
  119. package/dist/counterfactuals.js.map +1 -0
  120. package/dist/enhancement-prompts.d.ts +41 -0
  121. package/dist/enhancement-prompts.d.ts.map +1 -0
  122. package/dist/enhancement-prompts.js +88 -0
  123. package/dist/enhancement-prompts.js.map +1 -0
  124. package/dist/envelopes.d.ts +20 -0
  125. package/dist/envelopes.d.ts.map +1 -0
  126. package/dist/envelopes.js +790 -0
  127. package/dist/envelopes.js.map +1 -0
  128. package/dist/format-normalizer.d.ts +71 -0
  129. package/dist/format-normalizer.d.ts.map +1 -0
  130. package/dist/format-normalizer.js +1331 -0
  131. package/dist/format-normalizer.js.map +1 -0
  132. package/dist/history.d.ts +79 -0
  133. package/dist/history.d.ts.map +1 -0
  134. package/dist/history.js +313 -0
  135. package/dist/history.js.map +1 -0
  136. package/dist/html.d.ts +11 -0
  137. package/dist/html.d.ts.map +1 -0
  138. package/dist/html.js +463 -0
  139. package/dist/html.js.map +1 -0
  140. package/dist/impact.d.ts +42 -0
  141. package/dist/impact.d.ts.map +1 -0
  142. package/dist/impact.js +443 -0
  143. package/dist/impact.js.map +1 -0
  144. package/dist/index.d.ts +26 -0
  145. package/dist/index.d.ts.map +1 -0
  146. package/dist/index.js +34 -0
  147. package/dist/index.js.map +1 -0
  148. package/dist/insights.d.ts +5 -0
  149. package/dist/insights.d.ts.map +1 -0
  150. package/dist/insights.js +271 -0
  151. package/dist/insights.js.map +1 -0
  152. package/dist/joiner.d.ts +9 -0
  153. package/dist/joiner.d.ts.map +1 -0
  154. package/dist/joiner.js +247 -0
  155. package/dist/joiner.js.map +1 -0
  156. package/dist/orchestrator.d.ts +34 -0
  157. package/dist/orchestrator.d.ts.map +1 -0
  158. package/dist/orchestrator.js +827 -0
  159. package/dist/orchestrator.js.map +1 -0
  160. package/dist/pdf.d.ts +26 -0
  161. package/dist/pdf.d.ts.map +1 -0
  162. package/dist/pdf.js +84 -0
  163. package/dist/pdf.js.map +1 -0
  164. package/dist/prediction.d.ts +33 -0
  165. package/dist/prediction.d.ts.map +1 -0
  166. package/dist/prediction.js +316 -0
  167. package/dist/prediction.js.map +1 -0
  168. package/dist/prompts/loader.d.ts +38 -0
  169. package/dist/prompts/loader.d.ts.map +1 -0
  170. package/dist/prompts/loader.js +60 -0
  171. package/dist/prompts/loader.js.map +1 -0
  172. package/dist/renderer.d.ts +64 -0
  173. package/dist/renderer.d.ts.map +1 -0
  174. package/dist/renderer.js +923 -0
  175. package/dist/renderer.js.map +1 -0
  176. package/dist/runid.d.ts +57 -0
  177. package/dist/runid.d.ts.map +1 -0
  178. package/dist/runid.js +199 -0
  179. package/dist/runid.js.map +1 -0
  180. package/dist/runtime.d.ts +29 -0
  181. package/dist/runtime.d.ts.map +1 -0
  182. package/dist/runtime.js +366 -0
  183. package/dist/runtime.js.map +1 -0
  184. package/dist/scanner.d.ts +11 -0
  185. package/dist/scanner.d.ts.map +1 -0
  186. package/dist/scanner.js +426 -0
  187. package/dist/scanner.js.map +1 -0
  188. package/dist/templates.d.ts +120 -0
  189. package/dist/templates.d.ts.map +1 -0
  190. package/dist/templates.js +429 -0
  191. package/dist/templates.js.map +1 -0
  192. package/dist/tools/index.d.ts +153 -0
  193. package/dist/tools/index.d.ts.map +1 -0
  194. package/dist/tools/index.js +177 -0
  195. package/dist/tools/index.js.map +1 -0
  196. package/dist/types.d.ts +3647 -0
  197. package/dist/types.d.ts.map +1 -0
  198. package/dist/types.js +703 -0
  199. package/dist/types.js.map +1 -0
  200. package/dist/version.d.ts +7 -0
  201. package/dist/version.d.ts.map +1 -0
  202. package/dist/version.js +23 -0
  203. package/dist/version.js.map +1 -0
  204. package/docs/demo-guide.md +423 -0
  205. package/docs/events-format.md +295 -0
  206. package/docs/inferencemap-spec.md +344 -0
  207. package/docs/migration-v2.md +293 -0
  208. package/fixtures/demo/precomputed.json +142 -0
  209. package/fixtures/demo-project/README.md +52 -0
  210. package/fixtures/demo-project/ai-service.ts +65 -0
  211. package/fixtures/demo-project/sample-events.jsonl +15 -0
  212. package/fixtures/demo-project/src/ai-service.ts +128 -0
  213. package/fixtures/demo-project/src/llm-client.ts +155 -0
  214. package/package.json +65 -0
  215. package/prompts/agent-analyzer.yaml +47 -0
  216. package/prompts/ci-gate.yaml +98 -0
  217. package/prompts/correlation-analyzer.yaml +178 -0
  218. package/prompts/format-normalizer.yaml +46 -0
  219. package/prompts/peak-performance.yaml +180 -0
  220. package/prompts/pr-comment.yaml +111 -0
  221. package/prompts/runtime-analyzer.yaml +189 -0
  222. package/prompts/unified-analyzer.yaml +241 -0
  223. package/schemas/inference-map.v0.1.json +215 -0
  224. package/scripts/benchmark.ts +394 -0
  225. package/scripts/demo-v1.5.sh +158 -0
  226. package/scripts/sync-from-site.sh +197 -0
  227. package/scripts/validate-sync.sh +178 -0
  228. package/src/agent-analyzer.ts +481 -0
  229. package/src/agent.ts +1232 -0
  230. package/src/agents/correlation-analyzer.ts +353 -0
  231. package/src/agents/index.ts +235 -0
  232. package/src/agents/runtime-analyzer.ts +343 -0
  233. package/src/analysis-types.ts +558 -0
  234. package/src/analytics.ts +100 -0
  235. package/src/analyzer.ts +692 -0
  236. package/src/artifacts.ts +218 -0
  237. package/src/benchmarks/index.ts +309 -0
  238. package/src/cli.ts +503 -0
  239. package/src/commands/ci.ts +336 -0
  240. package/src/commands/config.ts +288 -0
  241. package/src/commands/demo.ts +175 -0
  242. package/src/commands/export.ts +297 -0
  243. package/src/commands/history.ts +425 -0
  244. package/src/commands/template.ts +385 -0
  245. package/src/commands/validate-map.ts +324 -0
  246. package/src/commands/whatif.ts +272 -0
  247. package/src/comparison.ts +283 -0
  248. package/src/config.ts +188 -0
  249. package/src/connectors/helicone.ts +164 -0
  250. package/src/connectors/index.ts +93 -0
  251. package/src/connectors/langsmith.ts +179 -0
  252. package/src/connectors/types.ts +180 -0
  253. package/src/cost-estimator.ts +146 -0
  254. package/src/costs.ts +347 -0
  255. package/src/counterfactuals.ts +516 -0
  256. package/src/enhancement-prompts.ts +118 -0
  257. package/src/envelopes.ts +814 -0
  258. package/src/format-normalizer.ts +1486 -0
  259. package/src/history.ts +400 -0
  260. package/src/html.ts +512 -0
  261. package/src/impact.ts +522 -0
  262. package/src/index.ts +83 -0
  263. package/src/insights.ts +341 -0
  264. package/src/joiner.ts +289 -0
  265. package/src/orchestrator.ts +1015 -0
  266. package/src/pdf.ts +110 -0
  267. package/src/prediction.ts +392 -0
  268. package/src/prompts/loader.ts +88 -0
  269. package/src/renderer.ts +1045 -0
  270. package/src/runid.ts +261 -0
  271. package/src/runtime.ts +450 -0
  272. package/src/scanner.ts +508 -0
  273. package/src/templates.ts +561 -0
  274. package/src/tools/index.ts +214 -0
  275. package/src/types.ts +873 -0
  276. package/src/version.ts +24 -0
  277. package/templates/context-accumulation.yaml +23 -0
  278. package/templates/cost-concentration.yaml +20 -0
  279. package/templates/dead-code.yaml +20 -0
  280. package/templates/latency-explainer.yaml +23 -0
  281. package/templates/optimizations/ab-testing-framework.yaml +74 -0
  282. package/templates/optimizations/api-gateway-optimization.yaml +81 -0
  283. package/templates/optimizations/api-model-routing-strategy.yaml +126 -0
  284. package/templates/optimizations/auto-scaling-optimization.yaml +85 -0
  285. package/templates/optimizations/batch-utilization-diagnostic.yaml +142 -0
  286. package/templates/optimizations/comprehensive-apm.yaml +76 -0
  287. package/templates/optimizations/context-window-optimization.yaml +91 -0
  288. package/templates/optimizations/cost-sensitive-batch-processing.yaml +77 -0
  289. package/templates/optimizations/distributed-training-optimization.yaml +77 -0
  290. package/templates/optimizations/document-analysis-edge.yaml +77 -0
  291. package/templates/optimizations/document-pipeline-optimization.yaml +78 -0
  292. package/templates/optimizations/domain-specific-distillation.yaml +78 -0
  293. package/templates/optimizations/error-handling-optimization.yaml +76 -0
  294. package/templates/optimizations/gptq-4bit-quantization.yaml +96 -0
  295. package/templates/optimizations/long-context-memory-management.yaml +78 -0
  296. package/templates/optimizations/max-tokens-optimization.yaml +76 -0
  297. package/templates/optimizations/memory-bandwidth-optimization.yaml +73 -0
  298. package/templates/optimizations/multi-framework-resilience.yaml +75 -0
  299. package/templates/optimizations/multi-tenant-optimization.yaml +75 -0
  300. package/templates/optimizations/prompt-caching-optimization.yaml +143 -0
  301. package/templates/optimizations/pytorch-to-onnx-migration.yaml +109 -0
  302. package/templates/optimizations/quality-monitoring.yaml +74 -0
  303. package/templates/optimizations/realtime-budget-controls.yaml +74 -0
  304. package/templates/optimizations/realtime-latency-optimization.yaml +74 -0
  305. package/templates/optimizations/sglang-concurrency-optimization.yaml +78 -0
  306. package/templates/optimizations/smart-model-routing.yaml +96 -0
  307. package/templates/optimizations/streaming-batch-selection.yaml +167 -0
  308. package/templates/optimizations/system-prompt-optimization.yaml +75 -0
  309. package/templates/optimizations/tensorrt-llm-performance.yaml +77 -0
  310. package/templates/optimizations/vllm-high-throughput-optimization.yaml +93 -0
  311. package/templates/optimizations/vllm-migration-memory-bound.yaml +78 -0
  312. package/templates/overpowered-extraction.yaml +32 -0
  313. package/templates/overpowered-model.yaml +31 -0
  314. package/templates/prompt-bloat.yaml +24 -0
  315. package/templates/retry-explosion.yaml +28 -0
  316. package/templates/schema/insight.schema.json +113 -0
  317. package/templates/schema/optimization.schema.json +180 -0
  318. package/templates/streaming-drift.yaml +30 -0
  319. package/templates/throughput-gap.yaml +21 -0
  320. package/templates/token-underutilization.yaml +28 -0
  321. package/templates/untested-fallback.yaml +21 -0
  322. package/tests/accuracy/drift-detection.test.ts +184 -0
  323. package/tests/accuracy/false-positives.test.ts +166 -0
  324. package/tests/accuracy/templates.test.ts +205 -0
  325. package/tests/action/commands.test.ts +125 -0
  326. package/tests/action/comments.test.ts +347 -0
  327. package/tests/cli.test.ts +203 -0
  328. package/tests/comparison.test.ts +309 -0
  329. package/tests/correlation-analyzer.test.ts +534 -0
  330. package/tests/counterfactuals.test.ts +347 -0
  331. package/tests/fixtures/events/missing-id.jsonl +1 -0
  332. package/tests/fixtures/events/missing-input.jsonl +1 -0
  333. package/tests/fixtures/events/missing-latency.jsonl +1 -0
  334. package/tests/fixtures/events/missing-model.jsonl +1 -0
  335. package/tests/fixtures/events/missing-output.jsonl +1 -0
  336. package/tests/fixtures/events/missing-provider.jsonl +1 -0
  337. package/tests/fixtures/events/missing-ts.jsonl +1 -0
  338. package/tests/fixtures/events/valid.csv +3 -0
  339. package/tests/fixtures/events/valid.json +1 -0
  340. package/tests/fixtures/events/valid.jsonl +2 -0
  341. package/tests/fixtures/events/with-callsite.jsonl +1 -0
  342. package/tests/fixtures/events/with-intent.jsonl +1 -0
  343. package/tests/fixtures/events/wrong-type.jsonl +1 -0
  344. package/tests/fixtures/repos/empty/.gitkeep +0 -0
  345. package/tests/fixtures/repos/hybrid-router/router.py +35 -0
  346. package/tests/fixtures/repos/saas-anthropic/agent.ts +27 -0
  347. package/tests/fixtures/repos/saas-openai/assistant.js +33 -0
  348. package/tests/fixtures/repos/saas-openai/client.py +26 -0
  349. package/tests/fixtures/repos/self-hosted-vllm/inference.py +22 -0
  350. package/tests/github-action.test.ts +292 -0
  351. package/tests/insights.test.ts +878 -0
  352. package/tests/joiner.test.ts +168 -0
  353. package/tests/performance/action-latency.test.ts +132 -0
  354. package/tests/performance/benchmark.test.ts +189 -0
  355. package/tests/performance/cli-latency.test.ts +102 -0
  356. package/tests/pr-comment.test.ts +313 -0
  357. package/tests/prediction.test.ts +296 -0
  358. package/tests/runtime-analyzer.test.ts +375 -0
  359. package/tests/runtime.test.ts +205 -0
  360. package/tests/scanner.test.ts +122 -0
  361. package/tests/template-conformance.test.ts +526 -0
  362. package/tests/unit/cost-calculator.test.ts +303 -0
  363. package/tests/unit/credits.test.ts +180 -0
  364. package/tests/unit/inference-map.test.ts +276 -0
  365. package/tests/unit/schema.test.ts +300 -0
  366. package/tsconfig.json +20 -0
  367. 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
+ }