aria-code 0.52.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 (478) hide show
  1. aria_code/__init__.py +42 -0
  2. aria_code/_version.py +20 -0
  3. aria_code/adk_apps/__init__.py +1 -0
  4. aria_code/adk_apps/aria_code_review/__init__.py +1 -0
  5. aria_code/adk_apps/aria_code_review/agent.py +32 -0
  6. aria_code/adk_apps/aria_research/__init__.py +1 -0
  7. aria_code/adk_apps/aria_research/agent.py +31 -0
  8. aria_code/agents/__init__.py +34 -0
  9. aria_code/agents/base.py +318 -0
  10. aria_code/agents/code_review.py +200 -0
  11. aria_code/agents/deep/__init__.py +37 -0
  12. aria_code/agents/deep/calibration_loop.py +144 -0
  13. aria_code/agents/deep/critic.py +125 -0
  14. aria_code/agents/deep/deepen.py +193 -0
  15. aria_code/agents/deep/models.py +149 -0
  16. aria_code/agents/deep/pipeline.py +164 -0
  17. aria_code/agents/deep/quant_fusion.py +192 -0
  18. aria_code/agents/deep/themes.py +95 -0
  19. aria_code/agents/deep/tiers.py +106 -0
  20. aria_code/agents/engineering/__init__.py +14 -0
  21. aria_code/agents/engineering/coder.py +222 -0
  22. aria_code/agents/engineering/tester.py +145 -0
  23. aria_code/agents/financial/__init__.py +21 -0
  24. aria_code/agents/financial/cashflow_burn.py +119 -0
  25. aria_code/agents/financial/catalyst.py +279 -0
  26. aria_code/agents/financial/corporate_finance.py +175 -0
  27. aria_code/agents/financial/debate.py +224 -0
  28. aria_code/agents/financial/earnings.py +303 -0
  29. aria_code/agents/financial/fundamental.py +191 -0
  30. aria_code/agents/financial/macro.py +99 -0
  31. aria_code/agents/financial/news.py +207 -0
  32. aria_code/agents/financial/risk.py +165 -0
  33. aria_code/agents/financial/sector.py +279 -0
  34. aria_code/agents/financial/strategist.py +121 -0
  35. aria_code/agents/financial/stripe_revenue.py +127 -0
  36. aria_code/agents/financial/synthesis.py +274 -0
  37. aria_code/agents/financial/technical.py +258 -0
  38. aria_code/agents/orchestrator.py +44 -0
  39. aria_code/agents/portfolio_agent.py +363 -0
  40. aria_code/agents/realty/__init__.py +62 -0
  41. aria_code/agents/realty/asset_diagnosis.py +152 -0
  42. aria_code/agents/realty/business_match.py +166 -0
  43. aria_code/agents/realty/cashflow_verify.py +209 -0
  44. aria_code/agents/realty/contract_rules.py +210 -0
  45. aria_code/agents/realty/energy_anomaly.py +189 -0
  46. aria_code/agents/realty/exit_settlement.py +208 -0
  47. aria_code/agents/realty/fulfillment_risk.py +206 -0
  48. aria_code/agents/realty/ops_optimize.py +160 -0
  49. aria_code/agents/realty/revenue_share.py +215 -0
  50. aria_code/agents/registry.py +158 -0
  51. aria_code/agents/signal_scheme.py +111 -0
  52. aria_code/agents/sports/__init__.py +0 -0
  53. aria_code/agents/sports/football_agent.py +197 -0
  54. aria_code/agents/supervisor.py +113 -0
  55. aria_code/agents/team.py +492 -0
  56. aria_code/agents/warehouse/__init__.py +25 -0
  57. aria_code/agents/warehouse/contracts.py +26 -0
  58. aria_code/agents/warehouse/dashboard.py +155 -0
  59. aria_code/agents/warehouse/fulfillment_leadtime.py +95 -0
  60. aria_code/agents/warehouse/inbound_exceptions.py +25 -0
  61. aria_code/agents/warehouse/inventory_health.py +24 -0
  62. aria_code/agents/warehouse/local_run.py +71 -0
  63. aria_code/agents/warehouse/local_snapshot.py +132 -0
  64. aria_code/agents/warehouse/logistics_cost.py +84 -0
  65. aria_code/agents/warehouse/logistics_sync.py +21 -0
  66. aria_code/agents/warehouse/workflow.py +31 -0
  67. aria_code/aliyun_data_client.py +4 -0
  68. aria_code/apps/README.md +12 -0
  69. aria_code/apps/__init__.py +2 -0
  70. aria_code/apps/channels/README.md +15 -0
  71. aria_code/apps/channels/__init__.py +17 -0
  72. aria_code/apps/channels/intake.py +92 -0
  73. aria_code/apps/channels/registry.py +95 -0
  74. aria_code/apps/channels/tradingview.py +94 -0
  75. aria_code/apps/cli/README.md +13 -0
  76. aria_code/apps/cli/__init__.py +2 -0
  77. aria_code/apps/cli/_optional.py +120 -0
  78. aria_code/apps/cli/bootstrap.py +145 -0
  79. aria_code/apps/cli/broker_render.py +206 -0
  80. aria_code/apps/cli/codegen_paths.py +29 -0
  81. aria_code/apps/cli/commands/__init__.py +16 -0
  82. aria_code/apps/cli/commands/_business_render.py +78 -0
  83. aria_code/apps/cli/commands/_ui.py +71 -0
  84. aria_code/apps/cli/commands/analysis_cmds.py +369 -0
  85. aria_code/apps/cli/commands/ashare_prediction_cmds.py +327 -0
  86. aria_code/apps/cli/commands/auth_cmds.py +239 -0
  87. aria_code/apps/cli/commands/backtest_cmds.py +2543 -0
  88. aria_code/apps/cli/commands/broker_cmds.py +1234 -0
  89. aria_code/apps/cli/commands/business_workflow_cmds.py +319 -0
  90. aria_code/apps/cli/commands/canvas_cmds.py +77 -0
  91. aria_code/apps/cli/commands/catalog.py +87 -0
  92. aria_code/apps/cli/commands/core_cmds.py +2520 -0
  93. aria_code/apps/cli/commands/data_cmds.py +457 -0
  94. aria_code/apps/cli/commands/diagnostic_cmds.py +230 -0
  95. aria_code/apps/cli/commands/diagnostic_ops_cmds.py +813 -0
  96. aria_code/apps/cli/commands/file_cmds.py +312 -0
  97. aria_code/apps/cli/commands/finance_service_cmds.py +60 -0
  98. aria_code/apps/cli/commands/fx_commodity_cmds.py +123 -0
  99. aria_code/apps/cli/commands/market.py +434 -0
  100. aria_code/apps/cli/commands/market_cmds.py +1381 -0
  101. aria_code/apps/cli/commands/market_context.py +425 -0
  102. aria_code/apps/cli/commands/market_render.py +7 -0
  103. aria_code/apps/cli/commands/model_cmds.py +2153 -0
  104. aria_code/apps/cli/commands/ops_cmds.py +721 -0
  105. aria_code/apps/cli/commands/orchestrator_cmds.py +179 -0
  106. aria_code/apps/cli/commands/pdf_export_cmds.py +125 -0
  107. aria_code/apps/cli/commands/portfolio_cmds.py +1278 -0
  108. aria_code/apps/cli/commands/report.py +437 -0
  109. aria_code/apps/cli/commands/scaffold_templates.py +617 -0
  110. aria_code/apps/cli/commands/session_cmds.py +236 -0
  111. aria_code/apps/cli/commands/session_ux_cmds.py +359 -0
  112. aria_code/apps/cli/commands/team.py +669 -0
  113. aria_code/apps/cli/commands/team_render.py +8 -0
  114. aria_code/apps/cli/commands/ui_cmds.py +401 -0
  115. aria_code/apps/cli/commands/warehouse_cmds.py +79 -0
  116. aria_code/apps/cli/commands/workflow_cmds.py +480 -0
  117. aria_code/apps/cli/commands/workspace_cmds.py +1494 -0
  118. aria_code/apps/cli/config_paths.py +73 -0
  119. aria_code/apps/cli/config_store.py +78 -0
  120. aria_code/apps/cli/constants.py +88 -0
  121. aria_code/apps/cli/context.py +34 -0
  122. aria_code/apps/cli/deterministic.py +191 -0
  123. aria_code/apps/cli/direct.py +48 -0
  124. aria_code/apps/cli/finance_service_catalog.py +111 -0
  125. aria_code/apps/cli/football_reports.py +231 -0
  126. aria_code/apps/cli/github_app_auth.py +135 -0
  127. aria_code/apps/cli/google_login.py +196 -0
  128. aria_code/apps/cli/handlers/__init__.py +11 -0
  129. aria_code/apps/cli/handlers/broker_handlers.py +122 -0
  130. aria_code/apps/cli/handlers/chart_handlers.py +1309 -0
  131. aria_code/apps/cli/handlers/market_handlers.py +2513 -0
  132. aria_code/apps/cli/handlers/realty_handlers.py +114 -0
  133. aria_code/apps/cli/handlers/sports_handlers.py +22 -0
  134. aria_code/apps/cli/handlers/strategy_advice.py +82 -0
  135. aria_code/apps/cli/helpers.py +302 -0
  136. aria_code/apps/cli/hooks.py +180 -0
  137. aria_code/apps/cli/i18n.py +297 -0
  138. aria_code/apps/cli/intent.py +136 -0
  139. aria_code/apps/cli/intent_router.py +276 -0
  140. aria_code/apps/cli/intent_signals.py +344 -0
  141. aria_code/apps/cli/lifecycle_hooks.py +48 -0
  142. aria_code/apps/cli/local_tool_schemas.py +536 -0
  143. aria_code/apps/cli/main.py +29 -0
  144. aria_code/apps/cli/market_metadata.py +135 -0
  145. aria_code/apps/cli/market_universe.py +487 -0
  146. aria_code/apps/cli/message_processing.py +310 -0
  147. aria_code/apps/cli/model_catalog.py +620 -0
  148. aria_code/apps/cli/pdf_report.py +977 -0
  149. aria_code/apps/cli/plan_mode.py +139 -0
  150. aria_code/apps/cli/plotly_html.py +15 -0
  151. aria_code/apps/cli/prediction_feedback.py +202 -0
  152. aria_code/apps/cli/preflight.py +502 -0
  153. aria_code/apps/cli/project_aria.py +60 -0
  154. aria_code/apps/cli/prompt_assembly.py +59 -0
  155. aria_code/apps/cli/prompts/__init__.py +0 -0
  156. aria_code/apps/cli/prompts/coding.py +53 -0
  157. aria_code/apps/cli/prompts/select.py +142 -0
  158. aria_code/apps/cli/prompts/system_prompts.py +552 -0
  159. aria_code/apps/cli/prompts/ui.py +436 -0
  160. aria_code/apps/cli/provider_endpoints.py +237 -0
  161. aria_code/apps/cli/provider_keys.py +73 -0
  162. aria_code/apps/cli/providers/__init__.py +1 -0
  163. aria_code/apps/cli/providers/base.py +521 -0
  164. aria_code/apps/cli/providers/chat_routing.py +128 -0
  165. aria_code/apps/cli/providers/collaboration.py +98 -0
  166. aria_code/apps/cli/providers/llm/__init__.py +1 -0
  167. aria_code/apps/cli/providers/llm/offline_responses.py +68 -0
  168. aria_code/apps/cli/providers/llm/ollama_stream.py +1594 -0
  169. aria_code/apps/cli/providers/llm/response_cache.py +60 -0
  170. aria_code/apps/cli/providers/llm/sse_stream.py +257 -0
  171. aria_code/apps/cli/providers/runtime_bridge.py +353 -0
  172. aria_code/apps/cli/providers/vertexai_stream.py +316 -0
  173. aria_code/apps/cli/runtime_consumer.py +657 -0
  174. aria_code/apps/cli/sandbox.py +32 -0
  175. aria_code/apps/cli/session_export.py +120 -0
  176. aria_code/apps/cli/session_jsonl.py +208 -0
  177. aria_code/apps/cli/session_store.py +112 -0
  178. aria_code/apps/cli/skills_catalog.py +273 -0
  179. aria_code/apps/cli/todo_tracker.py +190 -0
  180. aria_code/apps/cli/tool_executor.py +823 -0
  181. aria_code/apps/cli/tool_registry.py +34 -0
  182. aria_code/apps/cli/tool_scope.py +106 -0
  183. aria_code/apps/cli/tools/__init__.py +40 -0
  184. aria_code/apps/cli/tools/context.py +46 -0
  185. aria_code/apps/cli/tools/file_tools.py +180 -0
  186. aria_code/apps/cli/tools/market_tools.py +620 -0
  187. aria_code/apps/cli/tools/notebook_tools.py +111 -0
  188. aria_code/apps/cli/tools/system_tools.py +726 -0
  189. aria_code/apps/cli/tools/write_tools.py +845 -0
  190. aria_code/apps/cli/tradingview_bridge.py +529 -0
  191. aria_code/apps/cli/turn_planning.py +137 -0
  192. aria_code/apps/cli/update_check.py +153 -0
  193. aria_code/apps/cli/utils/__init__.py +0 -0
  194. aria_code/apps/cli/utils/market_detect.py +1597 -0
  195. aria_code/apps/daemon/README.md +14 -0
  196. aria_code/apps/vscode/README.md +115 -0
  197. aria_code/apps/vscode/package.json +70 -0
  198. aria_code/aria_cli.py +6743 -0
  199. aria_code/aria_daemon.py +1398 -0
  200. aria_code/aria_feishu_bot.py +1423 -0
  201. aria_code/aria_mcp_server.py +7 -0
  202. aria_code/aria_relay_client.py +4 -0
  203. aria_code/aria_relay_server.py +490 -0
  204. aria_code/aria_telegram_bot.py +202 -0
  205. aria_code/ariarc.py +385 -0
  206. aria_code/artifacts.py +581 -0
  207. aria_code/backtest_engine.py +4 -0
  208. aria_code/backtest_report.py +4 -0
  209. aria_code/brokers/__init__.py +78 -0
  210. aria_code/brokers/automation.py +193 -0
  211. aria_code/brokers/base.py +231 -0
  212. aria_code/brokers/capabilities.py +264 -0
  213. aria_code/brokers/cn/__init__.py +10 -0
  214. aria_code/brokers/cn/easytrader_broker.py +193 -0
  215. aria_code/brokers/cn/futu_broker.py +194 -0
  216. aria_code/brokers/cn/longbridge_broker.py +190 -0
  217. aria_code/brokers/cn/tiger_broker.py +196 -0
  218. aria_code/brokers/cn/xtquant_broker.py +225 -0
  219. aria_code/brokers/config.py +414 -0
  220. aria_code/brokers/intl/__init__.py +5 -0
  221. aria_code/brokers/intl/alpaca_broker.py +183 -0
  222. aria_code/brokers/intl/ibkr_broker.py +215 -0
  223. aria_code/brokers/intl/trading212_broker.py +183 -0
  224. aria_code/brokers/intl/webull_broker.py +156 -0
  225. aria_code/brokers/paper_broker.py +266 -0
  226. aria_code/brokers/planning.py +296 -0
  227. aria_code/brokers/registry.py +213 -0
  228. aria_code/brokers/trading.py +267 -0
  229. aria_code/canva_client.py +4 -0
  230. aria_code/change_store.py +127 -0
  231. aria_code/clients/__init__.py +0 -0
  232. aria_code/clients/aliyun_data_client.py +660 -0
  233. aria_code/clients/aria_relay_client.py +182 -0
  234. aria_code/clients/canva_client.py +284 -0
  235. aria_code/clients/figma_client.py +100 -0
  236. aria_code/clients/football_data_client.py +1671 -0
  237. aria_code/clients/kling_video_client.py +182 -0
  238. aria_code/clients/market_data_client.py +1976 -0
  239. aria_code/clients/openai_image_client.py +242 -0
  240. aria_code/clients/runway_video_client.py +157 -0
  241. aria_code/clients/warehouse_erp_client.py +105 -0
  242. aria_code/cloud_memory.py +181 -0
  243. aria_code/command_safety.py +19 -0
  244. aria_code/computer_use_tools.py +4 -0
  245. aria_code/dashboard_generator.py +579 -0
  246. aria_code/data_analysis_tools.py +4 -0
  247. aria_code/data_cleaner.py +483 -0
  248. aria_code/data_service.py +513 -0
  249. aria_code/datasources/__init__.py +23 -0
  250. aria_code/datasources/base.py +166 -0
  251. aria_code/datasources/router.py +253 -0
  252. aria_code/datasources/sources/__init__.py +15 -0
  253. aria_code/datasources/sources/akshare_source.py +269 -0
  254. aria_code/datasources/sources/alpha_vantage_source.py +203 -0
  255. aria_code/datasources/sources/edgar_source.py +219 -0
  256. aria_code/datasources/sources/finnhub_source.py +198 -0
  257. aria_code/datasources/sources/fred_source.py +223 -0
  258. aria_code/datasources/sources/tushare_source.py +143 -0
  259. aria_code/datasources/sources/web_scraper_source.py +278 -0
  260. aria_code/datasources/sources/world_bank_source.py +206 -0
  261. aria_code/datasources/sources/yfinance_source.py +152 -0
  262. aria_code/demo_player.py +207 -0
  263. aria_code/doctor.py +813 -0
  264. aria_code/domain/__init__.py +0 -0
  265. aria_code/domain/backtest_engine.py +354 -0
  266. aria_code/domain/backtest_report.py +472 -0
  267. aria_code/domain/finance_formulas.py +389 -0
  268. aria_code/domain/portfolio_ledger.py +307 -0
  269. aria_code/domain/strategy_vault.py +421 -0
  270. aria_code/evals/__init__.py +37 -0
  271. aria_code/evals/harness.py +598 -0
  272. aria_code/evals/runner.py +238 -0
  273. aria_code/external_agent_runner.py +115 -0
  274. aria_code/figma_client.py +4 -0
  275. aria_code/file_analysis_tools.py +4 -0
  276. aria_code/finance_formulas.py +4 -0
  277. aria_code/football_data_client.py +4 -0
  278. aria_code/image_gen_tools.py +4 -0
  279. aria_code/intent_classifier.py +386 -0
  280. aria_code/kling_video_client.py +4 -0
  281. aria_code/licensing.py +151 -0
  282. aria_code/local_finance_tools.py +4 -0
  283. aria_code/local_image_provider.py +4 -0
  284. aria_code/local_llm_provider.py +4 -0
  285. aria_code/macro_tools.py +4 -0
  286. aria_code/markdown_pdf.py +289 -0
  287. aria_code/market_data_client.py +4 -0
  288. aria_code/mcp_client.py +639 -0
  289. aria_code/memory_manager.py +246 -0
  290. aria_code/model_capability.py +486 -0
  291. aria_code/notification_tools.py +4 -0
  292. aria_code/openai_image_client.py +4 -0
  293. aria_code/packages/__init__.py +23 -0
  294. aria_code/packages/adk_bridge/__init__.py +11 -0
  295. aria_code/packages/adk_bridge/code_review_tools.py +49 -0
  296. aria_code/packages/adk_bridge/market_tools.py +125 -0
  297. aria_code/packages/aria_agents/__init__.py +5 -0
  298. aria_code/packages/aria_agents/manifest.py +78 -0
  299. aria_code/packages/aria_core/__init__.py +34 -0
  300. aria_code/packages/aria_core/architecture.py +269 -0
  301. aria_code/packages/aria_core/export.py +147 -0
  302. aria_code/packages/aria_core/manifest.py +65 -0
  303. aria_code/packages/aria_core/paths.py +57 -0
  304. aria_code/packages/aria_core/secure_file.py +68 -0
  305. aria_code/packages/aria_infra/__init__.py +15 -0
  306. aria_code/packages/aria_infra/arthera.py +52 -0
  307. aria_code/packages/aria_infra/doctor.py +246 -0
  308. aria_code/packages/aria_infra/product.py +40 -0
  309. aria_code/packages/aria_mcp/__init__.py +25 -0
  310. aria_code/packages/aria_mcp/bridge.py +201 -0
  311. aria_code/packages/aria_mcp/circuit.py +82 -0
  312. aria_code/packages/aria_mcp/config.py +111 -0
  313. aria_code/packages/aria_mcp/installer.py +95 -0
  314. aria_code/packages/aria_mcp/server.py +1550 -0
  315. aria_code/packages/aria_mcp/tools.py +81 -0
  316. aria_code/packages/aria_sdk/__init__.py +19 -0
  317. aria_code/packages/aria_sdk/client.py +396 -0
  318. aria_code/packages/aria_sdk/providers.py +70 -0
  319. aria_code/packages/aria_sdk/streaming.py +79 -0
  320. aria_code/packages/aria_sdk/types.py +86 -0
  321. aria_code/packages/aria_services/__init__.py +92 -0
  322. aria_code/packages/aria_services/cache.py +60 -0
  323. aria_code/packages/aria_services/context.py +356 -0
  324. aria_code/packages/aria_services/context_checkpoints.py +157 -0
  325. aria_code/packages/aria_services/data.py +11 -0
  326. aria_code/packages/aria_services/provider_health.py +268 -0
  327. aria_code/packages/aria_services/references.py +247 -0
  328. aria_code/packages/aria_services/registry.py +249 -0
  329. aria_code/packages/aria_services/research_protocol.py +57 -0
  330. aria_code/packages/aria_services/research_quality.py +186 -0
  331. aria_code/packages/aria_services/settings.py +157 -0
  332. aria_code/packages/aria_services/usage.py +151 -0
  333. aria_code/packages/aria_skills/__init__.py +46 -0
  334. aria_code/packages/aria_skills/catalog.py +149 -0
  335. aria_code/packages/aria_skills/loader.py +455 -0
  336. aria_code/packages/aria_skills/registry.py +84 -0
  337. aria_code/packages/aria_tools/__init__.py +5 -0
  338. aria_code/packages/aria_tools/financial/__init__.py +8 -0
  339. aria_code/packages/aria_tools/financial/_paths.py +130 -0
  340. aria_code/packages/aria_tools/financial/compliance.py +31 -0
  341. aria_code/packages/aria_tools/financial/factors.py +33 -0
  342. aria_code/packages/aria_tools/financial/risk_tools.py +33 -0
  343. aria_code/packages/aria_tools/financial/strategy.py +31 -0
  344. aria_code/packages/aria_tools/registry.py +128 -0
  345. aria_code/packages/quant_engine/__init__.py +34 -0
  346. aria_code/packages/quant_engine/_namespace.py +62 -0
  347. aria_code/packages/quant_engine/backtest/__init__.py +14 -0
  348. aria_code/packages/quant_engine/backtest/core.py +53 -0
  349. aria_code/packages/quant_engine/backtest/engine.py +96 -0
  350. aria_code/packages/quant_engine/portfolio/__init__.py +13 -0
  351. aria_code/packages/quant_engine/portfolio/optimizer.py +85 -0
  352. aria_code/packages/quant_engine/sports/__init__.py +80 -0
  353. aria_code/packages/quant_engine/sports/calibrator.py +354 -0
  354. aria_code/packages/quant_engine/sports/dixon_coles.py +234 -0
  355. aria_code/packages/quant_engine/sports/elo.py +300 -0
  356. aria_code/packages/quant_engine/sports/form.py +188 -0
  357. aria_code/packages/quant_engine/sports/h2h.py +195 -0
  358. aria_code/packages/quant_engine/sports/ml_model.py +377 -0
  359. aria_code/packages/quant_engine/sports/predictor.py +312 -0
  360. aria_code/packages/quant_engine/sports/tracker.py +665 -0
  361. aria_code/packages/quant_engine/stochastic/__init__.py +35 -0
  362. aria_code/packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
  363. aria_code/packages/quant_engine/stochastic/ito_calculus.py +477 -0
  364. aria_code/packages/quant_engine/stochastic/kelly_criterion.py +181 -0
  365. aria_code/packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
  366. aria_code/packages/quant_engine/stochastic/options_pricing.py +576 -0
  367. aria_code/packages/quant_engine/stochastic/stochastic_processes.py +90 -0
  368. aria_code/packs/__init__.py +85 -0
  369. aria_code/packs/base.py +168 -0
  370. aria_code/packs/finance/__init__.py +250 -0
  371. aria_code/packs/logistics/__init__.py +169 -0
  372. aria_code/packs/payments/__init__.py +139 -0
  373. aria_code/packs/realty/__init__.py +190 -0
  374. aria_code/packs/registry.py +202 -0
  375. aria_code/packs/rules.py +90 -0
  376. aria_code/plan_utils.py +194 -0
  377. aria_code/plugin_loader.py +328 -0
  378. aria_code/portfolio_ledger.py +4 -0
  379. aria_code/preview_server.py +449 -0
  380. aria_code/privacy/__init__.py +5 -0
  381. aria_code/privacy/feedback.py +123 -0
  382. aria_code/project_review.py +278 -0
  383. aria_code/project_review_api.py +235 -0
  384. aria_code/project_review_server.py +18 -0
  385. aria_code/project_tools.py +4 -0
  386. aria_code/providers/__init__.py +30 -0
  387. aria_code/providers/llm/__init__.py +19 -0
  388. aria_code/providers/llm/anthropic.py +184 -0
  389. aria_code/providers/llm/autoconfig.py +213 -0
  390. aria_code/providers/llm/base.py +139 -0
  391. aria_code/providers/llm/custom.py +158 -0
  392. aria_code/providers/llm/ollama.py +129 -0
  393. aria_code/providers/llm/openai_compat.py +313 -0
  394. aria_code/providers/llm/registry.py +444 -0
  395. aria_code/providers/local_image_provider.py +202 -0
  396. aria_code/providers/local_llm_provider.py +589 -0
  397. aria_code/realty_data_tools.py +4 -0
  398. aria_code/report_exporters.py +150 -0
  399. aria_code/report_generator.py +1764 -0
  400. aria_code/runtime/__init__.py +169 -0
  401. aria_code/runtime/acceptance.py +481 -0
  402. aria_code/runtime/agent_loop.py +1520 -0
  403. aria_code/runtime/approval.py +57 -0
  404. aria_code/runtime/budget.py +232 -0
  405. aria_code/runtime/checkpoints.py +432 -0
  406. aria_code/runtime/crash_report.py +174 -0
  407. aria_code/runtime/events.py +113 -0
  408. aria_code/runtime/gateway.py +150 -0
  409. aria_code/runtime/lsp.py +484 -0
  410. aria_code/runtime/orchestrator.py +55 -0
  411. aria_code/runtime/repo_map.py +700 -0
  412. aria_code/runtime/run_state.py +127 -0
  413. aria_code/runtime/run_store.py +450 -0
  414. aria_code/runtime/self_healing.py +422 -0
  415. aria_code/runtime/subagent.py +547 -0
  416. aria_code/runtime/task_ledger.py +86 -0
  417. aria_code/runtime/task_orchestrator.py +90 -0
  418. aria_code/runtime/tool_executor.py +217 -0
  419. aria_code/runtime/tool_policy.py +107 -0
  420. aria_code/runtime/worktrees.py +215 -0
  421. aria_code/runway_video_client.py +4 -0
  422. aria_code/safety/__init__.py +23 -0
  423. aria_code/safety/permissions.py +306 -0
  424. aria_code/safety/service.py +102 -0
  425. aria_code/setup_wizard.py +673 -0
  426. aria_code/spreadsheet_tools.py +4 -0
  427. aria_code/strategy_vault.py +4 -0
  428. aria_code/tools/__init__.py +0 -0
  429. aria_code/tools/artifact_tools.py +252 -0
  430. aria_code/tools/broker_tools.py +92 -0
  431. aria_code/tools/build_quant_engine.py +97 -0
  432. aria_code/tools/code_audit_tools.py +192 -0
  433. aria_code/tools/computer_use_tools.py +504 -0
  434. aria_code/tools/data_analysis_tools.py +809 -0
  435. aria_code/tools/enterprise_finance_tools.py +156 -0
  436. aria_code/tools/extended_tools.py +240 -0
  437. aria_code/tools/file_analysis_tools.py +885 -0
  438. aria_code/tools/image_gen_tools.py +114 -0
  439. aria_code/tools/local_finance_tools.py +3417 -0
  440. aria_code/tools/logistics_tools.py +135 -0
  441. aria_code/tools/macro_tools.py +369 -0
  442. aria_code/tools/notification_tools.py +249 -0
  443. aria_code/tools/project_tools.py +525 -0
  444. aria_code/tools/realty_data_tools.py +659 -0
  445. aria_code/tools/spreadsheet_tools.py +388 -0
  446. aria_code/tools/stripe_tools.py +115 -0
  447. aria_code/ui/__init__.py +105 -0
  448. aria_code/ui/banner.py +508 -0
  449. aria_code/ui/completer.py +501 -0
  450. aria_code/ui/console.py +281 -0
  451. aria_code/ui/image_render.py +243 -0
  452. aria_code/ui/input_box.py +488 -0
  453. aria_code/ui/picker.py +201 -0
  454. aria_code/ui/render/__init__.py +11 -0
  455. aria_code/ui/render/finance.py +1705 -0
  456. aria_code/ui/render/market.py +225 -0
  457. aria_code/ui/render/output.py +775 -0
  458. aria_code/ui/render/responsive.py +75 -0
  459. aria_code/ui/render/team.py +354 -0
  460. aria_code/ui/robot.py +235 -0
  461. aria_code/ui/startup_dashboard.py +109 -0
  462. aria_code/utils/vscode_uri.py +37 -0
  463. aria_code/video_analysis.py +147 -0
  464. aria_code/video_editor.py +271 -0
  465. aria_code/workspace/__init__.py +6 -0
  466. aria_code/workspace/files.py +193 -0
  467. aria_code/workspace/verify.py +113 -0
  468. aria_code/workspace_changes.py +199 -0
  469. aria_code/workspace_context.py +233 -0
  470. aria_code/workspace_service.py +196 -0
  471. aria_code/workspace_tools.py +150 -0
  472. aria_code-0.52.0.dist-info/METADATA +237 -0
  473. aria_code-0.52.0.dist-info/RECORD +478 -0
  474. aria_code-0.52.0.dist-info/WHEEL +5 -0
  475. aria_code-0.52.0.dist-info/entry_points.txt +3 -0
  476. aria_code-0.52.0.dist-info/licenses/LICENSE +201 -0
  477. aria_code-0.52.0.dist-info/licenses/NOTICE +49 -0
  478. aria_code-0.52.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,885 @@
