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,242 @@
|
|
|
1
|
+
"""OpenAI image generation/editing client — the execution backend for prompts
|
|
2
|
+
compiled by the `minimal-editorial-poster` / `minimal-editorial-exports`
|
|
3
|
+
skills.
|
|
4
|
+
|
|
5
|
+
Two capabilities, matching OpenAI's two endpoints (verified against the
|
|
6
|
+
official openai-python SDK source, since the public docs page 403s to
|
|
7
|
+
scrapers):
|
|
8
|
+
generate_image() -> POST /v1/images/generations (text -> new image)
|
|
9
|
+
edit_image() -> POST /v1/images/edits (existing photo + prompt
|
|
10
|
+
-> transformed image; multipart, not JSON)
|
|
11
|
+
|
|
12
|
+
Both use gpt-image-1, which always returns base64 (no response_format /
|
|
13
|
+
url option for GPT image models, only for dall-e-2/3).
|
|
14
|
+
|
|
15
|
+
Key resolution mirrors aria_cli.py's _get_provider_key(): env var
|
|
16
|
+
OPENAI_API_KEY first, then providers.json's "llm" then "data" sections
|
|
17
|
+
under "openai" — so a key already set via `/apikey set openai sk-...` for
|
|
18
|
+
chat is reused here, no separate setup needed.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import base64
|
|
24
|
+
import json
|
|
25
|
+
import os
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Any, Dict, Optional
|
|
28
|
+
|
|
29
|
+
API_BASE = "https://api.openai.com/v1"
|
|
30
|
+
DEFAULT_MODEL = "gpt-image-1"
|
|
31
|
+
|
|
32
|
+
# Per-image USD cost by (quality, size), verified against OpenAI's published
|
|
33
|
+
# gpt-image-1 output-token pricing (developers.openai.com/api/docs/guides/
|
|
34
|
+
# image-generation) — output tokens only; input text/image tokens add a few
|
|
35
|
+
# thousandths of a cent and are ignored here as noise.
|
|
36
|
+
_COST_USD: Dict[str, Dict[str, float]] = {
|
|
37
|
+
"low": {"1024x1024": 0.011, "1024x1536": 0.016, "1536x1024": 0.016},
|
|
38
|
+
"medium": {"1024x1024": 0.042, "1024x1536": 0.063, "1536x1024": 0.063},
|
|
39
|
+
"high": {"1024x1024": 0.167, "1024x1536": 0.25, "1536x1024": 0.25},
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def estimate_cost(size: str = "1024x1536", quality: str = "high") -> Dict[str, Any]:
|
|
44
|
+
"""Rough cost estimate — call before generate_image()/edit_image(),
|
|
45
|
+
not a live quote. "auto" quality/size can't be priced ahead of time
|
|
46
|
+
(OpenAI picks the actual value), so it's reported as unknown rather
|
|
47
|
+
than guessed."""
|
|
48
|
+
per_image = (_COST_USD.get(quality) or {}).get(size)
|
|
49
|
+
return {
|
|
50
|
+
"provider": "openai",
|
|
51
|
+
"model": DEFAULT_MODEL,
|
|
52
|
+
"size": size,
|
|
53
|
+
"quality": quality,
|
|
54
|
+
"estimated_cost_usd": per_image,
|
|
55
|
+
"note": (
|
|
56
|
+
"Illustrative estimate from OpenAI's published per-image pricing; "
|
|
57
|
+
"verify current pricing at platform.openai.com/docs/pricing."
|
|
58
|
+
if per_image is not None
|
|
59
|
+
else "'auto' size/quality is resolved by OpenAI at request time and can't be priced ahead of submission."
|
|
60
|
+
),
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _providers_path() -> Path:
|
|
65
|
+
from apps.cli.config_paths import resolve_paths
|
|
66
|
+
return resolve_paths().providers_file
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _api_key() -> str:
|
|
70
|
+
env = os.getenv("OPENAI_API_KEY", "").strip()
|
|
71
|
+
if env:
|
|
72
|
+
return env
|
|
73
|
+
path = _providers_path()
|
|
74
|
+
if not path.exists():
|
|
75
|
+
return ""
|
|
76
|
+
try:
|
|
77
|
+
raw = json.loads(path.read_text(encoding="utf-8"))
|
|
78
|
+
except Exception:
|
|
79
|
+
return ""
|
|
80
|
+
for section in ("llm", "data"):
|
|
81
|
+
entry = raw.get(section, {}).get("openai", {}) or {}
|
|
82
|
+
if entry.get("api_key"):
|
|
83
|
+
return entry["api_key"]
|
|
84
|
+
return ""
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _require_key() -> str:
|
|
88
|
+
key = _api_key()
|
|
89
|
+
if not key:
|
|
90
|
+
raise RuntimeError(
|
|
91
|
+
"No OpenAI API key found. Set one with `/apikey set openai sk-...` "
|
|
92
|
+
"or export OPENAI_API_KEY."
|
|
93
|
+
)
|
|
94
|
+
return key
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _save_b64_image(b64_data: str, dest: Path) -> Path:
|
|
98
|
+
dest.write_bytes(base64.b64decode(b64_data))
|
|
99
|
+
return dest
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _normalize_image_for_upload(path: Path, *, keep_alpha: bool = False) -> tuple[str, bytes]:
|
|
103
|
+
"""Re-encode an arbitrary local image file into a clean single-frame PNG
|
|
104
|
+
before uploading to OpenAI's edits endpoint.
|
|
105
|
+
|
|
106
|
+
Two real problems this fixes, not just defensive coding — both
|
|
107
|
+
reproduced directly against real iPhone photos:
|
|
108
|
+
1. iPhones commonly save JPEGs as MPO (Multi-Picture Object — an
|
|
109
|
+
embedded-stereo-frame container, not a plain single-frame JPEG)
|
|
110
|
+
even with a plain .jpg extension. OpenAI's edit endpoint rejects
|
|
111
|
+
these with a 400 "Invalid image file or mode" even though the
|
|
112
|
+
extension and declared docs (png/webp/jpg) suggest it should work.
|
|
113
|
+
2. A portrait photo is commonly stored as landscape pixels plus an
|
|
114
|
+
EXIF orientation tag telling viewers to rotate on display — the
|
|
115
|
+
raw bytes, uploaded as-is, are genuinely sideways. Reproduced: a
|
|
116
|
+
raw PIL open() of a real orientation=6 photo, no exif_transpose,
|
|
117
|
+
is visibly sideways.
|
|
118
|
+
Re-saving through PIL with exif_transpose applied produces a single-
|
|
119
|
+
frame PNG immune to both, regardless of the source container/orientation.
|
|
120
|
+
"""
|
|
121
|
+
import io
|
|
122
|
+
|
|
123
|
+
from PIL import Image, ImageOps
|
|
124
|
+
|
|
125
|
+
img = ImageOps.exif_transpose(Image.open(path))
|
|
126
|
+
img = img.convert("RGBA" if keep_alpha else "RGB")
|
|
127
|
+
buf = io.BytesIO()
|
|
128
|
+
img.save(buf, format="PNG")
|
|
129
|
+
return f"{path.stem}.png", buf.getvalue()
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def generate_image(
|
|
133
|
+
prompt: str,
|
|
134
|
+
*,
|
|
135
|
+
size: str = "1024x1536",
|
|
136
|
+
quality: str = "high",
|
|
137
|
+
confirmed: bool = False,
|
|
138
|
+
) -> Dict[str, Any]:
|
|
139
|
+
"""Generate a new image from a text prompt (e.g. minimal-editorial-poster's
|
|
140
|
+
compiled prompt) via POST /v1/images/generations — real per-call cost,
|
|
141
|
+
billed by OpenAI the instant this succeeds (see estimate_cost()).
|
|
142
|
+
|
|
143
|
+
Blocking (uses requests) — callers on an event loop should run this in
|
|
144
|
+
an executor, same as every other client in this codebase.
|
|
145
|
+
"""
|
|
146
|
+
if not confirmed:
|
|
147
|
+
return {
|
|
148
|
+
"success": False,
|
|
149
|
+
"error": (
|
|
150
|
+
"confirmed must be true to generate a real (paid) image. "
|
|
151
|
+
"Call estimate_cost() first to see the cost, then resubmit with confirmed=true."
|
|
152
|
+
),
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
import requests
|
|
156
|
+
|
|
157
|
+
from artifacts import create_user_artifact
|
|
158
|
+
|
|
159
|
+
key = _require_key()
|
|
160
|
+
resp = requests.post(
|
|
161
|
+
f"{API_BASE}/images/generations",
|
|
162
|
+
headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"},
|
|
163
|
+
json={"model": DEFAULT_MODEL, "prompt": prompt, "size": size, "quality": quality},
|
|
164
|
+
timeout=120,
|
|
165
|
+
)
|
|
166
|
+
if resp.status_code != 200:
|
|
167
|
+
return {"success": False, "error": f"OpenAI image generation failed: {resp.status_code} {resp.text[:300]}"}
|
|
168
|
+
data = (resp.json().get("data") or [{}])[0]
|
|
169
|
+
b64 = data.get("b64_json")
|
|
170
|
+
if not b64:
|
|
171
|
+
return {"success": False, "error": "OpenAI response had no b64_json image data"}
|
|
172
|
+
|
|
173
|
+
artifact = create_user_artifact("image", "generated", "poster", ".png")
|
|
174
|
+
_save_b64_image(b64, artifact.path)
|
|
175
|
+
return {"success": True, "path": str(artifact.path)}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def edit_image(
|
|
179
|
+
image_path: str,
|
|
180
|
+
prompt: str,
|
|
181
|
+
*,
|
|
182
|
+
size: str = "1024x1536",
|
|
183
|
+
quality: str = "high",
|
|
184
|
+
mask_path: Optional[str] = None,
|
|
185
|
+
confirmed: bool = False,
|
|
186
|
+
) -> Dict[str, Any]:
|
|
187
|
+
"""Transform an existing local photo per `prompt` (e.g. "convert to duotone,
|
|
188
|
+
simplify background to negative space, add scan-noise texture...") via
|
|
189
|
+
POST /v1/images/edits — the execution path for a real user-provided photo
|
|
190
|
+
run through the minimal-editorial-poster skill. Real per-call cost,
|
|
191
|
+
billed by OpenAI the instant this succeeds (see estimate_cost()).
|
|
192
|
+
|
|
193
|
+
Blocking (uses requests) — same threading note as generate_image.
|
|
194
|
+
"""
|
|
195
|
+
if not confirmed:
|
|
196
|
+
return {
|
|
197
|
+
"success": False,
|
|
198
|
+
"error": (
|
|
199
|
+
"confirmed must be true to generate a real (paid) image edit. "
|
|
200
|
+
"Call estimate_cost() first to see the cost, then resubmit with confirmed=true."
|
|
201
|
+
),
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
import requests
|
|
205
|
+
|
|
206
|
+
from artifacts import create_user_artifact
|
|
207
|
+
|
|
208
|
+
src = Path(image_path)
|
|
209
|
+
if not src.exists():
|
|
210
|
+
return {"success": False, "error": f"File not found: {image_path}"}
|
|
211
|
+
|
|
212
|
+
key = _require_key()
|
|
213
|
+
try:
|
|
214
|
+
files = {"image": _normalize_image_for_upload(src)}
|
|
215
|
+
except Exception as exc:
|
|
216
|
+
return {"success": False, "error": f"Could not read image_path: {exc}"}
|
|
217
|
+
if mask_path:
|
|
218
|
+
mask = Path(mask_path)
|
|
219
|
+
if not mask.exists():
|
|
220
|
+
return {"success": False, "error": f"Mask file not found: {mask_path}"}
|
|
221
|
+
try:
|
|
222
|
+
files["mask"] = _normalize_image_for_upload(mask, keep_alpha=True)
|
|
223
|
+
except Exception as exc:
|
|
224
|
+
return {"success": False, "error": f"Could not read mask_path: {exc}"}
|
|
225
|
+
|
|
226
|
+
resp = requests.post(
|
|
227
|
+
f"{API_BASE}/images/edits",
|
|
228
|
+
headers={"Authorization": f"Bearer {key}"},
|
|
229
|
+
data={"model": DEFAULT_MODEL, "prompt": prompt, "size": size, "quality": quality},
|
|
230
|
+
files=files,
|
|
231
|
+
timeout=180,
|
|
232
|
+
)
|
|
233
|
+
if resp.status_code != 200:
|
|
234
|
+
return {"success": False, "error": f"OpenAI image edit failed: {resp.status_code} {resp.text[:300]}"}
|
|
235
|
+
data = (resp.json().get("data") or [{}])[0]
|
|
236
|
+
b64 = data.get("b64_json")
|
|
237
|
+
if not b64:
|
|
238
|
+
return {"success": False, "error": "OpenAI response had no b64_json image data"}
|
|
239
|
+
|
|
240
|
+
artifact = create_user_artifact("image", "edited", f"{src.stem}_edited", ".png")
|
|
241
|
+
_save_b64_image(b64, artifact.path)
|
|
242
|
+
return {"success": True, "path": str(artifact.path)}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""Runway video generation client — Layer 3 (cloud AI generation) of the
|
|
2
|
+
video architecture, one of two provider clients (see also
|
|
3
|
+
kling_video_client.py) behind the same submit/poll shape so
|
|
4
|
+
packages/aria_mcp/server.py can route to either without provider-specific
|
|
5
|
+
logic outside these two files.
|
|
6
|
+
|
|
7
|
+
Verified against Runway's own generated SDK source
|
|
8
|
+
(github.com/runwayml/sdk-python), not the public docs page (which returned
|
|
9
|
+
an unfetchable error) — same discipline as openai_image_client.py and
|
|
10
|
+
canva_client.py: primary-source field names, not guessed ones.
|
|
11
|
+
|
|
12
|
+
base_url: https://api.dev.runwayml.com
|
|
13
|
+
auth: Authorization: Bearer {api_key}, X-Runway-Version: 2024-11-06
|
|
14
|
+
submit: POST /v1/text_to_video {model, promptText, ratio, duration?, ...}
|
|
15
|
+
poll: GET /v1/tasks/{id} -> status PENDING|THROTTLED|RUNNING|FAILED|SUCCEEDED
|
|
16
|
+
(SUCCEEDED carries `output`: a list of video URLs, valid 24-48h)
|
|
17
|
+
|
|
18
|
+
Real per-request cost — every submission is a genuine payment. This module
|
|
19
|
+
never gates that itself (no local "confirmed" concept); the MCP server layer
|
|
20
|
+
enforces the confirm-after-estimate gate, matching the broker order-preview
|
|
21
|
+
pattern's reasoning: submission is the moment money is spent, so the caller
|
|
22
|
+
must have deliberately opted in.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import os
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
from typing import Any, Dict, Optional
|
|
30
|
+
|
|
31
|
+
API_BASE = "https://api.dev.runwayml.com"
|
|
32
|
+
API_VERSION = "2024-11-06"
|
|
33
|
+
|
|
34
|
+
# Rough, illustrative-only pricing — Runway doesn't publish a stable public
|
|
35
|
+
# per-second API rate the way Kling does; verify current pricing on your
|
|
36
|
+
# account dashboard before relying on this for a real cost decision.
|
|
37
|
+
APPROX_COST_PER_SECOND_USD = 0.25
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _providers_path() -> Path:
|
|
41
|
+
from apps.cli.config_paths import resolve_paths
|
|
42
|
+
return resolve_paths().providers_file
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _api_key() -> str:
|
|
46
|
+
env = os.getenv("RUNWAYML_API_SECRET", "").strip()
|
|
47
|
+
if env:
|
|
48
|
+
return env
|
|
49
|
+
path = _providers_path()
|
|
50
|
+
if not path.exists():
|
|
51
|
+
return ""
|
|
52
|
+
try:
|
|
53
|
+
import json
|
|
54
|
+
raw = json.loads(path.read_text(encoding="utf-8"))
|
|
55
|
+
except Exception:
|
|
56
|
+
return ""
|
|
57
|
+
for section in ("llm", "data"):
|
|
58
|
+
entry = raw.get(section, {}).get("runway", {}) or {}
|
|
59
|
+
if entry.get("api_key"):
|
|
60
|
+
return entry["api_key"]
|
|
61
|
+
return ""
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _require_key() -> str:
|
|
65
|
+
key = _api_key()
|
|
66
|
+
if not key:
|
|
67
|
+
raise RuntimeError(
|
|
68
|
+
"No Runway API key found. Set one with `/apikey set runway <key>` "
|
|
69
|
+
"or export RUNWAYML_API_SECRET."
|
|
70
|
+
)
|
|
71
|
+
return key
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _headers() -> Dict[str, str]:
|
|
75
|
+
return {
|
|
76
|
+
"Authorization": f"Bearer {_require_key()}",
|
|
77
|
+
"X-Runway-Version": API_VERSION,
|
|
78
|
+
"Content-Type": "application/json",
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def estimate_cost(duration: int) -> Dict[str, Any]:
|
|
83
|
+
"""Rough cost estimate — call before submit_video(), not a live quote."""
|
|
84
|
+
return {
|
|
85
|
+
"provider": "runway",
|
|
86
|
+
"duration_sec": duration,
|
|
87
|
+
"estimated_cost_usd": round(duration * APPROX_COST_PER_SECOND_USD, 2),
|
|
88
|
+
"note": "Illustrative estimate — verify current pricing on your Runway account dashboard.",
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def submit_video(
|
|
93
|
+
prompt: str,
|
|
94
|
+
*,
|
|
95
|
+
model: str = "gen4.5",
|
|
96
|
+
duration: int = 5,
|
|
97
|
+
ratio: str = "1280:720",
|
|
98
|
+
) -> Dict[str, Any]:
|
|
99
|
+
"""Submit a text-to-video generation job. Returns immediately with a
|
|
100
|
+
task id — this does NOT wait for the video, since generation takes
|
|
101
|
+
minutes; poll with poll_video().
|
|
102
|
+
|
|
103
|
+
duration must be 2-10 for gen4.5; ratio must be one of the values that
|
|
104
|
+
model supports ("1280:720"/"720:1280" for gen4.5; veo3.1/veo3.1_fast
|
|
105
|
+
additionally support "1080:1920"/"1920:1080" — see Runway's docs for the
|
|
106
|
+
full per-model matrix, verified in this repo only for gen4.5's basic
|
|
107
|
+
shape).
|
|
108
|
+
"""
|
|
109
|
+
import requests
|
|
110
|
+
|
|
111
|
+
body = {"model": model, "promptText": prompt, "ratio": ratio, "duration": duration}
|
|
112
|
+
try:
|
|
113
|
+
resp = requests.post(f"{API_BASE}/v1/text_to_video", headers=_headers(), json=body, timeout=30)
|
|
114
|
+
except Exception as exc:
|
|
115
|
+
return {"success": False, "error": str(exc)}
|
|
116
|
+
if resp.status_code not in (200, 201, 202):
|
|
117
|
+
return {"success": False, "error": f"Runway submission failed: {resp.status_code} {resp.text[:400]}"}
|
|
118
|
+
data = resp.json()
|
|
119
|
+
task_id = data.get("id", "")
|
|
120
|
+
if not task_id:
|
|
121
|
+
return {"success": False, "error": "Runway did not return a task id"}
|
|
122
|
+
return {"success": True, "task_id": task_id, "provider": "runway"}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def poll_video(task_id: str, *, download: bool = True) -> Dict[str, Any]:
|
|
126
|
+
"""Check a submitted task's status. On SUCCEEDED, optionally downloads
|
|
127
|
+
the first output video to the artifacts folder."""
|
|
128
|
+
import requests
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
resp = requests.get(f"{API_BASE}/v1/tasks/{task_id}", headers=_headers(), timeout=30)
|
|
132
|
+
except Exception as exc:
|
|
133
|
+
return {"success": False, "error": str(exc)}
|
|
134
|
+
if resp.status_code != 200:
|
|
135
|
+
return {"success": False, "error": f"Runway task lookup failed: {resp.status_code} {resp.text[:400]}"}
|
|
136
|
+
data = resp.json()
|
|
137
|
+
status = data.get("status", "")
|
|
138
|
+
|
|
139
|
+
if status == "SUCCEEDED":
|
|
140
|
+
outputs = data.get("output") or []
|
|
141
|
+
if not outputs:
|
|
142
|
+
return {"success": False, "status": status, "error": "SUCCEEDED but no output URLs present"}
|
|
143
|
+
result = {"success": True, "status": status, "video_url": outputs[0]}
|
|
144
|
+
if download:
|
|
145
|
+
from artifacts import create_user_artifact
|
|
146
|
+
|
|
147
|
+
video_resp = requests.get(outputs[0], timeout=120)
|
|
148
|
+
if video_resp.status_code == 200:
|
|
149
|
+
artifact = create_user_artifact("video", "runway", "runway_video", ".mp4")
|
|
150
|
+
artifact.path.write_bytes(video_resp.content)
|
|
151
|
+
result["path"] = str(artifact.path)
|
|
152
|
+
return result
|
|
153
|
+
|
|
154
|
+
if status == "FAILED":
|
|
155
|
+
return {"success": False, "status": status, "error": data.get("failure", "generation failed")}
|
|
156
|
+
|
|
157
|
+
return {"success": True, "status": status, "progress": data.get("progress")}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Read-only client for the warehouse ERP agent snapshot endpoint.
|
|
2
|
+
|
|
3
|
+
The client intentionally exposes one narrow endpoint instead of a generic ERP
|
|
4
|
+
proxy. That keeps the agent boundary auditable: the CLI can only retrieve the
|
|
5
|
+
small operational snapshot required for analysis and has no write capability.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from collections.abc import Mapping
|
|
12
|
+
from typing import Any
|
|
13
|
+
from urllib.parse import quote, urlparse
|
|
14
|
+
|
|
15
|
+
import httpx
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class WarehouseERPConfigurationError(ValueError):
|
|
19
|
+
"""Raised when the local ERP integration configuration is invalid."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class WarehouseERPRequestError(RuntimeError):
|
|
23
|
+
"""Raised with a safe, user-facing ERP request error."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
_DEFAULT_SNAPSHOT_PATH = "/api/v1/warehouses/{warehouse_id}/agent-snapshot"
|
|
27
|
+
_RECORD_LISTS = ("connectors", "inbounds", "skus", "locations")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class WarehouseERPClient:
|
|
31
|
+
"""Fetch a minimal, read-only warehouse snapshot over HTTPS or HTTP."""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
base_url: str,
|
|
36
|
+
token: str = "",
|
|
37
|
+
snapshot_path: str = _DEFAULT_SNAPSHOT_PATH,
|
|
38
|
+
timeout_seconds: float = 10.0,
|
|
39
|
+
transport: httpx.AsyncBaseTransport | None = None,
|
|
40
|
+
) -> None:
|
|
41
|
+
parsed = urlparse(str(base_url or "").strip())
|
|
42
|
+
if parsed.scheme not in {"http", "https"} or not parsed.netloc:
|
|
43
|
+
raise WarehouseERPConfigurationError("ARIA_WAREHOUSE_ERP_URL 必须是完整的 http(s) URL")
|
|
44
|
+
if not snapshot_path.startswith("/") or "{warehouse_id}" not in snapshot_path:
|
|
45
|
+
raise WarehouseERPConfigurationError(
|
|
46
|
+
"ARIA_WAREHOUSE_ERP_SNAPSHOT_PATH 必须是包含 {warehouse_id} 的相对路径"
|
|
47
|
+
)
|
|
48
|
+
self.base_url = str(base_url).rstrip("/")
|
|
49
|
+
self.token = str(token or "").strip()
|
|
50
|
+
self.snapshot_path = snapshot_path
|
|
51
|
+
self.timeout_seconds = float(timeout_seconds)
|
|
52
|
+
self.transport = transport
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def from_env(cls) -> "WarehouseERPClient":
|
|
56
|
+
return cls(
|
|
57
|
+
base_url=os.environ.get("ARIA_WAREHOUSE_ERP_URL", ""),
|
|
58
|
+
token=os.environ.get("ARIA_WAREHOUSE_ERP_TOKEN", ""),
|
|
59
|
+
snapshot_path=os.environ.get("ARIA_WAREHOUSE_ERP_SNAPSHOT_PATH", _DEFAULT_SNAPSHOT_PATH),
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def _snapshot_url(self, warehouse_id: str) -> str:
|
|
63
|
+
cleaned = str(warehouse_id or "").strip()
|
|
64
|
+
if not cleaned:
|
|
65
|
+
raise WarehouseERPConfigurationError("仓库编号不能为空")
|
|
66
|
+
return self.base_url + self.snapshot_path.format(warehouse_id=quote(cleaned, safe=""))
|
|
67
|
+
|
|
68
|
+
async def fetch_snapshot(self, warehouse_id: str) -> dict[str, Any]:
|
|
69
|
+
headers = {"Accept": "application/json"}
|
|
70
|
+
if self.token:
|
|
71
|
+
headers["Authorization"] = f"Bearer {self.token}"
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
async with httpx.AsyncClient(
|
|
75
|
+
timeout=self.timeout_seconds,
|
|
76
|
+
follow_redirects=False,
|
|
77
|
+
transport=self.transport,
|
|
78
|
+
) as client:
|
|
79
|
+
response = await client.get(self._snapshot_url(warehouse_id), headers=headers)
|
|
80
|
+
except httpx.TimeoutException as exc:
|
|
81
|
+
raise WarehouseERPRequestError("ERP 请求超时,请稍后重试。") from exc
|
|
82
|
+
except httpx.HTTPError as exc:
|
|
83
|
+
raise WarehouseERPRequestError("无法连接 ERP 服务,请检查网络或服务状态。") from exc
|
|
84
|
+
|
|
85
|
+
if response.status_code != 200:
|
|
86
|
+
raise WarehouseERPRequestError(f"ERP 服务返回 HTTP {response.status_code}。")
|
|
87
|
+
try:
|
|
88
|
+
payload = response.json()
|
|
89
|
+
except ValueError as exc:
|
|
90
|
+
raise WarehouseERPRequestError("ERP 服务返回的不是有效 JSON。") from exc
|
|
91
|
+
return self._normalise_snapshot(warehouse_id, payload)
|
|
92
|
+
|
|
93
|
+
@staticmethod
|
|
94
|
+
def _normalise_snapshot(warehouse_id: str, payload: Any) -> dict[str, Any]:
|
|
95
|
+
root = payload.get("data", payload) if isinstance(payload, Mapping) else None
|
|
96
|
+
if not isinstance(root, Mapping):
|
|
97
|
+
raise WarehouseERPRequestError("ERP 快照必须是 JSON 对象。")
|
|
98
|
+
|
|
99
|
+
snapshot: dict[str, Any] = {
|
|
100
|
+
"warehouse_id": str(root.get("warehouse_id") or warehouse_id),
|
|
101
|
+
}
|
|
102
|
+
for key in _RECORD_LISTS:
|
|
103
|
+
value = root.get(key, [])
|
|
104
|
+
snapshot[key] = [dict(item) for item in value if isinstance(item, Mapping)] if isinstance(value, list) else []
|
|
105
|
+
return snapshot
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""Cross-platform memory sync for the CLI.
|
|
2
|
+
|
|
3
|
+
MemoryManager keeps facts in ~/.arthera/memory/ so the CLI works offline and
|
|
4
|
+
without an account. This module carries the durable ones to the same store the
|
|
5
|
+
web, desktop and iOS clients read (`/api/v2/memory/items`), so a preference
|
|
6
|
+
stated in the terminal is not invisible everywhere else.
|
|
7
|
+
|
|
8
|
+
Local stays the working copy on purpose. Sync is best-effort: an expired token,
|
|
9
|
+
an offline laptop or a backend outage must never cost the user a memory or block
|
|
10
|
+
the turn they are in the middle of. Every call here returns a value instead of
|
|
11
|
+
raising, and the caller treats failure as "not synced yet".
|
|
12
|
+
|
|
13
|
+
Identity comes from `aria login` (see apps/cli/google_login.py). Without a token
|
|
14
|
+
this module does nothing at all — memory is per-user on the server, and there is
|
|
15
|
+
no sensible anonymous bucket to write into.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import logging
|
|
22
|
+
import urllib.error
|
|
23
|
+
import urllib.parse
|
|
24
|
+
import urllib.request
|
|
25
|
+
from typing import Any, Dict, List, Optional
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
_TIMEOUT_SECONDS = 8
|
|
30
|
+
_MAX_CONTENT_CHARS = 4_000 # matches MemoryItemCreate.content on the server
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class CloudMemoryClient:
|
|
34
|
+
"""Thin client over the cross-platform memory API.
|
|
35
|
+
|
|
36
|
+
Instantiate with the API base URL and the Firebase ID token stored by
|
|
37
|
+
`/login`. `available` is False when there is no token, which callers should
|
|
38
|
+
check rather than relying on every method quietly no-opping.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, api_url: str, token: Optional[str]) -> None:
|
|
42
|
+
self.api_url = (api_url or "").rstrip("/")
|
|
43
|
+
self.token = (token or "").strip()
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def available(self) -> bool:
|
|
47
|
+
return bool(self.api_url and self.token)
|
|
48
|
+
|
|
49
|
+
# ── transport ────────────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
def _request(
|
|
52
|
+
self,
|
|
53
|
+
method: str,
|
|
54
|
+
path: str,
|
|
55
|
+
*,
|
|
56
|
+
body: Optional[Dict[str, Any]] = None,
|
|
57
|
+
query: Optional[Dict[str, Any]] = None,
|
|
58
|
+
) -> Optional[Dict[str, Any]]:
|
|
59
|
+
if not self.available:
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
url = f"{self.api_url}{path}"
|
|
63
|
+
if query:
|
|
64
|
+
pairs = {k: v for k, v in query.items() if v is not None}
|
|
65
|
+
if pairs:
|
|
66
|
+
url = f"{url}?{urllib.parse.urlencode(pairs)}"
|
|
67
|
+
|
|
68
|
+
data = json.dumps(body).encode("utf-8") if body is not None else None
|
|
69
|
+
request = urllib.request.Request(
|
|
70
|
+
url,
|
|
71
|
+
data=data,
|
|
72
|
+
method=method,
|
|
73
|
+
headers={
|
|
74
|
+
"Authorization": f"Bearer {self.token}",
|
|
75
|
+
**({"Content-Type": "application/json"} if data else {}),
|
|
76
|
+
},
|
|
77
|
+
)
|
|
78
|
+
try:
|
|
79
|
+
with urllib.request.urlopen(request, timeout=_TIMEOUT_SECONDS) as response:
|
|
80
|
+
raw = response.read().decode("utf-8")
|
|
81
|
+
return json.loads(raw) if raw else {}
|
|
82
|
+
except urllib.error.HTTPError as exc:
|
|
83
|
+
# 401 is the ordinary case of an ID token older than an hour, not an
|
|
84
|
+
# error worth interrupting the user over. Say so at debug level and
|
|
85
|
+
# let the next /login refresh it.
|
|
86
|
+
level = logging.DEBUG if exc.code == 401 else logging.WARNING
|
|
87
|
+
logger.log(level, "Cloud memory %s %s failed: HTTP %s", method, path, exc.code)
|
|
88
|
+
return None
|
|
89
|
+
except Exception as exc:
|
|
90
|
+
logger.debug("Cloud memory %s %s unreachable: %s", method, path, exc)
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
# ── items ────────────────────────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
def push_item(
|
|
96
|
+
self,
|
|
97
|
+
content: str,
|
|
98
|
+
*,
|
|
99
|
+
kind: str = "durable",
|
|
100
|
+
scope: str = "user",
|
|
101
|
+
project_id: Optional[str] = None,
|
|
102
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
103
|
+
) -> Optional[str]:
|
|
104
|
+
"""Store one memory server-side. Returns its id, or None if not synced.
|
|
105
|
+
|
|
106
|
+
`classification` is left at the server default. Anything the server
|
|
107
|
+
considers sensitive, financial or connected requires explicit consent
|
|
108
|
+
there, and a CLI turn is not a place to imply that consent was given.
|
|
109
|
+
"""
|
|
110
|
+
text = (content or "").strip()
|
|
111
|
+
if not text:
|
|
112
|
+
return None
|
|
113
|
+
if scope == "project" and not project_id:
|
|
114
|
+
# The server rejects this combination; catching it here keeps a
|
|
115
|
+
# pointless round trip and a confusing 422 out of the logs.
|
|
116
|
+
logger.debug("Cloud memory: project scope needs a project_id")
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
payload: Dict[str, Any] = {
|
|
120
|
+
"content": text[:_MAX_CONTENT_CHARS],
|
|
121
|
+
"kind": kind,
|
|
122
|
+
"scope": scope,
|
|
123
|
+
"metadata": {"source": "aria-code", **(metadata or {})},
|
|
124
|
+
}
|
|
125
|
+
if project_id:
|
|
126
|
+
payload["project_id"] = project_id
|
|
127
|
+
|
|
128
|
+
result = self._request("POST", "/api/v2/memory/items", body=payload)
|
|
129
|
+
if not isinstance(result, dict):
|
|
130
|
+
return None
|
|
131
|
+
item_id = result.get("id") or (result.get("item") or {}).get("id")
|
|
132
|
+
return str(item_id) if item_id else None
|
|
133
|
+
|
|
134
|
+
def list_items(
|
|
135
|
+
self,
|
|
136
|
+
*,
|
|
137
|
+
scope: Optional[str] = None,
|
|
138
|
+
project_id: Optional[str] = None,
|
|
139
|
+
limit: int = 50,
|
|
140
|
+
) -> List[Dict[str, Any]]:
|
|
141
|
+
"""Return stored memories, newest first. Empty list when unavailable."""
|
|
142
|
+
result = self._request(
|
|
143
|
+
"GET",
|
|
144
|
+
"/api/v2/memory/items",
|
|
145
|
+
query={
|
|
146
|
+
"scope": scope,
|
|
147
|
+
"project_id": project_id,
|
|
148
|
+
"limit": max(1, min(int(limit), 100)),
|
|
149
|
+
},
|
|
150
|
+
)
|
|
151
|
+
if not isinstance(result, dict):
|
|
152
|
+
return []
|
|
153
|
+
items = result.get("items")
|
|
154
|
+
return items if isinstance(items, list) else []
|
|
155
|
+
|
|
156
|
+
# ── preferences ──────────────────────────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
def get_preferences(self) -> Dict[str, Any]:
|
|
159
|
+
result = self._request("GET", "/api/v2/memory/preferences")
|
|
160
|
+
return result if isinstance(result, dict) else {}
|
|
161
|
+
|
|
162
|
+
def put_preferences(self, preferences: Dict[str, str]) -> bool:
|
|
163
|
+
result = self._request(
|
|
164
|
+
"PUT", "/api/v2/memory/preferences", body=dict(preferences)
|
|
165
|
+
)
|
|
166
|
+
return result is not None
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def client_from_config(config: Dict[str, Any]) -> CloudMemoryClient:
|
|
170
|
+
"""Build a client from the CLI config written by /login.
|
|
171
|
+
|
|
172
|
+
Kept separate from CloudMemoryClient so the class stays testable without a
|
|
173
|
+
config dict, and so the key names live in one place if they change.
|
|
174
|
+
"""
|
|
175
|
+
return CloudMemoryClient(
|
|
176
|
+
api_url=str(config.get("api_url") or ""),
|
|
177
|
+
token=config.get("auth_token"),
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
__all__ = ["CloudMemoryClient", "client_from_config"]
|