PraisonAI 3.0.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 (393) hide show
  1. praisonai/__init__.py +54 -0
  2. praisonai/__main__.py +15 -0
  3. praisonai/acp/__init__.py +54 -0
  4. praisonai/acp/config.py +159 -0
  5. praisonai/acp/server.py +587 -0
  6. praisonai/acp/session.py +219 -0
  7. praisonai/adapters/__init__.py +50 -0
  8. praisonai/adapters/readers.py +395 -0
  9. praisonai/adapters/rerankers.py +315 -0
  10. praisonai/adapters/retrievers.py +394 -0
  11. praisonai/adapters/vector_stores.py +409 -0
  12. praisonai/agent_scheduler.py +337 -0
  13. praisonai/agents_generator.py +903 -0
  14. praisonai/api/call.py +292 -0
  15. praisonai/auto.py +1197 -0
  16. praisonai/capabilities/__init__.py +275 -0
  17. praisonai/capabilities/a2a.py +140 -0
  18. praisonai/capabilities/assistants.py +283 -0
  19. praisonai/capabilities/audio.py +320 -0
  20. praisonai/capabilities/batches.py +469 -0
  21. praisonai/capabilities/completions.py +336 -0
  22. praisonai/capabilities/container_files.py +155 -0
  23. praisonai/capabilities/containers.py +93 -0
  24. praisonai/capabilities/embeddings.py +158 -0
  25. praisonai/capabilities/files.py +467 -0
  26. praisonai/capabilities/fine_tuning.py +293 -0
  27. praisonai/capabilities/guardrails.py +182 -0
  28. praisonai/capabilities/images.py +330 -0
  29. praisonai/capabilities/mcp.py +190 -0
  30. praisonai/capabilities/messages.py +270 -0
  31. praisonai/capabilities/moderations.py +154 -0
  32. praisonai/capabilities/ocr.py +217 -0
  33. praisonai/capabilities/passthrough.py +204 -0
  34. praisonai/capabilities/rag.py +207 -0
  35. praisonai/capabilities/realtime.py +160 -0
  36. praisonai/capabilities/rerank.py +165 -0
  37. praisonai/capabilities/responses.py +266 -0
  38. praisonai/capabilities/search.py +109 -0
  39. praisonai/capabilities/skills.py +133 -0
  40. praisonai/capabilities/vector_store_files.py +334 -0
  41. praisonai/capabilities/vector_stores.py +304 -0
  42. praisonai/capabilities/videos.py +141 -0
  43. praisonai/chainlit_ui.py +304 -0
  44. praisonai/chat/__init__.py +106 -0
  45. praisonai/chat/app.py +125 -0
  46. praisonai/cli/__init__.py +26 -0
  47. praisonai/cli/app.py +213 -0
  48. praisonai/cli/commands/__init__.py +75 -0
  49. praisonai/cli/commands/acp.py +70 -0
  50. praisonai/cli/commands/completion.py +333 -0
  51. praisonai/cli/commands/config.py +166 -0
  52. praisonai/cli/commands/debug.py +142 -0
  53. praisonai/cli/commands/diag.py +55 -0
  54. praisonai/cli/commands/doctor.py +166 -0
  55. praisonai/cli/commands/environment.py +179 -0
  56. praisonai/cli/commands/lsp.py +112 -0
  57. praisonai/cli/commands/mcp.py +210 -0
  58. praisonai/cli/commands/profile.py +457 -0
  59. praisonai/cli/commands/run.py +228 -0
  60. praisonai/cli/commands/schedule.py +150 -0
  61. praisonai/cli/commands/serve.py +97 -0
  62. praisonai/cli/commands/session.py +212 -0
  63. praisonai/cli/commands/traces.py +145 -0
  64. praisonai/cli/commands/version.py +101 -0
  65. praisonai/cli/configuration/__init__.py +18 -0
  66. praisonai/cli/configuration/loader.py +353 -0
  67. praisonai/cli/configuration/paths.py +114 -0
  68. praisonai/cli/configuration/schema.py +164 -0
  69. praisonai/cli/features/__init__.py +268 -0
  70. praisonai/cli/features/acp.py +236 -0
  71. praisonai/cli/features/action_orchestrator.py +546 -0
  72. praisonai/cli/features/agent_scheduler.py +773 -0
  73. praisonai/cli/features/agent_tools.py +474 -0
  74. praisonai/cli/features/agents.py +375 -0
  75. praisonai/cli/features/at_mentions.py +471 -0
  76. praisonai/cli/features/auto_memory.py +182 -0
  77. praisonai/cli/features/autonomy_mode.py +490 -0
  78. praisonai/cli/features/background.py +356 -0
  79. praisonai/cli/features/base.py +168 -0
  80. praisonai/cli/features/capabilities.py +1326 -0
  81. praisonai/cli/features/checkpoints.py +338 -0
  82. praisonai/cli/features/code_intelligence.py +652 -0
  83. praisonai/cli/features/compaction.py +294 -0
  84. praisonai/cli/features/compare.py +534 -0
  85. praisonai/cli/features/cost_tracker.py +514 -0
  86. praisonai/cli/features/debug.py +810 -0
  87. praisonai/cli/features/deploy.py +517 -0
  88. praisonai/cli/features/diag.py +289 -0
  89. praisonai/cli/features/doctor/__init__.py +63 -0
  90. praisonai/cli/features/doctor/checks/__init__.py +24 -0
  91. praisonai/cli/features/doctor/checks/acp_checks.py +240 -0
  92. praisonai/cli/features/doctor/checks/config_checks.py +366 -0
  93. praisonai/cli/features/doctor/checks/db_checks.py +366 -0
  94. praisonai/cli/features/doctor/checks/env_checks.py +543 -0
  95. praisonai/cli/features/doctor/checks/lsp_checks.py +199 -0
  96. praisonai/cli/features/doctor/checks/mcp_checks.py +349 -0
  97. praisonai/cli/features/doctor/checks/memory_checks.py +268 -0
  98. praisonai/cli/features/doctor/checks/network_checks.py +251 -0
  99. praisonai/cli/features/doctor/checks/obs_checks.py +328 -0
  100. praisonai/cli/features/doctor/checks/performance_checks.py +235 -0
  101. praisonai/cli/features/doctor/checks/permissions_checks.py +259 -0
  102. praisonai/cli/features/doctor/checks/selftest_checks.py +322 -0
  103. praisonai/cli/features/doctor/checks/serve_checks.py +426 -0
  104. praisonai/cli/features/doctor/checks/skills_checks.py +231 -0
  105. praisonai/cli/features/doctor/checks/tools_checks.py +371 -0
  106. praisonai/cli/features/doctor/engine.py +266 -0
  107. praisonai/cli/features/doctor/formatters.py +310 -0
  108. praisonai/cli/features/doctor/handler.py +397 -0
  109. praisonai/cli/features/doctor/models.py +264 -0
  110. praisonai/cli/features/doctor/registry.py +239 -0
  111. praisonai/cli/features/endpoints.py +1019 -0
  112. praisonai/cli/features/eval.py +560 -0
  113. praisonai/cli/features/external_agents.py +231 -0
  114. praisonai/cli/features/fast_context.py +410 -0
  115. praisonai/cli/features/flow_display.py +566 -0
  116. praisonai/cli/features/git_integration.py +651 -0
  117. praisonai/cli/features/guardrail.py +171 -0
  118. praisonai/cli/features/handoff.py +185 -0
  119. praisonai/cli/features/hooks.py +583 -0
  120. praisonai/cli/features/image.py +384 -0
  121. praisonai/cli/features/interactive_runtime.py +585 -0
  122. praisonai/cli/features/interactive_tools.py +380 -0
  123. praisonai/cli/features/interactive_tui.py +603 -0
  124. praisonai/cli/features/jobs.py +632 -0
  125. praisonai/cli/features/knowledge.py +531 -0
  126. praisonai/cli/features/lite.py +244 -0
  127. praisonai/cli/features/lsp_cli.py +225 -0
  128. praisonai/cli/features/mcp.py +169 -0
  129. praisonai/cli/features/message_queue.py +587 -0
  130. praisonai/cli/features/metrics.py +211 -0
  131. praisonai/cli/features/n8n.py +673 -0
  132. praisonai/cli/features/observability.py +293 -0
  133. praisonai/cli/features/ollama.py +361 -0
  134. praisonai/cli/features/output_style.py +273 -0
  135. praisonai/cli/features/package.py +631 -0
  136. praisonai/cli/features/performance.py +308 -0
  137. praisonai/cli/features/persistence.py +636 -0
  138. praisonai/cli/features/profile.py +226 -0
  139. praisonai/cli/features/profiler/__init__.py +81 -0
  140. praisonai/cli/features/profiler/core.py +558 -0
  141. praisonai/cli/features/profiler/optimizations.py +652 -0
  142. praisonai/cli/features/profiler/suite.py +386 -0
  143. praisonai/cli/features/profiling.py +350 -0
  144. praisonai/cli/features/queue/__init__.py +73 -0
  145. praisonai/cli/features/queue/manager.py +395 -0
  146. praisonai/cli/features/queue/models.py +286 -0
  147. praisonai/cli/features/queue/persistence.py +564 -0
  148. praisonai/cli/features/queue/scheduler.py +484 -0
  149. praisonai/cli/features/queue/worker.py +372 -0
  150. praisonai/cli/features/recipe.py +1723 -0
  151. praisonai/cli/features/recipes.py +449 -0
  152. praisonai/cli/features/registry.py +229 -0
  153. praisonai/cli/features/repo_map.py +860 -0
  154. praisonai/cli/features/router.py +466 -0
  155. praisonai/cli/features/sandbox_executor.py +515 -0
  156. praisonai/cli/features/serve.py +829 -0
  157. praisonai/cli/features/session.py +222 -0
  158. praisonai/cli/features/skills.py +856 -0
  159. praisonai/cli/features/slash_commands.py +650 -0
  160. praisonai/cli/features/telemetry.py +179 -0
  161. praisonai/cli/features/templates.py +1384 -0
  162. praisonai/cli/features/thinking.py +305 -0
  163. praisonai/cli/features/todo.py +334 -0
  164. praisonai/cli/features/tools.py +680 -0
  165. praisonai/cli/features/tui/__init__.py +83 -0
  166. praisonai/cli/features/tui/app.py +580 -0
  167. praisonai/cli/features/tui/cli.py +566 -0
  168. praisonai/cli/features/tui/debug.py +511 -0
  169. praisonai/cli/features/tui/events.py +99 -0
  170. praisonai/cli/features/tui/mock_provider.py +328 -0
  171. praisonai/cli/features/tui/orchestrator.py +652 -0
  172. praisonai/cli/features/tui/screens/__init__.py +50 -0
  173. praisonai/cli/features/tui/screens/main.py +245 -0
  174. praisonai/cli/features/tui/screens/queue.py +174 -0
  175. praisonai/cli/features/tui/screens/session.py +124 -0
  176. praisonai/cli/features/tui/screens/settings.py +148 -0
  177. praisonai/cli/features/tui/widgets/__init__.py +56 -0
  178. praisonai/cli/features/tui/widgets/chat.py +261 -0
  179. praisonai/cli/features/tui/widgets/composer.py +224 -0
  180. praisonai/cli/features/tui/widgets/queue_panel.py +200 -0
  181. praisonai/cli/features/tui/widgets/status.py +167 -0
  182. praisonai/cli/features/tui/widgets/tool_panel.py +248 -0
  183. praisonai/cli/features/workflow.py +720 -0
  184. praisonai/cli/legacy.py +236 -0
  185. praisonai/cli/main.py +5559 -0
  186. praisonai/cli/schedule_cli.py +54 -0
  187. praisonai/cli/state/__init__.py +31 -0
  188. praisonai/cli/state/identifiers.py +161 -0
  189. praisonai/cli/state/sessions.py +313 -0
  190. praisonai/code/__init__.py +93 -0
  191. praisonai/code/agent_tools.py +344 -0
  192. praisonai/code/diff/__init__.py +21 -0
  193. praisonai/code/diff/diff_strategy.py +432 -0
  194. praisonai/code/tools/__init__.py +27 -0
  195. praisonai/code/tools/apply_diff.py +221 -0
  196. praisonai/code/tools/execute_command.py +275 -0
  197. praisonai/code/tools/list_files.py +274 -0
  198. praisonai/code/tools/read_file.py +206 -0
  199. praisonai/code/tools/search_replace.py +248 -0
  200. praisonai/code/tools/write_file.py +217 -0
  201. praisonai/code/utils/__init__.py +46 -0
  202. praisonai/code/utils/file_utils.py +307 -0
  203. praisonai/code/utils/ignore_utils.py +308 -0
  204. praisonai/code/utils/text_utils.py +276 -0
  205. praisonai/db/__init__.py +64 -0
  206. praisonai/db/adapter.py +531 -0
  207. praisonai/deploy/__init__.py +62 -0
  208. praisonai/deploy/api.py +231 -0
  209. praisonai/deploy/docker.py +454 -0
  210. praisonai/deploy/doctor.py +367 -0
  211. praisonai/deploy/main.py +327 -0
  212. praisonai/deploy/models.py +179 -0
  213. praisonai/deploy/providers/__init__.py +33 -0
  214. praisonai/deploy/providers/aws.py +331 -0
  215. praisonai/deploy/providers/azure.py +358 -0
  216. praisonai/deploy/providers/base.py +101 -0
  217. praisonai/deploy/providers/gcp.py +314 -0
  218. praisonai/deploy/schema.py +208 -0
  219. praisonai/deploy.py +185 -0
  220. praisonai/endpoints/__init__.py +53 -0
  221. praisonai/endpoints/a2u_server.py +410 -0
  222. praisonai/endpoints/discovery.py +165 -0
  223. praisonai/endpoints/providers/__init__.py +28 -0
  224. praisonai/endpoints/providers/a2a.py +253 -0
  225. praisonai/endpoints/providers/a2u.py +208 -0
  226. praisonai/endpoints/providers/agents_api.py +171 -0
  227. praisonai/endpoints/providers/base.py +231 -0
  228. praisonai/endpoints/providers/mcp.py +263 -0
  229. praisonai/endpoints/providers/recipe.py +206 -0
  230. praisonai/endpoints/providers/tools_mcp.py +150 -0
  231. praisonai/endpoints/registry.py +131 -0
  232. praisonai/endpoints/server.py +161 -0
  233. praisonai/inbuilt_tools/__init__.py +24 -0
  234. praisonai/inbuilt_tools/autogen_tools.py +117 -0
  235. praisonai/inc/__init__.py +2 -0
  236. praisonai/inc/config.py +96 -0
  237. praisonai/inc/models.py +155 -0
  238. praisonai/integrations/__init__.py +56 -0
  239. praisonai/integrations/base.py +303 -0
  240. praisonai/integrations/claude_code.py +270 -0
  241. praisonai/integrations/codex_cli.py +255 -0
  242. praisonai/integrations/cursor_cli.py +195 -0
  243. praisonai/integrations/gemini_cli.py +222 -0
  244. praisonai/jobs/__init__.py +67 -0
  245. praisonai/jobs/executor.py +425 -0
  246. praisonai/jobs/models.py +230 -0
  247. praisonai/jobs/router.py +314 -0
  248. praisonai/jobs/server.py +186 -0
  249. praisonai/jobs/store.py +203 -0
  250. praisonai/llm/__init__.py +66 -0
  251. praisonai/llm/registry.py +382 -0
  252. praisonai/mcp_server/__init__.py +152 -0
  253. praisonai/mcp_server/adapters/__init__.py +74 -0
  254. praisonai/mcp_server/adapters/agents.py +128 -0
  255. praisonai/mcp_server/adapters/capabilities.py +168 -0
  256. praisonai/mcp_server/adapters/cli_tools.py +568 -0
  257. praisonai/mcp_server/adapters/extended_capabilities.py +462 -0
  258. praisonai/mcp_server/adapters/knowledge.py +93 -0
  259. praisonai/mcp_server/adapters/memory.py +104 -0
  260. praisonai/mcp_server/adapters/prompts.py +306 -0
  261. praisonai/mcp_server/adapters/resources.py +124 -0
  262. praisonai/mcp_server/adapters/tools_bridge.py +280 -0
  263. praisonai/mcp_server/auth/__init__.py +48 -0
  264. praisonai/mcp_server/auth/api_key.py +291 -0
  265. praisonai/mcp_server/auth/oauth.py +460 -0
  266. praisonai/mcp_server/auth/oidc.py +289 -0
  267. praisonai/mcp_server/auth/scopes.py +260 -0
  268. praisonai/mcp_server/cli.py +852 -0
  269. praisonai/mcp_server/elicitation.py +445 -0
  270. praisonai/mcp_server/icons.py +302 -0
  271. praisonai/mcp_server/recipe_adapter.py +573 -0
  272. praisonai/mcp_server/recipe_cli.py +824 -0
  273. praisonai/mcp_server/registry.py +703 -0
  274. praisonai/mcp_server/sampling.py +422 -0
  275. praisonai/mcp_server/server.py +490 -0
  276. praisonai/mcp_server/tasks.py +443 -0
  277. praisonai/mcp_server/transports/__init__.py +18 -0
  278. praisonai/mcp_server/transports/http_stream.py +376 -0
  279. praisonai/mcp_server/transports/stdio.py +132 -0
  280. praisonai/persistence/__init__.py +84 -0
  281. praisonai/persistence/config.py +238 -0
  282. praisonai/persistence/conversation/__init__.py +25 -0
  283. praisonai/persistence/conversation/async_mysql.py +427 -0
  284. praisonai/persistence/conversation/async_postgres.py +410 -0
  285. praisonai/persistence/conversation/async_sqlite.py +371 -0
  286. praisonai/persistence/conversation/base.py +151 -0
  287. praisonai/persistence/conversation/json_store.py +250 -0
  288. praisonai/persistence/conversation/mysql.py +387 -0
  289. praisonai/persistence/conversation/postgres.py +401 -0
  290. praisonai/persistence/conversation/singlestore.py +240 -0
  291. praisonai/persistence/conversation/sqlite.py +341 -0
  292. praisonai/persistence/conversation/supabase.py +203 -0
  293. praisonai/persistence/conversation/surrealdb.py +287 -0
  294. praisonai/persistence/factory.py +301 -0
  295. praisonai/persistence/hooks/__init__.py +18 -0
  296. praisonai/persistence/hooks/agent_hooks.py +297 -0
  297. praisonai/persistence/knowledge/__init__.py +26 -0
  298. praisonai/persistence/knowledge/base.py +144 -0
  299. praisonai/persistence/knowledge/cassandra.py +232 -0
  300. praisonai/persistence/knowledge/chroma.py +295 -0
  301. praisonai/persistence/knowledge/clickhouse.py +242 -0
  302. praisonai/persistence/knowledge/cosmosdb_vector.py +438 -0
  303. praisonai/persistence/knowledge/couchbase.py +286 -0
  304. praisonai/persistence/knowledge/lancedb.py +216 -0
  305. praisonai/persistence/knowledge/langchain_adapter.py +291 -0
  306. praisonai/persistence/knowledge/lightrag_adapter.py +212 -0
  307. praisonai/persistence/knowledge/llamaindex_adapter.py +256 -0
  308. praisonai/persistence/knowledge/milvus.py +277 -0
  309. praisonai/persistence/knowledge/mongodb_vector.py +306 -0
  310. praisonai/persistence/knowledge/pgvector.py +335 -0
  311. praisonai/persistence/knowledge/pinecone.py +253 -0
  312. praisonai/persistence/knowledge/qdrant.py +301 -0
  313. praisonai/persistence/knowledge/redis_vector.py +291 -0
  314. praisonai/persistence/knowledge/singlestore_vector.py +299 -0
  315. praisonai/persistence/knowledge/surrealdb_vector.py +309 -0
  316. praisonai/persistence/knowledge/upstash_vector.py +266 -0
  317. praisonai/persistence/knowledge/weaviate.py +223 -0
  318. praisonai/persistence/migrations/__init__.py +10 -0
  319. praisonai/persistence/migrations/manager.py +251 -0
  320. praisonai/persistence/orchestrator.py +406 -0
  321. praisonai/persistence/state/__init__.py +21 -0
  322. praisonai/persistence/state/async_mongodb.py +200 -0
  323. praisonai/persistence/state/base.py +107 -0
  324. praisonai/persistence/state/dynamodb.py +226 -0
  325. praisonai/persistence/state/firestore.py +175 -0
  326. praisonai/persistence/state/gcs.py +155 -0
  327. praisonai/persistence/state/memory.py +245 -0
  328. praisonai/persistence/state/mongodb.py +158 -0
  329. praisonai/persistence/state/redis.py +190 -0
  330. praisonai/persistence/state/upstash.py +144 -0
  331. praisonai/persistence/tests/__init__.py +3 -0
  332. praisonai/persistence/tests/test_all_backends.py +633 -0
  333. praisonai/profiler.py +1214 -0
  334. praisonai/recipe/__init__.py +134 -0
  335. praisonai/recipe/bridge.py +278 -0
  336. praisonai/recipe/core.py +893 -0
  337. praisonai/recipe/exceptions.py +54 -0
  338. praisonai/recipe/history.py +402 -0
  339. praisonai/recipe/models.py +266 -0
  340. praisonai/recipe/operations.py +440 -0
  341. praisonai/recipe/policy.py +422 -0
  342. praisonai/recipe/registry.py +849 -0
  343. praisonai/recipe/runtime.py +214 -0
  344. praisonai/recipe/security.py +711 -0
  345. praisonai/recipe/serve.py +859 -0
  346. praisonai/recipe/server.py +613 -0
  347. praisonai/scheduler/__init__.py +45 -0
  348. praisonai/scheduler/agent_scheduler.py +552 -0
  349. praisonai/scheduler/base.py +124 -0
  350. praisonai/scheduler/daemon_manager.py +225 -0
  351. praisonai/scheduler/state_manager.py +155 -0
  352. praisonai/scheduler/yaml_loader.py +193 -0
  353. praisonai/scheduler.py +194 -0
  354. praisonai/setup/__init__.py +1 -0
  355. praisonai/setup/build.py +21 -0
  356. praisonai/setup/post_install.py +23 -0
  357. praisonai/setup/setup_conda_env.py +25 -0
  358. praisonai/setup.py +16 -0
  359. praisonai/templates/__init__.py +116 -0
  360. praisonai/templates/cache.py +364 -0
  361. praisonai/templates/dependency_checker.py +358 -0
  362. praisonai/templates/discovery.py +391 -0
  363. praisonai/templates/loader.py +564 -0
  364. praisonai/templates/registry.py +511 -0
  365. praisonai/templates/resolver.py +206 -0
  366. praisonai/templates/security.py +327 -0
  367. praisonai/templates/tool_override.py +498 -0
  368. praisonai/templates/tools_doctor.py +256 -0
  369. praisonai/test.py +105 -0
  370. praisonai/train.py +562 -0
  371. praisonai/train_vision.py +306 -0
  372. praisonai/ui/agents.py +824 -0
  373. praisonai/ui/callbacks.py +57 -0
  374. praisonai/ui/chainlit_compat.py +246 -0
  375. praisonai/ui/chat.py +532 -0
  376. praisonai/ui/code.py +717 -0
  377. praisonai/ui/colab.py +474 -0
  378. praisonai/ui/colab_chainlit.py +81 -0
  379. praisonai/ui/components/aicoder.py +284 -0
  380. praisonai/ui/context.py +283 -0
  381. praisonai/ui/database_config.py +56 -0
  382. praisonai/ui/db.py +294 -0
  383. praisonai/ui/realtime.py +488 -0
  384. praisonai/ui/realtimeclient/__init__.py +756 -0
  385. praisonai/ui/realtimeclient/tools.py +242 -0
  386. praisonai/ui/sql_alchemy.py +710 -0
  387. praisonai/upload_vision.py +140 -0
  388. praisonai/version.py +1 -0
  389. praisonai-3.0.0.dist-info/METADATA +3493 -0
  390. praisonai-3.0.0.dist-info/RECORD +393 -0
  391. praisonai-3.0.0.dist-info/WHEEL +5 -0
  392. praisonai-3.0.0.dist-info/entry_points.txt +4 -0
  393. praisonai-3.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,652 @@
