agent-learning-kit 0.1.0__py3-none-any.whl

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 (642) hide show
  1. agent_learning_kit-0.1.0.dist-info/METADATA +381 -0
  2. agent_learning_kit-0.1.0.dist-info/RECORD +642 -0
  3. agent_learning_kit-0.1.0.dist-info/WHEEL +4 -0
  4. agent_learning_kit-0.1.0.dist-info/entry_points.txt +5 -0
  5. agent_learning_kit-0.1.0.dist-info/licenses/LICENSE +173 -0
  6. agent_learning_kit-0.1.0.dist-info/licenses/NOTICE +7 -0
  7. fi/__init__.py +5 -0
  8. fi/alk/__init__.py +57 -0
  9. fi/alk/_facade.py +31 -0
  10. fi/alk/_module_alias.py +68 -0
  11. fi/alk/_paths.py +14 -0
  12. fi/alk/_schema.py +522 -0
  13. fi/alk/actions.py +727 -0
  14. fi/alk/bench/__init__.py +517 -0
  15. fi/alk/bench/_codeexec.py +213 -0
  16. fi/alk/bench/_coding.py +215 -0
  17. fi/alk/bench/_docker.py +237 -0
  18. fi/alk/bench/_grader.py +286 -0
  19. fi/alk/bench/_pull.py +212 -0
  20. fi/alk/bench/_voice.py +147 -0
  21. fi/alk/capabilities.py +627 -0
  22. fi/alk/cli.py +6396 -0
  23. fi/alk/config.py +130 -0
  24. fi/alk/cua_loop.py +562 -0
  25. fi/alk/evals.py +2351 -0
  26. fi/alk/extensions.py +163 -0
  27. fi/alk/harness/ARCHITECTURE.md +231 -0
  28. fi/alk/harness/DESIGN.md +246 -0
  29. fi/alk/harness/ENVIRONMENT_CONFORMANCE.md +127 -0
  30. fi/alk/harness/HOW-IT-WORKS.md +297 -0
  31. fi/alk/harness/IMPLEMENTATION_AND_VALIDATION_STATUS.md +229 -0
  32. fi/alk/harness/README.md +417 -0
  33. fi/alk/harness/__init__.py +77 -0
  34. fi/alk/harness/__main__.py +3 -0
  35. fi/alk/harness/amend.py +312 -0
  36. fi/alk/harness/artifacts.py +319 -0
  37. fi/alk/harness/authoring_entrypoint.py +189 -0
  38. fi/alk/harness/authoring_runtime_validation.py +267 -0
  39. fi/alk/harness/backends/README.md +43 -0
  40. fi/alk/harness/backends/__init__.py +122 -0
  41. fi/alk/harness/backends/base.py +241 -0
  42. fi/alk/harness/backends/claude.py +211 -0
  43. fi/alk/harness/backends/files.py +182 -0
  44. fi/alk/harness/backends/vertex_gemini.py +457 -0
  45. fi/alk/harness/background_noise.py +95 -0
  46. fi/alk/harness/build.py +385 -0
  47. fi/alk/harness/bundle.py +593 -0
  48. fi/alk/harness/bundle_author_v2.py +1831 -0
  49. fi/alk/harness/bundle_v2.py +719 -0
  50. fi/alk/harness/call_runner.py +1440 -0
  51. fi/alk/harness/callback_http_adapter.py +111 -0
  52. fi/alk/harness/catalogue.py +287 -0
  53. fi/alk/harness/chat.py +428 -0
  54. fi/alk/harness/chat_call_runner.py +506 -0
  55. fi/alk/harness/checks.py +136 -0
  56. fi/alk/harness/cli.py +1354 -0
  57. fi/alk/harness/config.py +338 -0
  58. fi/alk/harness/contract.py +718 -0
  59. fi/alk/harness/credentials.py +674 -0
  60. fi/alk/harness/data/persona_vocabulary.json +111 -0
  61. fi/alk/harness/environment.py +99 -0
  62. fi/alk/harness/environment_plan.py +168 -0
  63. fi/alk/harness/events.py +125 -0
  64. fi/alk/harness/executor.py +304 -0
  65. fi/alk/harness/folder.py +234 -0
  66. fi/alk/harness/generated_runtime.py +815 -0
  67. fi/alk/harness/github.py +72 -0
  68. fi/alk/harness/hosted_authoring_entrypoint.py +183 -0
  69. fi/alk/harness/hosted_entrypoint.py +2402 -0
  70. fi/alk/harness/hosted_scheduler.py +2218 -0
  71. fi/alk/harness/job.py +426 -0
  72. fi/alk/harness/judge.py +184 -0
  73. fi/alk/harness/livekit_source.py +50 -0
  74. fi/alk/harness/livekit_tool_trace_bootstrap.py +71 -0
  75. fi/alk/harness/observability.py +208 -0
  76. fi/alk/harness/outbound.py +3252 -0
  77. fi/alk/harness/packaging.py +515 -0
  78. fi/alk/harness/persona_guides.py +157 -0
  79. fi/alk/harness/platform.py +692 -0
  80. fi/alk/harness/process_preflight.py +764 -0
  81. fi/alk/harness/process_runtime.py +5670 -0
  82. fi/alk/harness/prove.py +425 -0
  83. fi/alk/harness/provider_import.py +703 -0
  84. fi/alk/harness/provider_lifecycle.py +392 -0
  85. fi/alk/harness/provision.py +2896 -0
  86. fi/alk/harness/reception.py +147 -0
  87. fi/alk/harness/retell_chat_call_runner.py +373 -0
  88. fi/alk/harness/run/__init__.py +296 -0
  89. fi/alk/harness/run/alk.py +184 -0
  90. fi/alk/harness/run/call.py +162 -0
  91. fi/alk/harness/run/conversation.py +264 -0
  92. fi/alk/harness/run/data/voices_by_language_and_gender.json +693 -0
  93. fi/alk/harness/run/evidence.py +195 -0
  94. fi/alk/harness/run/grade.py +598 -0
  95. fi/alk/harness/run/live.py +297 -0
  96. fi/alk/harness/run/models.py +56 -0
  97. fi/alk/harness/run/platform_evals.py +227 -0
  98. fi/alk/harness/run/sdk_voice.py +130 -0
  99. fi/alk/harness/run/simulation.py +1209 -0
  100. fi/alk/harness/run/stage.py +91 -0
  101. fi/alk/harness/run/targets.py +508 -0
  102. fi/alk/harness/run/tools.py +601 -0
  103. fi/alk/harness/run/voice.py +340 -0
  104. fi/alk/harness/runtime.py +172 -0
  105. fi/alk/harness/sandbox_server.py +2011 -0
  106. fi/alk/harness/sandbox_worker.py +44 -0
  107. fi/alk/harness/scenario.py +1048 -0
  108. fi/alk/harness/scenario_source.py +879 -0
  109. fi/alk/harness/scenario_tools.py +1143 -0
  110. fi/alk/harness/scenarios.py +915 -0
  111. fi/alk/harness/secrets.py +168 -0
  112. fi/alk/harness/service_catalog.py +97 -0
  113. fi/alk/harness/session.py +391 -0
  114. fi/alk/harness/sessions.py +372 -0
  115. fi/alk/harness/simulator.py +76 -0
  116. fi/alk/harness/simulator_voice.py +928 -0
  117. fi/alk/harness/skills/build-environment/SKILL.md +538 -0
  118. fi/alk/harness/skills/harness.md +131 -0
  119. fi/alk/harness/skills/kinds/chat.md +48 -0
  120. fi/alk/harness/skills/kinds/voice-voicemail.md +63 -0
  121. fi/alk/harness/skills/kinds/voice.md +59 -0
  122. fi/alk/harness/skills/plan-suite/SKILL.md +103 -0
  123. fi/alk/harness/skills/provision-environment/SKILL.md +136 -0
  124. fi/alk/harness/skills/run-scenarios/SKILL.md +112 -0
  125. fi/alk/harness/skills/understand-agent/SKILL.md +251 -0
  126. fi/alk/harness/skills/write-scenarios/SKILL.md +606 -0
  127. fi/alk/harness/skills/write-scenarios/references/refusals.md +28 -0
  128. fi/alk/harness/skills/write-scenarios/references/world-api.md +92 -0
  129. fi/alk/harness/source_data_invariants.py +444 -0
  130. fi/alk/harness/source_tool_evidence.py +79 -0
  131. fi/alk/harness/sources.py +253 -0
  132. fi/alk/harness/spend.py +140 -0
  133. fi/alk/harness/tool_trace_proxy.py +104 -0
  134. fi/alk/harness/tools.py +1018 -0
  135. fi/alk/harness/understand.py +169 -0
  136. fi/alk/harness/voicemail_audio.py +74 -0
  137. fi/alk/harness/world/__init__.py +33 -0
  138. fi/alk/harness/world/errors.py +68 -0
  139. fi/alk/harness/world/expectations.py +91 -0
  140. fi/alk/harness/world/handle.py +538 -0
  141. fi/alk/harness/world/kinds.py +196 -0
  142. fi/alk/harness/world/mutate.py +186 -0
  143. fi/alk/harness/world/probe.py +413 -0
  144. fi/alk/harness/world/provision.py +511 -0
  145. fi/alk/harness/world/provisioned.py +191 -0
  146. fi/alk/harness/world/runtime.py +616 -0
  147. fi/alk/harness/world/snapshot.py +288 -0
  148. fi/alk/harness/world/stores/__init__.py +305 -0
  149. fi/alk/harness/world/stores/container.py +215 -0
  150. fi/alk/harness/world/stores/inprocess.py +346 -0
  151. fi/alk/harness/world/stores/postgres.py +481 -0
  152. fi/alk/harness/world/stores/prove.py +202 -0
  153. fi/alk/harness/world/stores/sqlite.py +245 -0
  154. fi/alk/harness/world/stores/written.py +182 -0
  155. fi/alk/harness/world/tools.py +1516 -0
  156. fi/alk/harness/world/workspace.py +144 -0
  157. fi/alk/image_loop.py +453 -0
  158. fi/alk/image_perturb.py +241 -0
  159. fi/alk/improve.py +274 -0
  160. fi/alk/live/__init__.py +154 -0
  161. fi/alk/live/_attribution.py +184 -0
  162. fi/alk/live/_capture.py +264 -0
  163. fi/alk/live/_codec.py +391 -0
  164. fi/alk/live/_contract.py +134 -0
  165. fi/alk/live/_loopback.py +316 -0
  166. fi/alk/live/_perturb.py +449 -0
  167. fi/alk/live/_runner.py +386 -0
  168. fi/alk/live/_stats.py +561 -0
  169. fi/alk/live/_transcript.py +240 -0
  170. fi/alk/live/_workers/__init__.py +9 -0
  171. fi/alk/live/_workers/a2a_worker.py +316 -0
  172. fi/alk/live/_workers/langgraph_worker.py +217 -0
  173. fi/alk/live/_workers/livekit_worker.py +207 -0
  174. fi/alk/live/_workers/mcp_loopback_server.py +46 -0
  175. fi/alk/live/_workers/mcp_worker.py +158 -0
  176. fi/alk/live/_workers/pipecat_worker.py +189 -0
  177. fi/alk/live/a2a_lane.py +138 -0
  178. fi/alk/live/langgraph_lane.py +339 -0
  179. fi/alk/live/livekit_lane.py +376 -0
  180. fi/alk/live/mcp_lane.py +172 -0
  181. fi/alk/live/pipecat_lane.py +341 -0
  182. fi/alk/live/voice_redteam.py +494 -0
  183. fi/alk/loss.py +306 -0
  184. fi/alk/optimize.py +36260 -0
  185. fi/alk/practice/__init__.py +51 -0
  186. fi/alk/practice/_assess.py +103 -0
  187. fi/alk/practice/_budget.py +81 -0
  188. fi/alk/practice/_calibrate.py +69 -0
  189. fi/alk/practice/_capstone.py +86 -0
  190. fi/alk/practice/_contract.py +91 -0
  191. fi/alk/practice/_diagnose.py +79 -0
  192. fi/alk/practice/_drill.py +196 -0
  193. fi/alk/practice/_experiment.py +720 -0
  194. fi/alk/practice/_schedule.py +102 -0
  195. fi/alk/practice/_store.py +194 -0
  196. fi/alk/practice/_trainer.py +245 -0
  197. fi/alk/practice/_update.py +125 -0
  198. fi/alk/redteam.py +2621 -0
  199. fi/alk/rewardhack.py +237 -0
  200. fi/alk/simulate.py +10351 -0
  201. fi/alk/studio/__init__.py +82 -0
  202. fi/alk/studio/_bias.py +314 -0
  203. fi/alk/studio/_calibration.py +522 -0
  204. fi/alk/studio/_coverage.py +262 -0
  205. fi/alk/studio/_download.py +665 -0
  206. fi/alk/studio/_fidelity_attack.py +114 -0
  207. fi/alk/studio/_generate.py +652 -0
  208. fi/alk/studio/_library.py +370 -0
  209. fi/alk/studio/_scan.py +134 -0
  210. fi/alk/studio/_upgrade.py +42 -0
  211. fi/alk/studio/_vendor.py +172 -0
  212. fi/alk/suite.py +4200 -0
  213. fi/alk/tasks.py +828 -0
  214. fi/alk/telemetry/__init__.py +149 -0
  215. fi/alk/telemetry/_contract.py +141 -0
  216. fi/alk/telemetry/_emit.py +182 -0
  217. fi/alk/telemetry/_ledger.py +296 -0
  218. fi/alk/telemetry/_queue.py +127 -0
  219. fi/alk/telemetry/_row.py +294 -0
  220. fi/alk/telemetry/_run.py +233 -0
  221. fi/alk/telemetry/_sync.py +193 -0
  222. fi/alk/telemetry/_url.py +119 -0
  223. fi/alk/trinity.py +49397 -0
  224. fi/alk/voice_loop.py +174 -0
  225. fi/api/__init__.py +1 -0
  226. fi/api/auth.py +137 -0
  227. fi/api/types.py +29 -0
  228. fi/cli/__init__.py +9 -0
  229. fi/cli/assertions/__init__.py +25 -0
  230. fi/cli/assertions/conditions.py +76 -0
  231. fi/cli/assertions/evaluator.py +286 -0
  232. fi/cli/assertions/exit_codes.py +20 -0
  233. fi/cli/assertions/parser.py +131 -0
  234. fi/cli/assertions/reporter.py +194 -0
  235. fi/cli/commands/__init__.py +9 -0
  236. fi/cli/commands/config.py +165 -0
  237. fi/cli/commands/export.py +208 -0
  238. fi/cli/commands/init.py +112 -0
  239. fi/cli/commands/list_cmd.py +213 -0
  240. fi/cli/commands/run.py +486 -0
  241. fi/cli/commands/validate.py +173 -0
  242. fi/cli/commands/view.py +424 -0
  243. fi/cli/config/__init__.py +6 -0
  244. fi/cli/config/defaults.py +206 -0
  245. fi/cli/config/loader.py +155 -0
  246. fi/cli/config/schema.py +174 -0
  247. fi/cli/main.py +78 -0
  248. fi/cli/output/__init__.py +6 -0
  249. fi/cli/output/formatters.py +106 -0
  250. fi/cli/output/reporters.py +46 -0
  251. fi/cli/storage/__init__.py +5 -0
  252. fi/cli/storage/run_history.py +249 -0
  253. fi/cli/utils/__init__.py +5 -0
  254. fi/cli/utils/console.py +44 -0
  255. fi/evals/__init__.py +131 -0
  256. fi/evals/autoeval/__init__.py +137 -0
  257. fi/evals/autoeval/analyzer.py +211 -0
  258. fi/evals/autoeval/config.py +244 -0
  259. fi/evals/autoeval/export.py +213 -0
  260. fi/evals/autoeval/interactive.py +283 -0
  261. fi/evals/autoeval/pipeline.py +625 -0
  262. fi/evals/autoeval/prompts.py +139 -0
  263. fi/evals/autoeval/recommender.py +242 -0
  264. fi/evals/autoeval/rules.py +589 -0
  265. fi/evals/autoeval/templates.py +299 -0
  266. fi/evals/autoeval/types.py +232 -0
  267. fi/evals/core/__init__.py +16 -0
  268. fi/evals/core/cloud_registry.py +184 -0
  269. fi/evals/core/engines.py +368 -0
  270. fi/evals/core/evaluate.py +319 -0
  271. fi/evals/core/judge_prompt.py +90 -0
  272. fi/evals/core/prompt_generator.py +83 -0
  273. fi/evals/core/registry.py +57 -0
  274. fi/evals/core/result.py +55 -0
  275. fi/evals/evaluator.py +721 -0
  276. fi/evals/execution.py +168 -0
  277. fi/evals/feedback/__init__.py +32 -0
  278. fi/evals/feedback/calibrator.py +160 -0
  279. fi/evals/feedback/collector.py +214 -0
  280. fi/evals/feedback/hooks.py +81 -0
  281. fi/evals/feedback/retriever.py +128 -0
  282. fi/evals/feedback/store.py +272 -0
  283. fi/evals/feedback/types.py +99 -0
  284. fi/evals/framework/README.md +79 -0
  285. fi/evals/framework/__init__.py +267 -0
  286. fi/evals/framework/backends/Dockerfile.eval-runner +33 -0
  287. fi/evals/framework/backends/__init__.py +99 -0
  288. fi/evals/framework/backends/_container.py +141 -0
  289. fi/evals/framework/backends/_utils.py +145 -0
  290. fi/evals/framework/backends/base.py +223 -0
  291. fi/evals/framework/backends/celery_backend.py +417 -0
  292. fi/evals/framework/backends/celery_worker.py +78 -0
  293. fi/evals/framework/backends/kubernetes_backend.py +665 -0
  294. fi/evals/framework/backends/ray_backend.py +521 -0
  295. fi/evals/framework/backends/temporal.py +350 -0
  296. fi/evals/framework/backends/temporal_worker.py +126 -0
  297. fi/evals/framework/backends/thread_pool.py +286 -0
  298. fi/evals/framework/context.py +258 -0
  299. fi/evals/framework/enrichment.py +306 -0
  300. fi/evals/framework/evals/__init__.py +68 -0
  301. fi/evals/framework/evals/agentic.py +399 -0
  302. fi/evals/framework/evals/builder.py +609 -0
  303. fi/evals/framework/evals/semantic.py +142 -0
  304. fi/evals/framework/evaluator.py +647 -0
  305. fi/evals/framework/evaluators/__init__.py +22 -0
  306. fi/evals/framework/evaluators/blocking.py +347 -0
  307. fi/evals/framework/evaluators/non_blocking.py +577 -0
  308. fi/evals/framework/propagation.py +421 -0
  309. fi/evals/framework/protocols.py +385 -0
  310. fi/evals/framework/registry.py +370 -0
  311. fi/evals/framework/resilience/__init__.py +150 -0
  312. fi/evals/framework/resilience/circuit_breaker.py +309 -0
  313. fi/evals/framework/resilience/degradation.py +355 -0
  314. fi/evals/framework/resilience/health.py +505 -0
  315. fi/evals/framework/resilience/rate_limiter.py +228 -0
  316. fi/evals/framework/resilience/retry.py +274 -0
  317. fi/evals/framework/resilience/types.py +288 -0
  318. fi/evals/framework/resilience/wrapper.py +433 -0
  319. fi/evals/framework/types.py +218 -0
  320. fi/evals/guardrails/README.md +915 -0
  321. fi/evals/guardrails/__init__.py +96 -0
  322. fi/evals/guardrails/backends/__init__.py +43 -0
  323. fi/evals/guardrails/backends/azure.py +361 -0
  324. fi/evals/guardrails/backends/base.py +88 -0
  325. fi/evals/guardrails/backends/generic_llm.py +163 -0
  326. fi/evals/guardrails/backends/granite.py +216 -0
  327. fi/evals/guardrails/backends/llamaguard.py +221 -0
  328. fi/evals/guardrails/backends/local_base.py +479 -0
  329. fi/evals/guardrails/backends/openai.py +365 -0
  330. fi/evals/guardrails/backends/qwen.py +170 -0
  331. fi/evals/guardrails/backends/shieldgemma.py +154 -0
  332. fi/evals/guardrails/backends/turing.py +235 -0
  333. fi/evals/guardrails/backends/vllm_client.py +321 -0
  334. fi/evals/guardrails/backends/wildguard.py +188 -0
  335. fi/evals/guardrails/base.py +888 -0
  336. fi/evals/guardrails/config.py +221 -0
  337. fi/evals/guardrails/discovery.py +243 -0
  338. fi/evals/guardrails/gateway.py +437 -0
  339. fi/evals/guardrails/registry.py +231 -0
  340. fi/evals/guardrails/scanners/__init__.py +127 -0
  341. fi/evals/guardrails/scanners/base.py +191 -0
  342. fi/evals/guardrails/scanners/code_injection.py +243 -0
  343. fi/evals/guardrails/scanners/eval_delegate.py +574 -0
  344. fi/evals/guardrails/scanners/invisible_chars.py +351 -0
  345. fi/evals/guardrails/scanners/jailbreak.py +412 -0
  346. fi/evals/guardrails/scanners/language.py +288 -0
  347. fi/evals/guardrails/scanners/pipeline.py +260 -0
  348. fi/evals/guardrails/scanners/regex.py +311 -0
  349. fi/evals/guardrails/scanners/secrets.py +274 -0
  350. fi/evals/guardrails/scanners/topics.py +649 -0
  351. fi/evals/guardrails/scanners/urls.py +341 -0
  352. fi/evals/guardrails/types.py +96 -0
  353. fi/evals/llm/__init__.py +3 -0
  354. fi/evals/llm/base_llm_provider.py +35 -0
  355. fi/evals/llm/providers/litellm.py +70 -0
  356. fi/evals/local/__init__.py +90 -0
  357. fi/evals/local/evaluator.py +690 -0
  358. fi/evals/local/execution_mode.py +121 -0
  359. fi/evals/local/llm.py +489 -0
  360. fi/evals/local/metrics/__init__.py +19 -0
  361. fi/evals/local/registry.py +360 -0
  362. fi/evals/manager.py +1018 -0
  363. fi/evals/manager_types.py +362 -0
  364. fi/evals/metrics/__init__.py +185 -0
  365. fi/evals/metrics/agents/__init__.py +74 -0
  366. fi/evals/metrics/agents/metrics.py +693 -0
  367. fi/evals/metrics/agents/report.py +36463 -0
  368. fi/evals/metrics/agents/types.py +160 -0
  369. fi/evals/metrics/base_llm_metric.py +111 -0
  370. fi/evals/metrics/base_metric.py +138 -0
  371. fi/evals/metrics/code_security/__init__.py +305 -0
  372. fi/evals/metrics/code_security/analyzer.py +985 -0
  373. fi/evals/metrics/code_security/benchmarks/__init__.py +73 -0
  374. fi/evals/metrics/code_security/benchmarks/builtin.py +750 -0
  375. fi/evals/metrics/code_security/benchmarks/loader.py +580 -0
  376. fi/evals/metrics/code_security/benchmarks/types.py +308 -0
  377. fi/evals/metrics/code_security/detectors/__init__.py +186 -0
  378. fi/evals/metrics/code_security/detectors/base.py +394 -0
  379. fi/evals/metrics/code_security/detectors/cryptography.py +345 -0
  380. fi/evals/metrics/code_security/detectors/injection.py +744 -0
  381. fi/evals/metrics/code_security/detectors/secrets.py +287 -0
  382. fi/evals/metrics/code_security/detectors/serialization.py +192 -0
  383. fi/evals/metrics/code_security/joint_metrics.py +588 -0
  384. fi/evals/metrics/code_security/judges/__init__.py +83 -0
  385. fi/evals/metrics/code_security/judges/base.py +238 -0
  386. fi/evals/metrics/code_security/judges/dual_judge.py +534 -0
  387. fi/evals/metrics/code_security/judges/llm_judge.py +301 -0
  388. fi/evals/metrics/code_security/judges/pattern_judge.py +515 -0
  389. fi/evals/metrics/code_security/metrics.py +388 -0
  390. fi/evals/metrics/code_security/modes/__init__.py +63 -0
  391. fi/evals/metrics/code_security/modes/adversarial.py +284 -0
  392. fi/evals/metrics/code_security/modes/autocomplete.py +198 -0
  393. fi/evals/metrics/code_security/modes/base.py +283 -0
  394. fi/evals/metrics/code_security/modes/instruct.py +253 -0
  395. fi/evals/metrics/code_security/modes/repair.py +230 -0
  396. fi/evals/metrics/code_security/reports/__init__.py +57 -0
  397. fi/evals/metrics/code_security/reports/generator.py +404 -0
  398. fi/evals/metrics/code_security/reports/leaderboard.py +509 -0
  399. fi/evals/metrics/code_security/types.py +534 -0
  400. fi/evals/metrics/function_calling/__init__.py +34 -0
  401. fi/evals/metrics/function_calling/metrics.py +573 -0
  402. fi/evals/metrics/function_calling/types.py +87 -0
  403. fi/evals/metrics/hallucination/__init__.py +54 -0
  404. fi/evals/metrics/hallucination/detector.py +149 -0
  405. fi/evals/metrics/hallucination/metrics.py +390 -0
  406. fi/evals/metrics/hallucination/nli.py +253 -0
  407. fi/evals/metrics/hallucination/sentinel.py +106 -0
  408. fi/evals/metrics/hallucination/types.py +132 -0
  409. fi/evals/metrics/heuristics/aggregation_metrics.py +85 -0
  410. fi/evals/metrics/heuristics/json_metrics.py +87 -0
  411. fi/evals/metrics/heuristics/similarity_metrics.py +375 -0
  412. fi/evals/metrics/heuristics/string_metrics.py +391 -0
  413. fi/evals/metrics/llm_as_judges/__init__.py +17 -0
  414. fi/evals/metrics/llm_as_judges/custom_judge/metric.py +112 -0
  415. fi/evals/metrics/llm_as_judges/custom_judge/prompts.py +26 -0
  416. fi/evals/metrics/llm_as_judges/types.py +48 -0
  417. fi/evals/metrics/rag/__init__.py +111 -0
  418. fi/evals/metrics/rag/advanced/__init__.py +14 -0
  419. fi/evals/metrics/rag/advanced/multi_hop.py +283 -0
  420. fi/evals/metrics/rag/advanced/source_attribution.py +344 -0
  421. fi/evals/metrics/rag/generation/__init__.py +17 -0
  422. fi/evals/metrics/rag/generation/answer_relevancy.py +176 -0
  423. fi/evals/metrics/rag/generation/context_utilization.py +245 -0
  424. fi/evals/metrics/rag/generation/faithfulness.py +241 -0
  425. fi/evals/metrics/rag/generation/groundedness.py +131 -0
  426. fi/evals/metrics/rag/rag_score.py +277 -0
  427. fi/evals/metrics/rag/retrieval/__init__.py +20 -0
  428. fi/evals/metrics/rag/retrieval/context_entity_recall.py +124 -0
  429. fi/evals/metrics/rag/retrieval/context_precision.py +158 -0
  430. fi/evals/metrics/rag/retrieval/context_recall.py +106 -0
  431. fi/evals/metrics/rag/retrieval/noise_sensitivity.py +163 -0
  432. fi/evals/metrics/rag/retrieval/ranking.py +261 -0
  433. fi/evals/metrics/rag/types.py +100 -0
  434. fi/evals/metrics/rag/utils/__init__.py +62 -0
  435. fi/evals/metrics/rag/utils/claims.py +189 -0
  436. fi/evals/metrics/rag/utils/entities.py +244 -0
  437. fi/evals/metrics/rag/utils/nli.py +92 -0
  438. fi/evals/metrics/rag/utils/similarity.py +345 -0
  439. fi/evals/metrics/structured/__init__.py +114 -0
  440. fi/evals/metrics/structured/field_completeness.py +313 -0
  441. fi/evals/metrics/structured/hierarchy_score.py +366 -0
  442. fi/evals/metrics/structured/json_validation.py +190 -0
  443. fi/evals/metrics/structured/schema_compliance.py +280 -0
  444. fi/evals/metrics/structured/structured_output_score.py +298 -0
  445. fi/evals/metrics/structured/types.py +108 -0
  446. fi/evals/metrics/structured/validators/__init__.py +30 -0
  447. fi/evals/metrics/structured/validators/base.py +189 -0
  448. fi/evals/metrics/structured/validators/json_validator.py +196 -0
  449. fi/evals/metrics/structured/validators/pydantic_validator.py +178 -0
  450. fi/evals/metrics/structured/validators/yaml_validator.py +248 -0
  451. fi/evals/otel/__init__.py +266 -0
  452. fi/evals/otel/config.py +400 -0
  453. fi/evals/otel/conventions.py +463 -0
  454. fi/evals/otel/enrichment.py +371 -0
  455. fi/evals/otel/instrumentors/__init__.py +140 -0
  456. fi/evals/otel/instrumentors/anthropic.py +517 -0
  457. fi/evals/otel/instrumentors/base.py +382 -0
  458. fi/evals/otel/instrumentors/openai.py +673 -0
  459. fi/evals/otel/processors/__init__.py +36 -0
  460. fi/evals/otel/processors/base.py +473 -0
  461. fi/evals/otel/processors/cost.py +445 -0
  462. fi/evals/otel/processors/evaluation.py +559 -0
  463. fi/evals/otel/processors/llm.py +462 -0
  464. fi/evals/otel/tracer.py +506 -0
  465. fi/evals/otel/types.py +232 -0
  466. fi/evals/otel_utils.py +23 -0
  467. fi/evals/protect.py +671 -0
  468. fi/evals/protect_input_adapter.py +154 -0
  469. fi/evals/streaming/__init__.py +88 -0
  470. fi/evals/streaming/buffer.py +213 -0
  471. fi/evals/streaming/evaluator.py +551 -0
  472. fi/evals/streaming/policy.py +307 -0
  473. fi/evals/streaming/scorers.py +368 -0
  474. fi/evals/streaming/types.py +238 -0
  475. fi/evals/templates.py +472 -0
  476. fi/evals/types.py +156 -0
  477. fi/opt/__init__.py +221 -0
  478. fi/opt/_objective_scoring.py +85 -0
  479. fi/opt/base/__init__.py +11 -0
  480. fi/opt/base/base_generator.py +33 -0
  481. fi/opt/base/base_mapper.py +26 -0
  482. fi/opt/base/base_optimizer.py +45 -0
  483. fi/opt/base/evaluator.py +211 -0
  484. fi/opt/components.py +3095 -0
  485. fi/opt/datamappers/__init__.py +3 -0
  486. fi/opt/datamappers/basic_mapper.py +40 -0
  487. fi/opt/deployment.py +1021 -0
  488. fi/opt/evidence.py +4332 -0
  489. fi/opt/generators/__init__.py +3 -0
  490. fi/opt/generators/litellm.py +66 -0
  491. fi/opt/integrations/__init__.py +23 -0
  492. fi/opt/integrations/generative_suite.py +410 -0
  493. fi/opt/integrations/simulate.py +1313 -0
  494. fi/opt/mutations.py +771 -0
  495. fi/opt/observability.py +4639 -0
  496. fi/opt/optimizer_trace.py +889 -0
  497. fi/opt/optimizers/__init__.py +80 -0
  498. fi/opt/optimizers/agent.py +331 -0
  499. fi/opt/optimizers/agent_bandit.py +392 -0
  500. fi/opt/optimizers/agent_curriculum.py +635 -0
  501. fi/opt/optimizers/agent_evolution.py +894 -0
  502. fi/opt/optimizers/agent_feedback.py +1863 -0
  503. fi/opt/optimizers/agent_pareto.py +547 -0
  504. fi/opt/optimizers/agent_social_memory.py +1113 -0
  505. fi/opt/optimizers/agent_tpe.py +321 -0
  506. fi/opt/optimizers/bayesian_search.py +449 -0
  507. fi/opt/optimizers/council.py +2075 -0
  508. fi/opt/optimizers/futureagi_replay.py +799 -0
  509. fi/opt/optimizers/gepa.py +322 -0
  510. fi/opt/optimizers/metaprompt.py +243 -0
  511. fi/opt/optimizers/promptwizard.py +417 -0
  512. fi/opt/optimizers/protegi.py +329 -0
  513. fi/opt/optimizers/random_search.py +224 -0
  514. fi/opt/research.py +518 -0
  515. fi/opt/simulation.py +260 -0
  516. fi/opt/targets.py +232 -0
  517. fi/opt/types.py +66 -0
  518. fi/opt/utils/__init__.py +4 -0
  519. fi/opt/utils/early_stopping.py +266 -0
  520. fi/opt/utils/setup_logging.py +82 -0
  521. fi/simulate/__init__.py +540 -0
  522. fi/simulate/_hashing.py +35 -0
  523. fi/simulate/_logging.py +10 -0
  524. fi/simulate/adapters.py +87 -0
  525. fi/simulate/agent/__init__.py +120 -0
  526. fi/simulate/agent/browser.py +658 -0
  527. fi/simulate/agent/definition.py +587 -0
  528. fi/simulate/agent/frameworks.py +3528 -0
  529. fi/simulate/agent/generic.py +8286 -0
  530. fi/simulate/agent/import_probe.py +227 -0
  531. fi/simulate/agent/memory.py +905 -0
  532. fi/simulate/agent/mocks.py +101 -0
  533. fi/simulate/agent/multi_agent.py +361 -0
  534. fi/simulate/agent/orchestration.py +903 -0
  535. fi/simulate/agent/realtime.py +665 -0
  536. fi/simulate/agent/wrapper.py +99 -0
  537. fi/simulate/agent/wrappers/__init__.py +18 -0
  538. fi/simulate/agent/wrappers/anthropic.py +62 -0
  539. fi/simulate/agent/wrappers/gemini.py +65 -0
  540. fi/simulate/agent/wrappers/http.py +404 -0
  541. fi/simulate/agent/wrappers/langchain.py +80 -0
  542. fi/simulate/agent/wrappers/openai.py +75 -0
  543. fi/simulate/agent/wrappers/websocket.py +326 -0
  544. fi/simulate/artifacts/__init__.py +11 -0
  545. fi/simulate/artifacts/manifest.py +62 -0
  546. fi/simulate/cli.py +20560 -0
  547. fi/simulate/endpoints/__init__.py +45 -0
  548. fi/simulate/endpoints/_http_actor.py +73 -0
  549. fi/simulate/endpoints/actor_sources.py +243 -0
  550. fi/simulate/endpoints/base.py +107 -0
  551. fi/simulate/endpoints/builtins.py +10 -0
  552. fi/simulate/endpoints/callable.py +95 -0
  553. fi/simulate/endpoints/http.py +76 -0
  554. fi/simulate/endpoints/livekit.py +138 -0
  555. fi/simulate/endpoints/originators.py +132 -0
  556. fi/simulate/endpoints/profiles.py +348 -0
  557. fi/simulate/endpoints/retell.py +633 -0
  558. fi/simulate/endpoints/vapi.py +205 -0
  559. fi/simulate/endpoints/websocket.py +76 -0
  560. fi/simulate/environment.py +33026 -0
  561. fi/simulate/environments/__init__.py +11 -0
  562. fi/simulate/environments/base.py +73 -0
  563. fi/simulate/environments/chat.py +697 -0
  564. fi/simulate/environments/voice.py +212 -0
  565. fi/simulate/evaluation/__init__.py +4 -0
  566. fi/simulate/evaluation/ai_eval.py +227 -0
  567. fi/simulate/evidence/__init__.py +35 -0
  568. fi/simulate/evidence/base.py +59 -0
  569. fi/simulate/evidence/caller_observed.py +50 -0
  570. fi/simulate/evidence/livekit_instrumentation.py +51 -0
  571. fi/simulate/evidence/livekit_room.py +50 -0
  572. fi/simulate/evidence/otel.py +49 -0
  573. fi/simulate/evidence/providers/__init__.py +24 -0
  574. fi/simulate/evidence/providers/base.py +61 -0
  575. fi/simulate/evidence/providers/retell.py +376 -0
  576. fi/simulate/evidence/providers/vapi.py +426 -0
  577. fi/simulate/hosted/__init__.py +32 -0
  578. fi/simulate/hosted/child_entrypoint.py +306 -0
  579. fi/simulate/hosted/job.py +150 -0
  580. fi/simulate/hosted/targets.py +53 -0
  581. fi/simulate/instrumentation/__init__.py +5 -0
  582. fi/simulate/instrumentation/livekit/__init__.py +122 -0
  583. fi/simulate/manifest.py +1033 -0
  584. fi/simulate/matrix_cli.py +165 -0
  585. fi/simulate/realtime/__init__.py +40 -0
  586. fi/simulate/realtime/events.py +107 -0
  587. fi/simulate/realtime/media.py +61 -0
  588. fi/simulate/realtime/session.py +91 -0
  589. fi/simulate/recording/__init__.py +5 -0
  590. fi/simulate/recording/room_recorder.py +326 -0
  591. fi/simulate/registry.py +185 -0
  592. fi/simulate/results/__init__.py +9 -0
  593. fi/simulate/results/base.py +18 -0
  594. fi/simulate/results/filesystem.py +71 -0
  595. fi/simulate/results/futureagi.py +1340 -0
  596. fi/simulate/runtime/__init__.py +85 -0
  597. fi/simulate/runtime/capabilities.py +40 -0
  598. fi/simulate/runtime/events.py +63 -0
  599. fi/simulate/runtime/failures.py +25 -0
  600. fi/simulate/runtime/ids.py +34 -0
  601. fi/simulate/runtime/plan.py +70 -0
  602. fi/simulate/runtime/planner.py +102 -0
  603. fi/simulate/runtime/report.py +174 -0
  604. fi/simulate/runtime/run.py +75 -0
  605. fi/simulate/runtime/runner.py +333 -0
  606. fi/simulate/runtime/spec.py +186 -0
  607. fi/simulate/simulation/__init__.py +30 -0
  608. fi/simulate/simulation/behavior_policy.py +425 -0
  609. fi/simulate/simulation/bridge/__init__.py +9 -0
  610. fi/simulate/simulation/bridge/audio.py +29 -0
  611. fi/simulate/simulation/bridge/connector.py +46 -0
  612. fi/simulate/simulation/bridge/livekit.py +252 -0
  613. fi/simulate/simulation/bridge/retell.py +188 -0
  614. fi/simulate/simulation/bridge/vapi.py +177 -0
  615. fi/simulate/simulation/contract.py +419 -0
  616. fi/simulate/simulation/engines/__init__.py +12 -0
  617. fi/simulate/simulation/engines/base.py +21 -0
  618. fi/simulate/simulation/engines/cloud.py +517 -0
  619. fi/simulate/simulation/engines/livekit.py +4167 -0
  620. fi/simulate/simulation/engines/local_text.py +89 -0
  621. fi/simulate/simulation/fidelity.py +374 -0
  622. fi/simulate/simulation/gemini_tts_stream.py +110 -0
  623. fi/simulate/simulation/generator.py +91 -0
  624. fi/simulate/simulation/goal_machine.py +185 -0
  625. fi/simulate/simulation/livekit_models.py +467 -0
  626. fi/simulate/simulation/matrix.py +170 -0
  627. fi/simulate/simulation/models.py +279 -0
  628. fi/simulate/simulation/runner.py +153 -0
  629. fi/simulate/simulation/synthetic.py +880 -0
  630. fi/simulate/simulation/voice_prompt.py +502 -0
  631. fi/simulate/simulator/__init__.py +55 -0
  632. fi/simulate/simulator/builtins.py +53 -0
  633. fi/simulate/suite.py +1288 -0
  634. fi/simulate/utils/routes.py +164 -0
  635. fi/simulate/voice.py +225 -0
  636. fi/simulate/voice_cli.py +182 -0
  637. fi/utils/__init__.py +1 -0
  638. fi/utils/constants.py +14 -0
  639. fi/utils/errors.py +200 -0
  640. fi/utils/executor.py +26 -0
  641. fi/utils/routes.py +119 -0
  642. fi/utils/utils.py +17 -0
