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,589 @@
1
+ """
2
+ local_llm_provider.py — Unified async streaming interface for local LLM backends.
3
+
4
+ Supported backends
5
+ ------------------
6
+ ollama → http://localhost:11434 (Ollama /api/chat)
7
+ lmstudio → http://localhost:1234 (LM Studio /v1/chat/completions)
8
+ vllm → http://localhost:8000 (vLLM /v1/chat/completions)
9
+ llamacpp → http://localhost:8080 (llama.cpp server /v1/chat/completions)
10
+ jan → http://localhost:1337 (Jan /v1/chat/completions)
11
+ openai → https://api.openai.com (OpenAI-compatible proxy)
12
+
13
+ All non-Ollama backends speak the OpenAI /v1/chat/completions SSE format.
14
+
15
+ Usage::
16
+
17
+ provider = LocalLLMProvider.from_config(config)
18
+ async for event in provider.stream(messages, tools=schemas):
19
+ if event["type"] == "token":
20
+ print(event["text"], end="", flush=True)
21
+ elif event["type"] == "tool_call":
22
+ handle_tool(event["name"], event["arguments"])
23
+ elif event["type"] == "done":
24
+ break
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import json
30
+ import os
31
+ import re
32
+ import urllib.request
33
+ from dataclasses import dataclass, field
34
+ from typing import Any, AsyncIterator, Dict, List, Optional
35
+
36
+ # ---------------------------------------------------------------------------
37
+ # Model resolution helpers
38
+ # ---------------------------------------------------------------------------
39
+
40
+ # Preferred model-name prefixes, highest priority first.
41
+ _PREFERRED_PREFIXES = [
42
+ "aria-", # native Aria models (future)
43
+ "qwen2.5:", # full qwen2.5 series
44
+ "qwen2.5-coder:", # coder variant (great for finance code tasks)
45
+ "qwen2.5", # any qwen2.5 variant without explicit tag
46
+ "qwen", # any qwen
47
+ "deepseek", # DeepSeek family
48
+ "gpt-oss", # locally-hosted GPT-compatible
49
+ "llama", # Meta Llama family
50
+ "mistral", # Mistral family
51
+ "gemma", # Google Gemma family
52
+ "phi", # Microsoft Phi
53
+ "command", # Cohere Command
54
+ ]
55
+
56
+ _model_cache: Dict[str, str] = {} # base_url → resolved model name
57
+ _cache_ts: Dict[str, float] = {}
58
+ _CACHE_TTL = 60.0 # seconds
59
+
60
+
61
+ def resolve_model_sync(base_url: str, requested: str) -> str:
62
+ """
63
+ Synchronously resolve the best available Ollama model.
64
+
65
+ Resolution order:
66
+ 1. Exact match with *requested*
67
+ 2. Same family prefix (e.g. "aria-sonata" → any model starting with "aria-")
68
+ 3. Priority-prefix list (_PREFERRED_PREFIXES)
69
+ 4. First model returned by /api/tags
70
+ 5. *requested* as-is (let Ollama surface the real error)
71
+ """
72
+ import time
73
+ key = f"{base_url}::{requested}"
74
+ now = time.time()
75
+ if key in _model_cache and now - _cache_ts.get(key, 0) < _CACHE_TTL:
76
+ return _model_cache[key]
77
+
78
+ tags_url = base_url.rstrip("/") + "/api/tags"
79
+ try:
80
+ with urllib.request.urlopen(tags_url, timeout=3) as r:
81
+ data = json.loads(r.read())
82
+ available: List[str] = [m["name"] for m in data.get("models", [])]
83
+ except Exception:
84
+ return requested # Ollama unreachable — pass through
85
+
86
+ if not available:
87
+ return requested
88
+
89
+ resolved = _pick_model(available, requested)
90
+ _model_cache[key] = resolved
91
+ _cache_ts[key] = now
92
+ return resolved
93
+
94
+
95
+ async def resolve_model_async(base_url: str, requested: str) -> str:
96
+ """Async variant of resolve_model_sync (uses aiohttp if available)."""
97
+ import time
98
+ key = f"{base_url}::{requested}"
99
+ now = time.time()
100
+ if key in _model_cache and now - _cache_ts.get(key, 0) < _CACHE_TTL:
101
+ return _model_cache[key]
102
+
103
+ tags_url = base_url.rstrip("/") + "/api/tags"
104
+ try:
105
+ if _HAS_AIOHTTP:
106
+ async with aiohttp.ClientSession() as s:
107
+ async with s.get(tags_url, timeout=aiohttp.ClientTimeout(total=3)) as r:
108
+ data = await r.json()
109
+ else:
110
+ with urllib.request.urlopen(tags_url, timeout=3) as r:
111
+ data = json.loads(r.read())
112
+ available: List[str] = [m["name"] for m in data.get("models", [])]
113
+ except Exception:
114
+ return requested
115
+
116
+ if not available:
117
+ return requested
118
+
119
+ resolved = _pick_model(available, requested)
120
+ _model_cache[key] = resolved
121
+ _cache_ts[key] = now
122
+ return resolved
123
+
124
+
125
+ def _pick_model(available: List[str], requested: str) -> str:
126
+ """Choose the best model from *available* given *requested*."""
127
+ # 1. Exact match
128
+ if requested in available:
129
+ return requested
130
+
131
+ # 2. Same family (strip tag, match prefix)
132
+ family = requested.split(":")[0]
133
+ hit = next((m for m in available if m.startswith(family)), None)
134
+ if hit:
135
+ return hit
136
+
137
+ # 3. Priority-prefix list
138
+ for prefix in _PREFERRED_PREFIXES:
139
+ hit = next((m for m in available if m.startswith(prefix)), None)
140
+ if hit:
141
+ return hit
142
+
143
+ # 4. First available
144
+ return available[0]
145
+
146
+
147
+ def list_ollama_models(base_url: str) -> List[str]:
148
+ """Return all model names from Ollama /api/tags (sync)."""
149
+ try:
150
+ with urllib.request.urlopen(base_url.rstrip("/") + "/api/tags", timeout=3) as r:
151
+ return [m["name"] for m in json.loads(r.read()).get("models", [])]
152
+ except Exception:
153
+ return []
154
+
155
+ try:
156
+ import aiohttp
157
+ _HAS_AIOHTTP = True
158
+ except ImportError:
159
+ _HAS_AIOHTTP = False
160
+
161
+ from aria_code.model_capability import (
162
+ ModelCapability,
163
+ get_model_capability,
164
+ build_ollama_tool_payload,
165
+ build_tool_system_prompt,
166
+ parse_tool_calls_from_response,
167
+ )
168
+
169
+
170
+ # ---------------------------------------------------------------------------
171
+ # Backend definitions
172
+ # ---------------------------------------------------------------------------
173
+
174
+ BACKEND_DEFAULTS: Dict[str, Dict[str, Any]] = {
175
+ "ollama": {"default_url": "http://localhost:11434", "api_path": "/api/chat", "protocol": "ollama"},
176
+ "lmstudio": {"default_url": "http://localhost:1234", "api_path": "/v1/chat/completions", "protocol": "openai"},
177
+ "vllm": {"default_url": "http://localhost:8000", "api_path": "/v1/chat/completions", "protocol": "openai"},
178
+ "llamacpp": {"default_url": "http://localhost:8080", "api_path": "/v1/chat/completions", "protocol": "openai"},
179
+ "jan": {"default_url": "http://localhost:1337", "api_path": "/v1/chat/completions", "protocol": "openai"},
180
+ "openai": {"default_url": "https://api.openai.com", "api_path": "/v1/chat/completions", "protocol": "openai"},
181
+ # Cloud providers (API key required)
182
+ "deepseek": {"default_url": "https://api.deepseek.com", "api_path": "/v1/chat/completions", "protocol": "openai"},
183
+ "groq": {"default_url": "https://api.groq.com/openai", "api_path": "/v1/chat/completions", "protocol": "openai"},
184
+ "anthropic": {"default_url": "https://api.anthropic.com", "api_path": "/v1/messages", "protocol": "anthropic"},
185
+ "together": {"default_url": "https://api.together.xyz", "api_path": "/v1/chat/completions", "protocol": "openai"},
186
+ "siliconflow": {"default_url": "https://api.siliconflow.cn", "api_path": "/v1/chat/completions", "protocol": "openai"},
187
+ "moonshot": {"default_url": "https://api.moonshot.cn/v1", "api_path": "/chat/completions", "protocol": "openai"},
188
+ # Custom user-defined OpenAI-compatible endpoint
189
+ "custom": {"default_url": "", "api_path": "/chat/completions", "protocol": "openai"},
190
+ }
191
+
192
+ LOCAL_BACKENDS = {"ollama", "lmstudio", "vllm", "llamacpp", "jan", "custom"}
193
+
194
+
195
+ def openai_models_url(base_url: str) -> str:
196
+ """Return the models endpoint for a versioned or unversioned base URL."""
197
+ base = base_url.rstrip("/")
198
+ if re.search(r"/v\d+(?:beta)?(?:/openai)?$", base):
199
+ return f"{base}/models"
200
+ return f"{base}/v1/models"
201
+
202
+
203
+ @dataclass
204
+ class LocalLLMProvider:
205
+ backend: str = "ollama" # one of BACKEND_DEFAULTS keys
206
+ base_url: str = "" # override; blank → use BACKEND_DEFAULTS default
207
+ model: str = "qwen2.5-coder:7b"
208
+ api_key: str = "" # needed for openai-compatible remotes
209
+ timeout: int = 300
210
+
211
+ # Derived from model_capability on first use
212
+ _capability: Optional[ModelCapability] = field(default=None, repr=False)
213
+
214
+ # ── constructor ────────────────────────────────────────────────────────
215
+
216
+ @classmethod
217
+ def from_config(cls, config: Dict[str, Any]) -> "LocalLLMProvider":
218
+ """Build from aria-code config dict."""
219
+ backend = config.get("local_provider", "ollama").lower()
220
+ info = BACKEND_DEFAULTS.get(backend, BACKEND_DEFAULTS["ollama"])
221
+ requested_model = config.get("model", "qwen2.5-coder:1.5b")
222
+
223
+ # Custom endpoint: user-specified base_url via /config set custom_endpoint=...
224
+ if backend == "custom":
225
+ url = config.get("custom_endpoint", "") or info["default_url"]
226
+ model = config.get("custom_model", requested_model)
227
+ api_key = config.get("local_api_key", os.getenv("LOCAL_LLM_API_KEY", ""))
228
+ return cls(backend=backend, base_url=url, model=model, api_key=api_key)
229
+
230
+ # Cloud provider backends: read API key from providers.json or env var
231
+ _cloud_env_map = {
232
+ "deepseek": "DEEPSEEK_API_KEY",
233
+ "openai": "OPENAI_API_KEY",
234
+ "anthropic": "ANTHROPIC_API_KEY",
235
+ "groq": "GROQ_API_KEY",
236
+ "together": "TOGETHER_API_KEY",
237
+ "siliconflow": "SILICONFLOW_API_KEY",
238
+ "moonshot": "MOONSHOT_API_KEY",
239
+ }
240
+ if backend in _cloud_env_map:
241
+ env_var = _cloud_env_map[backend]
242
+ api_key = (os.getenv(env_var, "")
243
+ or config.get("local_api_key", "")
244
+ or os.getenv("LOCAL_LLM_API_KEY", ""))
245
+ url = config.get("custom_endpoint") or info["default_url"]
246
+ return cls(backend=backend, base_url=url, model=requested_model, api_key=api_key)
247
+
248
+ # ``local_url`` belongs to Aria's local application server and used to
249
+ # accidentally route LM Studio/llama.cpp requests to port 8000. LLM
250
+ # endpoints have provider-specific keys, with ``llm_base_url`` as the
251
+ # shared explicit override.
252
+ provider_urls = config.get("provider_urls", {})
253
+ if not isinstance(provider_urls, dict):
254
+ provider_urls = {}
255
+ if backend == "ollama":
256
+ url = config.get("ollama_url") or info["default_url"]
257
+ else:
258
+ url = (
259
+ config.get(f"{backend}_url")
260
+ or provider_urls.get(backend)
261
+ or config.get("llm_base_url")
262
+ or info["default_url"]
263
+ )
264
+ api_key = config.get("local_api_key", os.getenv("LOCAL_LLM_API_KEY", ""))
265
+
266
+ # Resolve model at construction time so later callers always get a
267
+ # valid model name (Ollama-only: other backends manage their own catalog).
268
+ if backend == "ollama":
269
+ model = resolve_model_sync(url, requested_model)
270
+ else:
271
+ model = requested_model
272
+
273
+ return cls(backend=backend, base_url=url, model=model, api_key=api_key)
274
+
275
+ # ── capability ─────────────────────────────────────────────────────────
276
+
277
+ @property
278
+ def capability(self) -> ModelCapability:
279
+ if self._capability is None:
280
+ self._capability = get_model_capability(self.model)
281
+ return self._capability
282
+
283
+ # ── health check ───────────────────────────────────────────────────────
284
+
285
+ def is_available(self) -> bool:
286
+ """Quick synchronous check that the backend is reachable."""
287
+ info = BACKEND_DEFAULTS.get(self.backend, BACKEND_DEFAULTS["ollama"])
288
+ url = (self.base_url or info["default_url"]).rstrip("/")
289
+ probe = (
290
+ f"{url}/api/tags" if self.backend == "ollama"
291
+ else openai_models_url(url)
292
+ )
293
+ try:
294
+ with urllib.request.urlopen(probe, timeout=3) as r:
295
+ return r.status == 200
296
+ except Exception:
297
+ return False
298
+
299
+ def list_models(self) -> List[str]:
300
+ """Return available model names from backend."""
301
+ info = BACKEND_DEFAULTS.get(self.backend, BACKEND_DEFAULTS["ollama"])
302
+ url = (self.base_url or info["default_url"]).rstrip("/")
303
+ try:
304
+ if self.backend == "ollama":
305
+ with urllib.request.urlopen(f"{url}/api/tags", timeout=3) as r:
306
+ data = json.loads(r.read())
307
+ return [m["name"] for m in data.get("models", [])]
308
+ else:
309
+ req = urllib.request.Request(openai_models_url(url))
310
+ if self.api_key:
311
+ req.add_header("Authorization", f"Bearer {self.api_key}")
312
+ with urllib.request.urlopen(req, timeout=3) as r:
313
+ data = json.loads(r.read())
314
+ return [m["id"] for m in data.get("data", [])]
315
+ except Exception:
316
+ return []
317
+
318
+ # ── streaming core ─────────────────────────────────────────────────────
319
+
320
+ async def stream(
321
+ self,
322
+ messages: List[Dict[str, Any]],
323
+ tools: Optional[List[Dict]] = None,
324
+ *,
325
+ temperature: Optional[float] = None,
326
+ max_tokens: Optional[int] = None,
327
+ cancel_event=None,
328
+ ) -> AsyncIterator[Dict[str, Any]]:
329
+ """
330
+ Unified async generator. Yields event dicts::
331
+
332
+ {"type": "token", "text": str}
333
+ {"type": "thinking", "text": str} # for reasoning models
334
+ {"type": "tool_call", "name": str, "arguments": dict, "id": str}
335
+ {"type": "done", "usage": dict, "stop_reason": str}
336
+ {"type": "error", "message": str}
337
+ """
338
+ if not _HAS_AIOHTTP:
339
+ yield {"type": "error", "message": "aiohttp not installed: pip install aiohttp"}
340
+ return
341
+
342
+ info = BACKEND_DEFAULTS.get(self.backend, BACKEND_DEFAULTS["ollama"])
343
+ url = (self.base_url or info["default_url"]).rstrip("/") + info["api_path"]
344
+ protocol = info["protocol"]
345
+ cap = self.capability
346
+
347
+ temp = temperature if temperature is not None else cap.temperature
348
+ mtoks = max_tokens if max_tokens is not None else 4096
349
+
350
+ # Inject tool system prompt for text-only models
351
+ if tools and not cap.tool_calls:
352
+ tool_sys = build_tool_system_prompt(tools, self.model)
353
+ if tool_sys:
354
+ sys_msgs = [m for m in messages if m.get("role") == "system"]
355
+ if sys_msgs:
356
+ messages[0]["content"] = messages[0]["content"] + tool_sys
357
+ else:
358
+ messages = [{"role": "system", "content": tool_sys.strip()}] + messages
359
+
360
+ if protocol == "ollama":
361
+ async for ev in self._stream_ollama(url, messages, tools, temp, mtoks, cap, cancel_event):
362
+ yield ev
363
+ else:
364
+ async for ev in self._stream_openai(url, messages, tools, temp, mtoks, cap, cancel_event):
365
+ yield ev
366
+
367
+ # ── Ollama protocol ────────────────────────────────────────────────────
368
+
369
+ async def _stream_ollama(self, url, messages, tools, temp, max_tokens, cap, cancel_event):
370
+ # Re-resolve the model async before every call — handles the case where
371
+ # the model was changed or wasn't available at construction time.
372
+ base_url = url.rsplit("/api/chat", 1)[0]
373
+ model = await resolve_model_async(base_url, self.model)
374
+ if model != self.model:
375
+ self.model = model # keep in sync for future calls
376
+ self._capability = None # reset capability cache for new model
377
+
378
+ native_tools = build_ollama_tool_payload(tools or [], self.model) if tools else None
379
+ payload: Dict[str, Any] = {
380
+ "model": self.model,
381
+ "messages": messages,
382
+ "stream": True,
383
+ "options": {
384
+ "num_ctx": cap.context_window,
385
+ "temperature": temp,
386
+ "top_p": cap.top_p,
387
+ "repeat_penalty": 1.15,
388
+ "repeat_last_n": 128,
389
+ "num_predict": max_tokens,
390
+ },
391
+ }
392
+ if native_tools:
393
+ payload["tools"] = native_tools
394
+
395
+ usage = {"prompt_tokens": 0, "completion_tokens": 0}
396
+ full_text = ""
397
+
398
+ try:
399
+ async with aiohttp.ClientSession() as sess:
400
+ async with sess.post(url, json=payload,
401
+ timeout=aiohttp.ClientTimeout(total=self.timeout)) as resp:
402
+ if resp.status != 200:
403
+ body = await resp.text()
404
+ yield {"type": "error", "message": f"Ollama HTTP {resp.status}: {body[:200]}"}
405
+ return
406
+
407
+ async for line in resp.content:
408
+ if cancel_event and cancel_event.is_set():
409
+ yield {"type": "done", "usage": usage, "stop_reason": "cancelled"}
410
+ return
411
+
412
+ text = line.decode("utf-8", errors="ignore").strip()
413
+ if not text:
414
+ continue
415
+ try:
416
+ data = json.loads(text)
417
+ except json.JSONDecodeError:
418
+ continue
419
+
420
+ msg = data.get("message", {})
421
+
422
+ # Native tool calls
423
+ if msg.get("tool_calls"):
424
+ for tc in msg["tool_calls"]:
425
+ fn = tc.get("function", {})
426
+ name = fn.get("name", "")
427
+ args = fn.get("arguments", {})
428
+ if isinstance(args, str):
429
+ try:
430
+ args = json.loads(args)
431
+ except Exception:
432
+ args = {}
433
+ if name:
434
+ yield {"type": "tool_call", "name": name,
435
+ "arguments": args, "id": tc.get("id", "")}
436
+
437
+ if data.get("done"):
438
+ usage["prompt_tokens"] += data.get("prompt_eval_count", 0)
439
+ usage["completion_tokens"] += data.get("eval_count", 0)
440
+ break
441
+
442
+ token = msg.get("content", "")
443
+ if token:
444
+ full_text += token
445
+ # Suppress tokens that are part of <tool_call> tags
446
+ if not full_text.lstrip().startswith("<tool_call"):
447
+ yield {"type": "token", "text": token}
448
+
449
+ except Exception as exc:
450
+ yield {"type": "error", "message": f"Ollama stream error: {exc}"}
451
+ return
452
+
453
+ # Fallback: parse text-based tool calls
454
+ text_calls = parse_tool_calls_from_response(full_text, model_name=self.model)
455
+ for tc in text_calls:
456
+ yield {"type": "tool_call", "name": tc["tool"],
457
+ "arguments": tc["params"], "id": ""}
458
+
459
+ yield {"type": "done", "usage": usage, "stop_reason": "stop"}
460
+
461
+ # ── OpenAI-compatible protocol ─────────────────────────────────────────
462
+
463
+ async def _stream_openai(self, url, messages, tools, temp, max_tokens, cap, cancel_event):
464
+ headers: Dict[str, str] = {"Content-Type": "application/json"}
465
+ if self.api_key:
466
+ headers["Authorization"] = f"Bearer {self.api_key}"
467
+
468
+ payload: Dict[str, Any] = {
469
+ "model": self.model,
470
+ "messages": messages,
471
+ "stream": True,
472
+ "temperature": temp,
473
+ "max_tokens": max_tokens,
474
+ }
475
+ if tools and cap.tool_calls:
476
+ payload["tools"] = tools
477
+ payload["tool_choice"] = "auto"
478
+
479
+ usage = {"prompt_tokens": 0, "completion_tokens": 0}
480
+ full_text = ""
481
+ tool_call_accumulator: Dict[int, Dict] = {} # index → partial call
482
+
483
+ try:
484
+ async with aiohttp.ClientSession(headers=headers) as sess:
485
+ async with sess.post(url, json=payload,
486
+ timeout=aiohttp.ClientTimeout(total=self.timeout)) as resp:
487
+ if resp.status != 200:
488
+ body = await resp.text()
489
+ yield {"type": "error", "message": f"HTTP {resp.status}: {body[:200]}"}
490
+ return
491
+
492
+ async for line in resp.content:
493
+ if cancel_event and cancel_event.is_set():
494
+ yield {"type": "done", "usage": usage, "stop_reason": "cancelled"}
495
+ return
496
+
497
+ raw = line.decode("utf-8", errors="ignore").strip()
498
+ if not raw or raw == "data: [DONE]":
499
+ continue
500
+ if raw.startswith("data: "):
501
+ raw = raw[6:]
502
+ try:
503
+ data = json.loads(raw)
504
+ except json.JSONDecodeError:
505
+ continue
506
+
507
+ # Usage from final chunk
508
+ if data.get("usage"):
509
+ u = data["usage"]
510
+ usage["prompt_tokens"] = u.get("prompt_tokens", 0)
511
+ usage["completion_tokens"] = u.get("completion_tokens", 0)
512
+
513
+ choices = data.get("choices", [])
514
+ if not choices:
515
+ continue
516
+ delta = choices[0].get("delta", {})
517
+
518
+ # Token
519
+ content = delta.get("content", "")
520
+ if content:
521
+ full_text += content
522
+ yield {"type": "token", "text": content}
523
+
524
+ # Tool calls (streamed fragments)
525
+ for tc_delta in (delta.get("tool_calls") or []):
526
+ idx = tc_delta.get("index", 0)
527
+ if idx not in tool_call_accumulator:
528
+ tool_call_accumulator[idx] = {
529
+ "id": "", "name": "", "arguments": ""}
530
+ acc = tool_call_accumulator[idx]
531
+ acc["id"] += tc_delta.get("id", "")
532
+ fn = tc_delta.get("function", {})
533
+ acc["name"] += fn.get("name", "")
534
+ acc["arguments"] += fn.get("arguments", "")
535
+
536
+ fin_reason = choices[0].get("finish_reason")
537
+ if fin_reason in ("stop", "tool_calls", "length"):
538
+ break
539
+
540
+ except Exception as exc:
541
+ yield {"type": "error", "message": f"OpenAI-compat stream error: {exc}"}
542
+ return
543
+
544
+ # Emit accumulated tool calls
545
+ for acc in tool_call_accumulator.values():
546
+ args_str = acc.get("arguments", "{}")
547
+ try:
548
+ args = json.loads(args_str)
549
+ except Exception:
550
+ args = {}
551
+ if acc.get("name"):
552
+ yield {"type": "tool_call", "name": acc["name"],
553
+ "arguments": args, "id": acc.get("id", "")}
554
+
555
+ # Fallback text-based tool call parsing (for non-native models)
556
+ if not tool_call_accumulator:
557
+ text_calls = parse_tool_calls_from_response(full_text, model_name=self.model)
558
+ for tc in text_calls:
559
+ yield {"type": "tool_call", "name": tc["tool"],
560
+ "arguments": tc["params"], "id": ""}
561
+
562
+ yield {"type": "done", "usage": usage, "stop_reason": "stop"}
563
+
564
+
565
+ # ---------------------------------------------------------------------------
566
+ # Quick availability probe (synchronous, for startup checks)
567
+ # ---------------------------------------------------------------------------
568
+
569
+ def probe_all_backends() -> Dict[str, bool]:
570
+ """Return {backend: is_reachable} for all known backends."""
571
+ results = {}
572
+ for name, info in BACKEND_DEFAULTS.items():
573
+ url = info["default_url"]
574
+ probe = f"{url}/api/tags" if name == "ollama" else f"{url}/v1/models"
575
+ try:
576
+ with urllib.request.urlopen(probe, timeout=1) as r:
577
+ results[name] = r.status == 200
578
+ except Exception:
579
+ results[name] = False
580
+ return results
581
+
582
+
583
+ def probe_local_backends() -> Dict[str, bool]:
584
+ """Probe only desktop/local runtimes, never paid cloud APIs."""
585
+ return {
586
+ name: LocalLLMProvider(backend=name).is_available()
587
+ for name in LOCAL_BACKENDS
588
+ if name != "custom"
589
+ }
@@ -0,0 +1,4 @@
1
+ """Compatibility import for :mod:`tools.realty_data_tools`."""
2
+ from importlib import import_module as _import_module
3
+ import sys as _sys
4
+ _sys.modules[__name__] = _import_module("tools.realty_data_tools")