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,514 @@
1
+ """
2
+ Cost Tracking System for PraisonAI CLI.
3
+
4
+ Inspired by Aider's cost tracking and Gemini CLI's stats command.
5
+ Provides real-time cost and token usage tracking.
6
+
7
+ Architecture:
8
+ - CostTracker: Tracks tokens and costs per session
9
+ - ModelPricing: Pricing data for different models
10
+ - UsageStats: Aggregated usage statistics
11
+ """
12
+
13
+ from dataclasses import dataclass, field
14
+ from typing import Any, Dict, List, Optional
15
+ from datetime import datetime
16
+ import logging
17
+ import json
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ # ============================================================================
23
+ # Model Pricing Data
24
+ # ============================================================================
25
+
26
+ @dataclass
27
+ class ModelPricing:
28
+ """
29
+ Pricing information for a model.
30
+
31
+ Prices are per 1M tokens (as commonly quoted).
32
+ """
33
+ model_name: str
34
+ input_price_per_1m: float # Price per 1M input tokens
35
+ output_price_per_1m: float # Price per 1M output tokens
36
+ context_window: int = 128000
37
+
38
+ def calculate_cost(self, input_tokens: int, output_tokens: int) -> float:
39
+ """Calculate cost for given token counts."""
40
+ input_cost = (input_tokens / 1_000_000) * self.input_price_per_1m
41
+ output_cost = (output_tokens / 1_000_000) * self.output_price_per_1m
42
+ return input_cost + output_cost
43
+
44
+
45
+ # Default pricing for common models (as of late 2024)
46
+ DEFAULT_PRICING: Dict[str, ModelPricing] = {
47
+ # OpenAI models
48
+ "gpt-4o": ModelPricing("gpt-4o", 2.50, 10.00, 128000),
49
+ "gpt-4o-mini": ModelPricing("gpt-4o-mini", 0.15, 0.60, 128000),
50
+ "gpt-4-turbo": ModelPricing("gpt-4-turbo", 10.00, 30.00, 128000),
51
+ "gpt-4": ModelPricing("gpt-4", 30.00, 60.00, 8192),
52
+ "gpt-3.5-turbo": ModelPricing("gpt-3.5-turbo", 0.50, 1.50, 16385),
53
+ "o1": ModelPricing("o1", 15.00, 60.00, 200000),
54
+ "o1-mini": ModelPricing("o1-mini", 3.00, 12.00, 128000),
55
+ "o1-preview": ModelPricing("o1-preview", 15.00, 60.00, 128000),
56
+ "o3-mini": ModelPricing("o3-mini", 1.10, 4.40, 200000),
57
+
58
+ # Anthropic models
59
+ "claude-3-5-sonnet-20241022": ModelPricing("claude-3-5-sonnet", 3.00, 15.00, 200000),
60
+ "claude-3-5-sonnet": ModelPricing("claude-3-5-sonnet", 3.00, 15.00, 200000),
61
+ "claude-3-opus": ModelPricing("claude-3-opus", 15.00, 75.00, 200000),
62
+ "claude-3-sonnet": ModelPricing("claude-3-sonnet", 3.00, 15.00, 200000),
63
+ "claude-3-haiku": ModelPricing("claude-3-haiku", 0.25, 1.25, 200000),
64
+
65
+ # Google models
66
+ "gemini-2.0-flash": ModelPricing("gemini-2.0-flash", 0.10, 0.40, 1000000),
67
+ "gemini-1.5-pro": ModelPricing("gemini-1.5-pro", 1.25, 5.00, 2000000),
68
+ "gemini-1.5-flash": ModelPricing("gemini-1.5-flash", 0.075, 0.30, 1000000),
69
+
70
+ # Default fallback
71
+ "default": ModelPricing("default", 1.00, 3.00, 128000),
72
+ }
73
+
74
+
75
+ def get_pricing(model_name: str) -> ModelPricing:
76
+ """Get pricing for a model, with fallback to default."""
77
+ # Try exact match
78
+ if model_name in DEFAULT_PRICING:
79
+ return DEFAULT_PRICING[model_name]
80
+
81
+ # Try partial match
82
+ model_lower = model_name.lower()
83
+ for key, pricing in DEFAULT_PRICING.items():
84
+ if key in model_lower or model_lower in key:
85
+ return pricing
86
+
87
+ # Return default
88
+ logger.debug(f"No pricing found for model '{model_name}', using default")
89
+ return DEFAULT_PRICING["default"]
90
+
91
+
92
+ # ============================================================================
93
+ # Usage Statistics
94
+ # ============================================================================
95
+
96
+ @dataclass
97
+ class TokenUsage:
98
+ """Token usage for a single request."""
99
+ input_tokens: int = 0
100
+ output_tokens: int = 0
101
+ total_tokens: int = 0
102
+ cached_tokens: int = 0
103
+
104
+ def __post_init__(self):
105
+ if self.total_tokens == 0:
106
+ self.total_tokens = self.input_tokens + self.output_tokens
107
+
108
+
109
+ @dataclass
110
+ class RequestStats:
111
+ """Statistics for a single request."""
112
+ timestamp: datetime
113
+ model: str
114
+ usage: TokenUsage
115
+ cost: float
116
+ duration_ms: float = 0.0
117
+ prompt_preview: str = ""
118
+
119
+ def to_dict(self) -> Dict[str, Any]:
120
+ """Convert to dictionary for serialization."""
121
+ return {
122
+ "timestamp": self.timestamp.isoformat(),
123
+ "model": self.model,
124
+ "input_tokens": self.usage.input_tokens,
125
+ "output_tokens": self.usage.output_tokens,
126
+ "total_tokens": self.usage.total_tokens,
127
+ "cached_tokens": self.usage.cached_tokens,
128
+ "cost": self.cost,
129
+ "duration_ms": self.duration_ms,
130
+ }
131
+
132
+
133
+ @dataclass
134
+ class SessionStats:
135
+ """Aggregated statistics for a session."""
136
+ session_id: str
137
+ start_time: datetime
138
+ end_time: Optional[datetime] = None
139
+
140
+ total_requests: int = 0
141
+ total_input_tokens: int = 0
142
+ total_output_tokens: int = 0
143
+ total_tokens: int = 0
144
+ total_cached_tokens: int = 0
145
+ total_cost: float = 0.0
146
+ total_duration_ms: float = 0.0
147
+
148
+ models_used: Dict[str, int] = field(default_factory=dict)
149
+
150
+ def add_request(self, stats: RequestStats) -> None:
151
+ """Add request statistics to session totals."""
152
+ self.total_requests += 1
153
+ self.total_input_tokens += stats.usage.input_tokens
154
+ self.total_output_tokens += stats.usage.output_tokens
155
+ self.total_tokens += stats.usage.total_tokens
156
+ self.total_cached_tokens += stats.usage.cached_tokens
157
+ self.total_cost += stats.cost
158
+ self.total_duration_ms += stats.duration_ms
159
+
160
+ # Track model usage
161
+ if stats.model not in self.models_used:
162
+ self.models_used[stats.model] = 0
163
+ self.models_used[stats.model] += 1
164
+
165
+ @property
166
+ def duration_seconds(self) -> float:
167
+ """Get session duration in seconds."""
168
+ end = self.end_time or datetime.now()
169
+ return (end - self.start_time).total_seconds()
170
+
171
+ @property
172
+ def avg_tokens_per_request(self) -> float:
173
+ """Get average tokens per request."""
174
+ if self.total_requests == 0:
175
+ return 0.0
176
+ return self.total_tokens / self.total_requests
177
+
178
+ @property
179
+ def avg_cost_per_request(self) -> float:
180
+ """Get average cost per request."""
181
+ if self.total_requests == 0:
182
+ return 0.0
183
+ return self.total_cost / self.total_requests
184
+
185
+ def to_dict(self) -> Dict[str, Any]:
186
+ """Convert to dictionary for serialization."""
187
+ return {
188
+ "session_id": self.session_id,
189
+ "start_time": self.start_time.isoformat(),
190
+ "end_time": self.end_time.isoformat() if self.end_time else None,
191
+ "duration_seconds": self.duration_seconds,
192
+ "total_requests": self.total_requests,
193
+ "total_input_tokens": self.total_input_tokens,
194
+ "total_output_tokens": self.total_output_tokens,
195
+ "total_tokens": self.total_tokens,
196
+ "total_cached_tokens": self.total_cached_tokens,
197
+ "total_cost": self.total_cost,
198
+ "avg_tokens_per_request": self.avg_tokens_per_request,
199
+ "avg_cost_per_request": self.avg_cost_per_request,
200
+ "models_used": self.models_used,
201
+ }
202
+
203
+
204
+ # ============================================================================
205
+ # Cost Tracker
206
+ # ============================================================================
207
+
208
+ class CostTracker:
209
+ """
210
+ Tracks costs and token usage for a session.
211
+
212
+ Features:
213
+ - Real-time cost tracking
214
+ - Per-model statistics
215
+ - Session history
216
+ - Export to JSON
217
+ """
218
+
219
+ def __init__(
220
+ self,
221
+ session_id: Optional[str] = None,
222
+ pricing: Optional[Dict[str, ModelPricing]] = None,
223
+ verbose: bool = False
224
+ ):
225
+ self.session_id = session_id or self._generate_session_id()
226
+ self.pricing = pricing or DEFAULT_PRICING
227
+ self.verbose = verbose
228
+
229
+ self.session_stats = SessionStats(
230
+ session_id=self.session_id,
231
+ start_time=datetime.now()
232
+ )
233
+ self.request_history: List[RequestStats] = []
234
+
235
+ def _generate_session_id(self) -> str:
236
+ """Generate a unique session ID."""
237
+ import uuid
238
+ return str(uuid.uuid4())[:8]
239
+
240
+ def track_request(
241
+ self,
242
+ model: str,
243
+ input_tokens: int,
244
+ output_tokens: int,
245
+ cached_tokens: int = 0,
246
+ duration_ms: float = 0.0,
247
+ prompt_preview: str = ""
248
+ ) -> RequestStats:
249
+ """
250
+ Track a single request.
251
+
252
+ Returns:
253
+ RequestStats for the tracked request
254
+ """
255
+ usage = TokenUsage(
256
+ input_tokens=input_tokens,
257
+ output_tokens=output_tokens,
258
+ cached_tokens=cached_tokens
259
+ )
260
+
261
+ # Calculate cost
262
+ pricing = get_pricing(model)
263
+ cost = pricing.calculate_cost(input_tokens, output_tokens)
264
+
265
+ # Create request stats
266
+ stats = RequestStats(
267
+ timestamp=datetime.now(),
268
+ model=model,
269
+ usage=usage,
270
+ cost=cost,
271
+ duration_ms=duration_ms,
272
+ prompt_preview=prompt_preview[:100] if prompt_preview else ""
273
+ )
274
+
275
+ # Update session stats
276
+ self.session_stats.add_request(stats)
277
+ self.request_history.append(stats)
278
+
279
+ if self.verbose:
280
+ logger.info(
281
+ f"Request tracked: {model} - "
282
+ f"{usage.total_tokens} tokens, ${cost:.4f}"
283
+ )
284
+
285
+ return stats
286
+
287
+ def track_from_response(
288
+ self,
289
+ model: str,
290
+ response: Any,
291
+ duration_ms: float = 0.0
292
+ ) -> Optional[RequestStats]:
293
+ """
294
+ Track request from LLM response object.
295
+
296
+ Handles various response formats from different providers.
297
+ """
298
+ try:
299
+ # Try to extract usage from response
300
+ usage = None
301
+
302
+ # OpenAI format
303
+ if hasattr(response, 'usage'):
304
+ usage = response.usage
305
+ input_tokens = getattr(usage, 'prompt_tokens', 0)
306
+ output_tokens = getattr(usage, 'completion_tokens', 0)
307
+ cached_tokens = getattr(usage, 'cached_tokens', 0)
308
+ # Dict format
309
+ elif isinstance(response, dict) and 'usage' in response:
310
+ usage = response['usage']
311
+ input_tokens = usage.get('prompt_tokens', 0)
312
+ output_tokens = usage.get('completion_tokens', 0)
313
+ cached_tokens = usage.get('cached_tokens', 0)
314
+ else:
315
+ logger.debug("Could not extract usage from response")
316
+ return None
317
+
318
+ return self.track_request(
319
+ model=model,
320
+ input_tokens=input_tokens,
321
+ output_tokens=output_tokens,
322
+ cached_tokens=cached_tokens,
323
+ duration_ms=duration_ms
324
+ )
325
+ except Exception as e:
326
+ logger.error(f"Error tracking response: {e}")
327
+ return None
328
+
329
+ def get_current_stats(self) -> SessionStats:
330
+ """Get current session statistics."""
331
+ return self.session_stats
332
+
333
+ def get_total_cost(self) -> float:
334
+ """Get total cost for the session."""
335
+ return self.session_stats.total_cost
336
+
337
+ def get_total_tokens(self) -> int:
338
+ """Get total tokens for the session."""
339
+ return self.session_stats.total_tokens
340
+
341
+ def get_request_count(self) -> int:
342
+ """Get total request count."""
343
+ return self.session_stats.total_requests
344
+
345
+ def format_summary(self) -> str:
346
+ """Format a summary of the session statistics."""
347
+ stats = self.session_stats
348
+
349
+ lines = [
350
+ f"Session: {stats.session_id}",
351
+ f"Duration: {stats.duration_seconds:.1f}s",
352
+ f"Requests: {stats.total_requests}",
353
+ "",
354
+ "Tokens:",
355
+ f" Input: {stats.total_input_tokens:,}",
356
+ f" Output: {stats.total_output_tokens:,}",
357
+ f" Total: {stats.total_tokens:,}",
358
+ f" Cached: {stats.total_cached_tokens:,}",
359
+ "",
360
+ f"Cost: ${stats.total_cost:.4f}",
361
+ f"Avg per request: ${stats.avg_cost_per_request:.4f}",
362
+ ]
363
+
364
+ if stats.models_used:
365
+ lines.append("")
366
+ lines.append("Models used:")
367
+ for model, count in stats.models_used.items():
368
+ lines.append(f" {model}: {count} requests")
369
+
370
+ return "\n".join(lines)
371
+
372
+ def print_summary(self) -> None:
373
+ """Print a formatted summary to console."""
374
+ from rich.console import Console
375
+ from rich.panel import Panel
376
+ from rich.table import Table
377
+
378
+ console = Console()
379
+ stats = self.session_stats
380
+
381
+ # Create summary table
382
+ table = Table(show_header=True, header_style="bold cyan")
383
+ table.add_column("Metric")
384
+ table.add_column("Value", justify="right")
385
+
386
+ table.add_row("Session ID", stats.session_id)
387
+ table.add_row("Duration", f"{stats.duration_seconds:.1f}s")
388
+ table.add_row("Requests", str(stats.total_requests))
389
+ table.add_row("", "")
390
+ table.add_row("Input Tokens", f"{stats.total_input_tokens:,}")
391
+ table.add_row("Output Tokens", f"{stats.total_output_tokens:,}")
392
+ table.add_row("Total Tokens", f"{stats.total_tokens:,}")
393
+ table.add_row("Cached Tokens", f"{stats.total_cached_tokens:,}")
394
+ table.add_row("", "")
395
+ table.add_row("Total Cost", f"${stats.total_cost:.4f}")
396
+ table.add_row("Avg Cost/Request", f"${stats.avg_cost_per_request:.4f}")
397
+
398
+ console.print(Panel(table, title="💰 Session Statistics", border_style="green"))
399
+
400
+ # Print model breakdown if multiple models used
401
+ if len(stats.models_used) > 1:
402
+ model_table = Table(show_header=True, header_style="bold cyan")
403
+ model_table.add_column("Model")
404
+ model_table.add_column("Requests", justify="right")
405
+
406
+ for model, count in stats.models_used.items():
407
+ model_table.add_row(model, str(count))
408
+
409
+ console.print(Panel(model_table, title="Models Used", border_style="blue"))
410
+
411
+ def export_json(self, filepath: Optional[str] = None) -> str:
412
+ """
413
+ Export session data to JSON.
414
+
415
+ Args:
416
+ filepath: Optional file path to write to
417
+
418
+ Returns:
419
+ JSON string
420
+ """
421
+ data = {
422
+ "session": self.session_stats.to_dict(),
423
+ "requests": [r.to_dict() for r in self.request_history]
424
+ }
425
+
426
+ json_str = json.dumps(data, indent=2)
427
+
428
+ if filepath:
429
+ with open(filepath, 'w') as f:
430
+ f.write(json_str)
431
+ logger.info(f"Exported session data to {filepath}")
432
+
433
+ return json_str
434
+
435
+ def end_session(self) -> SessionStats:
436
+ """Mark the session as ended."""
437
+ self.session_stats.end_time = datetime.now()
438
+ return self.session_stats
439
+
440
+
441
+ # ============================================================================
442
+ # CLI Integration Handler
443
+ # ============================================================================
444
+
445
+ class CostTrackerHandler:
446
+ """
447
+ Handler for integrating cost tracking with PraisonAI CLI.
448
+ """
449
+
450
+ def __init__(self, verbose: bool = False):
451
+ self.verbose = verbose
452
+ self._tracker: Optional[CostTracker] = None
453
+
454
+ @property
455
+ def feature_name(self) -> str:
456
+ return "cost_tracker"
457
+
458
+ def initialize(self, session_id: Optional[str] = None) -> CostTracker:
459
+ """Initialize the cost tracker."""
460
+ self._tracker = CostTracker(
461
+ session_id=session_id,
462
+ verbose=self.verbose
463
+ )
464
+
465
+ if self.verbose:
466
+ from rich import print as rprint
467
+ rprint(f"[cyan]Cost tracking enabled for session: {self._tracker.session_id}[/cyan]")
468
+
469
+ return self._tracker
470
+
471
+ def get_tracker(self) -> Optional[CostTracker]:
472
+ """Get the current cost tracker."""
473
+ return self._tracker
474
+
475
+ def track_request(
476
+ self,
477
+ model: str,
478
+ input_tokens: int,
479
+ output_tokens: int,
480
+ **kwargs
481
+ ) -> Optional[RequestStats]:
482
+ """Track a request."""
483
+ if not self._tracker:
484
+ self._tracker = self.initialize()
485
+
486
+ return self._tracker.track_request(
487
+ model=model,
488
+ input_tokens=input_tokens,
489
+ output_tokens=output_tokens,
490
+ **kwargs
491
+ )
492
+
493
+ def get_summary(self) -> Dict[str, Any]:
494
+ """Get session summary as dict."""
495
+ if not self._tracker:
496
+ return {}
497
+ return self._tracker.session_stats.to_dict()
498
+
499
+ def print_summary(self) -> None:
500
+ """Print session summary."""
501
+ if self._tracker:
502
+ self._tracker.print_summary()
503
+
504
+ def get_cost(self) -> float:
505
+ """Get total cost."""
506
+ if self._tracker:
507
+ return self._tracker.get_total_cost()
508
+ return 0.0
509
+
510
+ def get_tokens(self) -> int:
511
+ """Get total tokens."""
512
+ if self._tracker:
513
+ return self._tracker.get_total_tokens()
514
+ return 0