@@ -0,0 +1,985 @@
1
+ """
2
+ Code Analyzer for Security Evaluation.
3
+
4
+ Provides language-aware code analysis including:
5
+ - AST parsing (Python native, regex-based for others)
6
+ - Function/class extraction
7
+ - String literal extraction
8
+ - Import analysis
9
+ - Language detection
10
+ """
11
+
12
+ import ast
13
+ import re
14
+ from abc import ABC, abstractmethod
15
+ from typing import Dict, List, Optional, Tuple, Any
16
+ from dataclasses import dataclass
17
+
18
+
19
+ @dataclass
20
+ class FunctionInfo:
21
+ """Information about a function definition."""
22
+ name: str
23
+ line: int
24
+ end_line: Optional[int]
25
+ args: List[str]
26
+ decorators: List[str]
27
+ is_async: bool
28
+ docstring: Optional[str]
29
+ calls: List[str] # Functions called within this function
30
+
31
+
32
+ @dataclass
33
+ class ImportInfo:
34
+ """Information about an import statement."""
35
+ module: str
36
+ names: List[str] # Specific names imported (empty for "import module")
37
+ alias: Optional[str]
38
+ line: int
39
+
40
+
41
+ @dataclass
42
+ class StringLiteral:
43
+ """A string literal found in code."""
44
+ value: str
45
+ line: int
46
+ column: Optional[int]
47
+ is_fstring: bool
48
+ context: Optional[str] # e.g., "sql_query", "file_path", "command"
49
+
50
+
51
+ @dataclass
52
+ class AnalysisResult:
53
+ """Result of code analysis."""
54
+ language: str
55
+ functions: List[FunctionInfo]
56
+ imports: List[ImportInfo]
57
+ strings: List[StringLiteral]
58
+ variables: Dict[str, List[int]] # Variable name -> lines where assigned
59
+ dangerous_calls: List[Tuple[str, int]] # (function_name, line)
60
+ parse_errors: List[str]
61
+ raw_ast: Optional[Any] # Language-specific AST
62
+
63
+
64
+ class LanguageAnalyzer(ABC):
65
+ """Base class for language-specific code analyzers."""
66
+
67
+ language: str = "unknown"
68
+
69
+ # Dangerous functions to track
70
+ DANGEROUS_FUNCTIONS: List[str] = []
71
+
72
+ @abstractmethod
73
+ def parse(self, code: str) -> Optional[Any]:
74
+ """Parse code into AST or structured representation."""
75
+ pass
76
+
77
+ @abstractmethod
78
+ def analyze(self, code: str) -> AnalysisResult:
79
+ """Perform full analysis of code."""
80
+ pass
81
+
82
+ def _find_dangerous_calls(
83
+ self, code: str, functions: Optional[List[str]] = None
84
+ ) -> List[Tuple[str, int]]:
85
+ """Find calls to dangerous functions."""
86
+ dangerous = functions or self.DANGEROUS_FUNCTIONS
87
+ calls = []
88
+
89
+ for i, line in enumerate(code.split("\n"), 1):
90
+ for func in dangerous:
91
+ # Handle both module.function and function patterns
92
+ patterns = [
93
+ rf"\b{re.escape(func)}\s*\(",
94
+ rf"\b{func.split('.')[-1]}\s*\(" if "." in func else None,
95
+ ]
96
+ for pattern in patterns:
97
+ if pattern and re.search(pattern, line):
98
+ calls.append((func, i))
99
+ break
100
+
101
+ return calls
102
+
103
+
104
+ class PythonAnalyzer(LanguageAnalyzer):
105
+ """Python-specific code analyzer using the ast module."""
106
+
107
+ language = "python"
108
+
109
+ DANGEROUS_FUNCTIONS = [
110
+ # Command execution
111
+ "os.system", "os.popen", "os.spawn", "os.spawnl", "os.spawnle",
112
+ "subprocess.call", "subprocess.run", "subprocess.Popen",
113
+ "subprocess.check_output", "subprocess.check_call",
114
+ "commands.getoutput", "commands.getstatusoutput",
115
+ # Code execution
116
+ "eval", "exec", "compile", "execfile",
117
+ # Deserialization
118
+ "pickle.loads", "pickle.load", "cPickle.loads", "cPickle.load",
119
+ "yaml.load", "yaml.unsafe_load",
120
+ "marshal.loads", "marshal.load",
121
+ # SQL
122
+ "cursor.execute", "connection.execute", "db.execute",
123
+ "engine.execute", "session.execute",
124
+ # File operations
125
+ "open", "file",
126
+ # Network
127
+ "urllib.urlopen", "urllib2.urlopen", "requests.get", "requests.post",
128
+ "httplib.HTTPConnection", "http.client.HTTPConnection",
129
+ # Crypto (weak)
130
+ "hashlib.md5", "hashlib.sha1",
131
+ "Crypto.Cipher.DES", "Crypto.Cipher.ARC4",
132
+ # Random (insecure)
133
+ "random.random", "random.randint", "random.choice",
134
+ ]
135
+
136
+ def parse(self, code: str) -> Optional[ast.AST]:
137
+ """Parse Python code into AST."""
138
+ try:
139
+ return ast.parse(code)
140
+ except SyntaxError:
141
+ return None
142
+
143
+ def analyze(self, code: str) -> AnalysisResult:
144
+ """Analyze Python code."""
145
+ tree = self.parse(code)
146
+ parse_errors = []
147
+
148
+ if tree is None:
149
+ # Try to extract what we can with regex
150
+ parse_errors.append("Failed to parse Python AST")
151
+ return AnalysisResult(
152
+ language=self.language,
153
+ functions=self._extract_functions_regex(code),
154
+ imports=self._extract_imports_regex(code),
155
+ strings=self._extract_strings_regex(code),
156
+ variables={},
157
+ dangerous_calls=self._find_dangerous_calls(code),
158
+ parse_errors=parse_errors,
159
+ raw_ast=None,
160
+ )
161
+
162
+ return AnalysisResult(
163
+ language=self.language,
164
+ functions=self._extract_functions(tree),
165
+ imports=self._extract_imports(tree),
166
+ strings=self._extract_strings(tree, code),
167
+ variables=self._extract_variables(tree),
168
+ dangerous_calls=self._find_dangerous_calls_ast(tree),
169
+ parse_errors=parse_errors,
170
+ raw_ast=tree,
171
+ )
172
+
173
+ def _extract_functions(self, tree: ast.AST) -> List[FunctionInfo]:
174
+ """Extract function definitions from AST."""
175
+ functions = []
176
+
177
+ for node in ast.walk(tree):
178
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
179
+ # Get decorator names
180
+ decorators = []
181
+ for dec in node.decorator_list:
182
+ if isinstance(dec, ast.Name):
183
+ decorators.append(dec.id)
184
+ elif isinstance(dec, ast.Attribute):
185
+ decorators.append(f"{self._get_full_name(dec)}")
186
+ elif isinstance(dec, ast.Call):
187
+ if isinstance(dec.func, ast.Name):
188
+ decorators.append(dec.func.id)
189
+ elif isinstance(dec.func, ast.Attribute):
190
+ decorators.append(self._get_full_name(dec.func))
191
+
192
+ # Get docstring
193
+ docstring = ast.get_docstring(node)
194
+
195
+ # Get function calls within this function
196
+ calls = []
197
+ for child in ast.walk(node):
198
+ if isinstance(child, ast.Call):
199
+ call_name = self._get_call_name(child)
200
+ if call_name:
201
+ calls.append(call_name)
202
+
203
+ functions.append(FunctionInfo(
204
+ name=node.name,
205
+ line=node.lineno,
206
+ end_line=getattr(node, 'end_lineno', None),
207
+ args=[arg.arg for arg in node.args.args],
208
+ decorators=decorators,
209
+ is_async=isinstance(node, ast.AsyncFunctionDef),
210
+ docstring=docstring,
211
+ calls=calls,
212
+ ))
213
+
214
+ return functions
215
+
216
+ def _extract_imports(self, tree: ast.AST) -> List[ImportInfo]:
217
+ """Extract import statements from AST."""
218
+ imports = []
219
+
220
+ for node in ast.walk(tree):
221
+ if isinstance(node, ast.Import):
222
+ for alias in node.names:
223
+ imports.append(ImportInfo(
224
+ module=alias.name,
225
+ names=[],
226
+ alias=alias.asname,
227
+ line=node.lineno,
228
+ ))
229
+ elif isinstance(node, ast.ImportFrom):
230
+ module = node.module or ""
231
+ names = [alias.name for alias in node.names]
232
+ imports.append(ImportInfo(
233
+ module=module,
234
+ names=names,
235
+ alias=None,
236
+ line=node.lineno,
237
+ ))
238
+
239
+ return imports
240
+
241
+ def _extract_strings(self, tree: ast.AST, code: str) -> List[StringLiteral]:
242
+ """Extract string literals from AST."""
243
+ strings = []
244
+ lines = code.split("\n")
245
+
246
+ for node in ast.walk(tree):
247
+ if isinstance(node, ast.Constant) and isinstance(node.value, str):
248
+ # Determine context
249
+ context = self._determine_string_context(node, tree, lines)
250
+ strings.append(StringLiteral(
251
+ value=node.value,
252
+ line=node.lineno,
253
+ column=getattr(node, 'col_offset', None),
254
+ is_fstring=False,
255
+ context=context,
256
+ ))
257
+ elif isinstance(node, ast.JoinedStr): # f-string
258
+ # Reconstruct f-string value
259
+ parts = []
260
+ for value in node.values:
261
+ if isinstance(value, ast.Constant):
262
+ parts.append(str(value.value))
263
+ elif isinstance(value, ast.FormattedValue):
264
+ parts.append("{...}")
265
+
266
+ strings.append(StringLiteral(
267
+ value="".join(parts),
268
+ line=node.lineno,
269
+ column=getattr(node, 'col_offset', None),
270
+ is_fstring=True,
271
+ context=self._determine_string_context(node, tree, lines),
272
+ ))
273
+
274
+ return strings
275
+
276
+ def _determine_string_context(
277
+ self, node: ast.AST, tree: ast.AST, lines: List[str]
278
+ ) -> Optional[str]:
279
+ """Determine the context of a string (SQL, command, path, etc.)."""
280
+ line_content = lines[node.lineno - 1].lower() if node.lineno <= len(lines) else ""
281
+
282
+ # Check for SQL keywords
283
+ sql_keywords = ["select", "insert", "update", "delete", "drop", "create", "alter"]
284
+ if any(kw in line_content for kw in sql_keywords):
285
+ return "sql_query"
286
+
287
+ # Check for file path indicators
288
+ if any(p in line_content for p in ["open(", "file(", "path", "filename"]):
289
+ return "file_path"
290
+
291
+ # Check for command execution
292
+ if any(c in line_content for c in ["os.system", "subprocess", "popen", "exec"]):
293
+ return "command"
294
+
295
+ # Check for URL
296
+ if any(u in line_content for u in ["http://", "https://", "url", "requests"]):
297
+ return "url"
298
+
299
+ return None
300
+
301
+ def _extract_variables(self, tree: ast.AST) -> Dict[str, List[int]]:
302
+ """Extract variable assignments."""
303
+ variables: Dict[str, List[int]] = {}
304
+
305
+ for node in ast.walk(tree):
306
+ if isinstance(node, ast.Assign):
307
+ for target in node.targets:
308
+ if isinstance(target, ast.Name):
309
+ if target.id not in variables:
310
+ variables[target.id] = []
311
+ variables[target.id].append(node.lineno)
312
+ elif isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name):
313
+ name = node.target.id
314
+ if name not in variables:
315
+ variables[name] = []
316
+ variables[name].append(node.lineno)
317
+
318
+ return variables
319
+
320
+ def _find_dangerous_calls_ast(self, tree: ast.AST) -> List[Tuple[str, int]]:
321
+ """Find dangerous function calls using AST."""
322
+ calls = []
323
+
324
+ for node in ast.walk(tree):
325
+ if isinstance(node, ast.Call):
326
+ call_name = self._get_call_name(node)
327
+ if call_name:
328
+ # Check if it's in our dangerous list
329
+ for dangerous in self.DANGEROUS_FUNCTIONS:
330
+ if call_name == dangerous or call_name.endswith(f".{dangerous}"):
331
+ calls.append((call_name, node.lineno))
332
+ break
333
+ # Also check if the last part matches
334
+ if "." in dangerous:
335
+ _, func_name = dangerous.rsplit(".", 1)
336
+ if call_name.endswith(func_name):
337
+ calls.append((call_name, node.lineno))
338
+ break
339
+
340
+ return calls
341
+
342
+ def _get_call_name(self, node: ast.Call) -> Optional[str]:
343
+ """Get the full name of a function call."""
344
+ if isinstance(node.func, ast.Name):
345
+ return node.func.id
346
+ elif isinstance(node.func, ast.Attribute):
347
+ return self._get_full_name(node.func)
348
+ return None
349
+
350
+ def _get_full_name(self, node: ast.Attribute) -> str:
351
+ """Get full dotted name from Attribute node."""
352
+ parts = []
353
+ current = node
354
+
355
+ while isinstance(current, ast.Attribute):
356
+ parts.append(current.attr)
357
+ current = current.value
358
+
359
+ if isinstance(current, ast.Name):
360
+ parts.append(current.id)
361
+
362
+ return ".".join(reversed(parts))
363
+
364
+ def _extract_functions_regex(self, code: str) -> List[FunctionInfo]:
365
+ """Fallback regex-based function extraction."""
366
+ functions = []
367
+ pattern = r"^\s*(async\s+)?def\s+(\w+)\s*\((.*?)\)"
368
+
369
+ for i, line in enumerate(code.split("\n"), 1):
370
+ match = re.match(pattern, line)
371
+ if match:
372
+ is_async = bool(match.group(1))
373
+ name = match.group(2)
374
+ args_str = match.group(3)
375
+ args = [a.strip().split(":")[0].split("=")[0].strip()
376
+ for a in args_str.split(",") if a.strip()]
377
+
378
+ functions.append(FunctionInfo(
379
+ name=name,
380
+ line=i,
381
+ end_line=None,
382
+ args=args,
383
+ decorators=[],
384
+ is_async=is_async,
385
+ docstring=None,
386
+ calls=[],
387
+ ))
388
+
389
+ return functions
390
+
391
+ def _extract_imports_regex(self, code: str) -> List[ImportInfo]:
392
+ """Fallback regex-based import extraction."""
393
+ imports = []
394
+
395
+ # import module
396
+ for i, line in enumerate(code.split("\n"), 1):
397
+ match = re.match(r"^\s*import\s+([\w.]+)(?:\s+as\s+(\w+))?", line)
398
+ if match:
399
+ imports.append(ImportInfo(
400
+ module=match.group(1),
401
+ names=[],
402
+ alias=match.group(2),
403
+ line=i,
404
+ ))
405
+
406
+ # from module import names
407
+ match = re.match(r"^\s*from\s+([\w.]+)\s+import\s+(.+)", line)
408
+ if match:
409
+ names = [n.strip().split(" as ")[0] for n in match.group(2).split(",")]
410
+ imports.append(ImportInfo(
411
+ module=match.group(1),
412
+ names=names,
413
+ alias=None,
414
+ line=i,
415
+ ))
416
+
417
+ return imports
418
+
419
+ def _extract_strings_regex(self, code: str) -> List[StringLiteral]:
420
+ """Fallback regex-based string extraction."""
421
+ strings = []
422
+
423
+ # Match various string patterns
424
+ patterns = [
425
+ (r'f["\']([^"\']*)["\']', True), # f-strings
426
+ (r'["\']([^"\']{3,})["\']', False), # Regular strings (3+ chars)
427
+ ]
428
+
429
+ for i, line in enumerate(code.split("\n"), 1):
430
+ for pattern, is_fstring in patterns:
431
+ for match in re.finditer(pattern, line):
432
+ strings.append(StringLiteral(
433
+ value=match.group(1),
434
+ line=i,
435
+ column=match.start(),
436
+ is_fstring=is_fstring,
437
+ context=None,
438
+ ))
439
+
440
+ return strings
441
+
442
+
443
+ class JavaScriptAnalyzer(LanguageAnalyzer):
444
+ """JavaScript/TypeScript analyzer using regex patterns."""
445
+
446
+ language = "javascript"
447
+
448
+ DANGEROUS_FUNCTIONS = [
449
+ # Code execution
450
+ "eval", "Function", "setTimeout", "setInterval",
451
+ # Command execution
452
+ "exec", "execSync", "spawn", "spawnSync", "execFile",
453
+ "child_process.exec", "child_process.spawn",
454
+ # DOM manipulation (XSS)
455
+ "innerHTML", "outerHTML", "document.write", "document.writeln",
456
+ # SQL (common ORMs)
457
+ "query", "raw", "execute",
458
+ # Deserialization
459
+ "JSON.parse",
460
+ ]
461
+
462
+ def parse(self, code: str) -> Optional[Dict[str, Any]]:
463
+ """Parse JavaScript using regex (tree-sitter optional)."""
464
+ # Basic structural analysis without full parsing
465
+ return {"code": code}
466
+
467
+ def analyze(self, code: str) -> AnalysisResult:
468
+ """Analyze JavaScript code."""
469
+ return AnalysisResult(
470
+ language=self.language,
471
+ functions=self._extract_functions(code),
472
+ imports=self._extract_imports(code),
473
+ strings=self._extract_strings(code),
474
+ variables=self._extract_variables(code),
475
+ dangerous_calls=self._find_dangerous_calls(code),
476
+ parse_errors=[],
477
+ raw_ast=None,
478
+ )
479
+
480
+ def _extract_functions(self, code: str) -> List[FunctionInfo]:
481
+ """Extract function definitions."""
482
+ functions = []
483
+
484
+ patterns = [
485
+ # function name(args)
486
+ r"function\s+(\w+)\s*\((.*?)\)",
487
+ # const/let/var name = function(args)
488
+ r"(?:const|let|var)\s+(\w+)\s*=\s*function\s*\((.*?)\)",
489
+ # const/let/var name = (args) =>
490
+ r"(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\((.*?)\)\s*=>",
491
+ # async function name(args)
492
+ r"async\s+function\s+(\w+)\s*\((.*?)\)",
493
+ ]
494
+
495
+ for i, line in enumerate(code.split("\n"), 1):
496
+ for pattern in patterns:
497
+ match = re.search(pattern, line)
498
+ if match:
499
+ name = match.group(1)
500
+ args_str = match.group(2)
501
+ args = [a.strip().split(":")[0].split("=")[0].strip()
502
+ for a in args_str.split(",") if a.strip()]
503
+
504
+ is_async = "async" in line[:match.start()]
505
+
506
+ functions.append(FunctionInfo(
507
+ name=name,
508
+ line=i,
509
+ end_line=None,
510
+ args=args,
511
+ decorators=[],
512
+ is_async=is_async,
513
+ docstring=None,
514
+ calls=[],
515
+ ))
516
+ break
517
+
518
+ return functions
519
+
520
+ def _extract_imports(self, code: str) -> List[ImportInfo]:
521
+ """Extract import statements."""
522
+ imports = []
523
+
524
+ patterns = [
525
+ # import x from 'module'
526
+ r"import\s+(\w+)\s+from\s+['\"]([^'\"]+)['\"]",
527
+ # import { x, y } from 'module'
528
+ r"import\s*\{([^}]+)\}\s*from\s*['\"]([^'\"]+)['\"]",
529
+ # const x = require('module')
530
+ r"(?:const|let|var)\s+(\w+)\s*=\s*require\s*\(['\"]([^'\"]+)['\"]\)",
531
+ ]
532
+
533
+ for i, line in enumerate(code.split("\n"), 1):
534
+ for pattern in patterns:
535
+ match = re.search(pattern, line)
536
+ if match:
537
+ if "{" in pattern:
538
+ names = [n.strip() for n in match.group(1).split(",")]
539
+ module = match.group(2)
540
+ else:
541
+ names = [match.group(1)]
542
+ module = match.group(2)
543
+
544
+ imports.append(ImportInfo(
545
+ module=module,
546
+ names=names,
547
+ alias=None,
548
+ line=i,
549
+ ))
550
+
551
+ return imports
552
+
553
+ def _extract_strings(self, code: str) -> List[StringLiteral]:
554
+ """Extract string literals."""
555
+ strings = []
556
+
557
+ for i, line in enumerate(code.split("\n"), 1):
558
+ # Template literals
559
+ for match in re.finditer(r"`([^`]*)`", line):
560
+ strings.append(StringLiteral(
561
+ value=match.group(1),
562
+ line=i,
563
+ column=match.start(),
564
+ is_fstring="${" in match.group(1),
565
+ context=None,
566
+ ))
567
+
568
+ # Regular strings
569
+ for match in re.finditer(r"['\"]([^'\"]{3,})['\"]", line):
570
+ if not re.search(r"['\"]" + re.escape(match.group(1)) + r"['\"]", line[:match.start()]):
571
+ strings.append(StringLiteral(
572
+ value=match.group(1),
573
+ line=i,
574
+ column=match.start(),
575
+ is_fstring=False,
576
+ context=None,
577
+ ))
578
+
579
+ return strings
580
+
581
+ def _extract_variables(self, code: str) -> Dict[str, List[int]]:
582
+ """Extract variable assignments."""
583
+ variables: Dict[str, List[int]] = {}
584
+
585
+ pattern = r"(?:const|let|var)\s+(\w+)\s*="
586
+ for i, line in enumerate(code.split("\n"), 1):
587
+ for match in re.finditer(pattern, line):
588
+ name = match.group(1)
589
+ if name not in variables:
590
+ variables[name] = []
591
+ variables[name].append(i)
592
+
593
+ return variables
594
+
595
+
596
+ class JavaAnalyzer(LanguageAnalyzer):
597
+ """Java analyzer using regex patterns."""
598
+
599
+ language = "java"
600
+
601
+ DANGEROUS_FUNCTIONS = [
602
+ # Command execution
603
+ "Runtime.exec", "ProcessBuilder",
604
+ # SQL
605
+ "executeQuery", "executeUpdate", "prepareStatement",
606
+ "createStatement", "Statement.execute",
607
+ # Deserialization
608
+ "ObjectInputStream.readObject", "XMLDecoder.readObject",
609
+ # File operations
610
+ "FileInputStream", "FileOutputStream", "FileReader", "FileWriter",
611
+ # Crypto (weak)
612
+ "MessageDigest.getInstance",
613
+ ]
614
+
615
+ def parse(self, code: str) -> Optional[Dict[str, Any]]:
616
+ """Parse Java using regex."""
617
+ return {"code": code}
618
+
619
+ def analyze(self, code: str) -> AnalysisResult:
620
+ """Analyze Java code."""
621
+ return AnalysisResult(
622
+ language=self.language,
623
+ functions=self._extract_functions(code),
624
+ imports=self._extract_imports(code),
625
+ strings=self._extract_strings(code),
626
+ variables=self._extract_variables(code),
627
+ dangerous_calls=self._find_dangerous_calls(code),
628
+ parse_errors=[],
629
+ raw_ast=None,
630
+ )
631
+
632
+ def _extract_functions(self, code: str) -> List[FunctionInfo]:
633
+ """Extract method definitions."""
634
+ functions = []
635
+
636
+ pattern = r"(?:public|private|protected)?\s*(?:static)?\s*(?:\w+)\s+(\w+)\s*\((.*?)\)"
637
+
638
+ for i, line in enumerate(code.split("\n"), 1):
639
+ match = re.search(pattern, line)
640
+ if match and match.group(1) not in ["if", "while", "for", "switch"]:
641
+ name = match.group(1)
642
+ args_str = match.group(2)
643
+ args = []
644
+ if args_str.strip():
645
+ for arg in args_str.split(","):
646
+ parts = arg.strip().split()
647
+ if len(parts) >= 2:
648
+ args.append(parts[-1])
649
+
650
+ functions.append(FunctionInfo(
651
+ name=name,
652
+ line=i,
653
+ end_line=None,
654
+ args=args,
655
+ decorators=[],
656
+ is_async=False,
657
+ docstring=None,
658
+ calls=[],
659
+ ))
660
+
661
+ return functions
662
+
663
+ def _extract_imports(self, code: str) -> List[ImportInfo]:
664
+ """Extract import statements."""
665
+ imports = []
666
+
667
+ for i, line in enumerate(code.split("\n"), 1):
668
+ match = re.match(r"import\s+([\w.]+);", line.strip())
669
+ if match:
670
+ imports.append(ImportInfo(
671
+ module=match.group(1),
672
+ names=[],
673
+ alias=None,
674
+ line=i,
675
+ ))
676
+
677
+ return imports
678
+
679
+ def _extract_strings(self, code: str) -> List[StringLiteral]:
680
+ """Extract string literals."""
681
+ strings = []
682
+
683
+ for i, line in enumerate(code.split("\n"), 1):
684
+ for match in re.finditer(r'"([^"]{3,})"', line):
685
+ strings.append(StringLiteral(
686
+ value=match.group(1),
687
+ line=i,
688
+ column=match.start(),
689
+ is_fstring=False,
690
+ context=None,
691
+ ))
692
+
693
+ return strings
694
+
695
+ def _extract_variables(self, code: str) -> Dict[str, List[int]]:
696
+ """Extract variable declarations."""
697
+ variables: Dict[str, List[int]] = {}
698
+
699
+ pattern = r"(?:final\s+)?(?:\w+)\s+(\w+)\s*="
700
+ for i, line in enumerate(code.split("\n"), 1):
701
+ for match in re.finditer(pattern, line):
702
+ name = match.group(1)
703
+ if name not in variables:
704
+ variables[name] = []
705
+ variables[name].append(i)
706
+
707
+ return variables
708
+
709
+
710
+ class GoAnalyzer(LanguageAnalyzer):
711
+ """Go analyzer using regex patterns."""
712
+
713
+ language = "go"
714
+
715
+ DANGEROUS_FUNCTIONS = [
716
+ # Command execution
717
+ "exec.Command", "os/exec.Command",
718
+ # SQL
719
+ "db.Query", "db.Exec", "db.QueryRow",
720
+ # File operations
721
+ "os.Open", "os.Create", "ioutil.ReadFile", "ioutil.WriteFile",
722
+ # Crypto (weak)
723
+ "md5.New", "sha1.New",
724
+ ]
725
+
726
+ def parse(self, code: str) -> Optional[Dict[str, Any]]:
727
+ """Parse Go using regex."""
728
+ return {"code": code}
729
+
730
+ def analyze(self, code: str) -> AnalysisResult:
731
+ """Analyze Go code."""
732
+ return AnalysisResult(
733
+ language=self.language,
734
+ functions=self._extract_functions(code),
735
+ imports=self._extract_imports(code),
736
+ strings=self._extract_strings(code),
737
+ variables=self._extract_variables(code),
738
+ dangerous_calls=self._find_dangerous_calls(code),
739
+ parse_errors=[],
740
+ raw_ast=None,
741
+ )
742
+
743
+ def _extract_functions(self, code: str) -> List[FunctionInfo]:
744
+ """Extract function definitions."""
745
+ functions = []
746
+
747
+ pattern = r"func\s+(?:\([^)]+\)\s+)?(\w+)\s*\((.*?)\)"
748
+
749
+ for i, line in enumerate(code.split("\n"), 1):
750
+ match = re.search(pattern, line)
751
+ if match:
752
+ name = match.group(1)
753
+ args_str = match.group(2)
754
+ args = []
755
+ if args_str.strip():
756
+ for arg in args_str.split(","):
757
+ parts = arg.strip().split()
758
+ if parts:
759
+ args.append(parts[0])
760
+
761
+ functions.append(FunctionInfo(
762
+ name=name,
763
+ line=i,
764
+ end_line=None,
765
+ args=args,
766
+ decorators=[],
767
+ is_async=False,
768
+ docstring=None,
769
+ calls=[],
770
+ ))
771
+
772
+ return functions
773
+
774
+ def _extract_imports(self, code: str) -> List[ImportInfo]:
775
+ """Extract import statements."""
776
+ imports = []
777
+
778
+ # Single import
779
+ for i, line in enumerate(code.split("\n"), 1):
780
+ match = re.match(r'import\s+"([^"]+)"', line.strip())
781
+ if match:
782
+ imports.append(ImportInfo(
783
+ module=match.group(1),
784
+ names=[],
785
+ alias=None,
786
+ line=i,
787
+ ))
788
+
789
+ # Multi-line import block
790
+ in_import = False
791
+ for i, line in enumerate(code.split("\n"), 1):
792
+ if "import (" in line:
793
+ in_import = True
794
+ continue
795
+ if in_import:
796
+ if ")" in line:
797
+ in_import = False
798
+ continue
799
+ match = re.search(r'"([^"]+)"', line)
800
+ if match:
801
+ imports.append(ImportInfo(
802
+ module=match.group(1),
803
+ names=[],
804
+ alias=None,
805
+ line=i,
806
+ ))
807
+
808
+ return imports
809
+
810
+ def _extract_strings(self, code: str) -> List[StringLiteral]:
811
+ """Extract string literals."""
812
+ strings = []
813
+
814
+ for i, line in enumerate(code.split("\n"), 1):
815
+ # Double-quoted strings
816
+ for match in re.finditer(r'"([^"]{3,})"', line):
817
+ strings.append(StringLiteral(
818
+ value=match.group(1),
819
+ line=i,
820
+ column=match.start(),
821
+ is_fstring=False,
822
+ context=None,
823
+ ))
824
+
825
+ # Backtick strings (raw)
826
+ for match in re.finditer(r"`([^`]{3,})`", line):
827
+ strings.append(StringLiteral(
828
+ value=match.group(1),
829
+ line=i,
830
+ column=match.start(),
831
+ is_fstring=False,
832
+ context=None,
833
+ ))
834
+
835
+ return strings
836
+
837
+ def _extract_variables(self, code: str) -> Dict[str, List[int]]:
838
+ """Extract variable declarations."""
839
+ variables: Dict[str, List[int]] = {}
840
+
841
+ patterns = [
842
+ r"(\w+)\s*:=", # Short declaration
843
+ r"var\s+(\w+)", # Var declaration
844
+ ]
845
+
846
+ for i, line in enumerate(code.split("\n"), 1):
847
+ for pattern in patterns:
848
+ for match in re.finditer(pattern, line):
849
+ name = match.group(1)
850
+ if name not in variables:
851
+ variables[name] = []
852
+ variables[name].append(i)
853
+
854
+ return variables
855
+
856
+
857
+ class CodeAnalyzer:
858
+ """
859
+ Main code analyzer that delegates to language-specific analyzers.
860
+
861
+ Usage:
862
+ analyzer = CodeAnalyzer()
863
+ result = analyzer.analyze(code, "python")
864
+
865
+ # Auto-detect language
866
+ result = analyzer.analyze(code)
867
+ """
868
+
869
+ ANALYZERS: Dict[str, type] = {
870
+ "python": PythonAnalyzer,
871
+ "py": PythonAnalyzer,
872
+ "javascript": JavaScriptAnalyzer,
873
+ "js": JavaScriptAnalyzer,
874
+ "typescript": JavaScriptAnalyzer,
875
+ "ts": JavaScriptAnalyzer,
876
+ "java": JavaAnalyzer,
877
+ "go": GoAnalyzer,
878
+ "golang": GoAnalyzer,
879
+ }
880
+
881
+ # Language detection patterns
882
+ LANGUAGE_PATTERNS: Dict[str, List[str]] = {
883
+ "python": [
884
+ r"^\s*def\s+\w+\s*\(",
885
+ r"^\s*import\s+\w+",
886
+ r"^\s*from\s+\w+\s+import",
887
+ r"^\s*class\s+\w+.*:",
888
+ r"^\s*if\s+__name__\s*==",
889
+ r"print\s*\(",
890
+ ],
891
+ "javascript": [
892
+ r"^\s*const\s+\w+\s*=",
893
+ r"^\s*let\s+\w+\s*=",
894
+ r"^\s*function\s+\w+\s*\(",
895
+ r"=>\s*{",
896
+ r"require\s*\(",
897
+ r"module\.exports",
898
+ r"console\.log",
899
+ ],
900
+ "java": [
901
+ r"^\s*public\s+class",
902
+ r"^\s*private\s+\w+",
903
+ r"^\s*import\s+java\.",
904
+ r"System\.out\.println",
905
+ r"public\s+static\s+void\s+main",
906
+ ],
907
+ "go": [
908
+ r"^\s*package\s+\w+",
909
+ r"^\s*func\s+\w+\s*\(",
910
+ r"^\s*import\s+\(",
911
+ r"fmt\.Print",
912
+ r":=",
913
+ ],
914
+ }
915
+
916
+ def __init__(self):
917
+ self._analyzers: Dict[str, LanguageAnalyzer] = {}
918
+
919
+ def get_analyzer(self, language: str) -> Optional[LanguageAnalyzer]:
920
+ """Get or create an analyzer for the given language."""
921
+ lang_lower = language.lower()
922
+ if lang_lower not in self._analyzers:
923
+ analyzer_cls = self.ANALYZERS.get(lang_lower)
924
+ if analyzer_cls:
925
+ self._analyzers[lang_lower] = analyzer_cls()
926
+ return self._analyzers.get(lang_lower)
927
+
928
+ def detect_language(self, code: str) -> str:
929
+ """Auto-detect the programming language of code."""
930
+ scores: Dict[str, int] = {lang: 0 for lang in self.LANGUAGE_PATTERNS}
931
+
932
+ for lang, patterns in self.LANGUAGE_PATTERNS.items():
933
+ for pattern in patterns:
934
+ if re.search(pattern, code, re.MULTILINE):
935
+ scores[lang] += 1
936
+
937
+ # Return language with highest score, default to python
938
+ if max(scores.values()) == 0:
939
+ return "python"
940
+
941
+ return max(scores, key=lambda k: scores[k])
942
+
943
+ def analyze(
944
+ self, code: str, language: Optional[str] = None
945
+ ) -> AnalysisResult:
946
+ """
947
+ Analyze code for security evaluation.
948
+
949
+ Args:
950
+ code: Source code to analyze
951
+ language: Programming language (auto-detected if not provided)
952
+
953
+ Returns:
954
+ AnalysisResult with functions, imports, strings, etc.
955
+ """
956
+ # Detect language if not provided
957
+ if language is None:
958
+ language = self.detect_language(code)
959
+
960
+ analyzer = self.get_analyzer(language)
961
+ if analyzer is None:
962
+ # Return basic analysis for unsupported languages
963
+ return AnalysisResult(
964
+ language=language,
965
+ functions=[],
966
+ imports=[],
967
+ strings=[],
968
+ variables={},
969
+ dangerous_calls=[],
970
+ parse_errors=[f"Unsupported language: {language}"],
971
+ raw_ast=None,
972
+ )
973
+
974
+ return analyzer.analyze(code)
975
+
976
+ def supports_language(self, language: str) -> bool:
977
+ """Check if a language is supported."""
978
+ return language.lower() in self.ANALYZERS
979
+
980
+ def supported_languages(self) -> List[str]:
981
+ """Get list of supported languages."""
982
+ return list(set(
983
+ analyzer_cls.language
984
+ for analyzer_cls in self.ANALYZERS.values()
985
+ ))