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.
- aria_code/__init__.py +42 -0
- aria_code/_version.py +20 -0
- aria_code/adk_apps/__init__.py +1 -0
- aria_code/adk_apps/aria_code_review/__init__.py +1 -0
- aria_code/adk_apps/aria_code_review/agent.py +32 -0
- aria_code/adk_apps/aria_research/__init__.py +1 -0
- aria_code/adk_apps/aria_research/agent.py +31 -0
- aria_code/agents/__init__.py +34 -0
- aria_code/agents/base.py +318 -0
- aria_code/agents/code_review.py +200 -0
- aria_code/agents/deep/__init__.py +37 -0
- aria_code/agents/deep/calibration_loop.py +144 -0
- aria_code/agents/deep/critic.py +125 -0
- aria_code/agents/deep/deepen.py +193 -0
- aria_code/agents/deep/models.py +149 -0
- aria_code/agents/deep/pipeline.py +164 -0
- aria_code/agents/deep/quant_fusion.py +192 -0
- aria_code/agents/deep/themes.py +95 -0
- aria_code/agents/deep/tiers.py +106 -0
- aria_code/agents/engineering/__init__.py +14 -0
- aria_code/agents/engineering/coder.py +222 -0
- aria_code/agents/engineering/tester.py +145 -0
- aria_code/agents/financial/__init__.py +21 -0
- aria_code/agents/financial/cashflow_burn.py +119 -0
- aria_code/agents/financial/catalyst.py +279 -0
- aria_code/agents/financial/corporate_finance.py +175 -0
- aria_code/agents/financial/debate.py +224 -0
- aria_code/agents/financial/earnings.py +303 -0
- aria_code/agents/financial/fundamental.py +191 -0
- aria_code/agents/financial/macro.py +99 -0
- aria_code/agents/financial/news.py +207 -0
- aria_code/agents/financial/risk.py +165 -0
- aria_code/agents/financial/sector.py +279 -0
- aria_code/agents/financial/strategist.py +121 -0
- aria_code/agents/financial/stripe_revenue.py +127 -0
- aria_code/agents/financial/synthesis.py +274 -0
- aria_code/agents/financial/technical.py +258 -0
- aria_code/agents/orchestrator.py +44 -0
- aria_code/agents/portfolio_agent.py +363 -0
- aria_code/agents/realty/__init__.py +62 -0
- aria_code/agents/realty/asset_diagnosis.py +152 -0
- aria_code/agents/realty/business_match.py +166 -0
- aria_code/agents/realty/cashflow_verify.py +209 -0
- aria_code/agents/realty/contract_rules.py +210 -0
- aria_code/agents/realty/energy_anomaly.py +189 -0
- aria_code/agents/realty/exit_settlement.py +208 -0
- aria_code/agents/realty/fulfillment_risk.py +206 -0
- aria_code/agents/realty/ops_optimize.py +160 -0
- aria_code/agents/realty/revenue_share.py +215 -0
- aria_code/agents/registry.py +158 -0
- aria_code/agents/signal_scheme.py +111 -0
- aria_code/agents/sports/__init__.py +0 -0
- aria_code/agents/sports/football_agent.py +197 -0
- aria_code/agents/supervisor.py +113 -0
- aria_code/agents/team.py +492 -0
- aria_code/agents/warehouse/__init__.py +25 -0
- aria_code/agents/warehouse/contracts.py +26 -0
- aria_code/agents/warehouse/dashboard.py +155 -0
- aria_code/agents/warehouse/fulfillment_leadtime.py +95 -0
- aria_code/agents/warehouse/inbound_exceptions.py +25 -0
- aria_code/agents/warehouse/inventory_health.py +24 -0
- aria_code/agents/warehouse/local_run.py +71 -0
- aria_code/agents/warehouse/local_snapshot.py +132 -0
- aria_code/agents/warehouse/logistics_cost.py +84 -0
- aria_code/agents/warehouse/logistics_sync.py +21 -0
- aria_code/agents/warehouse/workflow.py +31 -0
- aria_code/aliyun_data_client.py +4 -0
- aria_code/apps/README.md +12 -0
- aria_code/apps/__init__.py +2 -0
- aria_code/apps/channels/README.md +15 -0
- aria_code/apps/channels/__init__.py +17 -0
- aria_code/apps/channels/intake.py +92 -0
- aria_code/apps/channels/registry.py +95 -0
- aria_code/apps/channels/tradingview.py +94 -0
- aria_code/apps/cli/README.md +13 -0
- aria_code/apps/cli/__init__.py +2 -0
- aria_code/apps/cli/_optional.py +120 -0
- aria_code/apps/cli/bootstrap.py +145 -0
- aria_code/apps/cli/broker_render.py +206 -0
- aria_code/apps/cli/codegen_paths.py +29 -0
- aria_code/apps/cli/commands/__init__.py +16 -0
- aria_code/apps/cli/commands/_business_render.py +78 -0
- aria_code/apps/cli/commands/_ui.py +71 -0
- aria_code/apps/cli/commands/analysis_cmds.py +369 -0
- aria_code/apps/cli/commands/ashare_prediction_cmds.py +327 -0
- aria_code/apps/cli/commands/auth_cmds.py +239 -0
- aria_code/apps/cli/commands/backtest_cmds.py +2543 -0
- aria_code/apps/cli/commands/broker_cmds.py +1234 -0
- aria_code/apps/cli/commands/business_workflow_cmds.py +319 -0
- aria_code/apps/cli/commands/canvas_cmds.py +77 -0
- aria_code/apps/cli/commands/catalog.py +87 -0
- aria_code/apps/cli/commands/core_cmds.py +2520 -0
- aria_code/apps/cli/commands/data_cmds.py +457 -0
- aria_code/apps/cli/commands/diagnostic_cmds.py +230 -0
- aria_code/apps/cli/commands/diagnostic_ops_cmds.py +813 -0
- aria_code/apps/cli/commands/file_cmds.py +312 -0
- aria_code/apps/cli/commands/finance_service_cmds.py +60 -0
- aria_code/apps/cli/commands/fx_commodity_cmds.py +123 -0
- aria_code/apps/cli/commands/market.py +434 -0
- aria_code/apps/cli/commands/market_cmds.py +1381 -0
- aria_code/apps/cli/commands/market_context.py +425 -0
- aria_code/apps/cli/commands/market_render.py +7 -0
- aria_code/apps/cli/commands/model_cmds.py +2153 -0
- aria_code/apps/cli/commands/ops_cmds.py +721 -0
- aria_code/apps/cli/commands/orchestrator_cmds.py +179 -0
- aria_code/apps/cli/commands/pdf_export_cmds.py +125 -0
- aria_code/apps/cli/commands/portfolio_cmds.py +1278 -0
- aria_code/apps/cli/commands/report.py +437 -0
- aria_code/apps/cli/commands/scaffold_templates.py +617 -0
- aria_code/apps/cli/commands/session_cmds.py +236 -0
- aria_code/apps/cli/commands/session_ux_cmds.py +359 -0
- aria_code/apps/cli/commands/team.py +669 -0
- aria_code/apps/cli/commands/team_render.py +8 -0
- aria_code/apps/cli/commands/ui_cmds.py +401 -0
- aria_code/apps/cli/commands/warehouse_cmds.py +79 -0
- aria_code/apps/cli/commands/workflow_cmds.py +480 -0
- aria_code/apps/cli/commands/workspace_cmds.py +1494 -0
- aria_code/apps/cli/config_paths.py +73 -0
- aria_code/apps/cli/config_store.py +78 -0
- aria_code/apps/cli/constants.py +88 -0
- aria_code/apps/cli/context.py +34 -0
- aria_code/apps/cli/deterministic.py +191 -0
- aria_code/apps/cli/direct.py +48 -0
- aria_code/apps/cli/finance_service_catalog.py +111 -0
- aria_code/apps/cli/football_reports.py +231 -0
- aria_code/apps/cli/github_app_auth.py +135 -0
- aria_code/apps/cli/google_login.py +196 -0
- aria_code/apps/cli/handlers/__init__.py +11 -0
- aria_code/apps/cli/handlers/broker_handlers.py +122 -0
- aria_code/apps/cli/handlers/chart_handlers.py +1309 -0
- aria_code/apps/cli/handlers/market_handlers.py +2513 -0
- aria_code/apps/cli/handlers/realty_handlers.py +114 -0
- aria_code/apps/cli/handlers/sports_handlers.py +22 -0
- aria_code/apps/cli/handlers/strategy_advice.py +82 -0
- aria_code/apps/cli/helpers.py +302 -0
- aria_code/apps/cli/hooks.py +180 -0
- aria_code/apps/cli/i18n.py +297 -0
- aria_code/apps/cli/intent.py +136 -0
- aria_code/apps/cli/intent_router.py +276 -0
- aria_code/apps/cli/intent_signals.py +344 -0
- aria_code/apps/cli/lifecycle_hooks.py +48 -0
- aria_code/apps/cli/local_tool_schemas.py +536 -0
- aria_code/apps/cli/main.py +29 -0
- aria_code/apps/cli/market_metadata.py +135 -0
- aria_code/apps/cli/market_universe.py +487 -0
- aria_code/apps/cli/message_processing.py +310 -0
- aria_code/apps/cli/model_catalog.py +620 -0
- aria_code/apps/cli/pdf_report.py +977 -0
- aria_code/apps/cli/plan_mode.py +139 -0
- aria_code/apps/cli/plotly_html.py +15 -0
- aria_code/apps/cli/prediction_feedback.py +202 -0
- aria_code/apps/cli/preflight.py +502 -0
- aria_code/apps/cli/project_aria.py +60 -0
- aria_code/apps/cli/prompt_assembly.py +59 -0
- aria_code/apps/cli/prompts/__init__.py +0 -0
- aria_code/apps/cli/prompts/coding.py +53 -0
- aria_code/apps/cli/prompts/select.py +142 -0
- aria_code/apps/cli/prompts/system_prompts.py +552 -0
- aria_code/apps/cli/prompts/ui.py +436 -0
- aria_code/apps/cli/provider_endpoints.py +237 -0
- aria_code/apps/cli/provider_keys.py +73 -0
- aria_code/apps/cli/providers/__init__.py +1 -0
- aria_code/apps/cli/providers/base.py +521 -0
- aria_code/apps/cli/providers/chat_routing.py +128 -0
- aria_code/apps/cli/providers/collaboration.py +98 -0
- aria_code/apps/cli/providers/llm/__init__.py +1 -0
- aria_code/apps/cli/providers/llm/offline_responses.py +68 -0
- aria_code/apps/cli/providers/llm/ollama_stream.py +1594 -0
- aria_code/apps/cli/providers/llm/response_cache.py +60 -0
- aria_code/apps/cli/providers/llm/sse_stream.py +257 -0
- aria_code/apps/cli/providers/runtime_bridge.py +353 -0
- aria_code/apps/cli/providers/vertexai_stream.py +316 -0
- aria_code/apps/cli/runtime_consumer.py +657 -0
- aria_code/apps/cli/sandbox.py +32 -0
- aria_code/apps/cli/session_export.py +120 -0
- aria_code/apps/cli/session_jsonl.py +208 -0
- aria_code/apps/cli/session_store.py +112 -0
- aria_code/apps/cli/skills_catalog.py +273 -0
- aria_code/apps/cli/todo_tracker.py +190 -0
- aria_code/apps/cli/tool_executor.py +823 -0
- aria_code/apps/cli/tool_registry.py +34 -0
- aria_code/apps/cli/tool_scope.py +106 -0
- aria_code/apps/cli/tools/__init__.py +40 -0
- aria_code/apps/cli/tools/context.py +46 -0
- aria_code/apps/cli/tools/file_tools.py +180 -0
- aria_code/apps/cli/tools/market_tools.py +620 -0
- aria_code/apps/cli/tools/notebook_tools.py +111 -0
- aria_code/apps/cli/tools/system_tools.py +726 -0
- aria_code/apps/cli/tools/write_tools.py +845 -0
- aria_code/apps/cli/tradingview_bridge.py +529 -0
- aria_code/apps/cli/turn_planning.py +137 -0
- aria_code/apps/cli/update_check.py +153 -0
- aria_code/apps/cli/utils/__init__.py +0 -0
- aria_code/apps/cli/utils/market_detect.py +1597 -0
- aria_code/apps/daemon/README.md +14 -0
- aria_code/apps/vscode/README.md +115 -0
- aria_code/apps/vscode/package.json +70 -0
- aria_code/aria_cli.py +6743 -0
- aria_code/aria_daemon.py +1398 -0
- aria_code/aria_feishu_bot.py +1423 -0
- aria_code/aria_mcp_server.py +7 -0
- aria_code/aria_relay_client.py +4 -0
- aria_code/aria_relay_server.py +490 -0
- aria_code/aria_telegram_bot.py +202 -0
- aria_code/ariarc.py +385 -0
- aria_code/artifacts.py +581 -0
- aria_code/backtest_engine.py +4 -0
- aria_code/backtest_report.py +4 -0
- aria_code/brokers/__init__.py +78 -0
- aria_code/brokers/automation.py +193 -0
- aria_code/brokers/base.py +231 -0
- aria_code/brokers/capabilities.py +264 -0
- aria_code/brokers/cn/__init__.py +10 -0
- aria_code/brokers/cn/easytrader_broker.py +193 -0
- aria_code/brokers/cn/futu_broker.py +194 -0
- aria_code/brokers/cn/longbridge_broker.py +190 -0
- aria_code/brokers/cn/tiger_broker.py +196 -0
- aria_code/brokers/cn/xtquant_broker.py +225 -0
- aria_code/brokers/config.py +414 -0
- aria_code/brokers/intl/__init__.py +5 -0
- aria_code/brokers/intl/alpaca_broker.py +183 -0
- aria_code/brokers/intl/ibkr_broker.py +215 -0
- aria_code/brokers/intl/trading212_broker.py +183 -0
- aria_code/brokers/intl/webull_broker.py +156 -0
- aria_code/brokers/paper_broker.py +266 -0
- aria_code/brokers/planning.py +296 -0
- aria_code/brokers/registry.py +213 -0
- aria_code/brokers/trading.py +267 -0
- aria_code/canva_client.py +4 -0
- aria_code/change_store.py +127 -0
- aria_code/clients/__init__.py +0 -0
- aria_code/clients/aliyun_data_client.py +660 -0
- aria_code/clients/aria_relay_client.py +182 -0
- aria_code/clients/canva_client.py +284 -0
- aria_code/clients/figma_client.py +100 -0
- aria_code/clients/football_data_client.py +1671 -0
- aria_code/clients/kling_video_client.py +182 -0
- aria_code/clients/market_data_client.py +1976 -0
- aria_code/clients/openai_image_client.py +242 -0
- aria_code/clients/runway_video_client.py +157 -0
- aria_code/clients/warehouse_erp_client.py +105 -0
- aria_code/cloud_memory.py +181 -0
- aria_code/command_safety.py +19 -0
- aria_code/computer_use_tools.py +4 -0
- aria_code/dashboard_generator.py +579 -0
- aria_code/data_analysis_tools.py +4 -0
- aria_code/data_cleaner.py +483 -0
- aria_code/data_service.py +513 -0
- aria_code/datasources/__init__.py +23 -0
- aria_code/datasources/base.py +166 -0
- aria_code/datasources/router.py +253 -0
- aria_code/datasources/sources/__init__.py +15 -0
- aria_code/datasources/sources/akshare_source.py +269 -0
- aria_code/datasources/sources/alpha_vantage_source.py +203 -0
- aria_code/datasources/sources/edgar_source.py +219 -0
- aria_code/datasources/sources/finnhub_source.py +198 -0
- aria_code/datasources/sources/fred_source.py +223 -0
- aria_code/datasources/sources/tushare_source.py +143 -0
- aria_code/datasources/sources/web_scraper_source.py +278 -0
- aria_code/datasources/sources/world_bank_source.py +206 -0
- aria_code/datasources/sources/yfinance_source.py +152 -0
- aria_code/demo_player.py +207 -0
- aria_code/doctor.py +813 -0
- aria_code/domain/__init__.py +0 -0
- aria_code/domain/backtest_engine.py +354 -0
- aria_code/domain/backtest_report.py +472 -0
- aria_code/domain/finance_formulas.py +389 -0
- aria_code/domain/portfolio_ledger.py +307 -0
- aria_code/domain/strategy_vault.py +421 -0
- aria_code/evals/__init__.py +37 -0
- aria_code/evals/harness.py +598 -0
- aria_code/evals/runner.py +238 -0
- aria_code/external_agent_runner.py +115 -0
- aria_code/figma_client.py +4 -0
- aria_code/file_analysis_tools.py +4 -0
- aria_code/finance_formulas.py +4 -0
- aria_code/football_data_client.py +4 -0
- aria_code/image_gen_tools.py +4 -0
- aria_code/intent_classifier.py +386 -0
- aria_code/kling_video_client.py +4 -0
- aria_code/licensing.py +151 -0
- aria_code/local_finance_tools.py +4 -0
- aria_code/local_image_provider.py +4 -0
- aria_code/local_llm_provider.py +4 -0
- aria_code/macro_tools.py +4 -0
- aria_code/markdown_pdf.py +289 -0
- aria_code/market_data_client.py +4 -0
- aria_code/mcp_client.py +639 -0
- aria_code/memory_manager.py +246 -0
- aria_code/model_capability.py +486 -0
- aria_code/notification_tools.py +4 -0
- aria_code/openai_image_client.py +4 -0
- aria_code/packages/__init__.py +23 -0
- aria_code/packages/adk_bridge/__init__.py +11 -0
- aria_code/packages/adk_bridge/code_review_tools.py +49 -0
- aria_code/packages/adk_bridge/market_tools.py +125 -0
- aria_code/packages/aria_agents/__init__.py +5 -0
- aria_code/packages/aria_agents/manifest.py +78 -0
- aria_code/packages/aria_core/__init__.py +34 -0
- aria_code/packages/aria_core/architecture.py +269 -0
- aria_code/packages/aria_core/export.py +147 -0
- aria_code/packages/aria_core/manifest.py +65 -0
- aria_code/packages/aria_core/paths.py +57 -0
- aria_code/packages/aria_core/secure_file.py +68 -0
- aria_code/packages/aria_infra/__init__.py +15 -0
- aria_code/packages/aria_infra/arthera.py +52 -0
- aria_code/packages/aria_infra/doctor.py +246 -0
- aria_code/packages/aria_infra/product.py +40 -0
- aria_code/packages/aria_mcp/__init__.py +25 -0
- aria_code/packages/aria_mcp/bridge.py +201 -0
- aria_code/packages/aria_mcp/circuit.py +82 -0
- aria_code/packages/aria_mcp/config.py +111 -0
- aria_code/packages/aria_mcp/installer.py +95 -0
- aria_code/packages/aria_mcp/server.py +1550 -0
- aria_code/packages/aria_mcp/tools.py +81 -0
- aria_code/packages/aria_sdk/__init__.py +19 -0
- aria_code/packages/aria_sdk/client.py +396 -0
- aria_code/packages/aria_sdk/providers.py +70 -0
- aria_code/packages/aria_sdk/streaming.py +79 -0
- aria_code/packages/aria_sdk/types.py +86 -0
- aria_code/packages/aria_services/__init__.py +92 -0
- aria_code/packages/aria_services/cache.py +60 -0
- aria_code/packages/aria_services/context.py +356 -0
- aria_code/packages/aria_services/context_checkpoints.py +157 -0
- aria_code/packages/aria_services/data.py +11 -0
- aria_code/packages/aria_services/provider_health.py +268 -0
- aria_code/packages/aria_services/references.py +247 -0
- aria_code/packages/aria_services/registry.py +249 -0
- aria_code/packages/aria_services/research_protocol.py +57 -0
- aria_code/packages/aria_services/research_quality.py +186 -0
- aria_code/packages/aria_services/settings.py +157 -0
- aria_code/packages/aria_services/usage.py +151 -0
- aria_code/packages/aria_skills/__init__.py +46 -0
- aria_code/packages/aria_skills/catalog.py +149 -0
- aria_code/packages/aria_skills/loader.py +455 -0
- aria_code/packages/aria_skills/registry.py +84 -0
- aria_code/packages/aria_tools/__init__.py +5 -0
- aria_code/packages/aria_tools/financial/__init__.py +8 -0
- aria_code/packages/aria_tools/financial/_paths.py +130 -0
- aria_code/packages/aria_tools/financial/compliance.py +31 -0
- aria_code/packages/aria_tools/financial/factors.py +33 -0
- aria_code/packages/aria_tools/financial/risk_tools.py +33 -0
- aria_code/packages/aria_tools/financial/strategy.py +31 -0
- aria_code/packages/aria_tools/registry.py +128 -0
- aria_code/packages/quant_engine/__init__.py +34 -0
- aria_code/packages/quant_engine/_namespace.py +62 -0
- aria_code/packages/quant_engine/backtest/__init__.py +14 -0
- aria_code/packages/quant_engine/backtest/core.py +53 -0
- aria_code/packages/quant_engine/backtest/engine.py +96 -0
- aria_code/packages/quant_engine/portfolio/__init__.py +13 -0
- aria_code/packages/quant_engine/portfolio/optimizer.py +85 -0
- aria_code/packages/quant_engine/sports/__init__.py +80 -0
- aria_code/packages/quant_engine/sports/calibrator.py +354 -0
- aria_code/packages/quant_engine/sports/dixon_coles.py +234 -0
- aria_code/packages/quant_engine/sports/elo.py +300 -0
- aria_code/packages/quant_engine/sports/form.py +188 -0
- aria_code/packages/quant_engine/sports/h2h.py +195 -0
- aria_code/packages/quant_engine/sports/ml_model.py +377 -0
- aria_code/packages/quant_engine/sports/predictor.py +312 -0
- aria_code/packages/quant_engine/sports/tracker.py +665 -0
- aria_code/packages/quant_engine/stochastic/__init__.py +35 -0
- aria_code/packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
- aria_code/packages/quant_engine/stochastic/ito_calculus.py +477 -0
- aria_code/packages/quant_engine/stochastic/kelly_criterion.py +181 -0
- aria_code/packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
- aria_code/packages/quant_engine/stochastic/options_pricing.py +576 -0
- aria_code/packages/quant_engine/stochastic/stochastic_processes.py +90 -0
- aria_code/packs/__init__.py +85 -0
- aria_code/packs/base.py +168 -0
- aria_code/packs/finance/__init__.py +250 -0
- aria_code/packs/logistics/__init__.py +169 -0
- aria_code/packs/payments/__init__.py +139 -0
- aria_code/packs/realty/__init__.py +190 -0
- aria_code/packs/registry.py +202 -0
- aria_code/packs/rules.py +90 -0
- aria_code/plan_utils.py +194 -0
- aria_code/plugin_loader.py +328 -0
- aria_code/portfolio_ledger.py +4 -0
- aria_code/preview_server.py +449 -0
- aria_code/privacy/__init__.py +5 -0
- aria_code/privacy/feedback.py +123 -0
- aria_code/project_review.py +278 -0
- aria_code/project_review_api.py +235 -0
- aria_code/project_review_server.py +18 -0
- aria_code/project_tools.py +4 -0
- aria_code/providers/__init__.py +30 -0
- aria_code/providers/llm/__init__.py +19 -0
- aria_code/providers/llm/anthropic.py +184 -0
- aria_code/providers/llm/autoconfig.py +213 -0
- aria_code/providers/llm/base.py +139 -0
- aria_code/providers/llm/custom.py +158 -0
- aria_code/providers/llm/ollama.py +129 -0
- aria_code/providers/llm/openai_compat.py +313 -0
- aria_code/providers/llm/registry.py +444 -0
- aria_code/providers/local_image_provider.py +202 -0
- aria_code/providers/local_llm_provider.py +589 -0
- aria_code/realty_data_tools.py +4 -0
- aria_code/report_exporters.py +150 -0
- aria_code/report_generator.py +1764 -0
- aria_code/runtime/__init__.py +169 -0
- aria_code/runtime/acceptance.py +481 -0
- aria_code/runtime/agent_loop.py +1520 -0
- aria_code/runtime/approval.py +57 -0
- aria_code/runtime/budget.py +232 -0
- aria_code/runtime/checkpoints.py +432 -0
- aria_code/runtime/crash_report.py +174 -0
- aria_code/runtime/events.py +113 -0
- aria_code/runtime/gateway.py +150 -0
- aria_code/runtime/lsp.py +484 -0
- aria_code/runtime/orchestrator.py +55 -0
- aria_code/runtime/repo_map.py +700 -0
- aria_code/runtime/run_state.py +127 -0
- aria_code/runtime/run_store.py +450 -0
- aria_code/runtime/self_healing.py +422 -0
- aria_code/runtime/subagent.py +547 -0
- aria_code/runtime/task_ledger.py +86 -0
- aria_code/runtime/task_orchestrator.py +90 -0
- aria_code/runtime/tool_executor.py +217 -0
- aria_code/runtime/tool_policy.py +107 -0
- aria_code/runtime/worktrees.py +215 -0
- aria_code/runway_video_client.py +4 -0
- aria_code/safety/__init__.py +23 -0
- aria_code/safety/permissions.py +306 -0
- aria_code/safety/service.py +102 -0
- aria_code/setup_wizard.py +673 -0
- aria_code/spreadsheet_tools.py +4 -0
- aria_code/strategy_vault.py +4 -0
- aria_code/tools/__init__.py +0 -0
- aria_code/tools/artifact_tools.py +252 -0
- aria_code/tools/broker_tools.py +92 -0
- aria_code/tools/build_quant_engine.py +97 -0
- aria_code/tools/code_audit_tools.py +192 -0
- aria_code/tools/computer_use_tools.py +504 -0
- aria_code/tools/data_analysis_tools.py +809 -0
- aria_code/tools/enterprise_finance_tools.py +156 -0
- aria_code/tools/extended_tools.py +240 -0
- aria_code/tools/file_analysis_tools.py +885 -0
- aria_code/tools/image_gen_tools.py +114 -0
- aria_code/tools/local_finance_tools.py +3417 -0
- aria_code/tools/logistics_tools.py +135 -0
- aria_code/tools/macro_tools.py +369 -0
- aria_code/tools/notification_tools.py +249 -0
- aria_code/tools/project_tools.py +525 -0
- aria_code/tools/realty_data_tools.py +659 -0
- aria_code/tools/spreadsheet_tools.py +388 -0
- aria_code/tools/stripe_tools.py +115 -0
- aria_code/ui/__init__.py +105 -0
- aria_code/ui/banner.py +508 -0
- aria_code/ui/completer.py +501 -0
- aria_code/ui/console.py +281 -0
- aria_code/ui/image_render.py +243 -0
- aria_code/ui/input_box.py +488 -0
- aria_code/ui/picker.py +201 -0
- aria_code/ui/render/__init__.py +11 -0
- aria_code/ui/render/finance.py +1705 -0
- aria_code/ui/render/market.py +225 -0
- aria_code/ui/render/output.py +775 -0
- aria_code/ui/render/responsive.py +75 -0
- aria_code/ui/render/team.py +354 -0
- aria_code/ui/robot.py +235 -0
- aria_code/ui/startup_dashboard.py +109 -0
- aria_code/utils/vscode_uri.py +37 -0
- aria_code/video_analysis.py +147 -0
- aria_code/video_editor.py +271 -0
- aria_code/workspace/__init__.py +6 -0
- aria_code/workspace/files.py +193 -0
- aria_code/workspace/verify.py +113 -0
- aria_code/workspace_changes.py +199 -0
- aria_code/workspace_context.py +233 -0
- aria_code/workspace_service.py +196 -0
- aria_code/workspace_tools.py +150 -0
- aria_code-0.52.0.dist-info/METADATA +237 -0
- aria_code-0.52.0.dist-info/RECORD +478 -0
- aria_code-0.52.0.dist-info/WHEEL +5 -0
- aria_code-0.52.0.dist-info/entry_points.txt +3 -0
- aria_code-0.52.0.dist-info/licenses/LICENSE +201 -0
- aria_code-0.52.0.dist-info/licenses/NOTICE +49 -0
- aria_code-0.52.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
"""
|
|
2
|
+
providers/llm/registry.py — LLM Provider 注册中心
|
|
3
|
+
==================================================
|
|
4
|
+
• 从 ~/.aria/providers.yaml 或 .aria.json 加载用户配置
|
|
5
|
+
• 按优先级自动路由:本地 Ollama → DeepSeek → OpenAI → Anthropic → Groq
|
|
6
|
+
• 提供 stream_cloud_fallback() 供 aria_cli.py 调用
|
|
7
|
+
|
|
8
|
+
用户配置示例 (~/.aria/providers.yaml):
|
|
9
|
+
llm:
|
|
10
|
+
default: ollama/qwen2.5:7b
|
|
11
|
+
fallback:
|
|
12
|
+
- deepseek/deepseek-chat
|
|
13
|
+
- openai/gpt-4o-mini
|
|
14
|
+
- anthropic/claude-3-5-haiku-latest
|
|
15
|
+
code_tasks: ollama/qwen2.5-coder:7b
|
|
16
|
+
heavy_analysis: anthropic/claude-3-5-sonnet-20241022
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import asyncio
|
|
22
|
+
import logging
|
|
23
|
+
import os
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Any, AsyncIterator, Callable, Dict, List, Optional, Tuple, Type
|
|
26
|
+
|
|
27
|
+
import yaml
|
|
28
|
+
|
|
29
|
+
from .base import BaseLLMProvider, Message, ProviderConfig
|
|
30
|
+
from .ollama import OllamaProvider
|
|
31
|
+
from .openai_compat import (
|
|
32
|
+
DeepSeekProvider, OpenAIProvider, GroqProvider, GoogleProvider,
|
|
33
|
+
TogetherProvider, DashScopeProvider, LMStudioProvider,
|
|
34
|
+
SiliconFlowProvider, MoonshotProvider, ZhiPuProvider,
|
|
35
|
+
)
|
|
36
|
+
from .anthropic import AnthropicProvider
|
|
37
|
+
from .custom import CustomProviderSpec, build_custom_provider, parse_custom_providers
|
|
38
|
+
from aria_code.packages.aria_services.provider_health import (
|
|
39
|
+
GLOBAL_PROVIDER_HEALTH,
|
|
40
|
+
ProviderHealthRegistry,
|
|
41
|
+
classify_provider_error,
|
|
42
|
+
)
|
|
43
|
+
from aria_code.packages.aria_core.paths import aria_home
|
|
44
|
+
|
|
45
|
+
logger = logging.getLogger(__name__)
|
|
46
|
+
|
|
47
|
+
# ── Provider 目录:name → class ──────────────────────────────────────────────
|
|
48
|
+
_PROVIDER_CLASSES: Dict[str, Type[BaseLLMProvider]] = {
|
|
49
|
+
"ollama": OllamaProvider,
|
|
50
|
+
"deepseek": DeepSeekProvider,
|
|
51
|
+
"openai": OpenAIProvider,
|
|
52
|
+
"anthropic": AnthropicProvider,
|
|
53
|
+
# Gemini is the default model in the shipped config but had no registry
|
|
54
|
+
# entry, so it could not be reached through the provider chain at all.
|
|
55
|
+
"google": GoogleProvider,
|
|
56
|
+
"gemini": GoogleProvider,
|
|
57
|
+
"groq": GroqProvider,
|
|
58
|
+
"together": TogetherProvider,
|
|
59
|
+
"dashscope": DashScopeProvider,
|
|
60
|
+
"lmstudio": LMStudioProvider,
|
|
61
|
+
# 国内可访问
|
|
62
|
+
"siliconflow": SiliconFlowProvider,
|
|
63
|
+
"moonshot": MoonshotProvider,
|
|
64
|
+
"zhipu": ZhiPuProvider,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# ── 默认 fallback 优先级(无用户配置时)────────────────────────────────────
|
|
68
|
+
# 国内环境优先走 DeepSeek / SiliconFlow / DashScope,再尝试 OpenAI / Groq
|
|
69
|
+
_DEFAULT_FALLBACK_CHAIN = [
|
|
70
|
+
("ollama", None, None),
|
|
71
|
+
("deepseek", "DEEPSEEK_API_KEY", "deepseek-chat"),
|
|
72
|
+
("siliconflow", "SILICONFLOW_API_KEY", "deepseek-ai/DeepSeek-V3"),
|
|
73
|
+
("dashscope", "DASHSCOPE_API_KEY", "qwen-plus"),
|
|
74
|
+
("moonshot", "MOONSHOT_API_KEY", "moonshot-v1-8k"),
|
|
75
|
+
("zhipu", "ZHIPUAI_API_KEY", "glm-4-flash"),
|
|
76
|
+
("google", "GEMINI_API_KEY", "gemini-2.5-flash"),
|
|
77
|
+
("openai", "OPENAI_API_KEY", "gpt-4o-mini"),
|
|
78
|
+
("anthropic", "ANTHROPIC_API_KEY", "claude-3-5-haiku-latest"),
|
|
79
|
+
("groq", "GROQ_API_KEY", "llama-3.3-70b-versatile"),
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
# ── 用户可注册自定义 provider ─────────────────────────────────────────────────
|
|
83
|
+
def register_provider(name: str, cls: Type[BaseLLMProvider]) -> None:
|
|
84
|
+
"""注册自定义 provider 类(供插件/用户扩展使用)"""
|
|
85
|
+
_PROVIDER_CLASSES[name.lower()] = cls
|
|
86
|
+
logger.info(f"✓ 注册自定义 provider: {name}")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# ── 配置加载 ──────────────────────────────────────────────────────────────────
|
|
90
|
+
_CONFIG_PATHS = [
|
|
91
|
+
# ~/.arthera/providers.json is the primary path used by the aria-code CLI (/apikey command)
|
|
92
|
+
aria_home() / "providers.json",
|
|
93
|
+
# Legacy / alternative paths
|
|
94
|
+
Path.home() / ".aria" / "providers.yaml",
|
|
95
|
+
Path.home() / ".aria" / "providers.json",
|
|
96
|
+
Path(".aria.json"),
|
|
97
|
+
Path(".aria.yaml"),
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
_CUSTOM_SPECS_CACHE: Optional[Dict[str, "CustomProviderSpec"]] = None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _custom_provider_specs() -> Dict[str, "CustomProviderSpec"]:
|
|
105
|
+
"""解析一次并缓存。配置在进程生命周期内不变,重复解析只会重复刷日志。"""
|
|
106
|
+
global _CUSTOM_SPECS_CACHE
|
|
107
|
+
if _CUSTOM_SPECS_CACHE is None:
|
|
108
|
+
try:
|
|
109
|
+
_CUSTOM_SPECS_CACHE = parse_custom_providers(_load_raw_user_config())
|
|
110
|
+
except Exception as exc:
|
|
111
|
+
logger.warning("解析 model_providers 失败,忽略自定义 provider: %s", exc)
|
|
112
|
+
_CUSTOM_SPECS_CACHE = {}
|
|
113
|
+
return _CUSTOM_SPECS_CACHE
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def reload_custom_providers() -> Dict[str, "CustomProviderSpec"]:
|
|
117
|
+
"""清空缓存并重新解析(供 /config reload 之类的命令调用)。"""
|
|
118
|
+
global _CUSTOM_SPECS_CACHE
|
|
119
|
+
_CUSTOM_SPECS_CACHE = None
|
|
120
|
+
return _custom_provider_specs()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _load_raw_user_config() -> Dict:
|
|
124
|
+
"""读取用户配置的**顶层**结构。
|
|
125
|
+
|
|
126
|
+
注意跟 _load_user_config() 的区别:那个函数返回的是 `llm` 子节(历史行为,
|
|
127
|
+
调用方依赖它直接拿到 default/fallback),而 model_providers 与 llm 平级,
|
|
128
|
+
必须从顶层取,否则永远读不到。
|
|
129
|
+
"""
|
|
130
|
+
for path in _CONFIG_PATHS:
|
|
131
|
+
if not path.exists():
|
|
132
|
+
continue
|
|
133
|
+
try:
|
|
134
|
+
with open(path, encoding="utf-8") as fh:
|
|
135
|
+
data = yaml.safe_load(fh) if path.suffix in (".yaml", ".yml") \
|
|
136
|
+
else __import__("json").load(fh)
|
|
137
|
+
if isinstance(data, dict):
|
|
138
|
+
return data
|
|
139
|
+
except Exception as exc:
|
|
140
|
+
logger.debug("加载配置 %s 失败: %s", path, exc)
|
|
141
|
+
return {}
|
|
142
|
+
|
|
143
|
+
def _load_user_config() -> Dict:
|
|
144
|
+
for p in _CONFIG_PATHS:
|
|
145
|
+
if p.exists():
|
|
146
|
+
try:
|
|
147
|
+
with open(p, encoding="utf-8") as f:
|
|
148
|
+
data = yaml.safe_load(f) if p.suffix in (".yaml",".yml") \
|
|
149
|
+
else __import__("json").load(f)
|
|
150
|
+
return data.get("llm", data) if isinstance(data, dict) else {}
|
|
151
|
+
except Exception as e:
|
|
152
|
+
logger.debug(f"加载配置 {p} 失败: {e}")
|
|
153
|
+
return {}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _load_provider_cfg_from_file(name: str) -> Dict[str, str]:
|
|
157
|
+
"""
|
|
158
|
+
从 ~/.arthera/providers.json 的 llm 节读取指定 provider 的 api_key / base_url。
|
|
159
|
+
这是 /apikey set 命令写入的位置;ProviderConfig.from_env() 只读环境变量,
|
|
160
|
+
此函数补足文件侧的配置,让两者合并后才能正确工作。
|
|
161
|
+
"""
|
|
162
|
+
import json as _json
|
|
163
|
+
primary = aria_home() / "providers.json"
|
|
164
|
+
for p in [primary] + _CONFIG_PATHS:
|
|
165
|
+
if not p.exists():
|
|
166
|
+
continue
|
|
167
|
+
try:
|
|
168
|
+
raw = _json.loads(p.read_text(encoding="utf-8")) if p.suffix == ".json" \
|
|
169
|
+
else yaml.safe_load(p.read_text(encoding="utf-8"))
|
|
170
|
+
llm = raw.get("llm", raw) if isinstance(raw, dict) else {}
|
|
171
|
+
entry = llm.get(name.lower(), {})
|
|
172
|
+
if entry:
|
|
173
|
+
return {k: v for k, v in entry.items() if v}
|
|
174
|
+
except Exception:
|
|
175
|
+
pass
|
|
176
|
+
return {}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _parse_provider_spec(spec: str) -> Tuple[str, Optional[str]]:
|
|
180
|
+
"""
|
|
181
|
+
解析 'deepseek/deepseek-chat' → ('deepseek', 'deepseek-chat')
|
|
182
|
+
解析 'ollama' → ('ollama', None)
|
|
183
|
+
"""
|
|
184
|
+
if "/" in spec:
|
|
185
|
+
name, model = spec.split("/", 1)
|
|
186
|
+
return name.strip().lower(), model.strip()
|
|
187
|
+
return spec.strip().lower(), None
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def get_provider(
|
|
191
|
+
spec: str,
|
|
192
|
+
api_key: Optional[str] = None,
|
|
193
|
+
base_url: Optional[str] = None,
|
|
194
|
+
) -> BaseLLMProvider:
|
|
195
|
+
"""
|
|
196
|
+
按 spec 字符串实例化 provider。
|
|
197
|
+
|
|
198
|
+
Examples:
|
|
199
|
+
get_provider("ollama/qwen2.5:7b")
|
|
200
|
+
get_provider("deepseek/deepseek-chat")
|
|
201
|
+
get_provider("anthropic/claude-3-5-haiku-latest")
|
|
202
|
+
"""
|
|
203
|
+
name, model = _parse_provider_spec(spec)
|
|
204
|
+
|
|
205
|
+
# 用户在 model_providers 里声明的 provider 优先于内置同名项——用户显式写下
|
|
206
|
+
# 的配置应当能覆盖内置默认(比如把 openai 指向自建的兼容网关)。
|
|
207
|
+
custom = _custom_provider_specs().get(name)
|
|
208
|
+
if custom is not None:
|
|
209
|
+
if custom.env_key and not custom.api_key():
|
|
210
|
+
raise ValueError(custom.missing_key_hint())
|
|
211
|
+
provider = build_custom_provider(custom, model)
|
|
212
|
+
if api_key:
|
|
213
|
+
provider.config.api_key = api_key
|
|
214
|
+
if base_url:
|
|
215
|
+
provider.config.base_url = base_url
|
|
216
|
+
return provider
|
|
217
|
+
|
|
218
|
+
cls = _PROVIDER_CLASSES.get(name)
|
|
219
|
+
if not cls:
|
|
220
|
+
known = list(_PROVIDER_CLASSES) + list(_custom_provider_specs())
|
|
221
|
+
raise ValueError(
|
|
222
|
+
f"未知 provider: '{name}'。可用: {', '.join(sorted(known))}\n"
|
|
223
|
+
f"如需接入未列出的服务,在 ~/.aria/providers.yaml 的 model_providers "
|
|
224
|
+
f"段声明即可(只填 base_url 与 env_key,不要写密钥值)。"
|
|
225
|
+
)
|
|
226
|
+
cfg = _build_cfg(name, model)
|
|
227
|
+
# 调用方显式传入的参数优先级最高
|
|
228
|
+
if api_key:
|
|
229
|
+
cfg.api_key = api_key
|
|
230
|
+
if base_url:
|
|
231
|
+
cfg.base_url = base_url
|
|
232
|
+
return cls(cfg)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def list_available_providers() -> List[Dict[str, Any]]:
|
|
236
|
+
"""返回所有 provider 及其可用状态(同步,用于 /config 命令显示)"""
|
|
237
|
+
result = []
|
|
238
|
+
for name, cls in _PROVIDER_CLASSES.items():
|
|
239
|
+
cfg = _build_cfg(name) # 合并环境变量 + providers.json
|
|
240
|
+
available = cfg.is_configured()
|
|
241
|
+
result.append({
|
|
242
|
+
"name": name,
|
|
243
|
+
"available": available,
|
|
244
|
+
"local": cls.local,
|
|
245
|
+
"tools": cls.supports_tools,
|
|
246
|
+
"thinking": cls.supports_thinking,
|
|
247
|
+
"source": "builtin",
|
|
248
|
+
})
|
|
249
|
+
# 用户在 model_providers 里声明的 provider 也要出现在 /config 里——否则
|
|
250
|
+
# 用户配好了却在列表中看不到,会以为没生效。
|
|
251
|
+
for pid, spec in sorted(_custom_provider_specs().items()):
|
|
252
|
+
result.append({
|
|
253
|
+
"name": pid,
|
|
254
|
+
"available": bool(spec.api_key()) if spec.env_key else True,
|
|
255
|
+
"local": False,
|
|
256
|
+
"tools": True,
|
|
257
|
+
"thinking": False,
|
|
258
|
+
"source": "custom",
|
|
259
|
+
"base_url": spec.base_url,
|
|
260
|
+
"hint": "" if (not spec.env_key or spec.api_key()) else spec.missing_key_hint(),
|
|
261
|
+
})
|
|
262
|
+
return result
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _build_cfg(name: str, model: Optional[str] = None) -> ProviderConfig:
|
|
266
|
+
"""
|
|
267
|
+
构建 ProviderConfig:环境变量优先,再回落到 providers.json,
|
|
268
|
+
确保 /apikey set 保存的 key 能被实际使用。
|
|
269
|
+
"""
|
|
270
|
+
cfg = ProviderConfig.from_env(name)
|
|
271
|
+
file_cfg = _load_provider_cfg_from_file(name)
|
|
272
|
+
|
|
273
|
+
# 补充 api_key(文件里的)— 环境变量已在 from_env() 中优先读取;
|
|
274
|
+
# 文件是后备:提示用户改用环境变量以避免明文存储 key。
|
|
275
|
+
if not cfg.api_key and file_cfg.get("api_key"):
|
|
276
|
+
cfg.api_key = file_cfg["api_key"]
|
|
277
|
+
_env_names = {
|
|
278
|
+
"deepseek": "DEEPSEEK_API_KEY", "openai": "OPENAI_API_KEY",
|
|
279
|
+
"anthropic": "ANTHROPIC_API_KEY", "groq": "GROQ_API_KEY",
|
|
280
|
+
"siliconflow": "SILICONFLOW_API_KEY", "moonshot": "MOONSHOT_API_KEY",
|
|
281
|
+
"zhipu": "ZHIPUAI_API_KEY", "dashscope": "DASHSCOPE_API_KEY",
|
|
282
|
+
}
|
|
283
|
+
if name.lower() in _env_names:
|
|
284
|
+
logger.debug(
|
|
285
|
+
"⚠ API key for '%s' loaded from ~/.arthera/providers.json (plaintext). "
|
|
286
|
+
"Migrate to env var: export %s=<key> then remove api_key from aria_code.providers.json.",
|
|
287
|
+
name, _env_names[name.lower()],
|
|
288
|
+
)
|
|
289
|
+
# 补充 base_url(支持用户自定义端点 / 代理)
|
|
290
|
+
if not cfg.base_url and file_cfg.get("base_url"):
|
|
291
|
+
cfg.base_url = file_cfg["base_url"]
|
|
292
|
+
|
|
293
|
+
if model:
|
|
294
|
+
cfg.model = model
|
|
295
|
+
return cfg
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
async def _try_provider(
|
|
299
|
+
spec: str,
|
|
300
|
+
messages: List[Message],
|
|
301
|
+
on_token: Optional[Callable] = None,
|
|
302
|
+
cancel_event=None,
|
|
303
|
+
*,
|
|
304
|
+
health: ProviderHealthRegistry | None = None,
|
|
305
|
+
) -> Optional[Dict[str, Any]]:
|
|
306
|
+
"""尝试用指定 provider 完成对话,失败返回 None。"""
|
|
307
|
+
health = health or GLOBAL_PROVIDER_HEALTH
|
|
308
|
+
try:
|
|
309
|
+
name, model = _parse_provider_spec(spec)
|
|
310
|
+
cls = _PROVIDER_CLASSES.get(name)
|
|
311
|
+
if not cls:
|
|
312
|
+
return None
|
|
313
|
+
|
|
314
|
+
cfg = _build_cfg(name, model)
|
|
315
|
+
provider = cls(cfg)
|
|
316
|
+
|
|
317
|
+
if health.provider_in_cooldown(name):
|
|
318
|
+
logger.debug(f"[{name}] cooling down, skipped")
|
|
319
|
+
return None
|
|
320
|
+
|
|
321
|
+
if not await provider.is_available():
|
|
322
|
+
logger.debug(f"[{name}] 不可用,跳过")
|
|
323
|
+
health.mark_issue(classify_provider_error(name, "provider unavailable"))
|
|
324
|
+
return None
|
|
325
|
+
|
|
326
|
+
logger.info(f"[{name}] 尝试生成响应 (model={cfg.model})")
|
|
327
|
+
full_text = ""
|
|
328
|
+
async for event in provider.stream(
|
|
329
|
+
messages, cancel_event=cancel_event
|
|
330
|
+
):
|
|
331
|
+
t = event.get("type")
|
|
332
|
+
if t == "token":
|
|
333
|
+
tok = event.get("text", "")
|
|
334
|
+
full_text += tok
|
|
335
|
+
if on_token:
|
|
336
|
+
on_token(tok)
|
|
337
|
+
elif t == "error":
|
|
338
|
+
err = event.get("message")
|
|
339
|
+
logger.debug(f"[{name}] 流式错误: {err}")
|
|
340
|
+
health.mark_issue(classify_provider_error(name, err))
|
|
341
|
+
return None
|
|
342
|
+
elif t == "done":
|
|
343
|
+
break
|
|
344
|
+
|
|
345
|
+
if not full_text.strip():
|
|
346
|
+
health.mark_issue(classify_provider_error(name, "provider returned no usable data"))
|
|
347
|
+
return None
|
|
348
|
+
|
|
349
|
+
health.mark_success(name)
|
|
350
|
+
return {
|
|
351
|
+
"success": True,
|
|
352
|
+
"response": full_text,
|
|
353
|
+
"provider": name,
|
|
354
|
+
"model": cfg.model or "unknown",
|
|
355
|
+
}
|
|
356
|
+
except Exception as e:
|
|
357
|
+
logger.debug(f"[{spec}] 异常: {e}")
|
|
358
|
+
health.mark_issue(classify_provider_error(_parse_provider_spec(spec)[0], e))
|
|
359
|
+
return None
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
async def stream_cloud_fallback(
|
|
363
|
+
message: str,
|
|
364
|
+
history: List[Dict],
|
|
365
|
+
on_token: Optional[Callable] = None,
|
|
366
|
+
cancel_event=None,
|
|
367
|
+
*,
|
|
368
|
+
health: ProviderHealthRegistry | None = None,
|
|
369
|
+
include_defaults: bool = True,
|
|
370
|
+
) -> Dict[str, Any]:
|
|
371
|
+
"""
|
|
372
|
+
CLI fallback 入口:当 Ollama 不可用时调用。
|
|
373
|
+
按优先级依次尝试云端 provider,首个成功的直接返回。
|
|
374
|
+
|
|
375
|
+
优先级:
|
|
376
|
+
1. 用户 ~/.aria/providers.yaml 里的 fallback 列表
|
|
377
|
+
2. 内置默认链: DeepSeek → OpenAI → Anthropic → Groq → DashScope
|
|
378
|
+
"""
|
|
379
|
+
# 构建消息列表
|
|
380
|
+
msgs: List[Message] = [
|
|
381
|
+
Message(role="system", content=(
|
|
382
|
+
"You are Aria, an AI-native product and software-engineering assistant. "
|
|
383
|
+
"Help users review products, understand repositories, design architecture, "
|
|
384
|
+
"write and verify code, and improve quality. Finance is an optional domain "
|
|
385
|
+
"capability used only when the request is explicitly financial. Answer concisely "
|
|
386
|
+
"and never claim to have inspected files or run tests unless tool evidence proves it."
|
|
387
|
+
))
|
|
388
|
+
]
|
|
389
|
+
for h in (history or [])[-12:]:
|
|
390
|
+
role = h.get("role", "user")
|
|
391
|
+
if role in ("user", "assistant"):
|
|
392
|
+
msgs.append(Message(role=role, content=h.get("content", "")))
|
|
393
|
+
msgs.append(Message(role="user", content=message))
|
|
394
|
+
|
|
395
|
+
# 加载用户配置的 fallback 链
|
|
396
|
+
user_cfg = _load_user_config()
|
|
397
|
+
user_fallback: List[str] = user_cfg.get("fallback", [])
|
|
398
|
+
|
|
399
|
+
health = health or GLOBAL_PROVIDER_HEALTH
|
|
400
|
+
|
|
401
|
+
# 云端 provider 列表(跳过本地)
|
|
402
|
+
cloud_specs: List[str] = []
|
|
403
|
+
for spec in user_fallback:
|
|
404
|
+
name, _ = _parse_provider_spec(spec)
|
|
405
|
+
cls = _PROVIDER_CLASSES.get(name)
|
|
406
|
+
if cls and not cls.local and not health.provider_in_cooldown(name):
|
|
407
|
+
cloud_specs.append(spec)
|
|
408
|
+
|
|
409
|
+
# Built-in discovery is opt-in. The CLI default only follows the user's
|
|
410
|
+
# explicit fallback list, preventing a selected Ollama model from silently
|
|
411
|
+
# consuming an unrelated provider account.
|
|
412
|
+
if include_defaults:
|
|
413
|
+
for name, env_var, model in _DEFAULT_FALLBACK_CHAIN:
|
|
414
|
+
cls = _PROVIDER_CLASSES.get(name)
|
|
415
|
+
if not cls or cls.local:
|
|
416
|
+
continue
|
|
417
|
+
spec = f"{name}/{model}" if model else name
|
|
418
|
+
if not any(s.startswith(name) for s in cloud_specs):
|
|
419
|
+
# 环境变量 OR providers.json 任一有 key 即可
|
|
420
|
+
has_key = (env_var and os.getenv(env_var)) or \
|
|
421
|
+
bool(_load_provider_cfg_from_file(name).get("api_key"))
|
|
422
|
+
if has_key and not health.provider_in_cooldown(name):
|
|
423
|
+
cloud_specs.append(spec)
|
|
424
|
+
|
|
425
|
+
if not cloud_specs:
|
|
426
|
+
return {
|
|
427
|
+
"success": False,
|
|
428
|
+
"error": "no_cloud_provider",
|
|
429
|
+
"response": "",
|
|
430
|
+
"provider": "none",
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
for spec in cloud_specs:
|
|
434
|
+
result = await _try_provider(spec, msgs, on_token=on_token,
|
|
435
|
+
cancel_event=cancel_event, health=health)
|
|
436
|
+
if result:
|
|
437
|
+
return result
|
|
438
|
+
|
|
439
|
+
return {
|
|
440
|
+
"success": False,
|
|
441
|
+
"error": "all_providers_failed",
|
|
442
|
+
"response": "",
|
|
443
|
+
"provider": "none",
|
|
444
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""Local, self-hosted image generation — the non-OpenAI alternative to
|
|
2
|
+
openai_image_client.py, same relationship local_llm_provider.py (Ollama) has
|
|
3
|
+
to a cloud chat API: no per-call cost, no API key, runs entirely on this
|
|
4
|
+
machine via the open-weight `diffusers` pipeline.
|
|
5
|
+
|
|
6
|
+
Heavy dependencies (torch, diffusers, transformers, accelerate) are lazy-
|
|
7
|
+
imported and entirely optional — aria-code's core install stays lean, same
|
|
8
|
+
pattern as akshare/ccxt/playwright elsewhere in this codebase. Nothing here
|
|
9
|
+
downloads model weights or installs packages on import; that only happens
|
|
10
|
+
the first time generate_image_local()/edit_image_local() actually runs, and
|
|
11
|
+
raises a clear, actionable error if the extras aren't installed rather than
|
|
12
|
+
failing on a confusing internal import error.
|
|
13
|
+
|
|
14
|
+
Default model is SDXL-Turbo (~7GB, 1-4 step generation) rather than a full
|
|
15
|
+
SDXL/FLUX checkpoint (15-24GB) — chosen for feasibility on 16GB-class
|
|
16
|
+
unified-memory Macs and to leave room on disk, not because it's the best
|
|
17
|
+
possible quality. Override via ARIA_LOCAL_IMAGE_MODEL if you have the RAM
|
|
18
|
+
and disk for something larger.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import os
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Any, Dict, Optional
|
|
26
|
+
|
|
27
|
+
DEFAULT_MODEL = os.getenv("ARIA_LOCAL_IMAGE_MODEL", "stabilityai/sdxl-turbo")
|
|
28
|
+
|
|
29
|
+
# SDXL-Turbo is trained around ~1024px; a raw phone photo (commonly 12MP,
|
|
30
|
+
# e.g. 4032x3024) fed straight into img2img allocates latents sized to the
|
|
31
|
+
# INPUT resolution, not the model's native one — confirmed empirically on a
|
|
32
|
+
# 16GB-class Mac: five real 12MP photos through edit_image_local() all threw
|
|
33
|
+
# "MPS backend out of memory" before this cap existed. Downscaling to this
|
|
34
|
+
# budget keeps the long edge near SDXL-Turbo's trained regime regardless of
|
|
35
|
+
# source resolution.
|
|
36
|
+
_MAX_IMG2IMG_DIMENSION = 1024
|
|
37
|
+
|
|
38
|
+
_pipeline_cache: Dict[str, Any] = {}
|
|
39
|
+
_img2img_cache: Dict[str, Any] = {}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _resize_for_img2img(image, max_dim: int = _MAX_IMG2IMG_DIMENSION):
|
|
43
|
+
"""Downscale `image` so its longer edge is at most `max_dim`, rounding
|
|
44
|
+
both dimensions down to a multiple of 8 (required by SDXL's VAE).
|
|
45
|
+
A no-op (aside from the multiple-of-8 rounding) when already small."""
|
|
46
|
+
width, height = image.size
|
|
47
|
+
scale = min(1.0, max_dim / max(width, height))
|
|
48
|
+
new_width = max(8, int(width * scale) // 8 * 8)
|
|
49
|
+
new_height = max(8, int(height * scale) // 8 * 8)
|
|
50
|
+
if (new_width, new_height) == (width, height):
|
|
51
|
+
return image
|
|
52
|
+
from PIL import Image as _PILImage
|
|
53
|
+
return image.resize((new_width, new_height), _PILImage.LANCZOS)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _select_device() -> str:
|
|
57
|
+
import torch
|
|
58
|
+
|
|
59
|
+
if torch.backends.mps.is_available():
|
|
60
|
+
return "mps"
|
|
61
|
+
if torch.cuda.is_available():
|
|
62
|
+
return "cuda"
|
|
63
|
+
return "cpu"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _require_diffusers():
|
|
67
|
+
try:
|
|
68
|
+
import torch # noqa: F401
|
|
69
|
+
import diffusers # noqa: F401
|
|
70
|
+
except ImportError as exc:
|
|
71
|
+
raise RuntimeError(
|
|
72
|
+
"Local image generation needs the optional 'image_gen' extra: "
|
|
73
|
+
"pip install -e '.[image_gen]' (adds torch + diffusers, ~a few GB). "
|
|
74
|
+
f"Missing: {exc}"
|
|
75
|
+
) from exc
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _get_pipeline(model: str):
|
|
79
|
+
if model in _pipeline_cache:
|
|
80
|
+
return _pipeline_cache[model]
|
|
81
|
+
_require_diffusers()
|
|
82
|
+
import torch
|
|
83
|
+
from diffusers import AutoPipelineForText2Image
|
|
84
|
+
|
|
85
|
+
device = _select_device()
|
|
86
|
+
dtype = torch.float16 if device in ("mps", "cuda") else torch.float32
|
|
87
|
+
# variant="fp16" fetches the smaller fp16-suffixed weight files directly
|
|
88
|
+
# (roughly half the download of full fp32 checkpoints) instead of pulling
|
|
89
|
+
# fp32 weights and casting after — matters a lot on a flaky connection.
|
|
90
|
+
variant = "fp16" if dtype is torch.float16 else None
|
|
91
|
+
pipe = AutoPipelineForText2Image.from_pretrained(model, torch_dtype=dtype, variant=variant)
|
|
92
|
+
pipe = pipe.to(device)
|
|
93
|
+
_pipeline_cache[model] = pipe
|
|
94
|
+
return pipe
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _get_img2img_pipeline(model: str):
|
|
98
|
+
if model in _img2img_cache:
|
|
99
|
+
return _img2img_cache[model]
|
|
100
|
+
_require_diffusers()
|
|
101
|
+
import torch
|
|
102
|
+
from diffusers import AutoPipelineForImage2Image
|
|
103
|
+
|
|
104
|
+
device = _select_device()
|
|
105
|
+
dtype = torch.float16 if device in ("mps", "cuda") else torch.float32
|
|
106
|
+
variant = "fp16" if dtype is torch.float16 else None
|
|
107
|
+
pipe = AutoPipelineForImage2Image.from_pretrained(model, torch_dtype=dtype, variant=variant)
|
|
108
|
+
pipe = pipe.to(device)
|
|
109
|
+
_img2img_cache[model] = pipe
|
|
110
|
+
return pipe
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def generate_image_local(
|
|
114
|
+
prompt: str,
|
|
115
|
+
*,
|
|
116
|
+
model: str = DEFAULT_MODEL,
|
|
117
|
+
width: int = 1024,
|
|
118
|
+
height: int = 1024,
|
|
119
|
+
steps: int = 4,
|
|
120
|
+
guidance_scale: float = 0.0,
|
|
121
|
+
) -> Dict[str, Any]:
|
|
122
|
+
"""Generate a new image from a text prompt entirely locally.
|
|
123
|
+
|
|
124
|
+
guidance_scale=0.0 matches SDXL-Turbo's distilled few-step training —
|
|
125
|
+
raise it (e.g. 7.5) if you switch `model` to a non-turbo checkpoint that
|
|
126
|
+
expects classifier-free guidance.
|
|
127
|
+
|
|
128
|
+
Blocking, CPU/GPU-bound — callers on an event loop must run this in an
|
|
129
|
+
executor, same as every other client in this codebase. First call for a
|
|
130
|
+
given `model` loads weights from disk (or downloads them once) into
|
|
131
|
+
memory and is much slower than subsequent calls.
|
|
132
|
+
"""
|
|
133
|
+
from artifacts import create_user_artifact
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
pipe = _get_pipeline(model)
|
|
137
|
+
result = pipe(
|
|
138
|
+
prompt=prompt,
|
|
139
|
+
width=width,
|
|
140
|
+
height=height,
|
|
141
|
+
num_inference_steps=steps,
|
|
142
|
+
guidance_scale=guidance_scale,
|
|
143
|
+
)
|
|
144
|
+
image = result.images[0]
|
|
145
|
+
except Exception as exc:
|
|
146
|
+
return {"success": False, "error": str(exc)}
|
|
147
|
+
|
|
148
|
+
artifact = create_user_artifact("image", "generated", "poster_local", ".png")
|
|
149
|
+
image.save(artifact.path)
|
|
150
|
+
return {"success": True, "path": str(artifact.path), "model": model}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def edit_image_local(
|
|
154
|
+
image_path: str,
|
|
155
|
+
prompt: str,
|
|
156
|
+
*,
|
|
157
|
+
model: str = DEFAULT_MODEL,
|
|
158
|
+
strength: float = 0.6,
|
|
159
|
+
steps: int = 4,
|
|
160
|
+
guidance_scale: float = 0.0,
|
|
161
|
+
) -> Dict[str, Any]:
|
|
162
|
+
"""Transform an existing local photo per `prompt`, entirely locally
|
|
163
|
+
(image-to-image, not the mask-based inpainting openai_image_client uses).
|
|
164
|
+
|
|
165
|
+
`strength` (0-1) controls how much the output is allowed to diverge from
|
|
166
|
+
the input — low keeps it close to the original photo, high lets the
|
|
167
|
+
prompt dominate. 0.6 is a reasonable "restyle but keep composition"
|
|
168
|
+
default for the minimal-editorial-poster use case.
|
|
169
|
+
"""
|
|
170
|
+
from artifacts import create_user_artifact
|
|
171
|
+
|
|
172
|
+
src = Path(image_path)
|
|
173
|
+
if not src.exists():
|
|
174
|
+
return {"success": False, "error": f"File not found: {image_path}"}
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
from PIL import Image, ImageOps
|
|
178
|
+
|
|
179
|
+
# exif_transpose is required, not optional: a phone photo shot in
|
|
180
|
+
# portrait commonly stores its pixels landscape plus an EXIF
|
|
181
|
+
# orientation tag telling viewers to rotate it on display — PIL
|
|
182
|
+
# does NOT apply that automatically on open(). Skipping this fed
|
|
183
|
+
# the diffusion pipeline a sideways image; confirmed by reproducing
|
|
184
|
+
# it directly (raw PIL open of a real orientation=6 photo came out
|
|
185
|
+
# sideways, matching the sideways/garbled output this masked).
|
|
186
|
+
init_image = ImageOps.exif_transpose(Image.open(src)).convert("RGB")
|
|
187
|
+
init_image = _resize_for_img2img(init_image)
|
|
188
|
+
pipe = _get_img2img_pipeline(model)
|
|
189
|
+
result = pipe(
|
|
190
|
+
prompt=prompt,
|
|
191
|
+
image=init_image,
|
|
192
|
+
strength=strength,
|
|
193
|
+
num_inference_steps=steps,
|
|
194
|
+
guidance_scale=guidance_scale,
|
|
195
|
+
)
|
|
196
|
+
image = result.images[0]
|
|
197
|
+
except Exception as exc:
|
|
198
|
+
return {"success": False, "error": str(exc)}
|
|
199
|
+
|
|
200
|
+
artifact = create_user_artifact("image", "edited", f"{src.stem}_edited_local", ".png")
|
|
201
|
+
image.save(artifact.path)
|
|
202
|
+
return {"success": True, "path": str(artifact.path), "model": model}
|