utim-cli 2.5.1__tar.gz → 2.5.3__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.5.1 → utim_cli-2.5.3}/CHANGELOG.md +10 -2
- {utim_cli-2.5.1/utim_cli.egg-info → utim_cli-2.5.3}/PKG-INFO +1 -1
- {utim_cli-2.5.1 → utim_cli-2.5.3}/pyproject.toml +1 -1
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim/__init__.py +1 -1
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/__init__.py +1 -1
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/_version.py +1 -1
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/architecture_indexer.py +105 -82
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/client_utils.py +91 -8
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/context_pruner.py +563 -257
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/orchestrator.py +141 -58
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/orchestrator_helpers.py +55 -6
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/orchestrator_llm_transport.py +79 -11
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/orchestrator_turn_history.py +134 -41
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/auth.py +11 -0
- utim_cli-2.5.3/utim_cli/server/crypto_utils.py +219 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/db.py +119 -17
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/hobby_throttler.py +60 -30
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/models.py +312 -78
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/plan_integrity.py +94 -33
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/router.py +73 -66
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/admin_providers_routes.py +61 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/admin_separated_models_routes.py +989 -719
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/api_models_public_routes.py +14 -32
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/completion_routes.py +616 -173
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/credit_routes.py +139 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/utils.py +871 -862
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tools.py +5 -5
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/model_dialog.py +35 -17
- utim_cli-2.5.3/utim_cli/tui/verbosity_dialog.py +221 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/utim.py +79 -30
- {utim_cli-2.5.1 → utim_cli-2.5.3/utim_cli.egg-info}/PKG-INFO +1 -1
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli.egg-info/SOURCES.txt +3 -1
- {utim_cli-2.5.1 → utim_cli-2.5.3}/LICENSE +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/MANIFEST.in +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/README.md +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/setup.cfg +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/setup.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/_chip_test.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/_verify_repro.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/_verify_syntax.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/_wizard_smoke_test.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/agent.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/ask_helper.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/auth.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/backup.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/billing.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/blender_agent.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/bootstrap.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/brain.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/builtin/skills/utim-guide/SKILL.md +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/config.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/constants.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/context_inspector.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/crawler/__init__.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/crawler/engine.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/crawler/markdown_distiller.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/dependency_manager.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/doctor.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/evidence_registry.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/harbor.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/knowledge_graph.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/local_db.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/logger.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/mcp_clean_wrapper.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/mcp_client.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/mcp_registry.json +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/models.txt +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/orchestrator_tool_exec.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/personalities.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/platform_installer.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/plugins/__init__.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/plugins/hooks.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/plugins/loader.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/plugins/manager.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/plugins/registry.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/plugins/runtime.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/plugins/schema.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/plugins/tool_loader.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/reflection.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/report.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/scrapy_search.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/sdk/__init__.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/sdk/config.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/sdk/context.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/sdk/events.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/sdk/handlers.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/sdk/session.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/sdk/sidecar.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/__init__.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/admin_auth.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/api_batcher.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/api_models_db.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/attribution.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/audit_log.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/batch_processor.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/captcha.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/cli_auth.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/concurrency.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/docs_md/SECRET_PROVISIONING.md +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/docs_md/about.md +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/docs_md/changelog.md +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/docs_md/docs.md +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/docs_md/features.md +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/docs_md/license.md +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/docs_md/pricing.md +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/docs_md/privacy.md +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/docs_md/refund.md +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/docs_md/support.md +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/docs_md/terms.md +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/email_utils.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/exchange_rate.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/firebase.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/firewall.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/fix_duplicate_users.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/history.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/key_rotator.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/local_llm.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/logging_config.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/maintenance.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/micro_batcher.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/model_agent.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/platform_billing.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/pricing_updater.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/provider_balance.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/provider_discovery.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/provider_router.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/provision_build.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/rate_limit.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/request_batcher.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/request_queue.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/rewards_engine.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/__init__.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/admin_bonus_routes.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/admin_db_routes.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/api_gateway_extensions_routes.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/api_key_routes.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/api_models_admin_routes.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/auth_routes.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/billing_routes.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/feedback_routes.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/marketplace_routes.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/provider_compat_routes.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/quota_routes.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/quota_share_routes.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/referral_routes.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/rewards_routes.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/security_routes.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/session_routes.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/share_routes.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/wallet_routes.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/webhook_routes.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/security_alerts.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/server.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/signup_guard.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/storage_nodes.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/share.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/share_tui.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/situational_scoring.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/state.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/subagent_manager.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/syntax_fix.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/task_dispatcher.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/task_logger.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tools_grep.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/__init__.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/compression_dialog.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/feedback_dialog.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/history_dialog.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/marketplace_app_state.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/marketplace_dialog.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/marketplace_layout_engine.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/mcp_dialog.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/miniagents_dialog.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/personality_dialog.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/plugins_dialog.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/publish_dialog.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/quota_dialog.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/quota_redeem_dialog.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/quota_share_dialog.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/resume_dialog.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/rewards_tui.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/skills_dialog.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/subagents_dialog.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/tasks_dialog.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/thinking_display.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/tools_dialog.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/update_dialog.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/update_checker.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/utilities.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/utimmodel.txt +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/vector_memory.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/voice.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/voice_coding.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/voice_events.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/voice_handler.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/voice_instant.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/voice_orchestrator.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/voice_session.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/voice_stt.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/voice_tts.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/wheel.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/workspace.py +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli.egg-info/dependency_links.txt +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli.egg-info/entry_points.txt +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli.egg-info/requires.txt +0 -0
- {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli.egg-info/top_level.txt +0 -0
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.5.3] - 2026-10-20
|
|
4
|
+
|
|
5
|
+
### ✨ Improvements & Fixes
|
|
6
|
+
- Stability, streaming, and rate-limit handling improvements.
|
|
7
|
+
|
|
8
|
+
## [2.5.2] - 2026-10-18
|
|
9
|
+
|
|
10
|
+
### 🐛 Fixes
|
|
11
|
+
- **Paid Plan Model Gating & Aliases**: Fixed preflight model registry check in `completion_routes.py` so paid subscribers (Hobby/Pro/Max/Ultra) are never blocked by free-plan error notices.
|
|
12
|
+
|
|
3
13
|
## [2.5.1] - 2026-10-18
|
|
4
14
|
|
|
5
15
|
### 🐛 Fixes
|
|
6
16
|
- **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
17
|
|
|
10
18
|
## [2.5.0] - 2026-10-18
|
|
11
19
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: utim-cli
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.3
|
|
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
|
|
@@ -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.5.
|
|
17
|
+
__version__ = "2.5.3"
|
|
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
|
|
@@ -140,66 +140,53 @@ def architecture_lock(project_path: str = ".") -> str:
|
|
|
140
140
|
_now = _time.time()
|
|
141
141
|
_mem = _ARCH_LOCK_MEM_CACHE.get(proj_id)
|
|
142
142
|
if _mem and (_now - _mem.get("ts", 0) < _ARCH_LOCK_COOLDOWN_S):
|
|
143
|
-
|
|
143
|
+
_cached_file = _mem.get("arch_file")
|
|
144
144
|
_cached_result = _mem.get("result")
|
|
145
|
-
if
|
|
145
|
+
if _cached_file and Path(_cached_file).is_file() and _cached_result:
|
|
146
146
|
# Fresh within the session — return cached output instantly.
|
|
147
147
|
return (
|
|
148
148
|
"[CACHED — architecture lock already generated this session; NOT re-indexed]\n"
|
|
149
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."
|
|
150
|
+
+ f"\n\n⚠️ Do NOT call architecture_lock again this session. Read the file at: `{_cached_file}`"
|
|
152
151
|
)
|
|
153
152
|
|
|
154
153
|
# ── 2. Stable single directory (replaces per-call arch_{uuid} spam) ───────
|
|
155
154
|
arch_dir = proj_dir / "arch_lock"
|
|
156
155
|
arch_dir.mkdir(parents=True, exist_ok=True)
|
|
157
156
|
brain_file = arch_dir / "00_architecture_map.json"
|
|
157
|
+
arch_file = arch_dir / "architecture.md"
|
|
158
158
|
meta_file = arch_dir / ".arch_lock_meta.json"
|
|
159
159
|
|
|
160
|
-
# ── 3. Disk cache: reuse if the
|
|
160
|
+
# ── 3. Disk cache: reuse if the single-file index is fresh AND file tree unchanged
|
|
161
161
|
_tree_hash = _quick_file_tree_hash(abs_path)
|
|
162
162
|
try:
|
|
163
|
-
if brain_file.
|
|
163
|
+
if arch_file.is_file() and brain_file.is_file() and meta_file.is_file():
|
|
164
164
|
_meta = json.loads(meta_file.read_text(encoding="utf-8"))
|
|
165
165
|
_age = _now - float(_meta.get("ts", 0))
|
|
166
166
|
_same_tree = _meta.get("tree_hash") == _tree_hash
|
|
167
167
|
if _same_tree and _age < _ARCH_LOCK_DISK_TTL_S:
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
)
|
|
168
|
+
try:
|
|
169
|
+
_g = json.loads(brain_file.read_text(encoding="utf-8"))
|
|
170
|
+
_n_files = _g.get("total_files", "?")
|
|
171
|
+
_ptype = ", ".join(_g.get("project_type", [])) or "Unknown"
|
|
172
|
+
except Exception:
|
|
173
|
+
_n_files, _ptype = "?", "Unknown"
|
|
174
|
+
_arch_uri = f"file:///{str(arch_file.absolute()).replace(chr(92), '/')}"
|
|
175
|
+
_result = (
|
|
176
|
+
f"✓ Architecture lock (CACHED — reused, not re-indexed)\n"
|
|
177
|
+
f"Identified stack: {_ptype} | Indexed files: {_n_files} | Age: {int(_age)}s\n"
|
|
178
|
+
f"Architecture file: `{arch_file}`\n"
|
|
179
|
+
f"URI: {_arch_uri}\n\n"
|
|
180
|
+
f"NEXT STEP: Read the architecture with `read_file(\"{arch_file}\")` "
|
|
181
|
+
f"to inspect exact function/class signatures BEFORE editing.\n"
|
|
182
|
+
f"Do NOT call architecture_lock again unless the project file tree has changed."
|
|
183
|
+
)
|
|
184
|
+
_ARCH_LOCK_MEM_CACHE[proj_id] = {"ts": _now, "arch_file": str(arch_file), "result": _result}
|
|
185
|
+
return (
|
|
186
|
+
"[CACHED — architecture lock reused from disk; NOT re-indexed]\n"
|
|
187
|
+
+ _result
|
|
188
|
+
+ f"\n\n⚠️ Do NOT call architecture_lock again. Read the file at: `{arch_file}`"
|
|
189
|
+
)
|
|
203
190
|
except Exception:
|
|
204
191
|
pass # any cache failure → fall through to full index
|
|
205
192
|
|
|
@@ -310,41 +297,84 @@ def architecture_lock(project_path: str = ".") -> str:
|
|
|
310
297
|
md_block += f"```text\n{snippet}\n```\n"
|
|
311
298
|
clusters[cluster_name].append(md_block)
|
|
312
299
|
|
|
313
|
-
#
|
|
314
|
-
|
|
315
|
-
for
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
300
|
+
# ── Build single consolidated architecture.md (TOC + all clusters) ──────
|
|
301
|
+
# Remove stale split files from previous multi-file runs
|
|
302
|
+
for _stale in arch_dir.glob("component_*.md"):
|
|
303
|
+
try:
|
|
304
|
+
_stale.unlink()
|
|
305
|
+
except Exception:
|
|
306
|
+
pass
|
|
307
|
+
for _stale in (arch_dir / "conventions_summary.txt",):
|
|
308
|
+
try:
|
|
309
|
+
if _stale.is_file():
|
|
310
|
+
_stale.unlink()
|
|
311
|
+
except Exception:
|
|
312
|
+
pass
|
|
313
|
+
|
|
314
|
+
# Stable ordering for reproducible output
|
|
315
|
+
_ordered_clusters = sorted(clusters.items(), key=lambda kv: kv[0].lower())
|
|
316
|
+
# TOC entries — link to per-cluster anchors inside the single file
|
|
317
|
+
_toc_lines: List[str] = []
|
|
318
|
+
for cname, blocks in _ordered_clusters:
|
|
319
|
+
if not blocks:
|
|
320
|
+
continue
|
|
321
|
+
safe_cname = re.sub(r'[^a-z0-9_]', '_', cname.lower())
|
|
322
|
+
anchor = f"component-{safe_cname}"
|
|
323
|
+
_toc_lines.append(f"- [{cname.title()}](#{anchor}) — {len(blocks)} symbol(s)/note(s)")
|
|
324
|
+
|
|
325
|
+
_header = [
|
|
326
|
+
f"# Project Architecture — `{Path(abs_path).name}`",
|
|
327
|
+
"",
|
|
328
|
+
f"- **Stack:** {', '.join(project_type) or 'Unknown'}",
|
|
329
|
+
f"- **Root:** `{abs_path}`",
|
|
330
|
+
f"- **Indexed files:** {len(inventory)}",
|
|
331
|
+
f"- **Generated:** {arch_file.name} (single consolidated file — all domains inline)",
|
|
332
|
+
"",
|
|
333
|
+
"## Table of Contents",
|
|
334
|
+
"- [Conventions](#conventions)",
|
|
335
|
+
]
|
|
336
|
+
if _toc_lines:
|
|
337
|
+
_header.extend(_toc_lines)
|
|
338
|
+
_header.extend(["", "---", ""])
|
|
339
|
+
|
|
340
|
+
_conventions = [
|
|
341
|
+
"## Conventions",
|
|
342
|
+
"- **Component isolation**: Keep domains isolated. Do not bloat central routers.",
|
|
343
|
+
"- **Multifile expansion**: For large features, create new standalone files following existing directory structure.",
|
|
344
|
+
"- **Grounding**: This document is the zero-token structural ground truth. Prefer it over blind directory walks.",
|
|
345
|
+
"",
|
|
346
|
+
"---",
|
|
347
|
+
"",
|
|
333
348
|
]
|
|
334
|
-
(arch_dir / "conventions_summary.txt").write_text("\n".join(conventions), encoding="utf-8")
|
|
335
349
|
|
|
350
|
+
_body_parts: List[str] = _header + _conventions
|
|
351
|
+
for cname, blocks in _ordered_clusters:
|
|
352
|
+
if not blocks:
|
|
353
|
+
continue
|
|
354
|
+
safe_cname = re.sub(r'[^a-z0-9_]', '_', cname.lower())
|
|
355
|
+
anchor = f"component-{safe_cname}"
|
|
356
|
+
_body_parts.append(f'<a id="{anchor}"></a>')
|
|
357
|
+
_body_parts.append(f"## 🧩 {cname.title()}")
|
|
358
|
+
_body_parts.append("")
|
|
359
|
+
_body_parts.extend(blocks)
|
|
360
|
+
_body_parts.append("")
|
|
361
|
+
_body_parts.append("---")
|
|
362
|
+
_body_parts.append("")
|
|
363
|
+
|
|
364
|
+
_arch_content = "\n".join(_body_parts).rstrip() + "\n"
|
|
365
|
+
arch_file.write_text(_arch_content, encoding="utf-8")
|
|
366
|
+
|
|
367
|
+
# ── Structural dependency graph (kept for tooling; not the LLM-facing artifact)
|
|
336
368
|
unique_nodes = {n["id"]: n for n in nodes}.values()
|
|
337
|
-
|
|
338
369
|
graph_data = {
|
|
339
370
|
"project_path": abs_path,
|
|
340
371
|
"project_type": project_type,
|
|
341
372
|
"total_files": len(inventory),
|
|
342
373
|
"nodes": list(unique_nodes),
|
|
343
374
|
"edges": edges,
|
|
344
|
-
"capabilities": ["Project orientation", "Graph traversal", "Symbol resolution"]
|
|
375
|
+
"capabilities": ["Project orientation", "Graph traversal", "Symbol resolution"],
|
|
345
376
|
}
|
|
346
|
-
|
|
347
|
-
with open(brain_file, 'w', encoding='utf-8') as bf:
|
|
377
|
+
with open(brain_file, "w", encoding="utf-8") as bf:
|
|
348
378
|
json.dump(graph_data, bf, indent=2)
|
|
349
379
|
|
|
350
380
|
# Write meta sidecar for disk-cache invalidation (tree hash + timestamp)
|
|
@@ -353,24 +383,17 @@ def architecture_lock(project_path: str = ".") -> str:
|
|
|
353
383
|
except Exception:
|
|
354
384
|
pass
|
|
355
385
|
|
|
356
|
-
|
|
357
|
-
f"- {m}: [file:///{str(arch_dir.absolute() / m).replace(chr(92), '/')}]"
|
|
358
|
-
for m in generated_mds
|
|
359
|
-
]) if generated_mds else "- None"
|
|
360
|
-
|
|
386
|
+
_arch_uri = f"file:///{str(arch_file.absolute()).replace(chr(92), '/')}"
|
|
361
387
|
_result_str = (
|
|
362
|
-
f"✓ Architecture lock successfully generated
|
|
363
|
-
f"Identified stack: {', '.join(project_type) or 'Unknown'} | Indexed files: {len(inventory)}\n
|
|
364
|
-
f"
|
|
365
|
-
f"
|
|
366
|
-
f"
|
|
367
|
-
f"
|
|
368
|
-
f"
|
|
369
|
-
f"- Zero-token structural grounding eliminates hallucinations and token-wasting blind repo exploration.\n"
|
|
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."
|
|
388
|
+
f"✓ Architecture lock successfully generated\n"
|
|
389
|
+
f"Identified stack: {', '.join(project_type) or 'Unknown'} | Indexed files: {len(inventory)}\n"
|
|
390
|
+
f"Architecture file: `{arch_file}`\n"
|
|
391
|
+
f"URI: {_arch_uri}\n\n"
|
|
392
|
+
f"This single file contains ALL domain sections inline (TOC + conventions + {len([b for _, b in _ordered_clusters if b])} component groups).\n"
|
|
393
|
+
f"NEXT STEP: Read it with `read_file(\"{arch_file}\")` to inspect exact function/class signatures and dependencies before editing source code.\n"
|
|
394
|
+
f"Do NOT call architecture_lock again this session — re-use this file."
|
|
372
395
|
)
|
|
373
396
|
# Populate in-memory cache so a second call this session is instant
|
|
374
|
-
_ARCH_LOCK_MEM_CACHE[proj_id] = {"ts": _now, "
|
|
397
|
+
_ARCH_LOCK_MEM_CACHE[proj_id] = {"ts": _now, "arch_file": str(arch_file), "result": _result_str}
|
|
375
398
|
return _result_str
|
|
376
399
|
|
|
@@ -166,7 +166,7 @@ def get_server_model_catalog(timeout: float = 5.0) -> dict | None:
|
|
|
166
166
|
)
|
|
167
167
|
if resp.status_code == 200:
|
|
168
168
|
data = resp.json()
|
|
169
|
-
if isinstance(data, dict)
|
|
169
|
+
if isinstance(data, dict):
|
|
170
170
|
_catalog_cache = data
|
|
171
171
|
_catalog_cache_ts = now
|
|
172
172
|
return _catalog_cache
|
|
@@ -201,7 +201,7 @@ def fetch_remote_models_catalog(timeout: float = 3.0) -> dict:
|
|
|
201
201
|
resp = requests.get(url, timeout=timeout)
|
|
202
202
|
if resp.status_code == 200:
|
|
203
203
|
models_list = resp.json()
|
|
204
|
-
if
|
|
204
|
+
if isinstance(models_list, list):
|
|
205
205
|
from utim_cli.constants import APINEX_MODEL_IDS
|
|
206
206
|
valid_models = [
|
|
207
207
|
m for m in models_list
|
|
@@ -261,7 +261,7 @@ def fetch_remote_models_catalog(timeout: float = 3.0) -> dict:
|
|
|
261
261
|
resp = requests.get(url, params=params, headers=get_auth_headers(), timeout=timeout)
|
|
262
262
|
if resp.status_code == 200:
|
|
263
263
|
data = resp.json()
|
|
264
|
-
if
|
|
264
|
+
if isinstance(data, dict):
|
|
265
265
|
from utim_cli.constants import APINEX_MODEL_IDS
|
|
266
266
|
filtered_cat = {
|
|
267
267
|
k: [
|
|
@@ -281,7 +281,7 @@ def fetch_remote_models_catalog(timeout: float = 3.0) -> dict:
|
|
|
281
281
|
try:
|
|
282
282
|
from utim_cli.server.models import get_model_catalog
|
|
283
283
|
data = get_model_catalog()
|
|
284
|
-
if data:
|
|
284
|
+
if isinstance(data, dict):
|
|
285
285
|
_REMOTE_CATALOG_CACHE = data
|
|
286
286
|
_REMOTE_CATALOG_CACHE_TIME = now
|
|
287
287
|
return data
|
|
@@ -312,7 +312,7 @@ def _build_free_models_fallback_chain(now: float) -> list[str]:
|
|
|
312
312
|
resp = _req.get(f"{SERVER_URL.rstrip('/')}/models", timeout=3.0)
|
|
313
313
|
if resp.status_code == 200:
|
|
314
314
|
models_list = resp.json()
|
|
315
|
-
if
|
|
315
|
+
if isinstance(models_list, list):
|
|
316
316
|
from utim_cli.constants import APINEX_MODEL_IDS
|
|
317
317
|
for m in models_list:
|
|
318
318
|
mid = str(m.get("model_id") or "")
|
|
@@ -407,6 +407,86 @@ def resolve_model_provider_endpoint(model_id: Optional[str]) -> Optional[Dict[st
|
|
|
407
407
|
except Exception:
|
|
408
408
|
pass
|
|
409
409
|
|
|
410
|
+
# 1b. Server ModelEndpointDB and Separated Models (configured via admin_models.html)
|
|
411
|
+
try:
|
|
412
|
+
from utim_cli.server.db import SessionLocal, ModelEndpointDB, SEPARATED_MODEL_CLASSES
|
|
413
|
+
sdb = SessionLocal()
|
|
414
|
+
try:
|
|
415
|
+
cand_ids = {clean_id, clean_id.lower()}
|
|
416
|
+
stripped = clean_id
|
|
417
|
+
for pref in ("am/", "api/", "ag/"):
|
|
418
|
+
if clean_id.startswith(pref):
|
|
419
|
+
stripped = clean_id[len(pref):].strip()
|
|
420
|
+
break
|
|
421
|
+
cand_ids.add(stripped)
|
|
422
|
+
cand_ids.add(stripped.lower())
|
|
423
|
+
for pref in ("am/", "api/", "ag/"):
|
|
424
|
+
cand_ids.add(f"{pref}{stripped}")
|
|
425
|
+
cand_ids.add(f"{pref}{stripped}".lower())
|
|
426
|
+
if "/" in stripped:
|
|
427
|
+
base_part = stripped.split("/")[-1].strip()
|
|
428
|
+
cand_ids.add(base_part)
|
|
429
|
+
cand_ids.add(base_part.lower())
|
|
430
|
+
for pref in ("am/", "api/", "ag/"):
|
|
431
|
+
cand_ids.add(f"{pref}{base_part}")
|
|
432
|
+
cand_ids.add(f"{pref}{base_part}".lower())
|
|
433
|
+
if "/" in clean_id:
|
|
434
|
+
base_raw = clean_id.split("/")[-1].strip()
|
|
435
|
+
cand_ids.add(base_raw)
|
|
436
|
+
cand_ids.add(base_raw.lower())
|
|
437
|
+
for pref in ("am/", "api/", "ag/"):
|
|
438
|
+
cand_ids.add(f"{pref}{base_raw}")
|
|
439
|
+
cand_ids.add(f"{pref}{base_raw}".lower())
|
|
440
|
+
|
|
441
|
+
ep = sdb.query(ModelEndpointDB).filter(
|
|
442
|
+
ModelEndpointDB.model_id.in_(cand_ids),
|
|
443
|
+
(ModelEndpointDB.is_active == True) | (ModelEndpointDB.is_active.is_(None)),
|
|
444
|
+
ModelEndpointDB.endpoint_url.isnot(None),
|
|
445
|
+
ModelEndpointDB.endpoint_url != ""
|
|
446
|
+
).order_by(ModelEndpointDB.priority.asc()).first()
|
|
447
|
+
if ep and ep.endpoint_url:
|
|
448
|
+
base = ep.endpoint_url.rstrip("/")
|
|
449
|
+
url = base if base.endswith("/chat/completions") else f"{base}/chat/completions"
|
|
450
|
+
keys = ep.get_api_keys()
|
|
451
|
+
key = keys[0] if keys else ""
|
|
452
|
+
raw_up = (getattr(ep, "upstream_model_id", None) or "").strip()
|
|
453
|
+
target_m = raw_up if raw_up else (ep.model_id or clean_id)
|
|
454
|
+
return {
|
|
455
|
+
"url": url,
|
|
456
|
+
"base_url": base,
|
|
457
|
+
"api_key": key,
|
|
458
|
+
"auth_style": "bearer",
|
|
459
|
+
"target_model": target_m,
|
|
460
|
+
"provider_name": ep.provider_id or "custom",
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
for s_cls in SEPARATED_MODEL_CLASSES:
|
|
464
|
+
m_row = sdb.query(s_cls).filter(
|
|
465
|
+
s_cls.model_id.in_(cand_ids),
|
|
466
|
+
(s_cls.is_active == True) | (s_cls.is_active.is_(None)),
|
|
467
|
+
s_cls.endpoint_url.isnot(None),
|
|
468
|
+
s_cls.endpoint_url != ""
|
|
469
|
+
).first()
|
|
470
|
+
if m_row and getattr(m_row, "endpoint_url", None):
|
|
471
|
+
base = m_row.endpoint_url.rstrip("/")
|
|
472
|
+
url = base if base.endswith("/chat/completions") else f"{base}/chat/completions"
|
|
473
|
+
keys = m_row.get_api_keys() if hasattr(m_row, "get_api_keys") else []
|
|
474
|
+
key = keys[0] if keys else ""
|
|
475
|
+
raw_up = (getattr(m_row, "upstream_model_id", None) or "").strip()
|
|
476
|
+
target_m = raw_up if raw_up else (m_row.model_id or clean_id)
|
|
477
|
+
return {
|
|
478
|
+
"url": url,
|
|
479
|
+
"base_url": base,
|
|
480
|
+
"api_key": key,
|
|
481
|
+
"auth_style": "bearer",
|
|
482
|
+
"target_model": target_m,
|
|
483
|
+
"provider_name": getattr(m_row, "provider", "custom") or "custom",
|
|
484
|
+
}
|
|
485
|
+
finally:
|
|
486
|
+
sdb.close()
|
|
487
|
+
except Exception:
|
|
488
|
+
pass
|
|
489
|
+
|
|
410
490
|
# 2. Server ApiModel database (upstream provider attached in admin_models.html)
|
|
411
491
|
try:
|
|
412
492
|
from utim_cli.server.api_models_db import ApiSessionLocal, ApiModel, find_canonical_model
|
|
@@ -902,12 +982,15 @@ def sanitize_tools_for_api(tools: Optional[List[Dict[str, Any]]], model_id: Opti
|
|
|
902
982
|
if k in disallowed_keys:
|
|
903
983
|
continue
|
|
904
984
|
|
|
905
|
-
|
|
985
|
+
_valid = {"string", "number", "integer", "boolean", "array", "object", "null"}
|
|
906
986
|
if k == "type":
|
|
907
987
|
if isinstance(v, str):
|
|
908
|
-
|
|
988
|
+
_t = TYPE_MAP.get(v, v.lower())
|
|
989
|
+
v = _t if _t in _valid else "string"
|
|
909
990
|
elif isinstance(v, list):
|
|
910
|
-
|
|
991
|
+
_lst = [TYPE_MAP.get(item, item.lower()) if isinstance(item, str) else item for item in v]
|
|
992
|
+
_lst = [x for x in _lst if x in _valid]
|
|
993
|
+
v = _lst[0] if len(_lst)==1 else (_lst if _lst else "string")
|
|
911
994
|
|
|
912
995
|
if isinstance(v, dict):
|
|
913
996
|
cleaned[k] = _clean_schema_node(v)
|