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,184 @@
1
+ """
2
+ providers/llm/anthropic.py — Anthropic Claude Provider
3
+ =======================================================
4
+ 支持 claude-3-5-sonnet / claude-3-haiku / claude-3-opus 等。
5
+ 支持流式 thinking(扩展思考模式)。
6
+
7
+ 需要设置: ANTHROPIC_API_KEY=sk-ant-...
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ import logging
14
+ import os
15
+ from typing import Any, AsyncIterator, Dict, List, Optional
16
+
17
+ from .base import BaseLLMProvider, Message, ProviderConfig
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+ _ANTHROPIC_API_URL = "https://api.anthropic.com/v1/messages"
22
+ _ANTHROPIC_VERSION = "2023-06-01"
23
+
24
+
25
+ class AnthropicProvider(BaseLLMProvider):
26
+
27
+ provider_name = "anthropic"
28
+ supports_tools = True
29
+ supports_thinking = True # claude-3-5-sonnet 支持 extended thinking
30
+ local = False
31
+
32
+ DEFAULT_MODEL = "claude-3-5-haiku-latest"
33
+
34
+ def __init__(self, config: ProviderConfig):
35
+ super().__init__(config)
36
+ self.api_key = config.api_key or os.getenv("ANTHROPIC_API_KEY", "")
37
+ self.model = config.model or self.DEFAULT_MODEL
38
+
39
+ async def is_available(self) -> bool:
40
+ return bool(self.api_key)
41
+
42
+ # Minimum system-prompt length (chars) to bother caching.
43
+ # Prompts shorter than this don't benefit from prompt caching.
44
+ _CACHE_MIN_CHARS = 1024
45
+
46
+ def _build_cached_system(self, system_text: str) -> list:
47
+ """
48
+ Wrap a long system prompt in a cache_control block so Anthropic
49
+ caches it across calls. Returns the `system` field value (a list
50
+ of content blocks, or a plain string for short prompts).
51
+
52
+ Anthropic prompt caching docs:
53
+ https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching
54
+ """
55
+ if len(system_text) < self._CACHE_MIN_CHARS:
56
+ return system_text # type: ignore[return-value]
57
+ return [
58
+ {
59
+ "type": "text",
60
+ "text": system_text,
61
+ "cache_control": {"type": "ephemeral"},
62
+ }
63
+ ]
64
+
65
+ async def stream(
66
+ self,
67
+ messages: List[Message],
68
+ tools: Optional[List[Dict]] = None,
69
+ temperature: Optional[float] = None,
70
+ max_tokens: Optional[int] = None,
71
+ cancel_event=None,
72
+ ) -> AsyncIterator[Dict[str, Any]]:
73
+ import aiohttp
74
+
75
+ temp = temperature if temperature is not None else self.config.temperature
76
+ n_tokens = max_tokens if max_tokens is not None else self.config.max_tokens
77
+
78
+ # Anthropic 格式:system 单独提取
79
+ system_parts = [m.content for m in messages if m.role == "system"]
80
+ system_text = "\n\n".join(system_parts) if system_parts else None
81
+ anthro_msgs = [
82
+ {"role": m.role, "content": m.content}
83
+ for m in messages if m.role != "system"
84
+ ]
85
+
86
+ payload: Dict[str, Any] = {
87
+ "model": self.model,
88
+ "max_tokens": n_tokens,
89
+ "messages": anthro_msgs,
90
+ "stream": True,
91
+ }
92
+ if system_text:
93
+ # Use prompt caching for long system prompts to reduce TTFT and cost
94
+ payload["system"] = self._build_cached_system(system_text)
95
+ if temp > 0:
96
+ payload["temperature"] = temp
97
+ if tools:
98
+ payload["tools"] = [
99
+ {
100
+ "name": t.get("name"),
101
+ "description": t.get("description", ""),
102
+ "input_schema": t.get("parameters", {"type": "object", "properties": {}}),
103
+ }
104
+ for t in tools
105
+ ]
106
+
107
+ headers = {
108
+ "x-api-key": self.api_key,
109
+ "anthropic-version": _ANTHROPIC_VERSION,
110
+ "anthropic-beta": "prompt-caching-2024-07-31",
111
+ "content-type": "application/json",
112
+ }
113
+
114
+ proxy = (os.getenv("HTTPS_PROXY") or os.getenv("https_proxy")
115
+ or os.getenv("HTTP_PROXY") or os.getenv("http_proxy"))
116
+ try:
117
+ async with aiohttp.ClientSession() as sess:
118
+ async with sess.post(
119
+ _ANTHROPIC_API_URL, json=payload, headers=headers,
120
+ proxy=proxy,
121
+ timeout=aiohttp.ClientTimeout(total=self.config.timeout)
122
+ ) as resp:
123
+ if resp.status != 200:
124
+ body = await resp.text()
125
+ yield {"type": "error",
126
+ "message": f"Anthropic HTTP {resp.status}: {body[:300]}"}
127
+ return
128
+
129
+ _pending_tool_name = ""
130
+ _pending_tool_args = ""
131
+
132
+ async for raw in resp.content:
133
+ if cancel_event and cancel_event.is_set():
134
+ return
135
+ line = raw.decode("utf-8", errors="ignore").strip()
136
+ if not line or not line.startswith("data:"):
137
+ continue
138
+
139
+ try:
140
+ data = json.loads(line[5:].strip())
141
+ except json.JSONDecodeError:
142
+ continue
143
+
144
+ etype = data.get("type", "")
145
+
146
+ # 思考 token(扩展思考模式)
147
+ if etype == "content_block_start":
148
+ cb = data.get("content_block", {})
149
+ if cb.get("type") == "thinking":
150
+ yield {"type": "thinking", "text": cb.get("thinking", "")}
151
+ elif cb.get("type") == "tool_use":
152
+ _pending_tool_name = cb.get("name", "")
153
+ _pending_tool_args = ""
154
+
155
+ elif etype == "content_block_delta":
156
+ delta = data.get("delta", {})
157
+ dt = delta.get("type", "")
158
+ if dt == "text_delta":
159
+ yield {"type": "token", "text": delta.get("text", "")}
160
+ elif dt == "thinking_delta":
161
+ yield {"type": "thinking", "text": delta.get("thinking", "")}
162
+ elif dt == "input_json_delta":
163
+ _pending_tool_args += delta.get("partial_json", "")
164
+
165
+ elif etype == "content_block_stop":
166
+ if _pending_tool_name:
167
+ try:
168
+ args = json.loads(_pending_tool_args)
169
+ except Exception:
170
+ args = {"_raw": _pending_tool_args}
171
+ yield {"type": "tool_call",
172
+ "name": _pending_tool_name,
173
+ "arguments": args}
174
+ _pending_tool_name = ""
175
+ _pending_tool_args = ""
176
+
177
+ elif etype == "message_stop":
178
+ yield {"type": "done"}
179
+ return
180
+
181
+ except aiohttp.ClientConnectorError as e:
182
+ yield {"type": "error", "message": f"Anthropic 连接失败: {e}"}
183
+ except Exception as e:
184
+ yield {"type": "error", "message": f"Anthropic 错误: {e}"}
@@ -0,0 +1,213 @@
1
+ """按用户的**实际环境**生成 providers.yaml,而不是发一份通用模板。
2
+
3
+ 为什么不是写死一份文档让用户照抄:通用模板对每个人都是"差不多但不对"——
4
+ 装了 Ollama 的人不需要被告知怎么配 OpenAI,配了 DEEPSEEK_API_KEY 的人不该看到
5
+ 一堆用不上的占位符,而真正卡住他的(本地服务没起来 / key 名字拼错)模板里
6
+ 一个字都不会提。
7
+
8
+ 这里的做法是先探测再生成:
9
+ - 本地推理服务(Ollama / LM Studio)是否在跑、跑着哪些模型
10
+ - 哪些 provider 的环境变量已经设了
11
+ - 用户已有的配置文件里声明过什么
12
+
13
+ 然后产出一份**只包含这台机器上真的能用的东西**的配置,并把"还差什么、
14
+ 去哪拿"作为注释写进同一个文件——用户不必在文档和配置之间来回对照。
15
+
16
+ 探测全部是只读的:不发请求到任何云端 provider(那需要消耗用户的额度,也会
17
+ 把 key 泄给一次纯粹的探活),只查本地端口和环境变量。云端 provider 的判定
18
+ 标准就是"环境变量在不在",这跟 registry 实际取 key 的方式完全一致。
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import json
24
+ import os
25
+ import socket
26
+ import urllib.error
27
+ import urllib.request
28
+ from dataclasses import dataclass, field
29
+ from typing import Dict, List, Optional
30
+ from urllib.parse import urlparse
31
+
32
+ __all__ = ["Finding", "probe_environment", "render_providers_yaml", "suggest_default_chain"]
33
+
34
+ # (provider id, 环境变量, 默认模型, 去哪拿 key)
35
+ _CLOUD_PROVIDERS: List[tuple] = [
36
+ ("deepseek", "DEEPSEEK_API_KEY", "deepseek-chat", "https://platform.deepseek.com/api_keys"),
37
+ ("siliconflow", "SILICONFLOW_API_KEY", "deepseek-ai/DeepSeek-V3", "https://cloud.siliconflow.cn/account/ak"),
38
+ ("dashscope", "DASHSCOPE_API_KEY", "qwen-plus", "https://bailian.console.aliyun.com/"),
39
+ ("moonshot", "MOONSHOT_API_KEY", "moonshot-v1-8k", "https://platform.moonshot.cn/console/api-keys"),
40
+ ("zhipu", "ZHIPUAI_API_KEY", "glm-4-flash", "https://open.bigmodel.cn/usercenter/apikeys"),
41
+ ("openai", "OPENAI_API_KEY", "gpt-4o-mini", "https://platform.openai.com/api-keys"),
42
+ ("anthropic", "ANTHROPIC_API_KEY", "claude-3-5-haiku-latest", "https://console.anthropic.com/settings/keys"),
43
+ ("groq", "GROQ_API_KEY", "llama-3.3-70b-versatile", "https://console.groq.com/keys"),
44
+ ("together", "TOGETHER_API_KEY", "meta-llama/Llama-3.3-70B-Instruct-Turbo", "https://api.together.xyz/settings/api-keys"),
45
+ ]
46
+
47
+ # 本地推理服务:(provider id, 默认地址, 列模型的路径)
48
+ _LOCAL_PROVIDERS: List[tuple] = [
49
+ ("ollama", os.environ.get("OLLAMA_BASE_URL", "http://localhost:11434"), "/api/tags"),
50
+ ("lmstudio", os.environ.get("LMSTUDIO_BASE_URL", "http://localhost:1234"), "/v1/models"),
51
+ ]
52
+
53
+
54
+ @dataclass
55
+ class Finding:
56
+ """一个 provider 的探测结果。"""
57
+ provider: str
58
+ kind: str # "local" | "cloud"
59
+ available: bool
60
+ detail: str = ""
61
+ models: List[str] = field(default_factory=list)
62
+ env_key: Optional[str] = None
63
+ signup_url: str = ""
64
+ base_url: str = ""
65
+
66
+
67
+ def _port_open(url: str, timeout: float = 0.4) -> bool:
68
+ """只做 TCP 探活。比发 HTTP 请求快得多,且本地服务没起来时能立刻返回,
69
+ 不会让整个探测卡在连接超时上。"""
70
+ parsed = urlparse(url)
71
+ host = parsed.hostname or "localhost"
72
+ port = parsed.port or (443 if parsed.scheme == "https" else 80)
73
+ try:
74
+ with socket.create_connection((host, port), timeout=timeout):
75
+ return True
76
+ except (OSError, ValueError):
77
+ return False
78
+
79
+
80
+ def _fetch_json(url: str, timeout: float = 1.5) -> Optional[dict]:
81
+ try:
82
+ with urllib.request.urlopen(url, timeout=timeout) as resp:
83
+ return json.loads(resp.read())
84
+ except (urllib.error.URLError, OSError, ValueError, json.JSONDecodeError):
85
+ return None
86
+
87
+
88
+ def _local_models(provider: str, base_url: str, path: str) -> List[str]:
89
+ payload = _fetch_json(base_url.rstrip("/") + path)
90
+ if not payload:
91
+ return []
92
+ if provider == "ollama":
93
+ return [m.get("name", "") for m in payload.get("models", []) if m.get("name")]
94
+ return [m.get("id", "") for m in payload.get("data", []) if m.get("id")]
95
+
96
+
97
+ def probe_environment(*, check_local: bool = True) -> List[Finding]:
98
+ """探测这台机器上有什么。只读、不消耗任何额度。"""
99
+ findings: List[Finding] = []
100
+
101
+ if check_local:
102
+ for provider, base_url, path in _LOCAL_PROVIDERS:
103
+ if not _port_open(base_url):
104
+ findings.append(Finding(
105
+ provider=provider, kind="local", available=False,
106
+ base_url=base_url,
107
+ detail=f"{base_url} 未监听(服务没启动,或端口不是默认值)",
108
+ ))
109
+ continue
110
+ models = _local_models(provider, base_url, path)
111
+ findings.append(Finding(
112
+ provider=provider, kind="local", available=bool(models),
113
+ base_url=base_url, models=models,
114
+ detail=(f"在跑,{len(models)} 个模型" if models
115
+ else "端口通但没列出模型(可能还没 pull 过任何模型)"),
116
+ ))
117
+
118
+ for provider, env_key, _model, signup in _CLOUD_PROVIDERS:
119
+ has_key = bool(os.environ.get(env_key, "").strip())
120
+ findings.append(Finding(
121
+ provider=provider, kind="cloud", available=has_key,
122
+ env_key=env_key, signup_url=signup,
123
+ detail=(f"{env_key} 已设置" if has_key else f"{env_key} 未设置"),
124
+ ))
125
+
126
+ return findings
127
+
128
+
129
+ def suggest_default_chain(findings: List[Finding]) -> tuple[Optional[str], List[str]]:
130
+ """按探测结果给出 default 与 fallback 链。
131
+
132
+ 排序原则:本地优先(零成本、不出网、隐私最好),其次是已配好 key 的云端。
133
+ 没配 key 的一律不进链——把用不了的东西写进 fallback 只会在真正需要兜底时
134
+ 连续失败几次才走到能用的那个。
135
+ """
136
+ local_ready = [f for f in findings if f.kind == "local" and f.available]
137
+ cloud_ready = [f for f in findings if f.kind == "cloud" and f.available]
138
+
139
+ chain: List[str] = []
140
+ for f in local_ready:
141
+ model = f.models[0] if f.models else ""
142
+ chain.append(f"{f.provider}/{model}" if model else f.provider)
143
+
144
+ cloud_default = {p: m for p, _e, m, _s in _CLOUD_PROVIDERS}
145
+ for f in cloud_ready:
146
+ chain.append(f"{f.provider}/{cloud_default.get(f.provider, '')}".rstrip("/"))
147
+
148
+ return (chain[0] if chain else None), chain[1:]
149
+
150
+
151
+ def render_providers_yaml(findings: List[Finding]) -> str:
152
+ """生成配置文本。缺什么、去哪拿,作为注释写在同一个文件里。"""
153
+ default, fallback = suggest_default_chain(findings)
154
+ lines: List[str] = [
155
+ "# ~/.aria/providers.yaml",
156
+ "# 由 `aria-code /providers init` 按本机实际环境生成。",
157
+ "# 密钥一律不写在这里——只写环境变量名,值放 ~/.aria/.env。",
158
+ "",
159
+ ]
160
+
161
+ if not default:
162
+ lines += [
163
+ "# ⚠️ 这台机器上暂时没有可用的模型来源。最快的两条路:",
164
+ "# 1. 本地(免费、离线可用):",
165
+ "# brew install ollama && ollama serve && ollama pull qwen2.5:7b",
166
+ "# 2. 云端:拿一个 key 写进 ~/.aria/.env,然后重跑 /providers init",
167
+ "",
168
+ "llm:",
169
+ " default: ollama/qwen2.5:7b # 装好 Ollama 后即可用",
170
+ "",
171
+ ]
172
+ else:
173
+ lines += ["llm:", f" default: {default}"]
174
+ if fallback:
175
+ lines.append(" fallback:")
176
+ lines += [f" - {item}" for item in fallback]
177
+ else:
178
+ lines += [
179
+ " # 只探测到一个可用来源,暂无 fallback。多配一个 key 可以让",
180
+ " # 默认来源故障时自动切换。",
181
+ ]
182
+ lines.append("")
183
+
184
+ ready_local = [f for f in findings if f.kind == "local" and f.available]
185
+ if ready_local:
186
+ lines.append("# ── 本机检测到的本地模型 ─────────────────────────────")
187
+ for f in ready_local:
188
+ preview = ", ".join(f.models[:6]) + (" …" if len(f.models) > 6 else "")
189
+ lines.append(f"# {f.provider} @ {f.base_url} → {preview}")
190
+ lines.append("")
191
+
192
+ missing = [f for f in findings if f.kind == "cloud" and not f.available]
193
+ if missing:
194
+ lines.append("# ── 还没配的云端 provider(想用哪个就取哪个 key)────────")
195
+ lines.append("# 把值写进 ~/.aria/.env,格式 KEY=值(不要加引号)")
196
+ for f in missing:
197
+ lines.append(f"# {f.env_key:<22} {f.signup_url}")
198
+ lines.append("")
199
+
200
+ lines += [
201
+ "# ── 接入未列出的服务(自建 vLLM / 内网网关 / 任意 OpenAI 兼容端点)──",
202
+ "# 取消注释并改成你自己的地址即可;之后 mycorp/<model> 就能用在上面的",
203
+ "# default 和 fallback 里。",
204
+ "#",
205
+ "# model_providers:",
206
+ "# mycorp:",
207
+ "# base_url: http://192.168.1.50:8000/v1",
208
+ "# env_key: MYCORP_LLM_TOKEN # 只写变量名,不写值",
209
+ "# env_key_instructions: 找基础架构组申请",
210
+ "# model: qwen-72b",
211
+ "",
212
+ ]
213
+ return "\n".join(lines)
@@ -0,0 +1,139 @@
1
+ """
2
+ providers/llm/base.py — LLM Provider 统一抽象基类
3
+ ==================================================
4
+ 所有 provider(本地/云端)实现同一接口,上层代码无需关心具体后端。
5
+
6
+ 事件类型 (stream yields):
7
+ {"type": "token", "text": "..."} # 文本增量
8
+ {"type": "thinking", "text": "..."} # 思考过程 (Claude/DeepSeek-R1)
9
+ {"type": "tool_call", "name": "...", "arguments": {...}} # 工具调用
10
+ {"type": "done", "text": "完整响应"} # 流结束
11
+ {"type": "error", "message": "..."} # 错误
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import os
17
+ from abc import ABC, abstractmethod
18
+ from dataclasses import dataclass, field
19
+ from typing import Any, AsyncIterator, Dict, List, Optional
20
+
21
+
22
+ @dataclass
23
+ class Message:
24
+ role: str # "system" | "user" | "assistant" | "tool"
25
+ content: str
26
+ name: Optional[str] = None # tool name (for role=tool)
27
+ tool_call_id: Optional[str] = None
28
+
29
+
30
+ @dataclass
31
+ class ProviderConfig:
32
+ """可由 providers.yaml / 环境变量 / CLI 参数覆盖"""
33
+ name: str
34
+ api_key: Optional[str] = None
35
+ base_url: Optional[str] = None
36
+ model: Optional[str] = None
37
+ temperature: float = 0.3
38
+ max_tokens: int = 4096
39
+ timeout: int = 120
40
+ # 扩展字段(各 provider 可自定义)
41
+ extra: Dict[str, Any] = field(default_factory=dict)
42
+
43
+ @classmethod
44
+ def from_env(cls, name: str, **defaults) -> "ProviderConfig":
45
+ """从环境变量自动读取 API Key(DEEPSEEK_API_KEY / OPENAI_API_KEY 等)"""
46
+ key_map = {
47
+ "deepseek": "DEEPSEEK_API_KEY",
48
+ "openai": "OPENAI_API_KEY",
49
+ "anthropic": "ANTHROPIC_API_KEY",
50
+ "groq": "GROQ_API_KEY",
51
+ "together": "TOGETHER_API_KEY",
52
+ "dashscope": "DASHSCOPE_API_KEY",
53
+ "siliconflow": "SILICONFLOW_API_KEY",
54
+ "moonshot": "MOONSHOT_API_KEY",
55
+ "zhipu": "ZHIPUAI_API_KEY",
56
+ }
57
+ env_var = key_map.get(name.lower())
58
+ api_key = os.getenv(env_var, "") if env_var else ""
59
+ return cls(name=name, api_key=api_key or None, **defaults)
60
+
61
+ def is_configured(self) -> bool:
62
+ """判断 provider 是否可用(本地 provider 无需 api_key)"""
63
+ _local = {"ollama", "lmstudio", "vllm", "llamacpp", "jan"}
64
+ if self.name.lower() in _local:
65
+ return True
66
+ return bool(self.api_key)
67
+
68
+
69
+ class BaseLLMProvider(ABC):
70
+ """
71
+ 所有 LLM provider 的统一基类。
72
+
73
+ 子类只需实现 `stream()` 方法;`complete()` 会自动聚合流结果。
74
+ """
75
+
76
+ # 子类声明这些属性
77
+ provider_name: str = "base"
78
+ supports_tools: bool = False
79
+ supports_thinking: bool = False
80
+ local: bool = False # True = 本地运行,不需要 api_key
81
+
82
+ def __init__(self, config: ProviderConfig):
83
+ self.config = config
84
+
85
+ # ── 必须实现 ──────────────────────────────────────────────────────────────
86
+
87
+ @abstractmethod
88
+ async def stream(
89
+ self,
90
+ messages: List[Message],
91
+ tools: Optional[List[Dict]] = None,
92
+ temperature: Optional[float] = None,
93
+ max_tokens: Optional[int] = None,
94
+ cancel_event=None,
95
+ ) -> AsyncIterator[Dict[str, Any]]:
96
+ """
97
+ 流式生成 token。
98
+
99
+ 每次 yield 一个事件 dict(见模块文档中的事件类型)。
100
+ """
101
+ ...
102
+ yield {} # 让 Python 识别为 async generator
103
+
104
+ # ── 默认实现(子类可覆盖)────────────────────────────────────────────────
105
+
106
+ async def complete(
107
+ self,
108
+ messages: List[Message],
109
+ tools: Optional[List[Dict]] = None,
110
+ temperature: Optional[float] = None,
111
+ max_tokens: Optional[int] = None,
112
+ ) -> Dict[str, Any]:
113
+ """聚合 stream() 事件,返回完整响应 dict。"""
114
+ full_text = ""
115
+ tool_calls = []
116
+ async for event in self.stream(
117
+ messages, tools=tools,
118
+ temperature=temperature, max_tokens=max_tokens,
119
+ ):
120
+ t = event.get("type")
121
+ if t == "token":
122
+ full_text += event.get("text", "")
123
+ elif t == "tool_call":
124
+ tool_calls.append({
125
+ "name": event.get("name"),
126
+ "arguments": event.get("arguments", {}),
127
+ })
128
+ elif t == "error":
129
+ return {"success": False, "error": event.get("message"),
130
+ "response": full_text, "tool_calls": tool_calls}
131
+ return {"success": True, "response": full_text, "tool_calls": tool_calls}
132
+
133
+ async def is_available(self) -> bool:
134
+ """检查 provider 是否在线/可用(子类覆盖以做实际探测)"""
135
+ return self.config.is_configured()
136
+
137
+ def __repr__(self) -> str:
138
+ model = self.config.model or "default"
139
+ return f"{self.__class__.__name__}(model={model})"