aria-code 0.167.0__py3-none-win_amd64.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 (601) hide show
  1. aria_bots/__init__.py +7 -0
  2. aria_bots/channels/README.md +97 -0
  3. aria_bots/channels/__init__.py +1 -0
  4. aria_bots/channels/approvals.py +263 -0
  5. aria_bots/channels/conversation.py +280 -0
  6. aria_bots/channels/digest.py +186 -0
  7. aria_bots/channels/registry.py +95 -0
  8. aria_bots/engine.py +144 -0
  9. aria_bots/feishu_bot.py +1385 -0
  10. aria_bots/logistics/__init__.py +1 -0
  11. aria_bots/logistics/carriers.py +253 -0
  12. aria_bots/logistics/freight.py +154 -0
  13. aria_bots/logistics/inventory.py +242 -0
  14. aria_bots/logistics/mcp_server.py +156 -0
  15. aria_bots/logistics/records.py +119 -0
  16. aria_bots/logistics/tenancy.py +152 -0
  17. aria_bots/market.py +91 -0
  18. aria_bots/relay_client.py +326 -0
  19. aria_bots/telegram_bot.py +299 -0
  20. aria_code/__init__.py +111 -0
  21. aria_code/_bin/aria-native.exe +0 -0
  22. aria_code/_bin/aria-runtime.exe +0 -0
  23. aria_code/_version.py +20 -0
  24. aria_code/account_memory.py +296 -0
  25. aria_code/account_session.py +166 -0
  26. aria_code/adk_apps/__init__.py +1 -0
  27. aria_code/adk_apps/aria_code_review/__init__.py +1 -0
  28. aria_code/adk_apps/aria_code_review/agent.py +32 -0
  29. aria_code/adk_apps/aria_research/__init__.py +1 -0
  30. aria_code/adk_apps/aria_research/agent.py +31 -0
  31. aria_code/agents/__init__.py +49 -0
  32. aria_code/agents/base.py +329 -0
  33. aria_code/agents/code_review.py +200 -0
  34. aria_code/agents/deep/__init__.py +37 -0
  35. aria_code/agents/deep/calibration_loop.py +144 -0
  36. aria_code/agents/deep/critic.py +125 -0
  37. aria_code/agents/deep/deepen.py +193 -0
  38. aria_code/agents/deep/models.py +149 -0
  39. aria_code/agents/deep/pipeline.py +164 -0
  40. aria_code/agents/deep/quant_fusion.py +192 -0
  41. aria_code/agents/deep/themes.py +95 -0
  42. aria_code/agents/deep/tiers.py +106 -0
  43. aria_code/agents/engineering/__init__.py +14 -0
  44. aria_code/agents/engineering/coder.py +222 -0
  45. aria_code/agents/engineering/tester.py +145 -0
  46. aria_code/agents/financial/__init__.py +21 -0
  47. aria_code/agents/financial/cashflow_burn.py +119 -0
  48. aria_code/agents/financial/catalyst.py +279 -0
  49. aria_code/agents/financial/corporate_finance.py +175 -0
  50. aria_code/agents/financial/debate.py +224 -0
  51. aria_code/agents/financial/earnings.py +315 -0
  52. aria_code/agents/financial/fundamental.py +178 -0
  53. aria_code/agents/financial/macro.py +111 -0
  54. aria_code/agents/financial/news.py +231 -0
  55. aria_code/agents/financial/risk.py +146 -0
  56. aria_code/agents/financial/sector.py +279 -0
  57. aria_code/agents/financial/strategist.py +121 -0
  58. aria_code/agents/financial/stripe_revenue.py +127 -0
  59. aria_code/agents/financial/synthesis.py +274 -0
  60. aria_code/agents/financial/technical.py +277 -0
  61. aria_code/agents/orchestrator.py +44 -0
  62. aria_code/agents/portfolio_agent.py +392 -0
  63. aria_code/agents/realty/__init__.py +62 -0
  64. aria_code/agents/realty/asset_diagnosis.py +152 -0
  65. aria_code/agents/realty/business_match.py +166 -0
  66. aria_code/agents/realty/cashflow_verify.py +209 -0
  67. aria_code/agents/realty/contract_rules.py +210 -0
  68. aria_code/agents/realty/energy_anomaly.py +189 -0
  69. aria_code/agents/realty/exit_settlement.py +208 -0
  70. aria_code/agents/realty/fulfillment_risk.py +206 -0
  71. aria_code/agents/realty/ops_optimize.py +160 -0
  72. aria_code/agents/realty/revenue_share.py +215 -0
  73. aria_code/agents/registry.py +168 -0
  74. aria_code/agents/signal_scheme.py +111 -0
  75. aria_code/agents/sports/__init__.py +0 -0
  76. aria_code/agents/sports/football_agent.py +197 -0
  77. aria_code/agents/supervisor.py +113 -0
  78. aria_code/agents/team.py +500 -0
  79. aria_code/agents/video/__init__.py +16 -0
  80. aria_code/agents/video/director.py +376 -0
  81. aria_code/agents/video/qa_agent.py +143 -0
  82. aria_code/agents/video/script_doctor.py +112 -0
  83. aria_code/agents/warehouse/__init__.py +25 -0
  84. aria_code/agents/warehouse/contracts.py +26 -0
  85. aria_code/agents/warehouse/dashboard.py +155 -0
  86. aria_code/agents/warehouse/fulfillment_leadtime.py +95 -0
  87. aria_code/agents/warehouse/inbound_exceptions.py +25 -0
  88. aria_code/agents/warehouse/inventory_health.py +24 -0
  89. aria_code/agents/warehouse/local_run.py +71 -0
  90. aria_code/agents/warehouse/local_snapshot.py +132 -0
  91. aria_code/agents/warehouse/logistics_cost.py +84 -0
  92. aria_code/agents/warehouse/logistics_sync.py +21 -0
  93. aria_code/agents/warehouse/navigation.schema.json +110 -0
  94. aria_code/agents/warehouse/task_agent.py +172 -0
  95. aria_code/agents/warehouse/workflow.py +31 -0
  96. aria_code/aliyun_data_client.py +4 -0
  97. aria_code/apps/README.md +12 -0
  98. aria_code/apps/__init__.py +2 -0
  99. aria_code/apps/channels/README.md +9 -0
  100. aria_code/apps/channels/__init__.py +17 -0
  101. aria_code/apps/channels/approvals.py +11 -0
  102. aria_code/apps/channels/conversation.py +11 -0
  103. aria_code/apps/channels/digest.py +11 -0
  104. aria_code/apps/channels/intake.py +92 -0
  105. aria_code/apps/channels/registry.py +11 -0
  106. aria_code/apps/channels/tradingview.py +94 -0
  107. aria_code/apps/cli/README.md +13 -0
  108. aria_code/apps/cli/__init__.py +2 -0
  109. aria_code/apps/cli/_optional.py +120 -0
  110. aria_code/apps/cli/app_server.py +656 -0
  111. aria_code/apps/cli/auto_update.py +125 -0
  112. aria_code/apps/cli/bootstrap.py +206 -0
  113. aria_code/apps/cli/broker_render.py +206 -0
  114. aria_code/apps/cli/chat_turn.py +1327 -0
  115. aria_code/apps/cli/cloud_models.py +231 -0
  116. aria_code/apps/cli/codegen_paths.py +29 -0
  117. aria_code/apps/cli/commands/__init__.py +34 -0
  118. aria_code/apps/cli/commands/_business_render.py +78 -0
  119. aria_code/apps/cli/commands/_ui.py +71 -0
  120. aria_code/apps/cli/commands/analysis_cmds.py +378 -0
  121. aria_code/apps/cli/commands/ashare_prediction_cmds.py +327 -0
  122. aria_code/apps/cli/commands/auth_cmds.py +215 -0
  123. aria_code/apps/cli/commands/backtest_cmds.py +2518 -0
  124. aria_code/apps/cli/commands/broker_cmds.py +1236 -0
  125. aria_code/apps/cli/commands/business_workflow_cmds.py +379 -0
  126. aria_code/apps/cli/commands/canvas_cmds.py +77 -0
  127. aria_code/apps/cli/commands/catalog.py +189 -0
  128. aria_code/apps/cli/commands/core_cmds.py +2748 -0
  129. aria_code/apps/cli/commands/data_cmds.py +521 -0
  130. aria_code/apps/cli/commands/diagnostic_cmds.py +285 -0
  131. aria_code/apps/cli/commands/diagnostic_ops_cmds.py +813 -0
  132. aria_code/apps/cli/commands/engineering_cmds.py +399 -0
  133. aria_code/apps/cli/commands/file_cmds.py +312 -0
  134. aria_code/apps/cli/commands/finance_service_cmds.py +60 -0
  135. aria_code/apps/cli/commands/fx_commodity_cmds.py +123 -0
  136. aria_code/apps/cli/commands/market.py +544 -0
  137. aria_code/apps/cli/commands/market_cmds.py +1419 -0
  138. aria_code/apps/cli/commands/market_context.py +432 -0
  139. aria_code/apps/cli/commands/market_render.py +7 -0
  140. aria_code/apps/cli/commands/model_cmds.py +2374 -0
  141. aria_code/apps/cli/commands/ops_cmds.py +721 -0
  142. aria_code/apps/cli/commands/orchestrator_cmds.py +181 -0
  143. aria_code/apps/cli/commands/pdf_export_cmds.py +125 -0
  144. aria_code/apps/cli/commands/portfolio_cmds.py +1316 -0
  145. aria_code/apps/cli/commands/report.py +476 -0
  146. aria_code/apps/cli/commands/scaffold_templates.py +617 -0
  147. aria_code/apps/cli/commands/session_cmds.py +236 -0
  148. aria_code/apps/cli/commands/session_ux_cmds.py +359 -0
  149. aria_code/apps/cli/commands/team.py +723 -0
  150. aria_code/apps/cli/commands/team_render.py +8 -0
  151. aria_code/apps/cli/commands/ui_cmds.py +401 -0
  152. aria_code/apps/cli/commands/warehouse_cmds.py +297 -0
  153. aria_code/apps/cli/commands/workflow_cmds.py +629 -0
  154. aria_code/apps/cli/commands/workspace_cmds.py +1543 -0
  155. aria_code/apps/cli/config_paths.py +73 -0
  156. aria_code/apps/cli/config_store.py +158 -0
  157. aria_code/apps/cli/constants.py +88 -0
  158. aria_code/apps/cli/context.py +34 -0
  159. aria_code/apps/cli/conversation_policy.py +53 -0
  160. aria_code/apps/cli/deterministic.py +200 -0
  161. aria_code/apps/cli/direct.py +48 -0
  162. aria_code/apps/cli/earnings_view.py +172 -0
  163. aria_code/apps/cli/endpoint_migration.py +64 -0
  164. aria_code/apps/cli/exec_events.py +255 -0
  165. aria_code/apps/cli/file_references.py +155 -0
  166. aria_code/apps/cli/finance_service_catalog.py +111 -0
  167. aria_code/apps/cli/football_reports.py +231 -0
  168. aria_code/apps/cli/github_app_auth.py +135 -0
  169. aria_code/apps/cli/google_login.py +239 -0
  170. aria_code/apps/cli/handlers/__init__.py +11 -0
  171. aria_code/apps/cli/handlers/broker_handlers.py +122 -0
  172. aria_code/apps/cli/handlers/chart_handlers.py +1314 -0
  173. aria_code/apps/cli/handlers/market_handlers.py +2570 -0
  174. aria_code/apps/cli/handlers/realty_handlers.py +114 -0
  175. aria_code/apps/cli/handlers/sports_handlers.py +22 -0
  176. aria_code/apps/cli/handlers/strategy_advice.py +82 -0
  177. aria_code/apps/cli/headless.py +352 -0
  178. aria_code/apps/cli/helpers.py +326 -0
  179. aria_code/apps/cli/hooks.py +180 -0
  180. aria_code/apps/cli/i18n.py +310 -0
  181. aria_code/apps/cli/intent.py +136 -0
  182. aria_code/apps/cli/intent_router.py +305 -0
  183. aria_code/apps/cli/intent_shadow.py +72 -0
  184. aria_code/apps/cli/intent_signals.py +347 -0
  185. aria_code/apps/cli/lifecycle_hooks.py +48 -0
  186. aria_code/apps/cli/local_tool_schemas.py +550 -0
  187. aria_code/apps/cli/main.py +39 -0
  188. aria_code/apps/cli/market_hours.py +100 -0
  189. aria_code/apps/cli/market_metadata.py +135 -0
  190. aria_code/apps/cli/market_universe.py +487 -0
  191. aria_code/apps/cli/message_processing.py +310 -0
  192. aria_code/apps/cli/mixin_binding.py +32 -0
  193. aria_code/apps/cli/model_catalog.py +652 -0
  194. aria_code/apps/cli/model_probe.py +169 -0
  195. aria_code/apps/cli/model_retirement.py +65 -0
  196. aria_code/apps/cli/native_branding.py +12 -0
  197. aria_code/apps/cli/native_bridge.py +118 -0
  198. aria_code/apps/cli/native_entry.py +58 -0
  199. aria_code/apps/cli/pdf_report.py +977 -0
  200. aria_code/apps/cli/plan_mode.py +139 -0
  201. aria_code/apps/cli/plotly_html.py +15 -0
  202. aria_code/apps/cli/prediction_feedback.py +202 -0
  203. aria_code/apps/cli/preflight.py +502 -0
  204. aria_code/apps/cli/project_aria.py +60 -0
  205. aria_code/apps/cli/prompt_assembly.py +78 -0
  206. aria_code/apps/cli/prompts/__init__.py +0 -0
  207. aria_code/apps/cli/prompts/coding.py +71 -0
  208. aria_code/apps/cli/prompts/select.py +193 -0
  209. aria_code/apps/cli/prompts/system_prompts.py +562 -0
  210. aria_code/apps/cli/prompts/ui.py +436 -0
  211. aria_code/apps/cli/provider_endpoints.py +237 -0
  212. aria_code/apps/cli/provider_keys.py +73 -0
  213. aria_code/apps/cli/providers/__init__.py +1 -0
  214. aria_code/apps/cli/providers/base.py +775 -0
  215. aria_code/apps/cli/providers/chat_routing.py +139 -0
  216. aria_code/apps/cli/providers/collaboration.py +98 -0
  217. aria_code/apps/cli/providers/llm/__init__.py +1 -0
  218. aria_code/apps/cli/providers/llm/offline_responses.py +68 -0
  219. aria_code/apps/cli/providers/llm/ollama_stream.py +1574 -0
  220. aria_code/apps/cli/providers/llm/response_cache.py +60 -0
  221. aria_code/apps/cli/providers/llm/sse_stream.py +298 -0
  222. aria_code/apps/cli/providers/runtime_bridge.py +586 -0
  223. aria_code/apps/cli/providers/vertexai_stream.py +592 -0
  224. aria_code/apps/cli/release_download.py +159 -0
  225. aria_code/apps/cli/review_runner.py +111 -0
  226. aria_code/apps/cli/runtime_consumer.py +769 -0
  227. aria_code/apps/cli/sandbox.py +32 -0
  228. aria_code/apps/cli/session_export.py +120 -0
  229. aria_code/apps/cli/session_jsonl.py +208 -0
  230. aria_code/apps/cli/session_refresh.py +123 -0
  231. aria_code/apps/cli/session_store.py +125 -0
  232. aria_code/apps/cli/skills_catalog.py +273 -0
  233. aria_code/apps/cli/task_isolation.py +211 -0
  234. aria_code/apps/cli/terminal_chrome.py +412 -0
  235. aria_code/apps/cli/todo_tracker.py +190 -0
  236. aria_code/apps/cli/tool_executor.py +932 -0
  237. aria_code/apps/cli/tool_registry.py +34 -0
  238. aria_code/apps/cli/tool_scope.py +113 -0
  239. aria_code/apps/cli/tools/__init__.py +40 -0
  240. aria_code/apps/cli/tools/context.py +46 -0
  241. aria_code/apps/cli/tools/file_tools.py +181 -0
  242. aria_code/apps/cli/tools/market_tools.py +626 -0
  243. aria_code/apps/cli/tools/notebook_tools.py +111 -0
  244. aria_code/apps/cli/tools/system_tools.py +656 -0
  245. aria_code/apps/cli/tools/write_tools.py +961 -0
  246. aria_code/apps/cli/tradingview_bridge.py +529 -0
  247. aria_code/apps/cli/transactions.py +269 -0
  248. aria_code/apps/cli/turn_planning.py +137 -0
  249. aria_code/apps/cli/update_check.py +257 -0
  250. aria_code/apps/cli/updater.py +373 -0
  251. aria_code/apps/cli/utils/__init__.py +0 -0
  252. aria_code/apps/cli/utils/market_detect.py +1663 -0
  253. aria_code/apps/cli/web_login.py +136 -0
  254. aria_code/apps/cli/workflow_runner.py +127 -0
  255. aria_code/apps/cli/workspace_route.py +121 -0
  256. aria_code/apps/daemon/README.md +14 -0
  257. aria_code/apps/vscode/README.md +115 -0
  258. aria_code/apps/vscode/package.json +70 -0
  259. aria_code/aria_cli.py +5244 -0
  260. aria_code/aria_daemon.py +1372 -0
  261. aria_code/aria_feishu_bot.py +16 -0
  262. aria_code/aria_mcp_server.py +61 -0
  263. aria_code/aria_relay_client.py +4 -0
  264. aria_code/aria_telegram_bot.py +11 -0
  265. aria_code/ariarc.py +385 -0
  266. aria_code/artifacts.py +581 -0
  267. aria_code/backtest_engine.py +4 -0
  268. aria_code/backtest_report.py +4 -0
  269. aria_code/brokers/__init__.py +78 -0
  270. aria_code/brokers/automation.py +193 -0
  271. aria_code/brokers/base.py +231 -0
  272. aria_code/brokers/capabilities.py +264 -0
  273. aria_code/brokers/cn/__init__.py +10 -0
  274. aria_code/brokers/cn/easytrader_broker.py +193 -0
  275. aria_code/brokers/cn/futu_broker.py +194 -0
  276. aria_code/brokers/cn/longbridge_broker.py +190 -0
  277. aria_code/brokers/cn/tiger_broker.py +196 -0
  278. aria_code/brokers/cn/xtquant_broker.py +225 -0
  279. aria_code/brokers/config.py +414 -0
  280. aria_code/brokers/intl/__init__.py +5 -0
  281. aria_code/brokers/intl/alpaca_broker.py +183 -0
  282. aria_code/brokers/intl/ibkr_broker.py +215 -0
  283. aria_code/brokers/intl/trading212_broker.py +183 -0
  284. aria_code/brokers/intl/webull_broker.py +156 -0
  285. aria_code/brokers/paper_broker.py +266 -0
  286. aria_code/brokers/planning.py +296 -0
  287. aria_code/brokers/registry.py +213 -0
  288. aria_code/brokers/trading.py +267 -0
  289. aria_code/canva_client.py +4 -0
  290. aria_code/change_store.py +127 -0
  291. aria_code/clients/__init__.py +0 -0
  292. aria_code/clients/aliyun_data_client.py +660 -0
  293. aria_code/clients/aria_relay_client.py +16 -0
  294. aria_code/clients/canva_client.py +284 -0
  295. aria_code/clients/figma_client.py +100 -0
  296. aria_code/clients/football_data_client.py +1671 -0
  297. aria_code/clients/kling_video_client.py +182 -0
  298. aria_code/clients/market_data_client.py +1985 -0
  299. aria_code/clients/openai_image_client.py +242 -0
  300. aria_code/clients/runway_video_client.py +157 -0
  301. aria_code/clients/warehouse_erp_client.py +105 -0
  302. aria_code/cloud_memory.py +190 -0
  303. aria_code/command_safety.py +19 -0
  304. aria_code/computer_use_server.py +276 -0
  305. aria_code/computer_use_tools.py +4 -0
  306. aria_code/dashboard_generator.py +579 -0
  307. aria_code/data_analysis_tools.py +4 -0
  308. aria_code/data_cleaner.py +483 -0
  309. aria_code/data_service.py +513 -0
  310. aria_code/datasources/__init__.py +23 -0
  311. aria_code/datasources/base.py +166 -0
  312. aria_code/datasources/router.py +253 -0
  313. aria_code/datasources/sec_statements.py +217 -0
  314. aria_code/datasources/sources/__init__.py +15 -0
  315. aria_code/datasources/sources/akshare_source.py +269 -0
  316. aria_code/datasources/sources/alpha_vantage_source.py +203 -0
  317. aria_code/datasources/sources/edgar_source.py +219 -0
  318. aria_code/datasources/sources/finnhub_source.py +198 -0
  319. aria_code/datasources/sources/fred_source.py +223 -0
  320. aria_code/datasources/sources/tushare_source.py +143 -0
  321. aria_code/datasources/sources/web_scraper_source.py +278 -0
  322. aria_code/datasources/sources/world_bank_source.py +206 -0
  323. aria_code/datasources/sources/yfinance_source.py +152 -0
  324. aria_code/doctor.py +860 -0
  325. aria_code/domain/__init__.py +0 -0
  326. aria_code/domain/_finance_core/__init__.py +5 -0
  327. aria_code/domain/_finance_core/backtest_engine.py +325 -0
  328. aria_code/domain/_finance_core/performance_stats.py +101 -0
  329. aria_code/domain/backtest_engine.py +75 -0
  330. aria_code/domain/backtest_report.py +488 -0
  331. aria_code/domain/finance_formulas.py +389 -0
  332. aria_code/domain/performance_stats.py +6 -0
  333. aria_code/domain/portfolio_ledger.py +307 -0
  334. aria_code/domain/strategy_vault.py +421 -0
  335. aria_code/evals/__init__.py +37 -0
  336. aria_code/evals/behavior.py +113 -0
  337. aria_code/evals/gap.py +93 -0
  338. aria_code/evals/harness.py +949 -0
  339. aria_code/evals/review_probe.py +132 -0
  340. aria_code/evals/runner.py +425 -0
  341. aria_code/evals/scorecard.py +322 -0
  342. aria_code/evals/summary.py +125 -0
  343. aria_code/external_agent_runner.py +115 -0
  344. aria_code/figma_client.py +4 -0
  345. aria_code/file_analysis_tools.py +4 -0
  346. aria_code/finance_formulas.py +4 -0
  347. aria_code/football_data_client.py +4 -0
  348. aria_code/image_gen_tools.py +4 -0
  349. aria_code/intent_classifier.py +459 -0
  350. aria_code/kling_video_client.py +4 -0
  351. aria_code/licensing.py +151 -0
  352. aria_code/local_finance_tools.py +4 -0
  353. aria_code/local_image_provider.py +4 -0
  354. aria_code/local_llm_provider.py +4 -0
  355. aria_code/macro_tools.py +4 -0
  356. aria_code/markdown_pdf.py +289 -0
  357. aria_code/market_data_client.py +4 -0
  358. aria_code/mcp_client.py +744 -0
  359. aria_code/memory_manager.py +273 -0
  360. aria_code/model_capability.py +512 -0
  361. aria_code/notification_tools.py +4 -0
  362. aria_code/openai_image_client.py +4 -0
  363. aria_code/packages/__init__.py +26 -0
  364. aria_code/packages/adk_bridge/__init__.py +11 -0
  365. aria_code/packages/adk_bridge/code_review_tools.py +49 -0
  366. aria_code/packages/adk_bridge/market_tools.py +125 -0
  367. aria_code/packages/aria_agents/__init__.py +5 -0
  368. aria_code/packages/aria_agents/manifest.py +78 -0
  369. aria_code/packages/aria_core/__init__.py +34 -0
  370. aria_code/packages/aria_core/architecture.py +269 -0
  371. aria_code/packages/aria_core/export.py +147 -0
  372. aria_code/packages/aria_core/manifest.py +65 -0
  373. aria_code/packages/aria_core/paths.py +57 -0
  374. aria_code/packages/aria_core/secure_file.py +68 -0
  375. aria_code/packages/aria_infra/__init__.py +15 -0
  376. aria_code/packages/aria_infra/arthera.py +58 -0
  377. aria_code/packages/aria_infra/doctor.py +246 -0
  378. aria_code/packages/aria_infra/product.py +40 -0
  379. aria_code/packages/aria_mcp/__init__.py +25 -0
  380. aria_code/packages/aria_mcp/bridge.py +232 -0
  381. aria_code/packages/aria_mcp/circuit.py +82 -0
  382. aria_code/packages/aria_mcp/config.py +162 -0
  383. aria_code/packages/aria_mcp/installer.py +95 -0
  384. aria_code/packages/aria_mcp/server.py +1705 -0
  385. aria_code/packages/aria_mcp/tools.py +81 -0
  386. aria_code/packages/aria_sdk/__init__.py +19 -0
  387. aria_code/packages/aria_sdk/client.py +393 -0
  388. aria_code/packages/aria_sdk/providers.py +70 -0
  389. aria_code/packages/aria_sdk/streaming.py +75 -0
  390. aria_code/packages/aria_sdk/types.py +86 -0
  391. aria_code/packages/aria_services/__init__.py +92 -0
  392. aria_code/packages/aria_services/cache.py +60 -0
  393. aria_code/packages/aria_services/context.py +356 -0
  394. aria_code/packages/aria_services/context_checkpoints.py +157 -0
  395. aria_code/packages/aria_services/data.py +11 -0
  396. aria_code/packages/aria_services/provider_health.py +304 -0
  397. aria_code/packages/aria_services/references.py +247 -0
  398. aria_code/packages/aria_services/registry.py +249 -0
  399. aria_code/packages/aria_services/research_protocol.py +137 -0
  400. aria_code/packages/aria_services/research_quality.py +186 -0
  401. aria_code/packages/aria_services/settings.py +160 -0
  402. aria_code/packages/aria_services/usage.py +151 -0
  403. aria_code/packages/aria_skills/__init__.py +46 -0
  404. aria_code/packages/aria_skills/catalog.py +149 -0
  405. aria_code/packages/aria_skills/loader.py +455 -0
  406. aria_code/packages/aria_skills/registry.py +84 -0
  407. aria_code/packages/aria_tools/__init__.py +5 -0
  408. aria_code/packages/aria_tools/financial/__init__.py +5 -0
  409. aria_code/packages/aria_tools/financial/_paths.py +92 -0
  410. aria_code/packages/aria_tools/registry.py +129 -0
  411. aria_code/packages/quant_engine/__init__.py +33 -0
  412. aria_code/packages/quant_engine/_namespace.py +24 -0
  413. aria_code/packages/quant_engine/backtest/__init__.py +10 -0
  414. aria_code/packages/quant_engine/backtest/core.py +53 -0
  415. aria_code/packages/quant_engine/backtest/engine.py +102 -0
  416. aria_code/packages/quant_engine/portfolio/__init__.py +9 -0
  417. aria_code/packages/quant_engine/portfolio/optimizer.py +85 -0
  418. aria_code/packages/quant_engine/sports/__init__.py +76 -0
  419. aria_code/packages/quant_engine/sports/calibrator.py +354 -0
  420. aria_code/packages/quant_engine/sports/dixon_coles.py +234 -0
  421. aria_code/packages/quant_engine/sports/elo.py +300 -0
  422. aria_code/packages/quant_engine/sports/form.py +188 -0
  423. aria_code/packages/quant_engine/sports/h2h.py +195 -0
  424. aria_code/packages/quant_engine/sports/predictor.py +312 -0
  425. aria_code/packages/quant_engine/sports/tracker.py +665 -0
  426. aria_code/packages/quant_engine/stochastic/__init__.py +31 -0
  427. aria_code/packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
  428. aria_code/packages/quant_engine/stochastic/ito_calculus.py +477 -0
  429. aria_code/packages/quant_engine/stochastic/kelly_criterion.py +181 -0
  430. aria_code/packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
  431. aria_code/packages/quant_engine/stochastic/options_pricing.py +576 -0
  432. aria_code/packages/quant_engine/stochastic/stochastic_processes.py +90 -0
  433. aria_code/packs/__init__.py +85 -0
  434. aria_code/packs/base.py +168 -0
  435. aria_code/packs/finance/__init__.py +250 -0
  436. aria_code/packs/healthcare/__init__.py +40 -0
  437. aria_code/packs/logistics/__init__.py +171 -0
  438. aria_code/packs/payments/__init__.py +139 -0
  439. aria_code/packs/realty/__init__.py +190 -0
  440. aria_code/packs/registry.py +202 -0
  441. aria_code/packs/rules.py +90 -0
  442. aria_code/plan_utils.py +194 -0
  443. aria_code/plugin_loader.py +328 -0
  444. aria_code/portfolio_ledger.py +4 -0
  445. aria_code/preview_server.py +449 -0
  446. aria_code/privacy/__init__.py +5 -0
  447. aria_code/privacy/feedback.py +123 -0
  448. aria_code/project_review.py +278 -0
  449. aria_code/project_review_api.py +235 -0
  450. aria_code/project_review_server.py +18 -0
  451. aria_code/project_tools.py +4 -0
  452. aria_code/providers/__init__.py +30 -0
  453. aria_code/providers/google_video_provider.py +256 -0
  454. aria_code/providers/llm/__init__.py +19 -0
  455. aria_code/providers/llm/anthropic.py +184 -0
  456. aria_code/providers/llm/arthera_gateway.py +90 -0
  457. aria_code/providers/llm/autoconfig.py +213 -0
  458. aria_code/providers/llm/base.py +139 -0
  459. aria_code/providers/llm/custom.py +158 -0
  460. aria_code/providers/llm/ollama.py +137 -0
  461. aria_code/providers/llm/openai_compat.py +350 -0
  462. aria_code/providers/llm/registry.py +457 -0
  463. aria_code/providers/local_image_provider.py +202 -0
  464. aria_code/providers/local_llm_provider.py +549 -0
  465. aria_code/providers/ollama_models.py +79 -0
  466. aria_code/providers/openai_params.py +32 -0
  467. aria_code/providers/tool_messages.py +100 -0
  468. aria_code/realty_data_tools.py +4 -0
  469. aria_code/report_exporters.py +150 -0
  470. aria_code/report_generator.py +1764 -0
  471. aria_code/review_service.py +497 -0
  472. aria_code/runtime/__init__.py +176 -0
  473. aria_code/runtime/acceptance.py +632 -0
  474. aria_code/runtime/agent_loop.py +1851 -0
  475. aria_code/runtime/agent_profiles.py +75 -0
  476. aria_code/runtime/approval.py +71 -0
  477. aria_code/runtime/baseline.py +219 -0
  478. aria_code/runtime/budget.py +232 -0
  479. aria_code/runtime/checkpoints.py +481 -0
  480. aria_code/runtime/consult.py +65 -0
  481. aria_code/runtime/contract.py +282 -0
  482. aria_code/runtime/delivery.py +432 -0
  483. aria_code/runtime/events.py +113 -0
  484. aria_code/runtime/gateway.py +167 -0
  485. aria_code/runtime/lsp.py +484 -0
  486. aria_code/runtime/native_index.py +153 -0
  487. aria_code/runtime/orchestrator.py +55 -0
  488. aria_code/runtime/processes.py +343 -0
  489. aria_code/runtime/project_graph.py +614 -0
  490. aria_code/runtime/repo_map.py +712 -0
  491. aria_code/runtime/research_bundle.py +105 -0
  492. aria_code/runtime/review.py +269 -0
  493. aria_code/runtime/run_state.py +127 -0
  494. aria_code/runtime/run_store.py +522 -0
  495. aria_code/runtime/scoped_runs.py +113 -0
  496. aria_code/runtime/self_healing.py +422 -0
  497. aria_code/runtime/semantic_diff.py +171 -0
  498. aria_code/runtime/subagent.py +721 -0
  499. aria_code/runtime/symbol_edit.py +117 -0
  500. aria_code/runtime/task_brief.py +46 -0
  501. aria_code/runtime/task_context.py +145 -0
  502. aria_code/runtime/task_ledger.py +108 -0
  503. aria_code/runtime/task_orchestrator.py +90 -0
  504. aria_code/runtime/task_outcome.py +33 -0
  505. aria_code/runtime/task_verification.py +266 -0
  506. aria_code/runtime/task_worktree.py +448 -0
  507. aria_code/runtime/tool_executor.py +329 -0
  508. aria_code/runtime/tool_policy.py +107 -0
  509. aria_code/runtime/transactions.py +216 -0
  510. aria_code/runtime/windows_job.py +115 -0
  511. aria_code/runtime/workflows.py +222 -0
  512. aria_code/runtime/worktrees.py +215 -0
  513. aria_code/runway_video_client.py +4 -0
  514. aria_code/safety/__init__.py +27 -0
  515. aria_code/safety/client_isolation.py +178 -0
  516. aria_code/safety/permissions.py +339 -0
  517. aria_code/safety/risk.py +699 -0
  518. aria_code/safety/sandbox.py +148 -0
  519. aria_code/safety/service.py +102 -0
  520. aria_code/service_endpoints.py +14 -0
  521. aria_code/setup_wizard.py +684 -0
  522. aria_code/spreadsheet_tools.py +4 -0
  523. aria_code/strategy_vault.py +4 -0
  524. aria_code/tools/__init__.py +0 -0
  525. aria_code/tools/artifact_tools.py +252 -0
  526. aria_code/tools/broker_tools.py +92 -0
  527. aria_code/tools/build_quant_engine.py +97 -0
  528. aria_code/tools/code_audit_tools.py +192 -0
  529. aria_code/tools/computer_use_tools.py +509 -0
  530. aria_code/tools/data_analysis_tools.py +831 -0
  531. aria_code/tools/enterprise_finance_tools.py +202 -0
  532. aria_code/tools/extended_tools.py +240 -0
  533. aria_code/tools/file_analysis_tools.py +880 -0
  534. aria_code/tools/image_gen_tools.py +114 -0
  535. aria_code/tools/local_finance_tools.py +3488 -0
  536. aria_code/tools/logistics_carriers.py +11 -0
  537. aria_code/tools/logistics_inventory.py +11 -0
  538. aria_code/tools/logistics_io.py +11 -0
  539. aria_code/tools/logistics_tenancy.py +11 -0
  540. aria_code/tools/logistics_tools.py +11 -0
  541. aria_code/tools/macro_tools.py +412 -0
  542. aria_code/tools/notification_tools.py +249 -0
  543. aria_code/tools/project_tools.py +525 -0
  544. aria_code/tools/realty_data_tools.py +662 -0
  545. aria_code/tools/spreadsheet_tools.py +399 -0
  546. aria_code/tools/stripe_tools.py +115 -0
  547. aria_code/tools/video_tools.py +500 -0
  548. aria_code/training/__init__.py +1 -0
  549. aria_code/training/compare.py +117 -0
  550. aria_code/training/prelude_commands.py +111 -0
  551. aria_code/training/prelude_eval.py +390 -0
  552. aria_code/training/prelude_real.py +183 -0
  553. aria_code/training/prelude_train.py +248 -0
  554. aria_code/training/sft_dataset.py +230 -0
  555. aria_code/training/trajectory_dataset.py +263 -0
  556. aria_code/ui/__init__.py +105 -0
  557. aria_code/ui/banner.py +404 -0
  558. aria_code/ui/completer.py +571 -0
  559. aria_code/ui/console.py +285 -0
  560. aria_code/ui/input_box.py +505 -0
  561. aria_code/ui/mentions.py +206 -0
  562. aria_code/ui/picker.py +248 -0
  563. aria_code/ui/render/__init__.py +11 -0
  564. aria_code/ui/render/actions.py +297 -0
  565. aria_code/ui/render/finance.py +1732 -0
  566. aria_code/ui/render/market.py +253 -0
  567. aria_code/ui/render/output.py +954 -0
  568. aria_code/ui/render/responsive.py +75 -0
  569. aria_code/ui/render/team.py +367 -0
  570. aria_code/ui/robot.py +239 -0
  571. aria_code/ui/startup_dashboard.py +110 -0
  572. aria_code/video_analysis.py +147 -0
  573. aria_code/video_editor.py +518 -0
  574. aria_code/video_workflow/__init__.py +8 -0
  575. aria_code/video_workflow/__main__.py +61 -0
  576. aria_code/video_workflow/engines/__init__.py +18 -0
  577. aria_code/video_workflow/engines/base.py +69 -0
  578. aria_code/video_workflow/engines/custom_model.py +204 -0
  579. aria_code/video_workflow/engines/google_veo.py +94 -0
  580. aria_code/video_workflow/engines/local_engine.py +71 -0
  581. aria_code/video_workflow/engines/registry.py +98 -0
  582. aria_code/video_workflow/google_models.py +278 -0
  583. aria_code/video_workflow/music/__init__.py +5 -0
  584. aria_code/video_workflow/music/foley_service.py +132 -0
  585. aria_code/video_workflow/music/service.py +212 -0
  586. aria_code/video_workflow/pipeline.py +620 -0
  587. aria_code/video_workflow/plan.py +202 -0
  588. aria_code/video_workflow/session.py +297 -0
  589. aria_code/workspace/__init__.py +6 -0
  590. aria_code/workspace/files.py +219 -0
  591. aria_code/workspace/verify.py +167 -0
  592. aria_code/workspace_changes.py +199 -0
  593. aria_code/workspace_service.py +196 -0
  594. aria_code/workspace_tools.py +150 -0
  595. aria_code-0.167.0.dist-info/METADATA +330 -0
  596. aria_code-0.167.0.dist-info/RECORD +601 -0
  597. aria_code-0.167.0.dist-info/WHEEL +5 -0
  598. aria_code-0.167.0.dist-info/entry_points.txt +5 -0
  599. aria_code-0.167.0.dist-info/licenses/LICENSE +201 -0
  600. aria_code-0.167.0.dist-info/licenses/NOTICE +49 -0
  601. aria_code-0.167.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,549 @@
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 asyncio
30
+ import json
31
+ import logging
32
+ import os
33
+ import re
34
+ import urllib.request
35
+ from dataclasses import dataclass, field
36
+ from typing import Any, AsyncIterator, Dict, List, Optional
37
+
38
+ logger = logging.getLogger(__name__)
39
+
40
+ # ---------------------------------------------------------------------------
41
+ # Model resolution helpers
42
+ # ---------------------------------------------------------------------------
43
+
44
+ from aria_code.providers.ollama_models import (
45
+ ollama_base_url, resolve_model_sync, resolve_model_async,
46
+ require_installed_model as _require_installed_model,
47
+ )
48
+
49
+
50
+ def _pick_model(available: List[str], requested: str) -> str:
51
+ return _require_installed_model(available, requested)
52
+
53
+
54
+
55
+ def list_ollama_models(base_url: str) -> List[str]:
56
+ """Return all model names from Ollama /api/tags (sync)."""
57
+ try:
58
+ with urllib.request.urlopen(base_url.rstrip("/") + "/api/tags", timeout=3) as r:
59
+ return [m["name"] for m in json.loads(r.read()).get("models", [])]
60
+ except Exception:
61
+ return []
62
+
63
+ try:
64
+ import aiohttp
65
+ _HAS_AIOHTTP = True
66
+ except ImportError:
67
+ _HAS_AIOHTTP = False
68
+
69
+ from aria_code.model_capability import (
70
+ ModelCapability,
71
+ get_model_capability,
72
+ build_ollama_tool_payload,
73
+ build_tool_system_prompt,
74
+ parse_tool_calls_from_response,
75
+ )
76
+
77
+
78
+ # ---------------------------------------------------------------------------
79
+ # Backend definitions
80
+ # ---------------------------------------------------------------------------
81
+
82
+ BACKEND_DEFAULTS: Dict[str, Dict[str, Any]] = {
83
+ "ollama": {"default_url": "http://localhost:11434", "api_path": "/api/chat", "protocol": "ollama"},
84
+ "lmstudio": {"default_url": "http://localhost:1234", "api_path": "/v1/chat/completions", "protocol": "openai"},
85
+ "vllm": {"default_url": "http://localhost:8000", "api_path": "/v1/chat/completions", "protocol": "openai"},
86
+ "llamacpp": {"default_url": "http://localhost:8080", "api_path": "/v1/chat/completions", "protocol": "openai"},
87
+ "jan": {"default_url": "http://localhost:1337", "api_path": "/v1/chat/completions", "protocol": "openai"},
88
+ "openai": {"default_url": "https://api.openai.com", "api_path": "/v1/chat/completions", "protocol": "openai"},
89
+ # Cloud providers (API key required)
90
+ "deepseek": {"default_url": "https://api.deepseek.com", "api_path": "/v1/chat/completions", "protocol": "openai"},
91
+ "groq": {"default_url": "https://api.groq.com/openai", "api_path": "/v1/chat/completions", "protocol": "openai"},
92
+ "anthropic": {"default_url": "https://api.anthropic.com", "api_path": "/v1/messages", "protocol": "anthropic"},
93
+ "together": {"default_url": "https://api.together.xyz", "api_path": "/v1/chat/completions", "protocol": "openai"},
94
+ "siliconflow": {"default_url": "https://api.siliconflow.cn", "api_path": "/v1/chat/completions", "protocol": "openai"},
95
+ "moonshot": {"default_url": "https://api.moonshot.cn/v1", "api_path": "/chat/completions", "protocol": "openai"},
96
+ # Custom user-defined OpenAI-compatible endpoint
97
+ "custom": {"default_url": "", "api_path": "/chat/completions", "protocol": "openai"},
98
+ }
99
+
100
+ LOCAL_BACKENDS = {"ollama", "lmstudio", "vllm", "llamacpp", "jan", "custom"}
101
+
102
+
103
+ def openai_models_url(base_url: str) -> str:
104
+ """Return the models endpoint for a versioned or unversioned base URL."""
105
+ base = base_url.rstrip("/")
106
+ if re.search(r"/v\d+(?:beta)?(?:/openai)?$", base):
107
+ return f"{base}/models"
108
+ return f"{base}/v1/models"
109
+
110
+
111
+ @dataclass
112
+ class LocalLLMProvider:
113
+ backend: str = "ollama" # one of BACKEND_DEFAULTS keys
114
+ base_url: str = "" # override; blank → use BACKEND_DEFAULTS default
115
+ model: str = "qwen2.5-coder:7b"
116
+ api_key: str = "" # needed for openai-compatible remotes
117
+ timeout: int = 300
118
+
119
+ # Derived from model_capability on first use
120
+ _capability: Optional[ModelCapability] = field(default=None, repr=False)
121
+
122
+ # ── constructor ────────────────────────────────────────────────────────
123
+
124
+ @classmethod
125
+ def from_config(cls, config: Dict[str, Any]) -> "LocalLLMProvider":
126
+ """Build from aria-code config dict."""
127
+ backend = config.get("local_provider", "ollama").lower()
128
+ info = BACKEND_DEFAULTS.get(backend, BACKEND_DEFAULTS["ollama"])
129
+ requested_model = config.get("model", "qwen2.5-coder:1.5b")
130
+
131
+ # Custom endpoint: user-specified base_url via /config set custom_endpoint=...
132
+ if backend == "custom":
133
+ url = config.get("custom_endpoint", "") or info["default_url"]
134
+ model = config.get("custom_model", requested_model)
135
+ api_key = config.get("local_api_key", os.getenv("LOCAL_LLM_API_KEY", ""))
136
+ return cls(backend=backend, base_url=url, model=model, api_key=api_key)
137
+
138
+ # Cloud provider backends: read API key from providers.json or env var
139
+ _cloud_env_map = {
140
+ "deepseek": "DEEPSEEK_API_KEY",
141
+ "openai": "OPENAI_API_KEY",
142
+ "anthropic": "ANTHROPIC_API_KEY",
143
+ "groq": "GROQ_API_KEY",
144
+ "together": "TOGETHER_API_KEY",
145
+ "siliconflow": "SILICONFLOW_API_KEY",
146
+ "moonshot": "MOONSHOT_API_KEY",
147
+ }
148
+ if backend in _cloud_env_map:
149
+ env_var = _cloud_env_map[backend]
150
+ api_key = (os.getenv(env_var, "")
151
+ or config.get("local_api_key", "")
152
+ or os.getenv("LOCAL_LLM_API_KEY", ""))
153
+ url = config.get("custom_endpoint") or info["default_url"]
154
+ return cls(backend=backend, base_url=url, model=requested_model, api_key=api_key)
155
+
156
+ # ``local_url`` belongs to Aria's local application server and used to
157
+ # accidentally route LM Studio/llama.cpp requests to port 8000. LLM
158
+ # endpoints have provider-specific keys, with ``llm_base_url`` as the
159
+ # shared explicit override.
160
+ provider_urls = config.get("provider_urls", {})
161
+ if not isinstance(provider_urls, dict):
162
+ provider_urls = {}
163
+ if backend == "ollama":
164
+ url = ollama_base_url(config)
165
+ else:
166
+ url = (
167
+ config.get(f"{backend}_url")
168
+ or provider_urls.get(backend)
169
+ or config.get("llm_base_url")
170
+ or info["default_url"]
171
+ )
172
+ api_key = config.get("local_api_key", os.getenv("LOCAL_LLM_API_KEY", ""))
173
+
174
+ # Discovery belongs to each request, not object construction: startup
175
+ # and configuration inspection must also work while Ollama is offline.
176
+ model = requested_model
177
+
178
+ return cls(backend=backend, base_url=url, model=model, api_key=api_key)
179
+
180
+ # ── capability ─────────────────────────────────────────────────────────
181
+
182
+ @property
183
+ def capability(self) -> ModelCapability:
184
+ if self._capability is None:
185
+ self._capability = get_model_capability(self.model)
186
+ return self._capability
187
+
188
+ # ── health check ───────────────────────────────────────────────────────
189
+
190
+ def is_available(self) -> bool:
191
+ """Quick synchronous check that the backend is reachable."""
192
+ info = BACKEND_DEFAULTS.get(self.backend, BACKEND_DEFAULTS["ollama"])
193
+ url = (self.base_url or info["default_url"]).rstrip("/")
194
+ probe = (
195
+ f"{url}/api/tags" if self.backend == "ollama"
196
+ else openai_models_url(url)
197
+ )
198
+ try:
199
+ with urllib.request.urlopen(probe, timeout=3) as r:
200
+ return r.status == 200
201
+ except Exception:
202
+ return False
203
+
204
+ def list_models(self) -> List[str]:
205
+ """Return available model names from backend."""
206
+ info = BACKEND_DEFAULTS.get(self.backend, BACKEND_DEFAULTS["ollama"])
207
+ url = (self.base_url or info["default_url"]).rstrip("/")
208
+ try:
209
+ if self.backend == "ollama":
210
+ with urllib.request.urlopen(f"{url}/api/tags", timeout=3) as r:
211
+ data = json.loads(r.read())
212
+ return [m["name"] for m in data.get("models", [])]
213
+ else:
214
+ req = urllib.request.Request(openai_models_url(url))
215
+ if self.api_key:
216
+ req.add_header("Authorization", f"Bearer {self.api_key}")
217
+ with urllib.request.urlopen(req, timeout=3) as r:
218
+ data = json.loads(r.read())
219
+ return [m["id"] for m in data.get("data", [])]
220
+ except Exception:
221
+ return []
222
+
223
+ # ── streaming core ─────────────────────────────────────────────────────
224
+
225
+ async def stream(
226
+ self,
227
+ messages: List[Dict[str, Any]],
228
+ tools: Optional[List[Dict]] = None,
229
+ *,
230
+ temperature: Optional[float] = None,
231
+ max_tokens: Optional[int] = None,
232
+ cancel_event=None,
233
+ ) -> AsyncIterator[Dict[str, Any]]:
234
+ """
235
+ Unified async generator. Yields event dicts::
236
+
237
+ {"type": "token", "text": str}
238
+ {"type": "thinking", "text": str} # for reasoning models
239
+ {"type": "tool_call", "name": str, "arguments": dict, "id": str}
240
+ {"type": "done", "usage": dict, "stop_reason": str}
241
+ {"type": "error", "message": str}
242
+ """
243
+ if not _HAS_AIOHTTP:
244
+ yield {"type": "error", "message": "aiohttp not installed: pip install aiohttp"}
245
+ return
246
+
247
+ info = BACKEND_DEFAULTS.get(self.backend, BACKEND_DEFAULTS["ollama"])
248
+ url = (self.base_url or info["default_url"]).rstrip("/") + info["api_path"]
249
+ protocol = info["protocol"]
250
+ cap = self.capability
251
+
252
+ temp = temperature if temperature is not None else cap.temperature
253
+ # Gemini 2.5 counts its thinking against the output cap, so 4096 left a
254
+ # long answer — a structured review, a whole file — cut off mid-JSON.
255
+ # Gemini allows 65k; other backends keep 4096 (DeepSeek, for one,
256
+ # rejects anything above 8192).
257
+ mtoks = max_tokens if max_tokens is not None else (32768 if "gemini" in self.model.lower() else 4096)
258
+
259
+ # Inject tool system prompt for text-only models
260
+ if tools and not cap.tool_calls:
261
+ tool_sys = build_tool_system_prompt(tools, self.model)
262
+ if tool_sys:
263
+ sys_msgs = [m for m in messages if m.get("role") == "system"]
264
+ if sys_msgs:
265
+ messages[0]["content"] = messages[0]["content"] + tool_sys
266
+ else:
267
+ messages = [{"role": "system", "content": tool_sys.strip()}] + messages
268
+
269
+ if protocol == "ollama":
270
+ async for ev in self._stream_ollama(url, messages, tools, temp, mtoks, cap, cancel_event):
271
+ yield ev
272
+ else:
273
+ async for ev in self._stream_openai(url, messages, tools, temp, mtoks, cap, cancel_event):
274
+ yield ev
275
+
276
+ # ── Ollama protocol ────────────────────────────────────────────────────
277
+
278
+ async def _stream_ollama(self, url, messages, tools, temp, max_tokens, cap, cancel_event):
279
+ # Re-resolve the model async before every call — handles the case where
280
+ # the model was changed or wasn't available at construction time.
281
+ base_url = url.rsplit("/api/chat", 1)[0]
282
+ try:
283
+ model = await resolve_model_async(base_url, self.model)
284
+ except ValueError as exc:
285
+ yield {"type": "error", "message": str(exc)}
286
+ return
287
+ if model != self.model:
288
+ self.model = model # keep in sync for future calls
289
+ self._capability = None # reset capability cache for new model
290
+
291
+ native_tools = build_ollama_tool_payload(tools or [], self.model) if tools else None
292
+ payload: Dict[str, Any] = {
293
+ "model": self.model,
294
+ "messages": messages,
295
+ "stream": True,
296
+ "options": {
297
+ "num_ctx": cap.context_window,
298
+ "temperature": temp,
299
+ "top_p": cap.top_p,
300
+ "repeat_penalty": 1.15,
301
+ "repeat_last_n": 128,
302
+ "num_predict": max_tokens,
303
+ },
304
+ }
305
+ if native_tools:
306
+ payload["tools"] = native_tools
307
+
308
+ usage = {"prompt_tokens": 0, "completion_tokens": 0}
309
+ full_text = ""
310
+
311
+ try:
312
+ async with aiohttp.ClientSession(trust_env=True) as sess:
313
+ async with sess.post(url, json=payload,
314
+ timeout=aiohttp.ClientTimeout(total=self.timeout)) as resp:
315
+ if resp.status != 200:
316
+ body = await resp.text()
317
+ yield {"type": "error", "message": f"Ollama HTTP {resp.status}: {body[:200]}"}
318
+ return
319
+
320
+ async for line in resp.content:
321
+ if cancel_event and cancel_event.is_set():
322
+ yield {"type": "done", "usage": usage, "stop_reason": "cancelled"}
323
+ return
324
+
325
+ text = line.decode("utf-8", errors="ignore").strip()
326
+ if not text:
327
+ continue
328
+ try:
329
+ data = json.loads(text)
330
+ except json.JSONDecodeError:
331
+ continue
332
+
333
+ msg = data.get("message", {})
334
+
335
+ # Native tool calls
336
+ if msg.get("tool_calls"):
337
+ for tc in msg["tool_calls"]:
338
+ fn = tc.get("function", {})
339
+ name = fn.get("name", "")
340
+ args = fn.get("arguments", {})
341
+ if isinstance(args, str):
342
+ try:
343
+ args = json.loads(args)
344
+ except Exception:
345
+ args = {}
346
+ if name:
347
+ yield {"type": "tool_call", "name": name,
348
+ "arguments": args, "id": tc.get("id", "")}
349
+
350
+ if data.get("done"):
351
+ usage["prompt_tokens"] += data.get("prompt_eval_count", 0)
352
+ usage["completion_tokens"] += data.get("eval_count", 0)
353
+ break
354
+
355
+ token = msg.get("content", "")
356
+ if token:
357
+ full_text += token
358
+ # Suppress tokens that are part of <tool_call> tags
359
+ if not full_text.lstrip().startswith("<tool_call"):
360
+ yield {"type": "token", "text": token}
361
+
362
+ except Exception as exc:
363
+ yield {"type": "error", "message": f"Ollama stream error: {exc or type(exc).__name__}"}
364
+ return
365
+
366
+ # Fallback: parse text-based tool calls
367
+ text_calls = parse_tool_calls_from_response(full_text, model_name=self.model)
368
+ for tc in text_calls:
369
+ yield {"type": "tool_call", "name": tc["tool"],
370
+ "arguments": tc["params"], "id": ""}
371
+
372
+ yield {"type": "done", "usage": usage, "stop_reason": "stop"}
373
+
374
+ # ── OpenAI-compatible protocol ─────────────────────────────────────────
375
+
376
+ RETRY_STATUS = frozenset({429, 500, 502, 503, 504})
377
+ MAX_ATTEMPTS = 5
378
+
379
+ async def _stream_openai(self, url, messages, tools, temp, max_tokens, cap, cancel_event):
380
+ """One OpenAI-compatible turn, retried on rate limits and dropped connections.
381
+
382
+ A 429 ("Resource exhausted" on Vertex AI) or a dropped connection
383
+ ended the whole agent turn at once, though both usually clear in
384
+ seconds. Retries back off exponentially (honouring Retry-After) and
385
+ happen only before anything reached the caller, so no token or tool
386
+ call is ever delivered twice.
387
+ """
388
+ import random
389
+
390
+ for attempt in range(1, self.MAX_ATTEMPTS + 1):
391
+ delivered = False
392
+ retry: Optional[Dict[str, Any]] = None
393
+ attempt_stream = self._stream_openai_once(url, messages, tools, temp, max_tokens, cap, cancel_event)
394
+ try:
395
+ async for event in attempt_stream:
396
+ if (event.get("type") == "error" and event.get("transient") and not delivered
397
+ and attempt < self.MAX_ATTEMPTS):
398
+ retry = event
399
+ break
400
+ if event.get("type") in ("token", "thinking", "tool_call"):
401
+ delivered = True
402
+ yield event
403
+ finally:
404
+ await attempt_stream.aclose()
405
+ if retry is None or (cancel_event and cancel_event.is_set()):
406
+ return
407
+ delay = retry.get("retry_after") or min(30.0, 2.0 ** attempt) * (1 + random.random() * 0.25)
408
+ logger.info("OpenAI-compatible request failed (%s); retry %d in %.1fs",
409
+ retry.get("message", "")[:80], attempt, delay)
410
+ await asyncio.sleep(delay)
411
+
412
+ async def _stream_openai_once(self, url, messages, tools, temp, max_tokens, cap, cancel_event):
413
+ headers: Dict[str, str] = {"Content-Type": "application/json"}
414
+ if self.api_key:
415
+ headers["Authorization"] = f"Bearer {self.api_key}"
416
+
417
+ from aria_code.providers.tool_messages import to_openai_tool_protocol
418
+
419
+ from aria_code.providers.openai_params import sampling_params
420
+
421
+ payload: Dict[str, Any] = {
422
+ "model": self.model,
423
+ "messages": to_openai_tool_protocol(messages),
424
+ "stream": True,
425
+ **sampling_params(url, self.model, temp, max_tokens),
426
+ }
427
+ if tools and cap.tool_calls:
428
+ payload["tools"] = tools
429
+ payload["tool_choice"] = "auto"
430
+
431
+ usage = {"prompt_tokens": 0, "completion_tokens": 0}
432
+ full_text = ""
433
+ tool_call_accumulator: Dict[int, Dict] = {} # index → partial call
434
+
435
+ try:
436
+ async with aiohttp.ClientSession(trust_env=True, headers=headers) as sess:
437
+ async with sess.post(url, json=payload,
438
+ timeout=aiohttp.ClientTimeout(total=self.timeout)) as resp:
439
+ if resp.status != 200:
440
+ body = await resp.text()
441
+ try:
442
+ retry_after = float(resp.headers.get("Retry-After", "") or 0) or None
443
+ except ValueError:
444
+ retry_after = None
445
+ yield {"type": "error", "message": f"HTTP {resp.status}: {body[:200]}",
446
+ "transient": resp.status in self.RETRY_STATUS, "retry_after": retry_after}
447
+ return
448
+
449
+ async for line in resp.content:
450
+ if cancel_event and cancel_event.is_set():
451
+ yield {"type": "done", "usage": usage, "stop_reason": "cancelled"}
452
+ return
453
+
454
+ raw = line.decode("utf-8", errors="ignore").strip()
455
+ if not raw or raw == "data: [DONE]":
456
+ continue
457
+ if raw.startswith("data: "):
458
+ raw = raw[6:]
459
+ try:
460
+ data = json.loads(raw)
461
+ except json.JSONDecodeError:
462
+ continue
463
+
464
+ # Usage from final chunk
465
+ if data.get("usage"):
466
+ u = data["usage"]
467
+ usage["prompt_tokens"] = u.get("prompt_tokens", 0)
468
+ usage["completion_tokens"] = u.get("completion_tokens", 0)
469
+
470
+ choices = data.get("choices", [])
471
+ if not choices:
472
+ continue
473
+ delta = choices[0].get("delta", {})
474
+
475
+ # Token
476
+ content = delta.get("content", "")
477
+ if content:
478
+ full_text += content
479
+ yield {"type": "token", "text": content}
480
+
481
+ # Tool calls (streamed fragments)
482
+ for tc_delta in (delta.get("tool_calls") or []):
483
+ idx = tc_delta.get("index", 0)
484
+ if idx not in tool_call_accumulator:
485
+ tool_call_accumulator[idx] = {
486
+ "id": "", "name": "", "arguments": ""}
487
+ acc = tool_call_accumulator[idx]
488
+ acc["id"] += tc_delta.get("id", "")
489
+ fn = tc_delta.get("function", {})
490
+ acc["name"] += fn.get("name", "")
491
+ acc["arguments"] += fn.get("arguments", "")
492
+
493
+ fin_reason = choices[0].get("finish_reason")
494
+ if fin_reason in ("stop", "tool_calls", "length"):
495
+ break
496
+
497
+ except Exception as exc:
498
+ transient = isinstance(exc, (aiohttp.ClientConnectionError, aiohttp.ServerDisconnectedError,
499
+ aiohttp.ClientPayloadError, ConnectionResetError))
500
+ yield {"type": "error", "message": f"OpenAI-compat stream error: {exc or type(exc).__name__}",
501
+ "transient": transient}
502
+ return
503
+
504
+ # Emit accumulated tool calls
505
+ for acc in tool_call_accumulator.values():
506
+ args_str = acc.get("arguments", "{}")
507
+ try:
508
+ args = json.loads(args_str)
509
+ except Exception:
510
+ args = {}
511
+ if acc.get("name"):
512
+ yield {"type": "tool_call", "name": acc["name"],
513
+ "arguments": args, "id": acc.get("id", "")}
514
+
515
+ # Fallback text-based tool call parsing (for non-native models)
516
+ if not tool_call_accumulator:
517
+ text_calls = parse_tool_calls_from_response(full_text, model_name=self.model)
518
+ for tc in text_calls:
519
+ yield {"type": "tool_call", "name": tc["tool"],
520
+ "arguments": tc["params"], "id": ""}
521
+
522
+ yield {"type": "done", "usage": usage, "stop_reason": "stop"}
523
+
524
+
525
+ # ---------------------------------------------------------------------------
526
+ # Quick availability probe (synchronous, for startup checks)
527
+ # ---------------------------------------------------------------------------
528
+
529
+ def probe_all_backends() -> Dict[str, bool]:
530
+ """Return {backend: is_reachable} for all known backends."""
531
+ results = {}
532
+ for name, info in BACKEND_DEFAULTS.items():
533
+ url = info["default_url"]
534
+ probe = f"{url}/api/tags" if name == "ollama" else f"{url}/v1/models"
535
+ try:
536
+ with urllib.request.urlopen(probe, timeout=1) as r:
537
+ results[name] = r.status == 200
538
+ except Exception:
539
+ results[name] = False
540
+ return results
541
+
542
+
543
+ def probe_local_backends() -> Dict[str, bool]:
544
+ """Probe only desktop/local runtimes, never paid cloud APIs."""
545
+ return {
546
+ name: LocalLLMProvider(backend=name).is_available()
547
+ for name in LOCAL_BACKENDS
548
+ if name != "custom"
549
+ }
@@ -0,0 +1,79 @@
1
+ """Fresh Ollama model discovery; an explicit selection is never substituted."""
2
+ from __future__ import annotations
3
+
4
+ import asyncio
5
+ import json
6
+ import os
7
+ import urllib.request
8
+ from urllib.parse import urlsplit, urlunsplit
9
+
10
+
11
+ class OllamaModelError(ValueError):
12
+ """The selected server/model cannot be verified for this request."""
13
+
14
+ def __str__(self) -> str:
15
+ # The runtime's error transport retains text, so keep the no-fallback
16
+ # category even through adapters that do not preserve extra metadata.
17
+ return "ollama_model_unavailable: " + super().__str__()
18
+
19
+
20
+ def ollama_base_url(config: dict | None = None) -> str:
21
+ cfg = config or {}
22
+ raw = str(cfg.get("ollama_base_url") or cfg.get("ollama_url") or os.getenv("OLLAMA_HOST") or "http://127.0.0.1:11434").strip()
23
+ if "://" not in raw:
24
+ raw = "http://" + raw
25
+ try:
26
+ u = urlsplit(raw)
27
+ if u.scheme not in {"http", "https"} or not u.hostname or u.username or u.password or u.query or u.fragment:
28
+ raise ValueError
29
+ _ = u.port
30
+ except ValueError:
31
+ raise OllamaModelError("Ollama address must be an http(s) URL without credentials, query or fragment") from None
32
+ path = u.path.rstrip("/")
33
+ if path.endswith("/v1"):
34
+ path = path[:-3]
35
+ return urlunsplit((u.scheme, u.netloc, path, "", ""))
36
+
37
+
38
+ def installed_models(base_url: str) -> list[str]:
39
+ """No positive cache: models removed since the last turn must be rejected."""
40
+ url = ollama_base_url({"ollama_base_url": base_url}) + "/api/tags"
41
+ try:
42
+ with urllib.request.urlopen(url, timeout=3) as response:
43
+ if response.status != 200:
44
+ raise ValueError
45
+ raw = response.read((1 << 20) + 1)
46
+ if len(raw) > 1 << 20:
47
+ raise ValueError
48
+ data = json.loads(raw)
49
+ rows = data["models"]
50
+ if not isinstance(rows, list):
51
+ raise ValueError
52
+ names = []
53
+ for row in rows:
54
+ name = row["name"]
55
+ if not isinstance(name, str) or not name or len(name) > 512 or any(c.isspace() or ord(c) < 32 or ord(c) == 127 for c in name):
56
+ raise ValueError
57
+ if name not in names:
58
+ names.append(name)
59
+ return names
60
+ except Exception as exc:
61
+ raise OllamaModelError("Cannot verify installed Ollama models. Check the service address, run ollama serve, then retry /model.") from exc
62
+
63
+
64
+ def require_installed_model(available: list[str], requested: str) -> str:
65
+ if requested in available:
66
+ return requested
67
+ # Ollama's untagged spelling is only an alias for :latest, not any tag.
68
+ if requested and ":" not in requested.rsplit("/", 1)[-1] and requested + ":latest" in available:
69
+ return requested + ":latest"
70
+ names = ", ".join(available[:10]) or "(none)"
71
+ raise OllamaModelError(f"Ollama model {requested!r} is not installed. Installed models: {names}. Run ollama pull <model>, then select it with /model.")
72
+
73
+
74
+ def resolve_model_sync(base_url: str, requested: str) -> str:
75
+ return require_installed_model(installed_models(base_url), requested)
76
+
77
+
78
+ async def resolve_model_async(base_url: str, requested: str) -> str:
79
+ return await asyncio.to_thread(resolve_model_sync, base_url, requested)
@@ -0,0 +1,32 @@
1
+ """Request parameters that differ between OpenAI's own API and the servers that imitate it.
2
+
3
+ OpenAI's newer models reject `max_tokens` ("use `max_completion_tokens`") and its
4
+ reasoning models (o-series, gpt-5 and later) accept only the default temperature.
5
+ Servers that merely speak the same protocol (DeepSeek, Groq, Ollama, vLLM, ...)
6
+ still expect `max_tokens` and take a temperature, so this changes nothing for them.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import re
12
+ from typing import Any, Dict, Optional
13
+ from urllib.parse import urlparse
14
+
15
+ # o1, o3-mini, gpt-5, gpt-5.4-mini, gpt-6.1-sol ... : reasoning models that fix the temperature.
16
+ _FIXED_TEMPERATURE = re.compile(r"^(o\d|gpt-(?:[5-9]|\d{2,}))", re.IGNORECASE)
17
+
18
+
19
+ def is_openai_api(url: str) -> bool:
20
+ host = (urlparse(url or "").hostname or "").lower()
21
+ return host == "openai.com" or host.endswith(".openai.com")
22
+
23
+
24
+ def sampling_params(url: str, model: str, temperature: Optional[float], max_tokens: Optional[int]) -> Dict[str, Any]:
25
+ """`temperature` and the output-length limit, under the names this server expects."""
26
+ params: Dict[str, Any] = {}
27
+ official = is_openai_api(url)
28
+ if temperature is not None and not (official and _FIXED_TEMPERATURE.match(model or "")):
29
+ params["temperature"] = temperature
30
+ if max_tokens is not None:
31
+ params["max_completion_tokens" if official else "max_tokens"] = max_tokens
32
+ return params