utim-cli 2.4.3__tar.gz → 2.5.1__tar.gz
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.
- {utim_cli-2.4.3 → utim_cli-2.5.1}/CHANGELOG.md +20 -0
- {utim_cli-2.4.3/utim_cli.egg-info → utim_cli-2.5.1}/PKG-INFO +4 -4
- {utim_cli-2.4.3 → utim_cli-2.5.1}/pyproject.toml +4 -4
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim/__init__.py +1 -1
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/__init__.py +1 -1
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/_version.py +1 -1
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/architecture_indexer.py +112 -5
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/ask_helper.py +88 -93
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/blender_agent.py +38 -48
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/client_utils.py +270 -32
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/config.py +17 -18
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/context_pruner.py +146 -44
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/dependency_manager.py +3 -3
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/orchestrator.py +30 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/orchestrator_helpers.py +88 -173
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/orchestrator_llm_transport.py +27 -101
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/orchestrator_turn_history.py +48 -31
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/reflection.py +10 -13
- utim_cli-2.5.1/utim_cli/server/api_batcher.py +442 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/api_models_db.py +20 -1
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/auth.py +17 -9
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/cli_auth.py +30 -5
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/db.py +396 -1
- utim_cli-2.5.1/utim_cli/server/firewall.py +471 -0
- utim_cli-2.5.1/utim_cli/server/hobby_throttler.py +329 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/key_rotator.py +50 -23
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/logging_config.py +82 -79
- utim_cli-2.5.1/utim_cli/server/maintenance.py +106 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/models.py +264 -11
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/plan_integrity.py +18 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/platform_billing.py +116 -1
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/rate_limit.py +15 -9
- utim_cli-2.5.1/utim_cli/server/request_batcher.py +510 -0
- utim_cli-2.5.1/utim_cli/server/request_queue.py +450 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/router.py +173 -48
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/routes/__init__.py +3 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/routes/admin_bonus_routes.py +80 -0
- utim_cli-2.5.1/utim_cli/server/routes/admin_separated_models_routes.py +719 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/routes/api_gateway_extensions_routes.py +542 -544
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/routes/api_models_admin_routes.py +8 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/routes/api_models_public_routes.py +686 -221
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/routes/auth_routes.py +10 -2
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/routes/completion_routes.py +285 -206
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/routes/credit_routes.py +36 -37
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/routes/provider_compat_routes.py +28 -2
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/routes/quota_routes.py +11 -1
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/routes/security_routes.py +13 -17
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/tools.py +31 -113
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/utim.py +140 -99
- {utim_cli-2.4.3 → utim_cli-2.5.1/utim_cli.egg-info}/PKG-INFO +4 -4
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli.egg-info/SOURCES.txt +7 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli.egg-info/requires.txt +3 -3
- {utim_cli-2.4.3 → utim_cli-2.5.1}/LICENSE +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/MANIFEST.in +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/README.md +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/setup.cfg +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/setup.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/_chip_test.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/_verify_repro.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/_verify_syntax.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/_wizard_smoke_test.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/agent.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/auth.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/backup.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/billing.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/bootstrap.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/brain.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/builtin/skills/utim-guide/SKILL.md +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/constants.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/context_inspector.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/crawler/__init__.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/crawler/engine.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/crawler/markdown_distiller.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/doctor.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/evidence_registry.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/harbor.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/knowledge_graph.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/local_db.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/logger.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/mcp_clean_wrapper.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/mcp_client.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/mcp_registry.json +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/models.txt +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/orchestrator_tool_exec.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/personalities.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/platform_installer.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/plugins/__init__.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/plugins/hooks.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/plugins/loader.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/plugins/manager.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/plugins/registry.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/plugins/runtime.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/plugins/schema.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/plugins/tool_loader.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/report.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/scrapy_search.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/sdk/__init__.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/sdk/config.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/sdk/context.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/sdk/events.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/sdk/handlers.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/sdk/session.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/sdk/sidecar.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/__init__.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/admin_auth.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/attribution.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/audit_log.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/batch_processor.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/captcha.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/concurrency.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/docs_md/SECRET_PROVISIONING.md +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/docs_md/about.md +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/docs_md/changelog.md +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/docs_md/docs.md +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/docs_md/features.md +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/docs_md/license.md +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/docs_md/pricing.md +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/docs_md/privacy.md +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/docs_md/refund.md +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/docs_md/support.md +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/docs_md/terms.md +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/email_utils.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/exchange_rate.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/firebase.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/fix_duplicate_users.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/history.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/local_llm.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/micro_batcher.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/model_agent.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/pricing_updater.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/provider_balance.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/provider_discovery.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/provider_router.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/provision_build.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/rewards_engine.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/routes/admin_db_routes.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/routes/admin_providers_routes.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/routes/api_key_routes.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/routes/billing_routes.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/routes/feedback_routes.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/routes/marketplace_routes.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/routes/quota_share_routes.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/routes/referral_routes.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/routes/rewards_routes.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/routes/session_routes.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/routes/share_routes.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/routes/wallet_routes.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/routes/webhook_routes.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/security_alerts.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/server.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/signup_guard.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/storage_nodes.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/server/utils.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/share.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/share_tui.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/situational_scoring.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/state.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/subagent_manager.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/syntax_fix.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/task_dispatcher.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/task_logger.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/tools_grep.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/tui/__init__.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/tui/compression_dialog.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/tui/feedback_dialog.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/tui/history_dialog.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/tui/marketplace_app_state.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/tui/marketplace_dialog.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/tui/marketplace_layout_engine.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/tui/mcp_dialog.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/tui/miniagents_dialog.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/tui/model_dialog.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/tui/personality_dialog.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/tui/plugins_dialog.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/tui/publish_dialog.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/tui/quota_dialog.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/tui/quota_redeem_dialog.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/tui/quota_share_dialog.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/tui/resume_dialog.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/tui/rewards_tui.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/tui/skills_dialog.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/tui/subagents_dialog.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/tui/tasks_dialog.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/tui/thinking_display.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/tui/tools_dialog.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/tui/update_dialog.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/update_checker.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/utilities.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/utimmodel.txt +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/vector_memory.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/voice.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/voice_coding.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/voice_events.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/voice_handler.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/voice_instant.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/voice_orchestrator.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/voice_session.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/voice_stt.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/voice_tts.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/wheel.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli/workspace.py +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli.egg-info/dependency_links.txt +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli.egg-info/entry_points.txt +0 -0
- {utim_cli-2.4.3 → utim_cli-2.5.1}/utim_cli.egg-info/top_level.txt +0 -0
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.5.1] - 2026-10-18
|
|
4
|
+
|
|
5
|
+
### 🐛 Fixes
|
|
6
|
+
- **Compression Empty-Response Spam**: `_call_compression_model_with_fallback` now always sets `last_error` on empty/stripped content and logs `model=… routing=… empty_hits=…` diagnostics — eliminates the endless `[COMPRESSION FALLBACK FAILURE] Last error: None` loop and surfaces the real reason (429 / empty content / bad shape).
|
|
7
|
+
- **Admin Portal CORS**: `router.py` now always allows `Origin: null` (downloaded `file://` HTML) and `allow_headers=["*"]` + `max_age=600` so `X-UTIM-Master-Key` / `Authorization` preflights for `/api/admin/*` and `/models` no longer fail with `No 'Access-Control-Allow-Origin'`.
|
|
8
|
+
- **Admin Firewall Bypass**: `firewall.py: _FIREWALL_SKIP_PREFIXES` and `cli_auth.py: PUBLIC_PREFIXES` now include `/admin`, `/admin.html`, `/admin_models.html`, `/static` so the browser-served admin panel bypasses proxy-abuse / burst throttling and CLI HMAC `401` (API auth via master-key session is still enforced).
|
|
9
|
+
|
|
10
|
+
## [2.5.0] - 2026-10-18
|
|
11
|
+
|
|
12
|
+
### ✨ Credit System → Unlimited UX + Internal Throttling
|
|
13
|
+
- **Paid = Unlimited Tokens (Rate-Limits Apply)**: All paid plans now display Unlimited Tokens; caps are enforced server-side as 5-hour refill + weekly fair-use throttling + Quota Bank rollover (never a hard credit wall).
|
|
14
|
+
- **Pricing & Docs Alignment**: Synced landing, docs, and prerender copy — Free stays 3,000 tokens/month.
|
|
15
|
+
|
|
16
|
+
### 🐛 Fixes
|
|
17
|
+
- **Keyring Startup Spam**: Deduped OS-keyring warning to once-per-process and demoted to `debug` so adding 4 BYOK models no longer floods the terminal.
|
|
18
|
+
- **Firewall & Compression Hardening**: External firewall suites remain isolated; BYOK/custom compression routing fixed.
|
|
19
|
+
|
|
20
|
+
### 🔧 Internal
|
|
21
|
+
- **BYOK / Custom Provider Compression**: `_resolve_compression_routing` now mirrors live `Orchestrator.model_source` so summarize/compress requests follow the same provider as the main model.
|
|
22
|
+
|
|
3
23
|
## [2.4.3] - 2026-10-13
|
|
4
24
|
|
|
5
25
|
### 🛡️ Process Immunity & Self-Kill Protection
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: utim-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.1
|
|
4
4
|
Summary: UTIM – Universal Terminal Intelligence Manager. A powerful agentic AI coding assistant for your terminal.
|
|
5
5
|
License: Emend AI Proprietary EULA
|
|
6
6
|
Project-URL: Homepage, https://utim.dev
|
|
@@ -20,12 +20,12 @@ Classifier: Intended Audience :: Developers
|
|
|
20
20
|
Requires-Python: >=3.9
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
|
-
Requires-Dist: requests
|
|
23
|
+
Requires-Dist: requests>=2.32.3
|
|
24
24
|
Requires-Dist: aiohttp>=3.9.5
|
|
25
25
|
Requires-Dist: httpx>=0.25.0
|
|
26
26
|
Requires-Dist: python-dotenv==1.0.1
|
|
27
|
-
Requires-Dist: urllib3
|
|
28
|
-
Requires-Dist: charset-normalizer
|
|
27
|
+
Requires-Dist: urllib3>=2.2.3
|
|
28
|
+
Requires-Dist: charset-normalizer>=3.3.2
|
|
29
29
|
Requires-Dist: chardet==4.0.0
|
|
30
30
|
Requires-Dist: typer>=0.9.0
|
|
31
31
|
Requires-Dist: rich>=13.0.0
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "utim-cli"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.5.1"
|
|
8
8
|
|
|
9
9
|
description = "UTIM – Universal Terminal Intelligence Manager. A powerful agentic AI coding assistant for your terminal."
|
|
10
10
|
readme = "README.md"
|
|
@@ -23,12 +23,12 @@ classifiers = [
|
|
|
23
23
|
"Intended Audience :: Developers",
|
|
24
24
|
]
|
|
25
25
|
dependencies = [
|
|
26
|
-
"requests
|
|
26
|
+
"requests>=2.32.3",
|
|
27
27
|
"aiohttp>=3.9.5",
|
|
28
28
|
"httpx>=0.25.0",
|
|
29
29
|
"python-dotenv==1.0.1",
|
|
30
|
-
"urllib3
|
|
31
|
-
"charset-normalizer
|
|
30
|
+
"urllib3>=2.2.3",
|
|
31
|
+
"charset-normalizer>=3.3.2",
|
|
32
32
|
"chardet==4.0.0",
|
|
33
33
|
"typer>=0.9.0",
|
|
34
34
|
"rich>=13.0.0",
|
|
@@ -14,7 +14,7 @@ Quickstart:
|
|
|
14
14
|
try:
|
|
15
15
|
from utim_cli._version import VERSION as __version__
|
|
16
16
|
except ImportError:
|
|
17
|
-
__version__ = "2.
|
|
17
|
+
__version__ = "2.5.1"
|
|
18
18
|
|
|
19
19
|
# Developer-friendly core classes (similar to `import genai` / `import openai`)
|
|
20
20
|
from utim_cli.sdk.session import UtimSession as Session
|
|
@@ -93,18 +93,115 @@ def index_generic_file(filepath: Path) -> dict:
|
|
|
93
93
|
except Exception:
|
|
94
94
|
return {"imports": [], "symbols": []}
|
|
95
95
|
|
|
96
|
+
# ── Session-level in-memory cache: prevents re-indexing within the same process ──
|
|
97
|
+
_ARCH_LOCK_MEM_CACHE: dict = {}
|
|
98
|
+
# Reuse window: if the same project is locked again within this TTL, return the
|
|
99
|
+
# cached result instantly at zero cost. 600 s = 10 min covers an entire session.
|
|
100
|
+
_ARCH_LOCK_COOLDOWN_S = 600
|
|
101
|
+
# Disk cache TTL for the stable arch_lock dir — if the graph file is fresher than
|
|
102
|
+
# this, the project file tree is considered unchanged unless force-refreshed.
|
|
103
|
+
_ARCH_LOCK_DISK_TTL_S = 600
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _quick_file_tree_hash(project_path: str) -> str:
|
|
107
|
+
"""Cheap hash of the project's file listing (names + mtimes) for change detection."""
|
|
108
|
+
import time as _t
|
|
109
|
+
h = hashlib.sha256()
|
|
110
|
+
try:
|
|
111
|
+
for root, dirs, files in os.walk(project_path):
|
|
112
|
+
# mirror _ignore_dir filtering so hash is stable
|
|
113
|
+
dirs[:] = [d for d in dirs if not _ignore_dir(d)]
|
|
114
|
+
for f in sorted(files):
|
|
115
|
+
if f.startswith('.'):
|
|
116
|
+
continue
|
|
117
|
+
fp = os.path.join(root, f)
|
|
118
|
+
try:
|
|
119
|
+
st = os.stat(fp)
|
|
120
|
+
h.update(f.encode())
|
|
121
|
+
h.update(str(int(st.st_mtime)).encode())
|
|
122
|
+
h.update(str(st.st_size).encode())
|
|
123
|
+
except Exception:
|
|
124
|
+
h.update(f.encode())
|
|
125
|
+
except Exception:
|
|
126
|
+
pass
|
|
127
|
+
return h.hexdigest()[:16]
|
|
128
|
+
|
|
129
|
+
|
|
96
130
|
def architecture_lock(project_path: str = ".") -> str:
|
|
97
|
-
import
|
|
131
|
+
import time as _time
|
|
98
132
|
from utim_cli.brain import get_current_resolved_project
|
|
99
133
|
abs_path = os.path.abspath(project_path)
|
|
100
134
|
if not os.path.isdir(abs_path):
|
|
101
135
|
return f"Error: Project path '{abs_path}' does not exist or is not a directory."
|
|
102
136
|
|
|
103
137
|
proj_id, proj_dir = get_current_resolved_project(abs_path)
|
|
104
|
-
|
|
105
|
-
|
|
138
|
+
|
|
139
|
+
# ── 1. In-memory session cache (zero-IO fast path) ───────────────────────
|
|
140
|
+
_now = _time.time()
|
|
141
|
+
_mem = _ARCH_LOCK_MEM_CACHE.get(proj_id)
|
|
142
|
+
if _mem and (_now - _mem.get("ts", 0) < _ARCH_LOCK_COOLDOWN_S):
|
|
143
|
+
_cached_dir = _mem.get("arch_dir")
|
|
144
|
+
_cached_result = _mem.get("result")
|
|
145
|
+
if _cached_dir and Path(_cached_dir).exists() and _cached_result:
|
|
146
|
+
# Fresh within the session — return cached output instantly.
|
|
147
|
+
return (
|
|
148
|
+
"[CACHED — architecture lock already generated this session; NOT re-indexed]\n"
|
|
149
|
+
+ _cached_result
|
|
150
|
+
+ "\n\n⚠️ Do NOT call architecture_lock again this session. "
|
|
151
|
+
"Use `read_file` on the component_<domain>.md blueprints listed above."
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# ── 2. Stable single directory (replaces per-call arch_{uuid} spam) ───────
|
|
155
|
+
arch_dir = proj_dir / "arch_lock"
|
|
106
156
|
arch_dir.mkdir(parents=True, exist_ok=True)
|
|
107
157
|
brain_file = arch_dir / "00_architecture_map.json"
|
|
158
|
+
meta_file = arch_dir / ".arch_lock_meta.json"
|
|
159
|
+
|
|
160
|
+
# ── 3. Disk cache: reuse if the graph is fresh AND file tree unchanged ───
|
|
161
|
+
_tree_hash = _quick_file_tree_hash(abs_path)
|
|
162
|
+
try:
|
|
163
|
+
if brain_file.exists() and meta_file.exists():
|
|
164
|
+
_meta = json.loads(meta_file.read_text(encoding="utf-8"))
|
|
165
|
+
_age = _now - float(_meta.get("ts", 0))
|
|
166
|
+
_same_tree = _meta.get("tree_hash") == _tree_hash
|
|
167
|
+
if _same_tree and _age < _ARCH_LOCK_DISK_TTL_S:
|
|
168
|
+
# Rebuild the lightweight return string from the cached dir
|
|
169
|
+
# without re-walking / re-parsing the entire codebase.
|
|
170
|
+
_cached_mds = sorted([p.name for p in arch_dir.glob("component_*.md")])
|
|
171
|
+
if _cached_mds:
|
|
172
|
+
# Count files from cached graph
|
|
173
|
+
try:
|
|
174
|
+
_g = json.loads(brain_file.read_text(encoding="utf-8"))
|
|
175
|
+
_n_files = _g.get("total_files", "?")
|
|
176
|
+
_ptype = ", ".join(_g.get("project_type", [])) or "Unknown"
|
|
177
|
+
except Exception:
|
|
178
|
+
_n_files, _ptype = "?", "Unknown"
|
|
179
|
+
_bullets = "\n".join(
|
|
180
|
+
f"- {m}: [file:///{str((arch_dir / m).absolute()).replace(chr(92), '/')}]"
|
|
181
|
+
for m in _cached_mds
|
|
182
|
+
)
|
|
183
|
+
_result = (
|
|
184
|
+
f"✓ Architecture lock (CACHED — reused, not re-indexed) at: {arch_dir}\n"
|
|
185
|
+
f"Identified stack: {_ptype} | Indexed files: {_n_files} | Age: {int(_age)}s\n\n"
|
|
186
|
+
f"WHAT WAS PROVIDED:\n"
|
|
187
|
+
f"1. Structural Dependency Graph: `00_architecture_map.json`\n"
|
|
188
|
+
f"2. Architecture Conventions: `conventions_summary.txt`\n"
|
|
189
|
+
f"3. Modular Component Blueprints ({len(_cached_mds)} domain modules):\n{_bullets}\n\n"
|
|
190
|
+
f"WHY THIS IS NEEDED & NEXT ACTION:\n"
|
|
191
|
+
f"- Zero-token structural grounding eliminates hallucinations and token-wasting blind exploration.\n"
|
|
192
|
+
f"- NEXT STEP: Use `read_file` on the relevant `component_<domain>.md` blueprint above "
|
|
193
|
+
f"matching your task to inspect exact function/class signatures BEFORE editing.\n"
|
|
194
|
+
f"- Do NOT call architecture_lock again unless the project file tree has changed."
|
|
195
|
+
)
|
|
196
|
+
_ARCH_LOCK_MEM_CACHE[proj_id] = {"ts": _now, "arch_dir": str(arch_dir), "result": _result}
|
|
197
|
+
return (
|
|
198
|
+
"[CACHED — architecture lock reused from disk; NOT re-indexed]\n"
|
|
199
|
+
+ _result
|
|
200
|
+
+ "\n\n⚠️ Do NOT call architecture_lock again. "
|
|
201
|
+
"Use `read_file` on the component blueprints listed above."
|
|
202
|
+
)
|
|
203
|
+
except Exception:
|
|
204
|
+
pass # any cache failure → fall through to full index
|
|
108
205
|
|
|
109
206
|
inventory = []
|
|
110
207
|
project_type = []
|
|
@@ -250,12 +347,18 @@ def architecture_lock(project_path: str = ".") -> str:
|
|
|
250
347
|
with open(brain_file, 'w', encoding='utf-8') as bf:
|
|
251
348
|
json.dump(graph_data, bf, indent=2)
|
|
252
349
|
|
|
350
|
+
# Write meta sidecar for disk-cache invalidation (tree hash + timestamp)
|
|
351
|
+
try:
|
|
352
|
+
meta_file.write_text(json.dumps({"ts": _now, "tree_hash": _tree_hash}, indent=2), encoding="utf-8")
|
|
353
|
+
except Exception:
|
|
354
|
+
pass
|
|
355
|
+
|
|
253
356
|
component_bullets = "\n".join([
|
|
254
357
|
f"- {m}: [file:///{str(arch_dir.absolute() / m).replace(chr(92), '/')}]"
|
|
255
358
|
for m in generated_mds
|
|
256
359
|
]) if generated_mds else "- None"
|
|
257
360
|
|
|
258
|
-
|
|
361
|
+
_result_str = (
|
|
259
362
|
f"✓ Architecture lock successfully generated at: {arch_dir}\n"
|
|
260
363
|
f"Identified stack: {', '.join(project_type) or 'Unknown'} | Indexed files: {len(inventory)}\n\n"
|
|
261
364
|
f"WHAT WAS PROVIDED:\n"
|
|
@@ -264,6 +367,10 @@ def architecture_lock(project_path: str = ".") -> str:
|
|
|
264
367
|
f"3. Modular Component Blueprints ({len(generated_mds)} domain modules):\n{component_bullets}\n\n"
|
|
265
368
|
f"WHY THIS IS NEEDED & NEXT ACTION:\n"
|
|
266
369
|
f"- Zero-token structural grounding eliminates hallucinations and token-wasting blind repo exploration.\n"
|
|
267
|
-
f"- NEXT STEP: Use `read_file` on the relevant `component_<domain>.md` blueprint above matching your task to inspect exact function/class signatures and dependencies before reading or editing source code
|
|
370
|
+
f"- NEXT STEP: Use `read_file` on the relevant `component_<domain>.md` blueprint above matching your task to inspect exact function/class signatures and dependencies before reading or editing source code.\n"
|
|
371
|
+
f"- Do NOT call architecture_lock again this session — reuse the blueprints above."
|
|
268
372
|
)
|
|
373
|
+
# Populate in-memory cache so a second call this session is instant
|
|
374
|
+
_ARCH_LOCK_MEM_CACHE[proj_id] = {"ts": _now, "arch_dir": str(arch_dir), "result": _result_str}
|
|
375
|
+
return _result_str
|
|
269
376
|
|
|
@@ -24,14 +24,15 @@ from rich.text import Text
|
|
|
24
24
|
from rich.markdown import Markdown
|
|
25
25
|
from rich import box
|
|
26
26
|
|
|
27
|
-
# ──
|
|
27
|
+
# ── Sidecar Helper Model ───────────────────────────────────────────
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
PRIMARY_HELPER_MODEL = "nvidia/nemotron-3.5-lightning:free"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_free_models_fallback_chain() -> List[str]:
|
|
33
|
+
"""Return single primary helper model (fallback chain disabled)."""
|
|
34
|
+
return [PRIMARY_HELPER_MODEL]
|
|
30
35
|
|
|
31
|
-
# Primary helper model = 'nvidia/nemotron-3.5-lightning:free' (verified free model), then falls
|
|
32
|
-
# back through the verified free models from the server DB registry.
|
|
33
|
-
_HELPER_FALLBACK_CHAIN = get_free_models_fallback_chain()
|
|
34
|
-
PRIMARY_HELPER_MODEL = _HELPER_FALLBACK_CHAIN[0] if _HELPER_FALLBACK_CHAIN else "nvidia/nemotron-3.5-lightning:free"
|
|
35
36
|
|
|
36
37
|
|
|
37
38
|
|
|
@@ -339,7 +340,7 @@ def execute_ask_helper(
|
|
|
339
340
|
if not console:
|
|
340
341
|
console = Console(highlight=False, markup=True)
|
|
341
342
|
|
|
342
|
-
|
|
343
|
+
model = PRIMARY_HELPER_MODEL
|
|
343
344
|
|
|
344
345
|
# Pre-extract live session chat history and context
|
|
345
346
|
chat_summary = read_chat_history(orchestrator, max_turns=12)
|
|
@@ -378,95 +379,89 @@ def execute_ask_helper(
|
|
|
378
379
|
call_success = False
|
|
379
380
|
last_error = ""
|
|
380
381
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
})
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
call_success = True
|
|
461
|
-
break
|
|
462
|
-
|
|
463
|
-
except Exception as e:
|
|
464
|
-
last_error = str(e)
|
|
465
|
-
continue
|
|
382
|
+
try:
|
|
383
|
+
from utim_cli.client_utils import proxy_openrouter_request
|
|
384
|
+
resp = proxy_openrouter_request(
|
|
385
|
+
json_data={
|
|
386
|
+
"model": model,
|
|
387
|
+
"messages": messages,
|
|
388
|
+
"tools": tools,
|
|
389
|
+
"max_tokens": 1000,
|
|
390
|
+
},
|
|
391
|
+
stream=False,
|
|
392
|
+
timeout=(15, 300),
|
|
393
|
+
is_reflection=False,
|
|
394
|
+
)
|
|
395
|
+
resp.raise_for_status()
|
|
396
|
+
|
|
397
|
+
data = resp.json()
|
|
398
|
+
choice = data.get("choices", [{}])[0]
|
|
399
|
+
msg = choice.get("message", {})
|
|
400
|
+
content = msg.get("content") or ""
|
|
401
|
+
tool_calls = msg.get("tool_calls") or []
|
|
402
|
+
|
|
403
|
+
# Clean thinking tags
|
|
404
|
+
content = re.sub(r"<think(?:ing)?>.*?</think(?:ing)?>", "", content, flags=re.DOTALL).strip()
|
|
405
|
+
|
|
406
|
+
if tool_calls:
|
|
407
|
+
messages.append({
|
|
408
|
+
"role": "assistant",
|
|
409
|
+
"content": content,
|
|
410
|
+
"tool_calls": tool_calls,
|
|
411
|
+
})
|
|
412
|
+
|
|
413
|
+
for tc in tool_calls:
|
|
414
|
+
fn = tc.get("function", {})
|
|
415
|
+
fname = fn.get("name", "")
|
|
416
|
+
call_id = tc.get("id", "call_tool")
|
|
417
|
+
|
|
418
|
+
if fname == "read_chat":
|
|
419
|
+
args_str = fn.get("arguments", "{}")
|
|
420
|
+
try:
|
|
421
|
+
args = json.loads(args_str) if isinstance(args_str, str) else args_str
|
|
422
|
+
except Exception:
|
|
423
|
+
args = {}
|
|
424
|
+
max_turns = args.get("max_turns", 15)
|
|
425
|
+
chat_result = read_chat_history(orchestrator, max_turns=max_turns)
|
|
426
|
+
messages.append({
|
|
427
|
+
"role": "tool",
|
|
428
|
+
"tool_call_id": call_id,
|
|
429
|
+
"name": "read_chat",
|
|
430
|
+
"content": chat_result,
|
|
431
|
+
})
|
|
432
|
+
elif fname in ("web_search", "websearch", "search"):
|
|
433
|
+
args_str = fn.get("arguments", "{}")
|
|
434
|
+
try:
|
|
435
|
+
args = json.loads(args_str) if isinstance(args_str, str) else args_str
|
|
436
|
+
except Exception:
|
|
437
|
+
args = {}
|
|
438
|
+
search_q = args.get("query") or args.get("prompt") or args.get("q") or question
|
|
439
|
+
search_res = execute_sidecar_websearch(search_q)
|
|
440
|
+
messages.append({
|
|
441
|
+
"role": "tool",
|
|
442
|
+
"tool_call_id": call_id,
|
|
443
|
+
"name": fname,
|
|
444
|
+
"content": search_res[:12000],
|
|
445
|
+
})
|
|
446
|
+
else:
|
|
447
|
+
messages.append({
|
|
448
|
+
"role": "tool",
|
|
449
|
+
"tool_call_id": call_id,
|
|
450
|
+
"name": fname,
|
|
451
|
+
"content": f"Error: Tool '{fname}' is not allowed. Only 'read_chat' and 'web_search' are available.",
|
|
452
|
+
})
|
|
453
|
+
call_success = True
|
|
454
|
+
|
|
455
|
+
elif content:
|
|
456
|
+
final_response = content
|
|
457
|
+
call_success = True
|
|
458
|
+
|
|
459
|
+
except Exception as e:
|
|
460
|
+
last_error = str(e)
|
|
466
461
|
|
|
467
462
|
if not call_success:
|
|
468
463
|
if not final_response:
|
|
469
|
-
final_response = f"I'm here to help, but encountered connection errors
|
|
464
|
+
final_response = f"I'm here to help, but encountered connection errors with model '{model}': {last_error}"
|
|
470
465
|
break
|
|
471
466
|
|
|
472
467
|
if final_response:
|
|
@@ -54,42 +54,32 @@ from utim_cli.constants import DEFAULT_MODEL
|
|
|
54
54
|
|
|
55
55
|
_OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions"
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
"google/gemma-4-26b-a4b-it:free",
|
|
60
|
-
"nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free",
|
|
61
|
-
"nvidia/nemotron-nano-12b-v2-vl:free",
|
|
62
|
-
]
|
|
63
|
-
|
|
64
|
-
from utim_cli.client_utils import get_free_models_fallback_chain
|
|
65
|
-
|
|
66
|
-
_CODE_MODELS: List[str] = get_free_models_fallback_chain()
|
|
67
|
-
|
|
68
|
-
|
|
57
|
+
_VISION_MODEL: str = "google/gemma-4-31b-it:free"
|
|
58
|
+
_CODE_MODEL: str = "nvidia/nemotron-3.5-lightning:free"
|
|
69
59
|
|
|
70
60
|
|
|
71
61
|
def _get_vision_models() -> List[str]:
|
|
72
|
-
"""Return vision model
|
|
62
|
+
"""Return vision model (single model, no fallbacks)."""
|
|
73
63
|
try:
|
|
74
64
|
from utim_cli.config import config
|
|
75
65
|
override = config.get("subagent_model_blender_vision")
|
|
76
66
|
if override and override not in ("__non_agent__", "__none__"):
|
|
77
|
-
return [override]
|
|
67
|
+
return [override]
|
|
78
68
|
except Exception:
|
|
79
69
|
pass
|
|
80
|
-
return
|
|
70
|
+
return [_VISION_MODEL]
|
|
81
71
|
|
|
82
72
|
|
|
83
73
|
def _get_code_models() -> List[str]:
|
|
84
|
-
"""Return code model
|
|
74
|
+
"""Return code model (single model, no fallbacks)."""
|
|
85
75
|
try:
|
|
86
76
|
from utim_cli.config import config
|
|
87
77
|
override = config.get("subagent_model_blender_code")
|
|
88
78
|
if override and override not in ("__non_agent__", "__none__"):
|
|
89
|
-
return [override]
|
|
79
|
+
return [override]
|
|
90
80
|
except Exception:
|
|
91
81
|
pass
|
|
92
|
-
return
|
|
82
|
+
return [_CODE_MODEL]
|
|
93
83
|
|
|
94
84
|
MAX_RETRIES = 3 # Script fix-and-retry attempts
|
|
95
85
|
|
|
@@ -552,36 +542,36 @@ def _llm_call(
|
|
|
552
542
|
else:
|
|
553
543
|
user_content = user_text
|
|
554
544
|
|
|
555
|
-
last_err: Exception = RuntimeError("No
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
raise RuntimeError(f"
|
|
545
|
+
last_err: Exception = RuntimeError("No model specified.")
|
|
546
|
+
model = models[0] if models else _CODE_MODEL
|
|
547
|
+
for attempt in range(3):
|
|
548
|
+
try:
|
|
549
|
+
from utim_cli.client_utils import proxy_openrouter_request
|
|
550
|
+
resp = proxy_openrouter_request(
|
|
551
|
+
json_data={
|
|
552
|
+
"model": model,
|
|
553
|
+
"messages": [
|
|
554
|
+
{"role": "system", "content": system},
|
|
555
|
+
{"role": "user", "content": user_content},
|
|
556
|
+
],
|
|
557
|
+
"max_tokens": max_tokens,
|
|
558
|
+
"stream": False,
|
|
559
|
+
},
|
|
560
|
+
stream=False,
|
|
561
|
+
timeout=timeout,
|
|
562
|
+
)
|
|
563
|
+
resp.raise_for_status()
|
|
564
|
+
data = resp.json()
|
|
565
|
+
text = data["choices"][0]["message"]["content"]
|
|
566
|
+
return text.strip()
|
|
567
|
+
except Exception as exc: # noqa: BLE001
|
|
568
|
+
last_err = exc
|
|
569
|
+
code = getattr(getattr(exc, "response", None), "status_code", 0)
|
|
570
|
+
if code == 429 and attempt < 2:
|
|
571
|
+
time.sleep(5 * (attempt + 1))
|
|
572
|
+
continue
|
|
573
|
+
break
|
|
574
|
+
raise RuntimeError(f"LLM model '{model}' failed: {last_err}") from last_err
|
|
585
575
|
|
|
586
576
|
|
|
587
577
|
# ---------------------------------------------------------------------------
|