1
+ """
2
+ Code Intelligence Router for PraisonAI.
3
+
4
+ Routes code-related queries to LSP when available, with fallback to grep/file search.
5
+ """
6
+
7
+ import logging
8
+ import re
9
+ from dataclasses import dataclass
10
+ from enum import Enum
11
+ from pathlib import Path
12
+ from typing import Any, Dict, List, Optional, TYPE_CHECKING
13
+
14
+ if TYPE_CHECKING:
15
+ from .interactive_runtime import InteractiveRuntime
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class CodeIntent(Enum):
21
+ """Classification of code-related intents."""
22
+ LIST_SYMBOLS = "list_symbols"
23
+ GO_TO_DEFINITION = "go_to_definition"
24
+ FIND_REFERENCES = "find_references"
25
+ GET_DIAGNOSTICS = "get_diagnostics"
26
+ EXPLAIN_CODE = "explain_code"
27
+ SEARCH_CODE = "search_code"
28
+ UNKNOWN = "unknown"
29
+
30
+
31
+ @dataclass
32
+ class CodeQueryResult:
33
+ """Result of a code intelligence query."""
34
+ intent: CodeIntent
35
+ success: bool
36
+ lsp_used: bool
37
+ data: Any = None
38
+ citations: List[Dict[str, Any]] = None
39
+ error: Optional[str] = None
40
+ fallback_used: bool = False
41
+
42
+ def __post_init__(self):
43
+ if self.citations is None:
44
+ self.citations = []
45
+
46
+ def to_dict(self) -> Dict[str, Any]:
47
+ return {
48
+ "intent": self.intent.value,
49
+ "success": self.success,
50
+ "lsp_used": self.lsp_used,
51
+ "data": self.data,
52
+ "citations": self.citations,
53
+ "error": self.error,
54
+ "fallback_used": self.fallback_used
55
+ }
56
+
57
+
58
+ class CodeIntelligenceRouter:
59
+ """
60
+ Routes code intelligence queries to LSP or fallback mechanisms.
61
+
62
+ LSP-first approach:
63
+ - If LSP is available, use it for semantic queries
64
+ - If LSP fails or unavailable, fall back to grep/file search
65
+ - Always provide citations with file:line references
66
+ """
67
+
68
+ # Patterns for intent classification
69
+ INTENT_PATTERNS = {
70
+ CodeIntent.LIST_SYMBOLS: [
71
+ r"list\s+(all\s+)?(functions?|classes?|methods?|symbols?)",
72
+ r"what\s+(functions?|classes?|methods?)\s+(are|exist)",
73
+ r"show\s+(me\s+)?(all\s+)?(functions?|classes?|symbols?)",
74
+ r"find\s+(all\s+)?(functions?|classes?|methods?)",
75
+ ],
76
+ CodeIntent.GO_TO_DEFINITION: [
77
+ r"(go\s+to|find|show|where\s+is)\s+(the\s+)?definition",
78
+ r"where\s+is\s+(\w+)\s+defined",
79
+ r"definition\s+of\s+(\w+)",
80
+ r"(\w+)\s+is\s+defined\s+where",
81
+ ],
82
+ CodeIntent.FIND_REFERENCES: [
83
+ r"(find|show|list)\s+(all\s+)?references",
84
+ r"where\s+is\s+(\w+)\s+used",
85
+ r"usages?\s+of\s+(\w+)",
86
+ r"who\s+(calls?|uses?)\s+(\w+)",
87
+ ],
88
+ CodeIntent.GET_DIAGNOSTICS: [
89
+ r"(show|list|get)\s+(all\s+)?(errors?|warnings?|diagnostics?|problems?)",
90
+ r"what('s|\s+is)\s+wrong",
91
+ r"any\s+(errors?|issues?|problems?)",
92
+ r"check\s+(for\s+)?(errors?|issues?)",
93
+ ],
94
+ CodeIntent.EXPLAIN_CODE: [
95
+ r"explain\s+(this|the)\s+code",
96
+ r"what\s+does\s+(this|the)\s+code\s+do",
97
+ r"how\s+does\s+(\w+)\s+work",
98
+ ],
99
+ CodeIntent.SEARCH_CODE: [
100
+ r"search\s+(for\s+)?",
101
+ r"find\s+(the\s+)?string",
102
+ r"grep\s+",
103
+ ],
104
+ }
105
+
106
+ def __init__(self, runtime: "InteractiveRuntime"):
107
+ """Initialize with runtime reference."""
108
+ self.runtime = runtime
109
+
110
+ def classify_intent(self, query: str) -> CodeIntent:
111
+ """Classify the intent of a code query."""
112
+ query_lower = query.lower()
113
+
114
+ for intent, patterns in self.INTENT_PATTERNS.items():
115
+ for pattern in patterns:
116
+ if re.search(pattern, query_lower):
117
+ return intent
118
+
119
+ return CodeIntent.UNKNOWN
120
+
121
+ async def handle_query(self, query: str, file_path: str = None) -> CodeQueryResult:
122
+ """
123
+ Handle a code intelligence query.
124
+
125
+ Args:
126
+ query: The user's query
127
+ file_path: Optional file path context
128
+
129
+ Returns:
130
+ CodeQueryResult with data and citations
131
+ """
132
+ intent = self.classify_intent(query)
133
+
134
+ if intent == CodeIntent.LIST_SYMBOLS:
135
+ return await self._handle_list_symbols(query, file_path)
136
+ elif intent == CodeIntent.GO_TO_DEFINITION:
137
+ return await self._handle_go_to_definition(query, file_path)
138
+ elif intent == CodeIntent.FIND_REFERENCES:
139
+ return await self._handle_find_references(query, file_path)
140
+ elif intent == CodeIntent.GET_DIAGNOSTICS:
141
+ return await self._handle_get_diagnostics(query, file_path)
142
+ elif intent == CodeIntent.SEARCH_CODE:
143
+ return await self._handle_search_code(query, file_path)
144
+ else:
145
+ return CodeQueryResult(
146
+ intent=intent,
147
+ success=False,
148
+ lsp_used=False,
149
+ error="Could not determine code query intent"
150
+ )
151
+
152
+ async def _handle_list_symbols(self, query: str, file_path: str = None) -> CodeQueryResult:
153
+ """Handle list symbols query."""
154
+ if not file_path:
155
+ # Try to find a file from the query
156
+ file_path = self._extract_file_from_query(query)
157
+
158
+ if not file_path:
159
+ return CodeQueryResult(
160
+ intent=CodeIntent.LIST_SYMBOLS,
161
+ success=False,
162
+ lsp_used=False,
163
+ error="No file specified. Please specify a file path."
164
+ )
165
+
166
+ # Try LSP first
167
+ if self.runtime.lsp_ready:
168
+ try:
169
+ symbols = await self.runtime.lsp_get_symbols(file_path)
170
+ if symbols:
171
+ citations = [
172
+ {"file": file_path, "type": "symbols", "count": len(symbols)}
173
+ ]
174
+ return CodeQueryResult(
175
+ intent=CodeIntent.LIST_SYMBOLS,
176
+ success=True,
177
+ lsp_used=True,
178
+ data=symbols,
179
+ citations=citations
180
+ )
181
+ except Exception as e:
182
+ logger.warning(f"LSP symbols failed: {e}")
183
+
184
+ # Fallback to regex-based symbol extraction
185
+ return await self._fallback_list_symbols(file_path)
186
+
187
+ async def _fallback_list_symbols(self, file_path: str) -> CodeQueryResult:
188
+ """Fallback symbol listing using regex."""
189
+ try:
190
+ path = Path(file_path)
191
+ if not path.exists():
192
+ path = Path(self.runtime.config.workspace) / file_path
193
+
194
+ if not path.exists():
195
+ return CodeQueryResult(
196
+ intent=CodeIntent.LIST_SYMBOLS,
197
+ success=False,
198
+ lsp_used=False,
199
+ fallback_used=True,
200
+ error=f"File not found: {file_path}"
201
+ )
202
+
203
+ content = path.read_text()
204
+ symbols = []
205
+
206
+ # Python patterns
207
+ if path.suffix == ".py":
208
+ # Functions
209
+ for match in re.finditer(r'^(async\s+)?def\s+(\w+)\s*\(', content, re.MULTILINE):
210
+ line_num = content[:match.start()].count('\n') + 1
211
+ symbols.append({
212
+ "name": match.group(2),
213
+ "kind": "function",
214
+ "line": line_num
215
+ })
216
+ # Classes
217
+ for match in re.finditer(r'^class\s+(\w+)\s*[:\(]', content, re.MULTILINE):
218
+ line_num = content[:match.start()].count('\n') + 1
219
+ symbols.append({
220
+ "name": match.group(1),
221
+ "kind": "class",
222
+ "line": line_num
223
+ })
224
+
225
+ # JavaScript/TypeScript patterns
226
+ elif path.suffix in [".js", ".ts", ".jsx", ".tsx"]:
227
+ # Functions
228
+ for match in re.finditer(r'(?:async\s+)?function\s+(\w+)\s*\(', content):
229
+ line_num = content[:match.start()].count('\n') + 1
230
+ symbols.append({
231
+ "name": match.group(1),
232
+ "kind": "function",
233
+ "line": line_num
234
+ })
235
+ # Arrow functions assigned to const/let/var
236
+ for match in re.finditer(r'(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\(', content):
237
+ line_num = content[:match.start()].count('\n') + 1
238
+ symbols.append({
239
+ "name": match.group(1),
240
+ "kind": "function",
241
+ "line": line_num
242
+ })
243
+ # Classes
244
+ for match in re.finditer(r'class\s+(\w+)\s*(?:extends|implements|{)', content):
245
+ line_num = content[:match.start()].count('\n') + 1
246
+ symbols.append({
247
+ "name": match.group(1),
248
+ "kind": "class",
249
+ "line": line_num
250
+ })
251
+
252
+ citations = [
253
+ {"file": str(path), "type": "symbols", "count": len(symbols)}
254
+ ]
255
+
256
+ return CodeQueryResult(
257
+ intent=CodeIntent.LIST_SYMBOLS,
258
+ success=True,
259
+ lsp_used=False,
260
+ fallback_used=True,
261
+ data=symbols,
262
+ citations=citations
263
+ )
264
+
265
+ except Exception as e:
266
+ return CodeQueryResult(
267
+ intent=CodeIntent.LIST_SYMBOLS,
268
+ success=False,
269
+ lsp_used=False,
270
+ fallback_used=True,
271
+ error=str(e)
272
+ )
273
+
274
+ async def _handle_go_to_definition(self, query: str, file_path: str = None) -> CodeQueryResult:
275
+ """Handle go to definition query."""
276
+ # Extract symbol name from query
277
+ symbol = self._extract_symbol_from_query(query)
278
+
279
+ if not symbol:
280
+ return CodeQueryResult(
281
+ intent=CodeIntent.GO_TO_DEFINITION,
282
+ success=False,
283
+ lsp_used=False,
284
+ error="Could not identify symbol to find definition for"
285
+ )
286
+
287
+ if not file_path:
288
+ file_path = self._extract_file_from_query(query)
289
+
290
+ # Try LSP first
291
+ if self.runtime.lsp_ready and file_path:
292
+ try:
293
+ # Need to find the symbol position first
294
+ line, col = await self._find_symbol_position(file_path, symbol)
295
+ if line is not None:
296
+ definitions = await self.runtime.lsp_get_definition(file_path, line, col)
297
+ if definitions:
298
+ citations = [
299
+ {"file": d.get("uri", "").replace("file://", ""),
300
+ "line": d.get("range", {}).get("start", {}).get("line", 0) + 1,
301
+ "type": "definition"}
302
+ for d in definitions
303
+ ]
304
+ return CodeQueryResult(
305
+ intent=CodeIntent.GO_TO_DEFINITION,
306
+ success=True,
307
+ lsp_used=True,
308
+ data={"symbol": symbol, "definitions": definitions},
309
+ citations=citations
310
+ )
311
+ except Exception as e:
312
+ logger.warning(f"LSP definition failed: {e}")
313
+
314
+ # Fallback to grep
315
+ return await self._fallback_find_definition(symbol)
316
+
317
+ async def _fallback_find_definition(self, symbol: str) -> CodeQueryResult:
318
+ """Fallback definition finding using grep."""
319
+ try:
320
+ import subprocess
321
+ workspace = self.runtime.config.workspace
322
+
323
+ # Search for definition patterns
324
+ patterns = [
325
+ f"def {symbol}\\s*\\(", # Python function
326
+ f"class {symbol}\\s*[:\\(]", # Python class
327
+ f"function {symbol}\\s*\\(", # JS function
328
+ f"const {symbol}\\s*=", # JS const
329
+ f"let {symbol}\\s*=", # JS let
330
+ ]
331
+
332
+ results = []
333
+ for pattern in patterns:
334
+ try:
335
+ result = subprocess.run(
336
+ ["grep", "-rn", "-E", pattern, workspace],
337
+ capture_output=True,
338
+ text=True,
339
+ timeout=5
340
+ )
341
+ if result.stdout:
342
+ for line in result.stdout.strip().split('\n'):
343
+ if line:
344
+ parts = line.split(':', 2)
345
+ if len(parts) >= 2:
346
+ results.append({
347
+ "file": parts[0],
348
+ "line": int(parts[1]),
349
+ "content": parts[2] if len(parts) > 2 else ""
350
+ })
351
+ except subprocess.TimeoutExpired:
352
+ pass
353
+
354
+ if results:
355
+ citations = [
356
+ {"file": r["file"], "line": r["line"], "type": "definition"}
357
+ for r in results
358
+ ]
359
+ return CodeQueryResult(
360
+ intent=CodeIntent.GO_TO_DEFINITION,
361
+ success=True,
362
+ lsp_used=False,
363
+ fallback_used=True,
364
+ data={"symbol": symbol, "definitions": results},
365
+ citations=citations
366
+ )
367
+
368
+ return CodeQueryResult(
369
+ intent=CodeIntent.GO_TO_DEFINITION,
370
+ success=False,
371
+ lsp_used=False,
372
+ fallback_used=True,
373
+ error=f"Definition for '{symbol}' not found"
374
+ )
375
+
376
+ except Exception as e:
377
+ return CodeQueryResult(
378
+ intent=CodeIntent.GO_TO_DEFINITION,
379
+ success=False,
380
+ lsp_used=False,
381
+ fallback_used=True,
382
+ error=str(e)
383
+ )
384
+
385
+ async def _handle_find_references(self, query: str, file_path: str = None) -> CodeQueryResult:
386
+ """Handle find references query."""
387
+ symbol = self._extract_symbol_from_query(query)
388
+
389
+ if not symbol:
390
+ return CodeQueryResult(
391
+ intent=CodeIntent.FIND_REFERENCES,
392
+ success=False,
393
+ lsp_used=False,
394
+ error="Could not identify symbol to find references for"
395
+ )
396
+
397
+ if not file_path:
398
+ file_path = self._extract_file_from_query(query)
399
+
400
+ # Try LSP first
401
+ if self.runtime.lsp_ready and file_path:
402
+ try:
403
+ line, col = await self._find_symbol_position(file_path, symbol)
404
+ if line is not None:
405
+ references = await self.runtime.lsp_get_references(file_path, line, col)
406
+ if references:
407
+ citations = [
408
+ {"file": r.get("uri", "").replace("file://", ""),
409
+ "line": r.get("range", {}).get("start", {}).get("line", 0) + 1,
410
+ "type": "reference"}
411
+ for r in references
412
+ ]
413
+ return CodeQueryResult(
414
+ intent=CodeIntent.FIND_REFERENCES,
415
+ success=True,
416
+ lsp_used=True,
417
+ data={"symbol": symbol, "references": references},
418
+ citations=citations
419
+ )
420
+ except Exception as e:
421
+ logger.warning(f"LSP references failed: {e}")
422
+
423
+ # Fallback to grep
424
+ return await self._fallback_find_references(symbol)
425
+
426
+ async def _fallback_find_references(self, symbol: str) -> CodeQueryResult:
427
+ """Fallback reference finding using grep."""
428
+ try:
429
+ import subprocess
430
+ workspace = self.runtime.config.workspace
431
+
432
+ result = subprocess.run(
433
+ ["grep", "-rn", "-w", symbol, workspace],
434
+ capture_output=True,
435
+ text=True,
436
+ timeout=10
437
+ )
438
+
439
+ results = []
440
+ if result.stdout:
441
+ for line in result.stdout.strip().split('\n'):
442
+ if line:
443
+ parts = line.split(':', 2)
444
+ if len(parts) >= 2:
445
+ results.append({
446
+ "file": parts[0],
447
+ "line": int(parts[1]),
448
+ "content": parts[2] if len(parts) > 2 else ""
449
+ })
450
+
451
+ if results:
452
+ citations = [
453
+ {"file": r["file"], "line": r["line"], "type": "reference"}
454
+ for r in results
455
+ ]
456
+ return CodeQueryResult(
457
+ intent=CodeIntent.FIND_REFERENCES,
458
+ success=True,
459
+ lsp_used=False,
460
+ fallback_used=True,
461
+ data={"symbol": symbol, "references": results},
462
+ citations=citations
463
+ )
464
+
465
+ return CodeQueryResult(
466
+ intent=CodeIntent.FIND_REFERENCES,
467
+ success=False,
468
+ lsp_used=False,
469
+ fallback_used=True,
470
+ error=f"No references found for '{symbol}'"
471
+ )
472
+
473
+ except Exception as e:
474
+ return CodeQueryResult(
475
+ intent=CodeIntent.FIND_REFERENCES,
476
+ success=False,
477
+ lsp_used=False,
478
+ fallback_used=True,
479
+ error=str(e)
480
+ )
481
+
482
+ async def _handle_get_diagnostics(self, query: str, file_path: str = None) -> CodeQueryResult:
483
+ """Handle get diagnostics query."""
484
+ if not file_path:
485
+ file_path = self._extract_file_from_query(query)
486
+
487
+ # Try LSP
488
+ if self.runtime.lsp_ready:
489
+ try:
490
+ diagnostics = await self.runtime.lsp_get_diagnostics(file_path)
491
+ citations = []
492
+ if diagnostics:
493
+ for d in diagnostics:
494
+ if isinstance(d, dict):
495
+ citations.append({
496
+ "file": file_path or "workspace",
497
+ "line": d.get("range", {}).get("start", {}).get("line", 0) + 1,
498
+ "type": "diagnostic",
499
+ "severity": d.get("severity", "unknown")
500
+ })
501
+
502
+ return CodeQueryResult(
503
+ intent=CodeIntent.GET_DIAGNOSTICS,
504
+ success=True,
505
+ lsp_used=True,
506
+ data=diagnostics,
507
+ citations=citations
508
+ )
509
+ except Exception as e:
510
+ logger.warning(f"LSP diagnostics failed: {e}")
511
+
512
+ return CodeQueryResult(
513
+ intent=CodeIntent.GET_DIAGNOSTICS,
514
+ success=False,
515
+ lsp_used=False,
516
+ error="LSP not available for diagnostics"
517
+ )
518
+
519
+ async def _handle_search_code(self, query: str, file_path: str = None) -> CodeQueryResult:
520
+ """Handle code search query."""
521
+ # Extract search term
522
+ search_term = self._extract_search_term(query)
523
+
524
+ if not search_term:
525
+ return CodeQueryResult(
526
+ intent=CodeIntent.SEARCH_CODE,
527
+ success=False,
528
+ lsp_used=False,
529
+ error="Could not identify search term"
530
+ )
531
+
532
+ try:
533
+ import subprocess
534
+ workspace = self.runtime.config.workspace
535
+
536
+ cmd = ["grep", "-rn", search_term]
537
+ if file_path:
538
+ cmd.append(file_path)
539
+ else:
540
+ cmd.append(workspace)
541
+
542
+ result = subprocess.run(
543
+ cmd,
544
+ capture_output=True,
545
+ text=True,
546
+ timeout=10
547
+ )
548
+
549
+ results = []
550
+ if result.stdout:
551
+ for line in result.stdout.strip().split('\n')[:50]: # Limit results
552
+ if line:
553
+ parts = line.split(':', 2)
554
+ if len(parts) >= 2:
555
+ results.append({
556
+ "file": parts[0],
557
+ "line": int(parts[1]),
558
+ "content": parts[2] if len(parts) > 2 else ""
559
+ })
560
+
561
+ citations = [
562
+ {"file": r["file"], "line": r["line"], "type": "search_result"}
563
+ for r in results
564
+ ]
565
+
566
+ return CodeQueryResult(
567
+ intent=CodeIntent.SEARCH_CODE,
568
+ success=True,
569
+ lsp_used=False,
570
+ data={"search_term": search_term, "results": results},
571
+ citations=citations
572
+ )
573
+
574
+ except Exception as e:
575
+ return CodeQueryResult(
576
+ intent=CodeIntent.SEARCH_CODE,
577
+ success=False,
578
+ lsp_used=False,
579
+ error=str(e)
580
+ )
581
+
582
+ def _extract_file_from_query(self, query: str) -> Optional[str]:
583
+ """Extract file path from query."""
584
+ # Look for file patterns
585
+ patterns = [
586
+ r'(?:in|from|file)\s+["\']?([^\s"\']+\.\w+)["\']?',
587
+ r'([^\s]+\.(?:py|js|ts|tsx|jsx|go|rs|java|cpp|c|h))',
588
+ ]
589
+
590
+ for pattern in patterns:
591
+ match = re.search(pattern, query, re.IGNORECASE)
592
+ if match:
593
+ return match.group(1)
594
+
595
+ return None
596
+
597
+ def _extract_symbol_from_query(self, query: str) -> Optional[str]:
598
+ """Extract symbol name from query."""
599
+ # Common patterns for symbol extraction
600
+ patterns = [
601
+ r'(?:definition\s+of|where\s+is|find|show)\s+["\']?(\w+)["\']?',
602
+ r'["\'](\w+)["\']',
603
+ r'`(\w+)`',
604
+ r'(\w+)\s+(?:is\s+)?(?:defined|used)',
605
+ ]
606
+
607
+ for pattern in patterns:
608
+ match = re.search(pattern, query, re.IGNORECASE)
609
+ if match:
610
+ symbol = match.group(1)
611
+ # Filter out common words
612
+ if symbol.lower() not in ['the', 'a', 'an', 'is', 'are', 'in', 'of', 'to', 'for']:
613
+ return symbol
614
+
615
+ return None
616
+
617
+ def _extract_search_term(self, query: str) -> Optional[str]:
618
+ """Extract search term from query."""
619
+ patterns = [
620
+ r'search\s+(?:for\s+)?["\']([^"\']+)["\']',
621
+ r'find\s+["\']([^"\']+)["\']',
622
+ r'grep\s+["\']?([^\s"\']+)["\']?',
623
+ ]
624
+
625
+ for pattern in patterns:
626
+ match = re.search(pattern, query, re.IGNORECASE)
627
+ if match:
628
+ return match.group(1)
629
+
630
+ return None
631
+
632
+ async def _find_symbol_position(self, file_path: str, symbol: str) -> tuple:
633
+ """Find the position of a symbol in a file."""
634
+ try:
635
+ path = Path(file_path)
636
+ if not path.exists():
637
+ path = Path(self.runtime.config.workspace) / file_path
638
+
639
+ if not path.exists():
640
+ return None, None
641
+
642
+ content = path.read_text()
643
+ lines = content.split('\n')
644
+
645
+ for i, line in enumerate(lines):
646
+ col = line.find(symbol)
647
+ if col >= 0:
648
+ return i, col
649
+
650
+ return None, None
651
+ except Exception:
652
+ return None, None