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