1
+ """
2
+ file_analysis_tools.py — 多格式文件解析与内容提取层
3
+ =====================================================
4
+ 支持格式:
5
+ PDF — pdfplumber (优先) 或 pypdf
6
+ Word/DOCX — python-docx
7
+ Excel/XLSX — openpyxl + pandas
8
+ CSV/TSV — pandas
9
+ JSON/JSONL — 内置 json
10
+ Markdown/TXT — 直接读取
11
+ 图片 — PIL/Pillow 元数据 + base64 (发给视觉模型)
12
+ HTML — BeautifulSoup4 提取正文
13
+ 代码文件 — 语法感知提取 (py/js/ts/go/java/cpp 等)
14
+
15
+ 全部函数返回统一格式:
16
+ {"success": bool, "type": str, "content": str, "metadata": dict, ...}
17
+
18
+ 依赖安装(可选,按需安装):
19
+ pip install pdfplumber python-docx openpyxl pandas pillow beautifulsoup4
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import base64
25
+ import importlib
26
+ import json
27
+ import logging
28
+ import mimetypes
29
+ import os
30
+ import re
31
+ from dataclasses import dataclass, field
32
+ from pathlib import Path
33
+ from typing import Any, Dict, List, Optional, Tuple
34
+
35
+ logger = logging.getLogger(__name__)
36
+
37
+ # ── Optional imports ──────────────────────────────────────────────────────────
38
+
39
+ def _try(mod):
40
+ try:
41
+ return __import__(mod)
42
+ except ImportError:
43
+ return None
44
+
45
+ # Detect available parsers at module load time (fast, no exceptions in hot path)
46
+ _pdfplumber = _try("pdfplumber")
47
+ _pypdf = _try("pypdf")
48
+ _docx_mod = _try("docx")
49
+ _openpyxl = _try("openpyxl")
50
+ _pd = _try("pandas")
51
+ _bs4 = _try("bs4")
52
+ _PIL = _try("PIL")
53
+
54
+
55
+ def refresh_optional_parsers() -> Dict[str, bool]:
56
+ """Refresh optional parser imports after an in-session package install.
57
+
58
+ ``/install`` uses the running interpreter, but this module historically
59
+ cached failed imports for the lifetime of the process. That made a
60
+ successful install look ineffective until Aria was restarted.
61
+ """
62
+ # Rebinding happens through globals() below, which needs no `global`.
63
+ importlib.invalidate_caches()
64
+ module_names = {
65
+ "_pdfplumber": "pdfplumber",
66
+ "_pypdf": "pypdf",
67
+ "_docx_mod": "docx",
68
+ "_openpyxl": "openpyxl",
69
+ "_pd": "pandas",
70
+ "_bs4": "bs4",
71
+ "_PIL": "PIL",
72
+ }
73
+ namespace = globals()
74
+ for variable, module_name in module_names.items():
75
+ if namespace.get(variable) is None:
76
+ namespace[variable] = _try(module_name)
77
+ return {
78
+ "pdfplumber": _pdfplumber is not None,
79
+ "pypdf": _pypdf is not None,
80
+ "python-docx": _docx_mod is not None,
81
+ "pandas": _pd is not None,
82
+ "openpyxl": _openpyxl is not None,
83
+ "beautifulsoup4": _bs4 is not None,
84
+ "Pillow": _PIL is not None,
85
+ }
86
+
87
+ # ── Data structures ───────────────────────────────────────────────────────────
88
+
89
+ @dataclass
90
+ class FileContent:
91
+ """Normalised result of parsing any file."""
92
+ success: bool
93
+ file_type: str # "pdf" | "docx" | "xlsx" | "csv" | "json" | "image" | "code" | "text"
94
+ path: str
95
+ filename: str
96
+ size_kb: float
97
+ content: str # Extracted text (may be truncated)
98
+ metadata: Dict[str, Any] = field(default_factory=dict)
99
+ tables: List[List[List[Any]]] = field(default_factory=list) # nested: [sheet[row[cell]]]
100
+ images_b64: List[str] = field(default_factory=list) # base64-encoded embedded images
101
+ error: Optional[str] = None
102
+ truncated: bool = False
103
+ char_count: int = 0
104
+
105
+
106
+ # ── Size / truncation limits ──────────────────────────────────────────────────
107
+
108
+ MAX_TEXT_CHARS = 80_000 # ~20k tokens — safe for most context windows
109
+ MAX_TABLE_ROWS = 200 # per sheet
110
+ MAX_PAGES = 50 # PDF page limit
111
+ SUMMARY_CHARS = 3_000 # for quick summary mode
112
+
113
+ # ── Main dispatcher ───────────────────────────────────────────────────────────
114
+
115
+ def parse_file(path_str: str, max_chars: int = MAX_TEXT_CHARS,
116
+ include_images: bool = False) -> FileContent:
117
+ """
118
+ Parse any supported file and return normalised FileContent.
119
+
120
+ path_str: absolute or ~-relative path
121
+ max_chars: truncate text extraction at this many chars
122
+ include_images: whether to base64-encode embedded images (expensive)
123
+ """
124
+ refresh_optional_parsers()
125
+ path = Path(path_str).expanduser().resolve()
126
+ if not path.exists():
127
+ return FileContent(False, "unknown", str(path), path.name, 0,
128
+ "", error=f"文件不存在: {path}")
129
+ if not path.is_file():
130
+ return FileContent(False, "unknown", str(path), path.name, 0,
131
+ "", error=f"不是文件: {path}")
132
+
133
+ size_kb = path.stat().st_size / 1024
134
+ if size_kb > 100 * 1024: # 100 MB guard
135
+ return FileContent(False, "unknown", str(path), path.name, size_kb,
136
+ "", error="文件过大(>100MB),请使用更小的文件")
137
+
138
+ suffix = path.suffix.lstrip(".").lower()
139
+ _DISPATCH = {
140
+ "pdf": _parse_pdf,
141
+ "docx": _parse_docx,
142
+ "doc": _parse_docx,
143
+ "xlsx": _parse_excel,
144
+ "xls": _parse_excel,
145
+ "csv": _parse_csv,
146
+ "tsv": _parse_csv,
147
+ "json": _parse_json,
148
+ "jsonl": _parse_json,
149
+ "html": _parse_html,
150
+ "htm": _parse_html,
151
+ "xml": _parse_html,
152
+ "md": _parse_text,
153
+ "txt": _parse_text,
154
+ "rst": _parse_text,
155
+ "log": _parse_text,
156
+ "yaml": _parse_text,
157
+ "yml": _parse_text,
158
+ "toml": _parse_text,
159
+ "ini": _parse_text,
160
+ "env": _parse_text,
161
+ "png": _parse_image,
162
+ "jpg": _parse_image,
163
+ "jpeg": _parse_image,
164
+ "gif": _parse_image,
165
+ "webp": _parse_image,
166
+ "bmp": _parse_image,
167
+ "mp4": _parse_video,
168
+ "mov": _parse_video,
169
+ "avi": _parse_video,
170
+ "mkv": _parse_video,
171
+ "webm": _parse_video,
172
+ "m4v": _parse_video,
173
+ }
174
+ # Code files
175
+ _CODE_EXT = {"py","js","ts","tsx","jsx","go","java","c","cpp","h","hpp",
176
+ "rs","rb","php","swift","kt","scala","sh","bash","zsh",
177
+ "sql","r","m","cs","vb","lua","perl","ps1"}
178
+ if suffix in _CODE_EXT:
179
+ fn = _parse_code
180
+ else:
181
+ fn = _DISPATCH.get(suffix, _parse_text)
182
+
183
+ try:
184
+ result = fn(path, max_chars=max_chars, include_images=include_images)
185
+ result.char_count = len(result.content)
186
+ return result
187
+ except Exception as e:
188
+ logger.exception("parse_file failed for %s", path)
189
+ return FileContent(False, suffix, str(path), path.name, size_kb,
190
+ "", error=f"解析失败: {e}")
191
+
192
+
193
+ # ── LLM-callable tool ─────────────────────────────────────────────────────────
194
+
195
+ # Keep the returned text small so a big document can't blow the context window
196
+ # (the agent can re-call with a larger max_chars when it needs more).
197
+ _TOOL_MAX_CHARS = 6000
198
+
199
+
200
+ def tool_analyze_file(params: dict) -> dict:
201
+ """Parse and analyse a local file so the agent can read it autonomously.
202
+
203
+ Supports pdf / docx / xls(x) / csv / json / html / markdown / code / images
204
+ (png·jpg·gif·webp·bmp). Image files are queued for the vision model so a
205
+ vision-capable model can actually see them. Returns extracted text (capped)
206
+ plus metadata; set ``max_chars`` higher to pull more of a long document.
207
+ """
208
+ path = str(params.get("path") or params.get("file_path") or "").strip()
209
+ if not path:
210
+ return {"success": False, "error": "path is required"}
211
+ try:
212
+ max_chars = int(params.get("max_chars", _TOOL_MAX_CHARS) or _TOOL_MAX_CHARS)
213
+ except (TypeError, ValueError):
214
+ max_chars = _TOOL_MAX_CHARS
215
+ max_chars = max(500, min(max_chars, MAX_TEXT_CHARS))
216
+
217
+ fc = parse_file(path, max_chars=max_chars, include_images=True)
218
+ if not fc.success:
219
+ return {"success": False, "path": path, "error": fc.error or "解析失败"}
220
+
221
+ # Queue the (first) image for vision-model injection on the next turn.
222
+ vision_attached = False
223
+ if fc.images_b64:
224
+ try:
225
+ from computer_use_tools import _store_screenshot
226
+ raw = fc.images_b64[0]
227
+ if "base64," in raw:
228
+ raw = raw.split("base64,", 1)[1]
229
+ _store_screenshot(raw)
230
+ vision_attached = True
231
+ except Exception:
232
+ pass
233
+
234
+ return {
235
+ "success": True,
236
+ "path": fc.path,
237
+ "filename": fc.filename,
238
+ "file_type": fc.file_type,
239
+ "size_kb": round(fc.size_kb, 1),
240
+ "char_count": fc.char_count,
241
+ "truncated": fc.truncated,
242
+ "n_tables": len(fc.tables),
243
+ "metadata": fc.metadata,
244
+ "vision_attached": vision_attached,
245
+ "content": fc.content,
246
+ }
247
+
248
+
249
+ # ── PDF ───────────────────────────────────────────────────────────────────────
250
+
251
+ def _parse_pdf(path: Path, max_chars: int, include_images: bool) -> FileContent:
252
+ size_kb = path.stat().st_size / 1024
253
+ meta: Dict[str, Any] = {"pages": 0}
254
+ text_parts = []
255
+ tables = []
256
+
257
+ if _pdfplumber:
258
+ import pdfplumber
259
+ with pdfplumber.open(str(path)) as pdf:
260
+ meta["pages"] = len(pdf.pages)
261
+ meta["pdf_info"] = dict(pdf.metadata or {})
262
+ for i, page in enumerate(pdf.pages[:MAX_PAGES]):
263
+ page_text = (page.extract_text() or "").strip()
264
+ if page_text:
265
+ text_parts.append(f"[第{i+1}页]\n{page_text}")
266
+ # Tables
267
+ for tbl in (page.extract_tables() or []):
268
+ if tbl:
269
+ tables.append(tbl)
270
+ elif _pypdf:
271
+ import pypdf
272
+ reader = pypdf.PdfReader(str(path))
273
+ meta["pages"] = len(reader.pages)
274
+ for i, page in enumerate(reader.pages[:MAX_PAGES]):
275
+ t = page.extract_text() or ""
276
+ if t.strip():
277
+ text_parts.append(f"[第{i+1}页]\n{t.strip()}")
278
+ else:
279
+ return FileContent(False, "pdf", str(path), path.name, size_kb, "",
280
+ error="未安装 PDF 解析库,请运行: pip install pdfplumber")
281
+
282
+ full_text = "\n\n".join(text_parts)
283
+ truncated = len(full_text) > max_chars
284
+ return FileContent(True, "pdf", str(path), path.name, size_kb,
285
+ full_text[:max_chars], meta, tables[:20],
286
+ truncated=truncated)
287
+
288
+
289
+ # ── DOCX ──────────────────────────────────────────────────────────────────────
290
+
291
+ def _parse_docx(path: Path, max_chars: int, include_images: bool) -> FileContent:
292
+ size_kb = path.stat().st_size / 1024
293
+ if not _docx_mod:
294
+ return FileContent(False, "docx", str(path), path.name, size_kb, "",
295
+ error="未安装 python-docx,请运行: pip install python-docx")
296
+ import docx
297
+ doc = docx.Document(str(path))
298
+
299
+ parts = []
300
+ tables = []
301
+
302
+ # Core properties
303
+ meta: Dict[str, Any] = {}
304
+ try:
305
+ cp = doc.core_properties
306
+ meta = {
307
+ "author": cp.author,
308
+ "created": str(cp.created)[:10] if cp.created else "",
309
+ "modified": str(cp.modified)[:10] if cp.modified else "",
310
+ "title": cp.title or "",
311
+ "subject": cp.subject or "",
312
+ }
313
+ except Exception:
314
+ pass
315
+
316
+ # Paragraphs
317
+ for para in doc.paragraphs:
318
+ t = para.text.strip()
319
+ if not t:
320
+ continue
321
+ # Heading levels
322
+ if para.style.name.startswith("Heading"):
323
+ level = para.style.name.replace("Heading ", "")
324
+ parts.append(f"\n{'#' * int(level) if level.isdigit() else '##'} {t}")
325
+ else:
326
+ parts.append(t)
327
+
328
+ # Tables
329
+ for tbl in doc.tables:
330
+ rows = []
331
+ for row in tbl.rows[:MAX_TABLE_ROWS]:
332
+ rows.append([cell.text.strip() for cell in row.cells])
333
+ if rows:
334
+ tables.append(rows)
335
+ # Inline text representation
336
+ header = " | ".join(rows[0])
337
+ sep = " | ".join(["---"] * len(rows[0]))
338
+ body = "\n".join(" | ".join(r) for r in rows[1:MAX_TABLE_ROWS])
339
+ parts.append(f"\n[表格]\n{header}\n{sep}\n{body}")
340
+
341
+ full_text = "\n".join(parts)
342
+ truncated = len(full_text) > max_chars
343
+ meta["paragraphs"] = len(doc.paragraphs)
344
+ meta["tables"] = len(doc.tables)
345
+ return FileContent(True, "docx", str(path), path.name, size_kb,
346
+ full_text[:max_chars], meta, tables[:10],
347
+ truncated=truncated)
348
+
349
+
350
+ # ── Excel ─────────────────────────────────────────────────────────────────────
351
+
352
+ def _parse_excel(path: Path, max_chars: int, include_images: bool) -> FileContent:
353
+ size_kb = path.stat().st_size / 1024
354
+ if not _pd:
355
+ return FileContent(False, "xlsx", str(path), path.name, size_kb, "",
356
+ error="未安装 pandas,请运行: pip install pandas openpyxl")
357
+
358
+ import pandas as pd
359
+ try:
360
+ sheets = pd.read_excel(str(path), sheet_name=None, engine="openpyxl",
361
+ nrows=MAX_TABLE_ROWS)
362
+ except Exception as e:
363
+ return FileContent(False, "xlsx", str(path), path.name, size_kb, "",
364
+ error=f"读取 Excel 失败: {e}")
365
+
366
+ parts = []
367
+ tables = []
368
+ meta: Dict[str, Any] = {"sheets": list(sheets.keys()), "sheet_count": len(sheets)}
369
+
370
+ for sname, df in sheets.items():
371
+ df = df.fillna("").astype(str)
372
+ rows = [list(df.columns)] + df.values.tolist()
373
+ tables.append(rows)
374
+ meta[f"sheet_{sname}_shape"] = f"{len(df)} 行 × {len(df.columns)} 列"
375
+
376
+ # Text representation (first 50 rows shown)
377
+ header = " | ".join(str(c) for c in df.columns)
378
+ sep = " | ".join(["---"] * len(df.columns))
379
+ body_rows = df.head(50).values.tolist()
380
+ body = "\n".join(" | ".join(str(v)[:30] for v in r) for r in body_rows)
381
+ parts.append(f"\n[Sheet: {sname}] ({len(df)} 行 × {len(df.columns)} 列)\n"
382
+ f"{header}\n{sep}\n{body}")
383
+
384
+ # Basic stats for numeric columns
385
+ num_cols = df.select_dtypes(include="number") if hasattr(df, "select_dtypes") else None
386
+ if num_cols is not None and not num_cols.empty:
387
+ stats_lines = []
388
+ for col in list(num_cols.columns)[:5]:
389
+ s = num_cols[col].describe()
390
+ stats_lines.append(f" {col}: 均值={s.get('mean','')} 最大={s.get('max','')} 最小={s.get('min','')}")
391
+ if stats_lines:
392
+ parts.append("[数值统计]\n" + "\n".join(stats_lines))
393
+
394
+ full_text = "\n".join(parts)
395
+ truncated = len(full_text) > max_chars
396
+ return FileContent(True, "xlsx", str(path), path.name, size_kb,
397
+ full_text[:max_chars], meta, tables,
398
+ truncated=truncated)
399
+
400
+
401
+ # ── CSV ───────────────────────────────────────────────────────────────────────
402
+
403
+ def _parse_csv(path: Path, max_chars: int, include_images: bool) -> FileContent:
404
+ size_kb = path.stat().st_size / 1024
405
+ if not _pd:
406
+ # Fallback: plain text
407
+ return _parse_text(path, max_chars, include_images)
408
+
409
+ import pandas as pd
410
+ # Detect delimiter
411
+ delim = "\t" if path.suffix.lower() == ".tsv" else ","
412
+ try:
413
+ df = pd.read_csv(str(path), sep=delim, nrows=MAX_TABLE_ROWS,
414
+ encoding="utf-8", on_bad_lines="skip")
415
+ except Exception:
416
+ try:
417
+ df = pd.read_csv(str(path), sep=delim, nrows=MAX_TABLE_ROWS,
418
+ encoding="gbk", on_bad_lines="skip")
419
+ except Exception as e:
420
+ return FileContent(False, "csv", str(path), path.name, size_kb, "",
421
+ error=f"CSV 读取失败: {e}")
422
+
423
+ meta: Dict[str, Any] = {
424
+ "rows": len(df),
425
+ "columns": len(df.columns),
426
+ "col_names": list(df.columns)[:30],
427
+ }
428
+ try:
429
+ desc = df.describe(include="all").to_string()
430
+ meta["stats_preview"] = desc[:1000]
431
+ except Exception:
432
+ pass
433
+
434
+ rows = [list(df.columns)] + df.values.tolist()
435
+ header = " | ".join(str(c) for c in df.columns)
436
+ sep = " | ".join(["---"] * len(df.columns))
437
+ body = "\n".join(" | ".join(str(v)[:25] for v in r) for r in df.head(80).values.tolist())
438
+ full_text = f"[CSV: {path.name}] {len(df)} 行 × {len(df.columns)} 列\n{header}\n{sep}\n{body}"
439
+ truncated = len(full_text) > max_chars
440
+ return FileContent(True, "csv", str(path), path.name, size_kb,
441
+ full_text[:max_chars], meta, [rows[:MAX_TABLE_ROWS]],
442
+ truncated=truncated)
443
+
444
+
445
+ # ── JSON ──────────────────────────────────────────────────────────────────────
446
+
447
+ def _parse_json(path: Path, max_chars: int, include_images: bool) -> FileContent:
448
+ size_kb = path.stat().st_size / 1024
449
+ suffix = path.suffix.lower()
450
+
451
+ try:
452
+ if suffix == ".jsonl":
453
+ lines = path.read_text(encoding="utf-8").splitlines()
454
+ records = []
455
+ for line in lines[:500]:
456
+ line = line.strip()
457
+ if line:
458
+ try: records.append(json.loads(line))
459
+ except Exception: pass
460
+ data = records
461
+ meta = {"format": "JSONL", "records": len(records), "sample": records[:3]}
462
+ else:
463
+ raw = path.read_text(encoding="utf-8")
464
+ data = json.loads(raw)
465
+ meta = {"format": "JSON", "type": type(data).__name__}
466
+ if isinstance(data, list):
467
+ meta["length"] = len(data)
468
+ meta["sample"] = data[:3]
469
+ elif isinstance(data, dict):
470
+ meta["keys"] = list(data.keys())[:20]
471
+
472
+ # Pretty-print (truncated)
473
+ text = json.dumps(data, ensure_ascii=False, indent=2)
474
+ truncated = len(text) > max_chars
475
+ return FileContent(True, "json", str(path), path.name, size_kb,
476
+ text[:max_chars], meta, truncated=truncated)
477
+ except Exception as e:
478
+ return FileContent(False, "json", str(path), path.name, size_kb, "",
479
+ error=f"JSON 解析失败: {e}")
480
+
481
+
482
+ # ── HTML ──────────────────────────────────────────────────────────────────────
483
+
484
+ def _parse_html(path: Path, max_chars: int, include_images: bool) -> FileContent:
485
+ size_kb = path.stat().st_size / 1024
486
+ raw = path.read_text(encoding="utf-8", errors="replace")
487
+ meta: Dict[str, Any] = {}
488
+
489
+ if _bs4:
490
+ from bs4 import BeautifulSoup
491
+ soup = BeautifulSoup(raw, "html.parser")
492
+ # Remove scripts/styles
493
+ for tag in soup(["script", "style", "nav", "footer", "header"]):
494
+ tag.decompose()
495
+ title = soup.find("title")
496
+ meta["title"] = title.get_text().strip() if title else ""
497
+ # Get main content
498
+ main = soup.find("main") or soup.find("article") or soup.find("body") or soup
499
+ text = main.get_text(separator="\n").strip()
500
+ # Collapse blank lines
501
+ text = re.sub(r"\n{3,}", "\n\n", text)
502
+ else:
503
+ # Simple tag stripping
504
+ text = re.sub(r"<[^>]+>", " ", raw)
505
+ text = re.sub(r"\s+", " ", text).strip()
506
+
507
+ truncated = len(text) > max_chars
508
+ return FileContent(True, "html", str(path), path.name, size_kb,
509
+ text[:max_chars], meta, truncated=truncated)
510
+
511
+
512
+ # ── Plain Text / Markdown / Code ──────────────────────────────────────────────
513
+
514
+ def _parse_text(path: Path, max_chars: int, include_images: bool) -> FileContent:
515
+ size_kb = path.stat().st_size / 1024
516
+ for enc in ("utf-8", "gbk", "latin-1"):
517
+ try:
518
+ text = path.read_text(encoding=enc)
519
+ break
520
+ except UnicodeDecodeError:
521
+ continue
522
+ else:
523
+ text = path.read_bytes().decode("utf-8", errors="replace")
524
+
525
+ lines = text.count("\n")
526
+ meta = {"lines": lines, "encoding": "utf-8"}
527
+ truncated = len(text) > max_chars
528
+ return FileContent(True, "text", str(path), path.name, size_kb,
529
+ text[:max_chars], meta, truncated=truncated)
530
+
531
+
532
+ def _parse_code(path: Path, max_chars: int, include_images: bool) -> FileContent:
533
+ size_kb = path.stat().st_size / 1024
534
+ for enc in ("utf-8", "gbk", "latin-1"):
535
+ try:
536
+ text = path.read_text(encoding=enc)
537
+ break
538
+ except UnicodeDecodeError:
539
+ continue
540
+ else:
541
+ text = path.read_bytes().decode("utf-8", errors="replace")
542
+
543
+ suffix = path.suffix.lstrip(".").lower()
544
+ lines = text.splitlines()
545
+ meta: Dict[str, Any] = {
546
+ "language": suffix,
547
+ "lines": len(lines),
548
+ "blank_lines": sum(1 for l in lines if not l.strip()),
549
+ }
550
+
551
+ # Extract function/class names
552
+ _PATTERNS = {
553
+ "py": (r"^(?:async\s+)?def\s+(\w+)", r"^class\s+(\w+)"),
554
+ "js": (r"function\s+(\w+)\s*\(", r"class\s+(\w+)"),
555
+ "ts": (r"function\s+(\w+)\s*\(", r"class\s+(\w+)", r"interface\s+(\w+)"),
556
+ "go": (r"^func\s+\(?[^)]*\)?\s*(\w+)\s*\(", r"^type\s+(\w+)\s+struct"),
557
+ "java": (r"(?:public|private|protected)?\s+\w+\s+(\w+)\s*\(", r"class\s+(\w+)"),
558
+ "rs": (r"^(?:pub\s+)?fn\s+(\w+)", r"^(?:pub\s+)?struct\s+(\w+)"),
559
+ }
560
+ patterns = _PATTERNS.get(suffix, [])
561
+ symbols = []
562
+ for pat in patterns:
563
+ found = re.findall(pat, text, re.MULTILINE)
564
+ symbols.extend(found[:20])
565
+ if symbols:
566
+ meta["symbols"] = list(dict.fromkeys(symbols))[:30]
567
+
568
+ truncated = len(text) > max_chars
569
+ return FileContent(True, "code", str(path), path.name, size_kb,
570
+ text[:max_chars], meta, truncated=truncated)
571
+
572
+
573
+ # ── Image ─────────────────────────────────────────────────────────────────────
574
+
575
+ def _parse_image(path: Path, max_chars: int, include_images: bool) -> FileContent:
576
+ size_kb = path.stat().st_size / 1024
577
+ suffix = path.suffix.lstrip(".").lower()
578
+ mime = {"png":"image/png","jpg":"image/jpeg","jpeg":"image/jpeg",
579
+ "gif":"image/gif","webp":"image/webp","bmp":"image/bmp"}.get(suffix,"image/png")
580
+ meta: Dict[str, Any] = {"mime": mime, "size_kb": round(size_kb, 1)}
581
+
582
+ if _PIL:
583
+ from PIL import Image
584
+ try:
585
+ with Image.open(str(path)) as img:
586
+ meta["width"] = img.width
587
+ meta["height"] = img.height
588
+ meta["mode"] = img.mode
589
+ meta["format"] = img.format
590
+ if hasattr(img, "_getexif") and img._getexif():
591
+ meta["has_exif"] = True
592
+ except Exception:
593
+ pass
594
+
595
+ # Base64 for vision models
596
+ images_b64 = []
597
+ if include_images and size_kb < 10 * 1024: # < 10MB
598
+ try:
599
+ b64 = base64.b64encode(path.read_bytes()).decode()
600
+ images_b64 = [f"data:{mime};base64,{b64}"]
601
+ except Exception:
602
+ pass
603
+
604
+ text = (f"[图片文件: {path.name}]\n"
605
+ f"尺寸: {meta.get('width','?')}×{meta.get('height','?')} px\n"
606
+ f"格式: {meta.get('format',suffix.upper())}\n"
607
+ f"大小: {size_kb:.1f} KB")
608
+ return FileContent(True, "image", str(path), path.name, size_kb,
609
+ text, meta, images_b64=images_b64)
610
+
611
+
612
+ def _parse_video(path: Path, max_chars: int, include_images: bool) -> FileContent:
613
+ """Video: metadata + evenly-spaced keyframes (base64) for vision models.
614
+
615
+ Needs opencv-python; degrades to metadata-only with an install hint when it
616
+ is absent (it is an optional 'video' extra, not a core dependency).
617
+ """
618
+ size_kb = path.stat().st_size / 1024
619
+ suffix = path.suffix.lstrip(".").lower()
620
+ meta: Dict[str, Any] = {"mime": f"video/{suffix}", "size_kb": round(size_kb, 1)}
621
+
622
+ try:
623
+ import cv2 # type: ignore
624
+ except Exception:
625
+ return FileContent(
626
+ True, "video", str(path), path.name, size_kb,
627
+ (f"[视频文件: {path.name}] {size_kb/1024:.1f} MB\n"
628
+ "未安装 opencv-python,无法抽帧分析。安装后重试: "
629
+ "pip install opencv-python 或 /install opencv-python"),
630
+ meta,
631
+ )
632
+
633
+ images_b64: List[str] = []
634
+ try:
635
+ cap = cv2.VideoCapture(str(path))
636
+ fps = float(cap.get(cv2.CAP_PROP_FPS) or 0)
637
+ frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT) or 0)
638
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH) or 0)
639
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT) or 0)
640
+ duration = frames / fps if fps else 0.0
641
+ meta.update({"fps": round(fps, 2), "frames": frames, "width": width,
642
+ "height": height, "duration_sec": round(duration, 1)})
643
+
644
+ # Sample keyframes between 10%–90% (skip black intros/outros). Only when
645
+ # images are requested — decoding frames is expensive.
646
+ if include_images and frames > 0:
647
+ import base64
648
+ n = min(4, frames)
649
+ if n == 1:
650
+ positions = [frames // 2]
651
+ else:
652
+ positions = [int(frames * (0.1 + 0.8 * i / (n - 1))) for i in range(n)]
653
+ for fi in positions:
654
+ cap.set(cv2.CAP_PROP_POS_FRAMES, fi)
655
+ ok, frame = cap.read()
656
+ if not ok:
657
+ continue
658
+ ok2, buf = cv2.imencode(".jpg", frame)
659
+ if ok2:
660
+ images_b64.append("data:image/jpeg;base64," + base64.b64encode(buf.tobytes()).decode())
661
+ cap.release()
662
+ except Exception as e:
663
+ return FileContent(True, "video", str(path), path.name, size_kb,
664
+ f"[视频文件: {path.name}] 读取失败: {e}", meta)
665
+
666
+ text = (f"[视频文件: {path.name}]\n"
667
+ f"时长: {meta.get('duration_sec','?')} 秒 帧率: {meta.get('fps','?')} fps\n"
668
+ f"分辨率: {meta.get('width','?')}×{meta.get('height','?')} 总帧数: {meta.get('frames','?')}\n"
669
+ f"已抽取 {len(images_b64)} 个关键帧供视觉模型分析。")
670
+ return FileContent(True, "video", str(path), path.name, size_kb,
671
+ text, meta, images_b64=images_b64)
672
+
673
+
674
+ # ── 多层分析提示词生成器 ──────────────────────────────────────────────────────
675
+
676
+ def build_analysis_prompt(fc: FileContent, layer: int = 1,
677
+ domain: str = "auto",
678
+ question: str = "") -> str:
679
+ """
680
+ 为不同分析层次生成系统/用户提示词。
681
+
682
+ layer 1 — 快速摘要 (300 字以内,Who/What/When/Why/Key metrics)
683
+ layer 2 — 深度分析 (结构、要点、数据模式、异常值)
684
+ layer 3 — 领域洞察 (财务/法律/技术/学术 — 由 domain 控制)
685
+ layer 4 — 行动建议 (风险点、优化建议、下一步)
686
+ """
687
+ type_zh = {
688
+ "pdf": "PDF 文档",
689
+ "docx": "Word 文档",
690
+ "xlsx": "Excel 表格",
691
+ "csv": "CSV 数据文件",
692
+ "json": "JSON 数据",
693
+ "image": "图片",
694
+ "code": "代码文件",
695
+ "text": "文本文件",
696
+ "html": "网页文件",
697
+ }.get(fc.file_type, "文件")
698
+
699
+ trunc_note = "\n\n⚠️ 注意:文件内容已被截断(超出上下文限制),以下为前段内容。" if fc.truncated else ""
700
+ meta_summary = _format_meta(fc)
701
+
702
+ # Auto-detect domain from content
703
+ if domain == "auto":
704
+ domain = _detect_domain(fc)
705
+
706
+ _DOMAIN_CONTEXT = {
707
+ "finance": "你是资深财务分析师,专注财报分析、现金流、盈利质量、风险敞口。",
708
+ "legal": "你是法律顾问,关注合同条款、风险条款、义务约束、免责声明。",
709
+ "tech": "你是高级软件工程师,评估代码质量、架构、安全、性能、可维护性。",
710
+ "research":"你是研究员,提炼论文方法论、数据、结论、局限性及引用。",
711
+ "realty": "你是不动产分析师,关注物业数据、租金、估值、市场趋势、合规风险。",
712
+ "medical": "你是医学顾问,总结诊断信息、治疗方案、用药风险(不构成诊断建议)。",
713
+ "general": "你是专业文档分析师,全面理解文件内容。",
714
+ }
715
+ domain_ctx = _DOMAIN_CONTEXT.get(domain, _DOMAIN_CONTEXT["general"])
716
+
717
+ layers = {
718
+ 1: f"""请对以下{type_zh}进行**快速摘要分析**(300字以内):
719
+ {meta_summary}
720
+ 要求:
721
+ 1. 用一句话说明文件核心主题
722
+ 2. 列出 3-5 个关键发现/数据点
723
+ 3. 标注文件覆盖的时间范围(如有)
724
+ 4. 指出最重要的结论或结果
725
+ {trunc_note}
726
+
727
+ ---文件内容---
728
+ {fc.content[:SUMMARY_CHARS]}""",
729
+
730
+ 2: f"""请对以下{type_zh}进行**深度内容分析**:
731
+ {meta_summary}
732
+ 分析维度:
733
+ 1. **结构分析** — 文件章节/字段组织,逻辑流程
734
+ 2. **数据要点** — 关键数字、趋势、比较(列表格式)
735
+ 3. **异常与亮点** — 与常规预期显著不同的点
736
+ 4. **信息完整性** — 是否有缺失、矛盾或模糊内容
737
+ 5. **数据质量** — 若为数据文件:空值率、一致性
738
+ {trunc_note}
739
+
740
+ ---文件内容---
741
+ {fc.content}""",
742
+
743
+ 3: f"""{domain_ctx}
744
+
745
+ 请对以下{type_zh}进行**领域专项分析**:
746
+ {meta_summary}
747
+ 重点关注:
748
+ 1. **核心指标解读** — 本领域最重要的量化指标及其含义
749
+ 2. **潜在风险** — 文件中反映或隐含的风险点
750
+ 3. **与行业基准的偏差** — 什么是正常水平,当前数据如何?
751
+ 4. **合规/规范性** — 是否符合本领域常规标准
752
+ 5. **深层逻辑** — 表面数据背后的原因/驱动因素
753
+ {trunc_note}
754
+
755
+ ---文件内容---
756
+ {fc.content}""",
757
+
758
+ 4: f"""{domain_ctx}
759
+
760
+ 基于以下{type_zh}的内容,请给出**可执行的行动建议**:
761
+ {meta_summary}
762
+ 要求:
763
+ 1. **立即行动** — 需要立刻处理的事项(按优先级)
764
+ 2. **改进建议** — 中期可以优化的方面
765
+ 3. **风险预警** — 需要关注但未必立刻行动的隐患
766
+ 4. **问题清单** — 文件不清晰/需要补充的 3-5 个问题
767
+ 5. **下一步** — 建议的后续分析或决策步骤
768
+ {trunc_note}
769
+
770
+ ---文件内容---
771
+ {fc.content[:max(len(fc.content)//2, SUMMARY_CHARS)]}""",
772
+ }
773
+
774
+ base_prompt = layers.get(layer, layers[1])
775
+ if question:
776
+ base_prompt = (f"关于以下{type_zh},用户提问:\n\n**{question}**\n\n"
777
+ f"{meta_summary}{trunc_note}\n\n---文件内容---\n{fc.content}")
778
+ return base_prompt
779
+
780
+
781
+ def _format_meta(fc: FileContent) -> str:
782
+ """Format metadata for prompt header."""
783
+ lines = [f"文件名: {fc.filename}", f"类型: {fc.file_type.upper()}",
784
+ f"大小: {fc.size_kb:.1f} KB"]
785
+ for k, v in fc.metadata.items():
786
+ if k in ("pages","rows","columns","lines","language","sheets",
787
+ "records","length","sheet_count","paragraphs","tables",
788
+ "title","author","created"):
789
+ lines.append(f"{k}: {v}")
790
+ if fc.tables:
791
+ lines.append(f"包含表格: {len(fc.tables)} 个")
792
+ return " ".join(lines[:8])
793
+
794
+
795
+ def _detect_domain(fc: FileContent) -> str:
796
+ """Auto-detect analysis domain from content keywords."""
797
+ text_lower = fc.content[:2000].lower()
798
+ scores = {
799
+ "finance": sum(text_lower.count(k) for k in ["revenue","profit","ebitda","cashflow",
800
+ "净利润","营收","现金流","资产负债","毛利率","eps","roe","pe","财报"]),
801
+ "legal": sum(text_lower.count(k) for k in ["合同","甲方","乙方","违约","协议",
802
+ "条款","liability","indemnify","agreement","breach"]),
803
+ "tech": sum(text_lower.count(k) for k in ["def ","class ","function","import",
804
+ "select ","return ","var ","const ","type "]) + (2 if fc.file_type=="code" else 0),
805
+ "research": sum(text_lower.count(k) for k in ["abstract","methodology","conclusion",
806
+ "hypothesis","摘要","结论","方法","样本量","显著性","p-value"]),
807
+ "realty": sum(text_lower.count(k) for k in ["租金","房价","物业","不动产","reit",
808
+ "产权","容积率","建筑面积","房地产","地块"]),
809
+ }
810
+ best = max(scores, key=scores.get)
811
+ return best if scores[best] >= 2 else "general"
812
+
813
+
814
+ # ── Session file store (used by /file command) ────────────────────────────────
815
+
816
+ class FileSession:
817
+ """
818
+ Holds loaded files for the current REPL session.
819
+ Provides multi-turn Q&A context injection.
820
+ """
821
+ def __init__(self):
822
+ self._files: Dict[str, FileContent] = {} # name → FileContent
823
+ self._active: Optional[str] = None
824
+
825
+ def load(self, path_str: str, include_images: bool = False) -> FileContent:
826
+ fc = parse_file(path_str, include_images=include_images)
827
+ if fc.success:
828
+ self._files[fc.filename] = fc
829
+ self._active = fc.filename
830
+ return fc
831
+
832
+ def get_active(self) -> Optional[FileContent]:
833
+ if self._active and self._active in self._files:
834
+ return self._files[self._active]
835
+ return None
836
+
837
+ def set_active(self, name: str) -> bool:
838
+ if name in self._files:
839
+ self._active = name
840
+ return True
841
+ # Partial match
842
+ for key in self._files:
843
+ if name.lower() in key.lower():
844
+ self._active = key
845
+ return True
846
+ return False
847
+
848
+ def list_files(self) -> List[Dict[str, Any]]:
849
+ result = []
850
+ for name, fc in self._files.items():
851
+ result.append({
852
+ "filename": name,
853
+ "type": fc.file_type,
854
+ "size_kb": round(fc.size_kb, 1),
855
+ "chars": fc.char_count,
856
+ "active": name == self._active,
857
+ "truncated": fc.truncated,
858
+ })
859
+ return result
860
+
861
+ def build_context_block(self, max_chars: int = 12_000) -> str:
862
+ """Return a context block to inject into the system prompt."""
863
+ fc = self.get_active()
864
+ if not fc:
865
+ return ""
866
+ meta = _format_meta(fc)
867
+ content_preview = fc.content[:max_chars]
868
+ trunc = f"\n[内容已截断至前 {max_chars} 字符]" if fc.truncated or len(fc.content) > max_chars else ""
869
+ return (f"\n\n---已加载文件: {fc.filename}---\n"
870
+ f"{meta}\n\n{content_preview}{trunc}\n---文件结束---")
871
+
872
+ def clear(self, name: Optional[str] = None):
873
+ if name:
874
+ self._files.pop(name, None)
875
+ if self._active == name:
876
+ self._active = next(iter(self._files), None)
877
+ else:
878
+ self._files.clear()
879
+ self._active = None
880
+
881
+
882
+ # ── Dependency check ──────────────────────────────────────────────────────────
883
+
884
+ def check_parsers() -> Dict[str, bool]:
885
+ return refresh_optional_parsers()