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,46 @@
1
+ """
2
+ Utility functions for PraisonAI Code module.
3
+ """
4
+
5
+ from .file_utils import (
6
+ add_line_numbers,
7
+ strip_line_numbers,
8
+ every_line_has_line_numbers,
9
+ normalize_line_endings,
10
+ get_file_extension,
11
+ is_binary_file,
12
+ create_directories_for_file,
13
+ file_exists,
14
+ )
15
+
16
+ from .text_utils import (
17
+ normalize_string,
18
+ unescape_html_entities,
19
+ get_similarity,
20
+ )
21
+
22
+ from .ignore_utils import (
23
+ FileAccessController,
24
+ load_gitignore_patterns,
25
+ should_ignore_path,
26
+ )
27
+
28
+ __all__ = [
29
+ # File utilities
30
+ "add_line_numbers",
31
+ "strip_line_numbers",
32
+ "every_line_has_line_numbers",
33
+ "normalize_line_endings",
34
+ "get_file_extension",
35
+ "is_binary_file",
36
+ "create_directories_for_file",
37
+ "file_exists",
38
+ # Text utilities
39
+ "normalize_string",
40
+ "unescape_html_entities",
41
+ "get_similarity",
42
+ # Ignore utilities
43
+ "FileAccessController",
44
+ "load_gitignore_patterns",
45
+ "should_ignore_path",
46
+ ]
@@ -0,0 +1,307 @@
1
+ """
2
+ File utility functions for PraisonAI Code module.
3
+
4
+ Provides utilities for file operations including:
5
+ - Line number handling (add/strip)
6
+ - File existence and type checking
7
+ - Directory creation
8
+ - Line ending normalization
9
+ """
10
+
11
+ import os
12
+ import re
13
+ from typing import Optional, Tuple
14
+
15
+
16
+ def add_line_numbers(content: str, start_line: int = 1) -> str:
17
+ """
18
+ Add line numbers to content in the format: " N | content"
19
+
20
+ Args:
21
+ content: The text content to add line numbers to
22
+ start_line: The starting line number (1-indexed)
23
+
24
+ Returns:
25
+ Content with line numbers prefixed to each line
26
+
27
+ Example:
28
+ >>> add_line_numbers("hello\\nworld", 1)
29
+ ' 1 | hello\\n 2 | world'
30
+ """
31
+ if not content:
32
+ return ""
33
+
34
+ lines = content.split('\n')
35
+ max_line_num = start_line + len(lines) - 1
36
+ width = len(str(max_line_num))
37
+
38
+ numbered_lines = []
39
+ for i, line in enumerate(lines):
40
+ line_num = start_line + i
41
+ numbered_lines.append(f"{line_num:>{width}} | {line}")
42
+
43
+ return '\n'.join(numbered_lines)
44
+
45
+
46
+ def strip_line_numbers(content: str, aggressive: bool = False) -> str:
47
+ """
48
+ Strip line numbers from content.
49
+
50
+ Handles formats like:
51
+ - " 1 | content"
52
+ - "1| content"
53
+ - " 1\tcontent"
54
+
55
+ Args:
56
+ content: Content with line numbers
57
+ aggressive: If True, also strip formats like "1:" or just leading numbers
58
+
59
+ Returns:
60
+ Content with line numbers removed
61
+ """
62
+ if not content:
63
+ return ""
64
+
65
+ lines = content.split('\n')
66
+ stripped_lines = []
67
+
68
+ # Pattern for standard format: optional spaces, digits, optional spaces, pipe/tab, content
69
+ standard_pattern = re.compile(r'^\s*\d+\s*[|\t]\s?(.*)$')
70
+ # Aggressive pattern: digits followed by colon or just leading digits with space
71
+ aggressive_pattern = re.compile(r'^\s*\d+[:\s]\s?(.*)$')
72
+
73
+ for line in lines:
74
+ match = standard_pattern.match(line)
75
+ if match:
76
+ stripped_lines.append(match.group(1))
77
+ elif aggressive:
78
+ match = aggressive_pattern.match(line)
79
+ if match:
80
+ stripped_lines.append(match.group(1))
81
+ else:
82
+ stripped_lines.append(line)
83
+ else:
84
+ stripped_lines.append(line)
85
+
86
+ return '\n'.join(stripped_lines)
87
+
88
+
89
+ def every_line_has_line_numbers(content: str) -> bool:
90
+ """
91
+ Check if every non-empty line in content has line numbers.
92
+
93
+ Args:
94
+ content: The content to check
95
+
96
+ Returns:
97
+ True if all non-empty lines have line numbers
98
+ """
99
+ if not content or not content.strip():
100
+ return False
101
+
102
+ lines = content.split('\n')
103
+ pattern = re.compile(r'^\s*\d+\s*[|\t]')
104
+
105
+ for line in lines:
106
+ if line.strip(): # Only check non-empty lines
107
+ if not pattern.match(line):
108
+ return False
109
+
110
+ return True
111
+
112
+
113
+ def normalize_line_endings(content: str, target: str = '\n') -> str:
114
+ """
115
+ Normalize line endings to a consistent format.
116
+
117
+ Args:
118
+ content: The content to normalize
119
+ target: The target line ending ('\\n' or '\\r\\n')
120
+
121
+ Returns:
122
+ Content with normalized line endings
123
+ """
124
+ # First normalize all to \n, then convert to target
125
+ normalized = content.replace('\r\n', '\n').replace('\r', '\n')
126
+ if target == '\r\n':
127
+ normalized = normalized.replace('\n', '\r\n')
128
+ return normalized
129
+
130
+
131
+ def detect_line_ending(content: str) -> str:
132
+ """
133
+ Detect the predominant line ending in content.
134
+
135
+ Args:
136
+ content: The content to analyze
137
+
138
+ Returns:
139
+ '\\r\\n' if Windows-style, '\\n' otherwise
140
+ """
141
+ if '\r\n' in content:
142
+ return '\r\n'
143
+ return '\n'
144
+
145
+
146
+ def get_file_extension(file_path: str) -> str:
147
+ """
148
+ Get the file extension from a path.
149
+
150
+ Args:
151
+ file_path: Path to the file
152
+
153
+ Returns:
154
+ File extension without the dot, or empty string
155
+ """
156
+ ext = os.path.splitext(file_path)[1]
157
+ return ext[1:] if ext else ""
158
+
159
+
160
+ def is_binary_file(file_path: str, sample_size: int = 8192) -> bool:
161
+ """
162
+ Check if a file is binary by reading a sample.
163
+
164
+ Args:
165
+ file_path: Path to the file
166
+ sample_size: Number of bytes to sample
167
+
168
+ Returns:
169
+ True if the file appears to be binary
170
+ """
171
+ try:
172
+ with open(file_path, 'rb') as f:
173
+ sample = f.read(sample_size)
174
+
175
+ # Check for null bytes (common in binary files)
176
+ if b'\x00' in sample:
177
+ return True
178
+
179
+ # Check the ratio of non-text characters
180
+ text_chars = bytearray({7, 8, 9, 10, 12, 13, 27} | set(range(0x20, 0x100)) - {0x7f})
181
+ non_text = sum(1 for byte in sample if byte not in text_chars)
182
+
183
+ # If more than 30% non-text characters, consider it binary
184
+ return len(sample) > 0 and (non_text / len(sample)) > 0.30
185
+
186
+ except (IOError, OSError):
187
+ return False
188
+
189
+
190
+ def create_directories_for_file(file_path: str) -> bool:
191
+ """
192
+ Create parent directories for a file path if they don't exist.
193
+
194
+ Args:
195
+ file_path: Path to the file
196
+
197
+ Returns:
198
+ True if directories were created or already exist
199
+ """
200
+ try:
201
+ parent_dir = os.path.dirname(file_path)
202
+ if parent_dir:
203
+ os.makedirs(parent_dir, exist_ok=True)
204
+ return True
205
+ except OSError:
206
+ return False
207
+
208
+
209
+ def file_exists(file_path: str) -> bool:
210
+ """
211
+ Check if a file exists.
212
+
213
+ Args:
214
+ file_path: Path to the file
215
+
216
+ Returns:
217
+ True if the file exists and is a file (not directory)
218
+ """
219
+ return os.path.isfile(file_path)
220
+
221
+
222
+ def get_relative_path(file_path: str, base_path: str) -> str:
223
+ """
224
+ Get the relative path from base_path to file_path.
225
+
226
+ Args:
227
+ file_path: The target file path
228
+ base_path: The base directory path
229
+
230
+ Returns:
231
+ Relative path string
232
+ """
233
+ try:
234
+ return os.path.relpath(file_path, base_path)
235
+ except ValueError:
236
+ # On Windows, relpath fails for paths on different drives
237
+ return file_path
238
+
239
+
240
+ def is_path_within_directory(file_path: str, directory: str) -> bool:
241
+ """
242
+ Check if a file path is within a directory (prevents path traversal).
243
+
244
+ Args:
245
+ file_path: The file path to check
246
+ directory: The directory that should contain the file
247
+
248
+ Returns:
249
+ True if file_path is within directory
250
+ """
251
+ # Resolve to absolute paths
252
+ abs_file = os.path.abspath(file_path)
253
+ abs_dir = os.path.abspath(directory)
254
+
255
+ # Ensure directory ends with separator for proper prefix matching
256
+ if not abs_dir.endswith(os.sep):
257
+ abs_dir += os.sep
258
+
259
+ return abs_file.startswith(abs_dir) or abs_file == abs_dir.rstrip(os.sep)
260
+
261
+
262
+ def read_file_lines(file_path: str, start_line: int = 1, end_line: Optional[int] = None) -> Tuple[str, int]:
263
+ """
264
+ Read specific lines from a file.
265
+
266
+ Args:
267
+ file_path: Path to the file
268
+ start_line: First line to read (1-indexed)
269
+ end_line: Last line to read (inclusive), None for all remaining
270
+
271
+ Returns:
272
+ Tuple of (content, total_lines)
273
+ """
274
+ with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
275
+ lines = f.readlines()
276
+
277
+ total_lines = len(lines)
278
+
279
+ # Convert to 0-indexed
280
+ start_idx = max(0, start_line - 1)
281
+ end_idx = end_line if end_line else total_lines
282
+
283
+ selected_lines = lines[start_idx:end_idx]
284
+ content = ''.join(selected_lines)
285
+
286
+ # Remove trailing newline if present
287
+ if content.endswith('\n'):
288
+ content = content[:-1]
289
+
290
+ return content, total_lines
291
+
292
+
293
+ def count_file_lines(file_path: str) -> int:
294
+ """
295
+ Count the number of lines in a file efficiently.
296
+
297
+ Args:
298
+ file_path: Path to the file
299
+
300
+ Returns:
301
+ Number of lines in the file
302
+ """
303
+ count = 0
304
+ with open(file_path, 'rb') as f:
305
+ for _ in f:
306
+ count += 1
307
+ return count
@@ -0,0 +1,308 @@
1
+ """
2
+ File access control utilities for PraisonAI Code module.
3
+
4
+ Provides utilities for:
5
+ - Loading and parsing .gitignore patterns
6
+ - Checking if paths should be ignored
7
+ - Controlling file access within workspaces
8
+ """
9
+
10
+ import os
11
+ import fnmatch
12
+ from typing import List, Optional, Set
13
+
14
+
15
+ def load_gitignore_patterns(directory: str) -> List[str]:
16
+ """
17
+ Load .gitignore patterns from a directory.
18
+
19
+ Args:
20
+ directory: The directory to search for .gitignore
21
+
22
+ Returns:
23
+ List of gitignore patterns
24
+ """
25
+ patterns = []
26
+ gitignore_path = os.path.join(directory, '.gitignore')
27
+
28
+ if os.path.isfile(gitignore_path):
29
+ try:
30
+ with open(gitignore_path, 'r', encoding='utf-8') as f:
31
+ for line in f:
32
+ line = line.strip()
33
+ # Skip empty lines and comments
34
+ if line and not line.startswith('#'):
35
+ patterns.append(line)
36
+ except (IOError, OSError):
37
+ pass
38
+
39
+ return patterns
40
+
41
+
42
+ def _pattern_to_regex_parts(pattern: str) -> tuple:
43
+ """
44
+ Convert a gitignore pattern to matching components.
45
+
46
+ Args:
47
+ pattern: A gitignore pattern
48
+
49
+ Returns:
50
+ Tuple of (is_negation, is_directory_only, cleaned_pattern)
51
+ """
52
+ is_negation = pattern.startswith('!')
53
+ if is_negation:
54
+ pattern = pattern[1:]
55
+
56
+ is_directory_only = pattern.endswith('/')
57
+ if is_directory_only:
58
+ pattern = pattern[:-1]
59
+
60
+ return is_negation, is_directory_only, pattern
61
+
62
+
63
+ def should_ignore_path(
64
+ path: str,
65
+ patterns: List[str],
66
+ base_dir: str,
67
+ is_directory: bool = False
68
+ ) -> bool:
69
+ """
70
+ Check if a path should be ignored based on gitignore patterns.
71
+
72
+ Args:
73
+ path: The path to check (relative or absolute)
74
+ patterns: List of gitignore patterns
75
+ base_dir: The base directory for relative path calculation
76
+ is_directory: Whether the path is a directory
77
+
78
+ Returns:
79
+ True if the path should be ignored
80
+ """
81
+ # Get relative path
82
+ if os.path.isabs(path):
83
+ try:
84
+ rel_path = os.path.relpath(path, base_dir)
85
+ except ValueError:
86
+ rel_path = path
87
+ else:
88
+ rel_path = path
89
+
90
+ # Normalize path separators
91
+ rel_path = rel_path.replace(os.sep, '/')
92
+
93
+ # Check each pattern
94
+ ignored = False
95
+
96
+ for pattern in patterns:
97
+ is_negation, is_directory_only, clean_pattern = _pattern_to_regex_parts(pattern)
98
+
99
+ # Skip directory-only patterns for files
100
+ if is_directory_only and not is_directory:
101
+ continue
102
+
103
+ # Check if pattern matches
104
+ matches = False
105
+
106
+ # Handle patterns with /
107
+ if '/' in clean_pattern:
108
+ # Pattern with / matches from root
109
+ if clean_pattern.startswith('/'):
110
+ clean_pattern = clean_pattern[1:]
111
+ matches = fnmatch.fnmatch(rel_path, clean_pattern)
112
+ else:
113
+ # Pattern without / matches any path component
114
+ path_parts = rel_path.split('/')
115
+ for part in path_parts:
116
+ if fnmatch.fnmatch(part, clean_pattern):
117
+ matches = True
118
+ break
119
+ # Also check full path for ** patterns
120
+ if not matches and '**' in clean_pattern:
121
+ matches = fnmatch.fnmatch(rel_path, clean_pattern)
122
+
123
+ if matches:
124
+ ignored = not is_negation
125
+
126
+ return ignored
127
+
128
+
129
+ class FileAccessController:
130
+ """
131
+ Controls file access within a workspace.
132
+
133
+ Respects .gitignore patterns and can be configured with
134
+ additional ignore/allow patterns.
135
+
136
+ Attributes:
137
+ workspace_root: The root directory of the workspace
138
+ ignore_patterns: Patterns for files to ignore
139
+ protected_patterns: Patterns for files that are read-only
140
+ """
141
+
142
+ def __init__(
143
+ self,
144
+ workspace_root: str,
145
+ load_gitignore: bool = True,
146
+ additional_ignore_patterns: Optional[List[str]] = None,
147
+ protected_patterns: Optional[List[str]] = None
148
+ ):
149
+ """
150
+ Initialize the FileAccessController.
151
+
152
+ Args:
153
+ workspace_root: The root directory of the workspace
154
+ load_gitignore: Whether to load .gitignore patterns
155
+ additional_ignore_patterns: Extra patterns to ignore
156
+ protected_patterns: Patterns for read-only files
157
+ """
158
+ self.workspace_root = os.path.abspath(workspace_root)
159
+ self.ignore_patterns: List[str] = []
160
+ self.protected_patterns: List[str] = protected_patterns or []
161
+ self._ignored_cache: Set[str] = set()
162
+
163
+ # Load .gitignore patterns
164
+ if load_gitignore:
165
+ self.ignore_patterns.extend(load_gitignore_patterns(self.workspace_root))
166
+
167
+ # Add additional patterns
168
+ if additional_ignore_patterns:
169
+ self.ignore_patterns.extend(additional_ignore_patterns)
170
+
171
+ # Always ignore common patterns
172
+ default_ignores = [
173
+ '.git',
174
+ '.git/**',
175
+ '__pycache__',
176
+ '__pycache__/**',
177
+ '*.pyc',
178
+ '.DS_Store',
179
+ 'node_modules',
180
+ 'node_modules/**',
181
+ ]
182
+ for pattern in default_ignores:
183
+ if pattern not in self.ignore_patterns:
184
+ self.ignore_patterns.append(pattern)
185
+
186
+ def is_path_allowed(self, path: str) -> bool:
187
+ """
188
+ Check if a path is allowed for access.
189
+
190
+ Args:
191
+ path: The path to check
192
+
193
+ Returns:
194
+ True if the path is allowed
195
+ """
196
+ # Resolve to absolute path
197
+ if not os.path.isabs(path):
198
+ abs_path = os.path.abspath(os.path.join(self.workspace_root, path))
199
+ else:
200
+ abs_path = os.path.abspath(path)
201
+
202
+ # Check if within workspace
203
+ if not self._is_within_workspace(abs_path):
204
+ return False
205
+
206
+ # Check if ignored
207
+ is_dir = os.path.isdir(abs_path)
208
+ if should_ignore_path(abs_path, self.ignore_patterns, self.workspace_root, is_dir):
209
+ return False
210
+
211
+ return True
212
+
213
+ def is_write_protected(self, path: str) -> bool:
214
+ """
215
+ Check if a path is write-protected.
216
+
217
+ Args:
218
+ path: The path to check
219
+
220
+ Returns:
221
+ True if the path is write-protected
222
+ """
223
+ if not self.protected_patterns:
224
+ return False
225
+
226
+ # Get relative path
227
+ if os.path.isabs(path):
228
+ try:
229
+ rel_path = os.path.relpath(path, self.workspace_root)
230
+ except ValueError:
231
+ rel_path = path
232
+ else:
233
+ rel_path = path
234
+
235
+ rel_path = rel_path.replace(os.sep, '/')
236
+
237
+ for pattern in self.protected_patterns:
238
+ if fnmatch.fnmatch(rel_path, pattern):
239
+ return True
240
+
241
+ return False
242
+
243
+ def validate_access(self, path: str, write: bool = False) -> bool:
244
+ """
245
+ Validate if access to a path is allowed.
246
+
247
+ Args:
248
+ path: The path to validate
249
+ write: Whether write access is needed
250
+
251
+ Returns:
252
+ True if access is allowed
253
+ """
254
+ if not self.is_path_allowed(path):
255
+ return False
256
+
257
+ if write and self.is_write_protected(path):
258
+ return False
259
+
260
+ return True
261
+
262
+ def _is_within_workspace(self, abs_path: str) -> bool:
263
+ """
264
+ Check if an absolute path is within the workspace.
265
+
266
+ Args:
267
+ abs_path: The absolute path to check
268
+
269
+ Returns:
270
+ True if within workspace
271
+ """
272
+ # Normalize paths
273
+ workspace = os.path.normpath(self.workspace_root)
274
+ target = os.path.normpath(abs_path)
275
+
276
+ # Check if target starts with workspace path
277
+ return target.startswith(workspace + os.sep) or target == workspace
278
+
279
+ def get_relative_path(self, path: str) -> str:
280
+ """
281
+ Get the relative path from workspace root.
282
+
283
+ Args:
284
+ path: The path to convert
285
+
286
+ Returns:
287
+ Relative path string
288
+ """
289
+ if os.path.isabs(path):
290
+ try:
291
+ return os.path.relpath(path, self.workspace_root)
292
+ except ValueError:
293
+ return path
294
+ return path
295
+
296
+ def get_absolute_path(self, path: str) -> str:
297
+ """
298
+ Get the absolute path for a relative path.
299
+
300
+ Args:
301
+ path: The relative path
302
+
303
+ Returns:
304
+ Absolute path string
305
+ """
306
+ if os.path.isabs(path):
307
+ return path
308
+ return os.path.abspath(os.path.join(self.workspace_root, path))