utim-cli 2.5.4__tar.gz → 2.5.5__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.4 → utim_cli-2.5.5}/CHANGELOG.md +18 -0
- {utim_cli-2.5.4/utim_cli.egg-info → utim_cli-2.5.5}/PKG-INFO +1 -1
- {utim_cli-2.5.4 → utim_cli-2.5.5}/pyproject.toml +1 -1
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim/__init__.py +1 -1
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/__init__.py +1 -1
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/_version.py +1 -1
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/ask_helper.py +9 -8
- utim_cli-2.5.5/utim_cli/background_models.py +187 -0
- utim_cli-2.5.5/utim_cli/behavior_telemetry.py +207 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/brain.py +11 -11
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/client_utils.py +158 -7
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/constants.py +2 -12
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/context_pruner.py +53 -13
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/orchestrator.py +87 -1
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/orchestrator_llm_transport.py +66 -11
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/orchestrator_turn_history.py +117 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/reflection.py +24 -16
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/api_models_db.py +65 -3
- utim_cli-2.5.5/utim_cli/server/api_request_logs.py +109 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/auth.py +58 -184
- utim_cli-2.5.5/utim_cli/server/bot_detection.py +1032 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/captcha.py +11 -5
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/db.py +160 -1
- utim_cli-2.5.5/utim_cli/server/disposable_email_guard.py +329 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/firewall.py +12 -38
- utim_cli-2.5.5/utim_cli/server/hobby_throttler.py +250 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/key_rotator.py +168 -1
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/models.py +120 -44
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/plan_integrity.py +14 -7
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/platform_billing.py +30 -15
- utim_cli-2.5.5/utim_cli/server/provider_batcher.py +470 -0
- utim_cli-2.5.5/utim_cli/server/purge_disposable_users.py +174 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/rate_limit.py +11 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/request_queue.py +545 -574
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/router.py +180 -5
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/routes/__init__.py +4 -2
- utim_cli-2.5.5/utim_cli/server/routes/activity_routes.py +192 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/routes/admin_bonus_routes.py +70 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/routes/admin_providers_routes.py +695 -640
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/routes/admin_separated_models_routes.py +1254 -989
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/routes/api_gateway_extensions_routes.py +7 -7
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/routes/api_models_admin_routes.py +129 -7
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/routes/api_models_public_routes.py +611 -163
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/routes/auth_routes.py +79 -5
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/routes/completion_routes.py +536 -493
- utim_cli-2.5.5/utim_cli/server/routes/compression_routes.py +403 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/routes/credit_routes.py +71 -59
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/routes/discord_routes.py +204 -5
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/routes/marketplace_routes.py +18 -3
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/routes/provider_compat_routes.py +12 -8
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/routes/quota_routes.py +36 -144
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/routes/share_routes.py +7 -1
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/signup_guard.py +30 -4
- utim_cli-2.5.5/utim_cli/server/tier_tables.py +487 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/uptime_monitor.py +21 -1
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/tools.py +6046 -8710
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/tui/marketplace_dialog.py +5 -13
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/tui/model_dialog.py +1259 -732
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/utim.py +160 -28
- {utim_cli-2.5.4 → utim_cli-2.5.5/utim_cli.egg-info}/PKG-INFO +1 -1
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli.egg-info/SOURCES.txt +9 -2
- utim_cli-2.5.4/utim_cli/server/api_batcher.py +0 -442
- utim_cli-2.5.4/utim_cli/server/hobby_throttler.py +0 -495
- utim_cli-2.5.4/utim_cli/server/micro_batcher.py +0 -343
- utim_cli-2.5.4/utim_cli/server/routes/activity_routes.py +0 -150
- {utim_cli-2.5.4 → utim_cli-2.5.5}/LICENSE +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/MANIFEST.in +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/README.md +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/setup.cfg +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/setup.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/_chip_test.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/_verify_repro.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/_verify_syntax.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/_wizard_smoke_test.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/adaptation_engine.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/agent.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/architecture_indexer.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/auth.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/backup.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/billing.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/blender_agent.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/bootstrap.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/builtin/skills/utim-guide/SKILL.md +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/config.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/context_inspector.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/crawler/__init__.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/crawler/engine.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/crawler/markdown_distiller.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/dependency_manager.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/doctor.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/evidence_registry.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/harbor.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/knowledge_graph.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/local_db.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/logger.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/mcp_clean_wrapper.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/mcp_client.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/mcp_registry.json +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/models.txt +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/orchestrator_helpers.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/orchestrator_tool_exec.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/personalities.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/platform_installer.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/plugins/__init__.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/plugins/hooks.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/plugins/loader.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/plugins/manager.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/plugins/registry.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/plugins/runtime.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/plugins/schema.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/plugins/tool_loader.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/report.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/scrapy_search.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/sdk/__init__.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/sdk/config.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/sdk/context.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/sdk/events.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/sdk/handlers.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/sdk/session.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/sdk/sidecar.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/__init__.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/admin_auth.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/attribution.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/audit_log.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/batch_processor.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/cli_auth.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/concurrency.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/crypto_utils.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/docs_md/SECRET_PROVISIONING.md +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/docs_md/about.md +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/docs_md/changelog.md +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/docs_md/docs.md +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/docs_md/features.md +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/docs_md/license.md +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/docs_md/pricing.md +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/docs_md/privacy.md +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/docs_md/refund.md +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/docs_md/support.md +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/docs_md/terms.md +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/email_utils.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/exchange_rate.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/firebase.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/fix_duplicate_users.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/history.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/local_llm.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/logging_config.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/maintenance.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/model_agent.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/pricing_updater.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/provider_balance.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/provider_discovery.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/provider_router.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/provision_build.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/request_batcher.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/rewards_engine.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/routes/admin_db_routes.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/routes/api_key_routes.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/routes/billing_routes.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/routes/feedback_routes.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/routes/quota_share_routes.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/routes/referral_routes.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/routes/rewards_routes.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/routes/security_routes.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/routes/session_routes.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/routes/wallet_routes.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/routes/webhook_routes.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/security_alerts.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/server.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/storage_nodes.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/server/utils.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/share.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/share_tui.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/situational_scoring.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/state.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/subagent_manager.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/syntax_fix.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/task_dispatcher.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/task_logger.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/tools_grep.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/tui/__init__.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/tui/compression_dialog.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/tui/feedback_dialog.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/tui/history_dialog.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/tui/marketplace_app_state.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/tui/marketplace_layout_engine.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/tui/mcp_dialog.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/tui/miniagents_dialog.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/tui/personality_dialog.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/tui/plugins_dialog.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/tui/publish_dialog.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/tui/quota_dialog.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/tui/quota_redeem_dialog.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/tui/quota_share_dialog.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/tui/resume_dialog.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/tui/rewards_tui.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/tui/skills_dialog.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/tui/subagents_dialog.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/tui/tasks_dialog.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/tui/thinking_display.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/tui/tools_dialog.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/tui/update_dialog.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/tui/verbosity_dialog.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/update_checker.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/utilities.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/utimmodel.txt +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/vector_memory.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/voice.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/voice_coding.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/voice_events.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/voice_handler.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/voice_instant.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/voice_orchestrator.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/voice_session.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/voice_stt.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/voice_tts.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/wheel.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli/workspace.py +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli.egg-info/dependency_links.txt +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli.egg-info/entry_points.txt +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli.egg-info/requires.txt +0 -0
- {utim_cli-2.5.4 → utim_cli-2.5.5}/utim_cli.egg-info/top_level.txt +0 -0
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.5.5] - 2026-10-29
|
|
4
|
+
|
|
5
|
+
### 🐛 Fixes
|
|
6
|
+
- **Free Models No Longer Gated On The Free Plan**: Added `is_free_zero_cost_model()` as the single source of truth for $0.00 pricing. Any model priced 0.00 is now always usable on the Free plan, regardless of whether a priced copy of the same slug lives in `hobby_models` / `pro_models` or whether the static registry has an entry for it. Fixes `nvidia/nemotron-3.5-lightning:free` being reported as unavailable on the Free plan.
|
|
7
|
+
- **Registry Gate Bypass For Zero-Price Models**: The non-paid-plan registry check no longer rejects unregistered or `dynamic`-tagged models when their real price is 0.00.
|
|
8
|
+
- **Picker And Endpoint Now Agree**: The public catalog's free classifier resolves explicit free slugs (`:free`, `/free`, `-free`, `_free`, `free/` prefix) *before* consulting the separated tier-table override, so a priced duplicate can no longer hide a free model from the website or the `/model` picker.
|
|
9
|
+
- **Compression Pipeline Routed Through The Server**: New `POST /completions/compression` endpoint resolves the model the user is actually running plus the provider that actually serves it, eliminating the client-side `openrouter/free` fallback that kept returning empty content.
|
|
10
|
+
|
|
11
|
+
## [2.5.4] - 2026-10-24
|
|
12
|
+
|
|
13
|
+
### 🐛 Fixes
|
|
14
|
+
- **Streamed Output Now Always Displays**: Fixed responses being received by the server but never printed — pending code-block buffers are now force-flushed at end-of-stream on all streaming paths, so content ending inside an unclosed code fence is no longer silently dropped.
|
|
15
|
+
- **Adaptation Engine Over-Firing**: The truncation detector no longer re-fires continuation nudges on already-complete responses. False-positive heuristics (unclosed fence, mid-sentence) now fire at most once per task, while genuine token-limit continuations keep their full budget.
|
|
16
|
+
- **Fence Detection Hardening**: Unclosed code-block detection now only counts real markdown fences (line-start markers), not inline backtick references in prose.
|
|
17
|
+
|
|
18
|
+
### ✨ Improvements
|
|
19
|
+
- **Dynamic Provider Adaptation Engine**: Enhanced reliability of provider health tracking, stitching, and continuation logic.
|
|
20
|
+
|
|
3
21
|
## [2.5.3] - 2026-10-20
|
|
4
22
|
|
|
5
23
|
### ✨ Improvements & Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: utim-cli
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.5
|
|
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.5"
|
|
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
|
|
@@ -5,8 +5,8 @@ A dedicated, lightweight helper miniagent sharing the core UTIM LLM execution ar
|
|
|
5
5
|
Provides real-time project insight, chat explanation, and conversational companionship
|
|
6
6
|
without interrupting the main agent's active execution loop.
|
|
7
7
|
|
|
8
|
-
Uses
|
|
9
|
-
|
|
8
|
+
Uses a spare model from the user's current plan (chosen dynamically, never the main
|
|
9
|
+
agent's model and never a hardcoded slug).
|
|
10
10
|
Has access to exactly one tool: 'read_chat'.
|
|
11
11
|
"""
|
|
12
12
|
|
|
@@ -26,12 +26,10 @@ from rich import box
|
|
|
26
26
|
|
|
27
27
|
# ── Sidecar Helper Model ───────────────────────────────────────────
|
|
28
28
|
|
|
29
|
-
PRIMARY_HELPER_MODEL = "nvidia/nemotron-3.5-lightning:free"
|
|
30
|
-
|
|
31
|
-
|
|
32
29
|
def get_free_models_fallback_chain() -> List[str]:
|
|
33
|
-
"""
|
|
34
|
-
|
|
30
|
+
"""Spare plan models for the sidecar helper (dynamic; main agent's model excluded)."""
|
|
31
|
+
from utim_cli.background_models import get_background_models
|
|
32
|
+
return get_background_models(limit=3)
|
|
35
33
|
|
|
36
34
|
|
|
37
35
|
|
|
@@ -340,7 +338,10 @@ def execute_ask_helper(
|
|
|
340
338
|
if not console:
|
|
341
339
|
console = Console(highlight=False, markup=True)
|
|
342
340
|
|
|
343
|
-
|
|
341
|
+
from utim_cli.background_models import get_background_model
|
|
342
|
+
model = get_background_model()
|
|
343
|
+
if not model:
|
|
344
|
+
return "No spare model is available on your plan for the sidecar helper (the main agent's model is never shared)."
|
|
344
345
|
|
|
345
346
|
# Pre-extract live session chat history and context
|
|
346
347
|
chat_summary = read_chat_history(orchestrator, max_turns=12)
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""Dynamic, plan-aware model selection for background (non-main-agent) work.
|
|
2
|
+
|
|
3
|
+
No model slugs are hardcoded here. Background tasks (compression, reflection,
|
|
4
|
+
sidecar helper, decision/judge calls, miniagents, ...) pick from the models the
|
|
5
|
+
server says the *current plan* may use, and they NEVER pick a model that the main
|
|
6
|
+
agent is occupying. If the plan has no spare model, an empty list is returned and
|
|
7
|
+
callers must degrade gracefully (local heuristics / skip) instead of stealing the
|
|
8
|
+
main agent's model.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import threading
|
|
14
|
+
import time
|
|
15
|
+
from typing import Iterable, List, Optional
|
|
16
|
+
|
|
17
|
+
_CACHE_TTL = 300.0
|
|
18
|
+
_lock = threading.Lock()
|
|
19
|
+
_cache: dict = {"ts": 0.0, "tier": None, "models": []}
|
|
20
|
+
|
|
21
|
+
_NON_TEXT_KEYWORDS = (
|
|
22
|
+
"lyria", "music", "audio", "whisper", "tts", "elevenlabs", "suno", "udio",
|
|
23
|
+
"video", "bge-m3", "rerank", "embed", "image", "krea",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _current_plan() -> str:
|
|
28
|
+
try:
|
|
29
|
+
from utim_cli.config import config
|
|
30
|
+
return str(config.get("user_plan") or "free").lower().strip() or "free"
|
|
31
|
+
except Exception:
|
|
32
|
+
return "free"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _is_text_model(m: dict) -> bool:
|
|
36
|
+
mid = str(m.get("model_id") or m.get("id") or "").lower()
|
|
37
|
+
if not mid or any(k in mid for k in _NON_TEXT_KEYWORDS):
|
|
38
|
+
return False
|
|
39
|
+
caps = [str(c).lower() for c in (m.get("capabilities") or [])]
|
|
40
|
+
if m.get("is_image_gen") or "image_generation" in caps or "imagegen" in caps:
|
|
41
|
+
return False
|
|
42
|
+
return True
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _cost(m: dict) -> float:
|
|
46
|
+
try:
|
|
47
|
+
return float(m.get("cost_input_per_1m") or 0.0) + float(m.get("cost_output_per_1m") or 0.0)
|
|
48
|
+
except Exception:
|
|
49
|
+
return 0.0
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_plan_models(force: bool = False) -> List[str]:
|
|
53
|
+
"""Text models available to the current plan, cheapest first (server-driven)."""
|
|
54
|
+
plan = _current_plan()
|
|
55
|
+
now = time.monotonic()
|
|
56
|
+
with _lock:
|
|
57
|
+
if (not force and _cache["tier"] == plan and _cache["models"]
|
|
58
|
+
and now - _cache["ts"] < _CACHE_TTL):
|
|
59
|
+
return list(_cache["models"])
|
|
60
|
+
|
|
61
|
+
rows: list = []
|
|
62
|
+
try:
|
|
63
|
+
from utim_cli.client_utils import fetch_remote_models_catalog
|
|
64
|
+
cat = fetch_remote_models_catalog() or {}
|
|
65
|
+
rows = list(cat.get("main_agent") or cat.get("all_text") or [])
|
|
66
|
+
except Exception:
|
|
67
|
+
rows = []
|
|
68
|
+
|
|
69
|
+
rows = [m for m in rows if isinstance(m, dict) and _is_text_model(m)]
|
|
70
|
+
rows.sort(key=_cost)
|
|
71
|
+
ids: List[str] = []
|
|
72
|
+
for m in rows:
|
|
73
|
+
mid = str(m.get("model_id") or m.get("id") or "").strip()
|
|
74
|
+
if mid and mid not in ids:
|
|
75
|
+
ids.append(mid)
|
|
76
|
+
|
|
77
|
+
with _lock:
|
|
78
|
+
if ids:
|
|
79
|
+
_cache.update(ts=now, tier=plan, models=ids)
|
|
80
|
+
elif _cache["tier"] == plan:
|
|
81
|
+
# Network failure: keep serving the last known plan list.
|
|
82
|
+
return list(_cache["models"])
|
|
83
|
+
return ids
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def occupied_main_models(extra: Iterable[Optional[str]] = ()) -> set:
|
|
87
|
+
"""Model ids the main agent is using right now (never used for background)."""
|
|
88
|
+
used = {str(x).strip() for x in extra if x}
|
|
89
|
+
try:
|
|
90
|
+
from utim_cli.config import config
|
|
91
|
+
mm = config.get("main_model")
|
|
92
|
+
if mm:
|
|
93
|
+
used.add(str(mm).strip())
|
|
94
|
+
except Exception:
|
|
95
|
+
pass
|
|
96
|
+
try:
|
|
97
|
+
from utim_cli import tools as _tools
|
|
98
|
+
mid = getattr(_tools, "_active_model_id", "") or ""
|
|
99
|
+
if mid:
|
|
100
|
+
used.add(str(mid).strip())
|
|
101
|
+
except Exception:
|
|
102
|
+
pass
|
|
103
|
+
return used
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def get_background_models(main_model: Optional[str] = None, limit: int = 3) -> List[str]:
|
|
107
|
+
"""Ordered background-capable models for the current plan, excluding main agent's."""
|
|
108
|
+
busy = occupied_main_models([main_model])
|
|
109
|
+
out = [m for m in get_plan_models() if m not in busy]
|
|
110
|
+
return out[:limit] if limit else out
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def get_background_model(main_model: Optional[str] = None) -> Optional[str]:
|
|
114
|
+
"""Single best background model, or None when the plan has no spare model."""
|
|
115
|
+
models = get_background_models(main_model, limit=1)
|
|
116
|
+
return models[0] if models else None
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
_gated: dict = {} # model_id -> expiry (monotonic)
|
|
120
|
+
_GATED_TTL = 600.0
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def mark_gated(model_id: Optional[str]) -> None:
|
|
124
|
+
"""Remember a model the server refused so it is never chosen as a fallback again."""
|
|
125
|
+
if model_id:
|
|
126
|
+
_gated[str(model_id).strip()] = time.monotonic() + _GATED_TTL
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _gated_now() -> set:
|
|
130
|
+
now = time.monotonic()
|
|
131
|
+
for k in [k for k, v in _gated.items() if v < now]:
|
|
132
|
+
_gated.pop(k, None)
|
|
133
|
+
return set(_gated)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _tier_catalog_rows() -> list:
|
|
137
|
+
"""Plan-filtered catalog (server applies tier gating) — not the unfiltered /models list."""
|
|
138
|
+
try:
|
|
139
|
+
import requests
|
|
140
|
+
from utim_cli.auth import SERVER_URL, get_auth_headers
|
|
141
|
+
resp = requests.get(
|
|
142
|
+
f"{SERVER_URL.rstrip('/')}/models/catalog",
|
|
143
|
+
params={"tier": _current_plan()},
|
|
144
|
+
headers=get_auth_headers(),
|
|
145
|
+
timeout=4.0,
|
|
146
|
+
)
|
|
147
|
+
if resp.status_code == 200:
|
|
148
|
+
data = resp.json()
|
|
149
|
+
if isinstance(data, dict):
|
|
150
|
+
return list(data.get("main_agent") or data.get("all_text") or [])
|
|
151
|
+
except Exception:
|
|
152
|
+
pass
|
|
153
|
+
return []
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def get_plan_fallback_model(exclude: Iterable[Optional[str]] = ()) -> Optional[str]:
|
|
157
|
+
"""Model to auto-switch the MAIN agent to when its current model is gated.
|
|
158
|
+
|
|
159
|
+
Dynamic and loop-safe: marks every excluded model as gated, then picks a free
|
|
160
|
+
model from the server's plan-filtered catalog that has not been gated before.
|
|
161
|
+
Returns None when nothing is left so the caller stops instead of ping-ponging.
|
|
162
|
+
"""
|
|
163
|
+
for x in exclude:
|
|
164
|
+
mark_gated(x)
|
|
165
|
+
skip = _gated_now()
|
|
166
|
+
|
|
167
|
+
rows = [m for m in _tier_catalog_rows() if isinstance(m, dict) and _is_text_model(m)]
|
|
168
|
+
if not rows:
|
|
169
|
+
try:
|
|
170
|
+
from utim_cli.client_utils import fetch_remote_models_catalog
|
|
171
|
+
cat = fetch_remote_models_catalog() or {}
|
|
172
|
+
rows = [m for m in (cat.get("main_agent") or cat.get("all_text") or [])
|
|
173
|
+
if isinstance(m, dict) and _is_text_model(m)]
|
|
174
|
+
except Exception:
|
|
175
|
+
rows = []
|
|
176
|
+
rows.sort(key=_cost)
|
|
177
|
+
|
|
178
|
+
def _id(m):
|
|
179
|
+
return str(m.get("model_id") or m.get("id") or "").strip()
|
|
180
|
+
|
|
181
|
+
def _free(m):
|
|
182
|
+
return bool(m.get("is_free") or str(m.get("tier", "")).lower() == "free" or _cost(m) == 0.0)
|
|
183
|
+
|
|
184
|
+
for m in rows:
|
|
185
|
+
if _id(m) and _id(m) not in skip and _free(m):
|
|
186
|
+
return _id(m)
|
|
187
|
+
return None
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""
|
|
2
|
+
utim_cli/behavior_telemetry.py
|
|
3
|
+
==============================
|
|
4
|
+
Client-side interaction telemetry for the server's bot detector.
|
|
5
|
+
|
|
6
|
+
Why this exists
|
|
7
|
+
---------------
|
|
8
|
+
The server can observe *what* an account sends but not *how* it was typed.
|
|
9
|
+
Typing cadence and error-correction behaviour are strong, cheap human signals:
|
|
10
|
+
a script producing pristine 40-char-per-second input is trivially separable
|
|
11
|
+
from a person who types ~6 CPS and backspaces twice a sentence.
|
|
12
|
+
|
|
13
|
+
Design constraints
|
|
14
|
+
------------------
|
|
15
|
+
1. **Privacy-first.** We never record prompt content, keystrokes themselves,
|
|
16
|
+
clipboard data, or anything identifying. Only *aggregate counters* over a
|
|
17
|
+
rolling window: characters typed, seconds spent typing, total keystrokes,
|
|
18
|
+
backspace count, paste events. No text ever leaves the machine.
|
|
19
|
+
2. **Zero latency.** Counting is a few integer increments on the prompt_toolkit
|
|
20
|
+
input path. Nothing blocks, nothing is hashed, no I/O.
|
|
21
|
+
3. **Advisory only.** The server treats this as corroborating evidence that may
|
|
22
|
+
raise suspicion but never lower it, and never convict on its own. A bug here
|
|
23
|
+
cannot get a legitimate user throttled.
|
|
24
|
+
4. **Cheap to disable.** Set ``UTIM_TELEMETRY=0`` to stop sending entirely; the
|
|
25
|
+
server renormalises onto its own server-side signals.
|
|
26
|
+
|
|
27
|
+
Window semantics
|
|
28
|
+
----------------
|
|
29
|
+
Counters accumulate over a sliding window (default 600s). After each request we
|
|
30
|
+
emit the current window snapshot and reset the *typing* counters, so each
|
|
31
|
+
request describes the effort that went into producing that specific prompt.
|
|
32
|
+
"""
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import json
|
|
36
|
+
import os
|
|
37
|
+
import threading
|
|
38
|
+
import time
|
|
39
|
+
from typing import Any, Dict, Optional
|
|
40
|
+
|
|
41
|
+
# Rolling window for accumulation. Long enough to cover composing a long prompt,
|
|
42
|
+
# short enough that an idle tab does not dilute the rate to zero.
|
|
43
|
+
_WINDOW_SECONDS = 600.0
|
|
44
|
+
|
|
45
|
+
_lock = threading.Lock()
|
|
46
|
+
|
|
47
|
+
_state: Dict[str, Any] = {
|
|
48
|
+
"window_start": 0.0,
|
|
49
|
+
"typing_chars": 0,
|
|
50
|
+
"typing_seconds": 0.0,
|
|
51
|
+
"keystrokes": 0,
|
|
52
|
+
"corrections": 0,
|
|
53
|
+
"paste_events": 0,
|
|
54
|
+
"session_start": 0.0,
|
|
55
|
+
"last_input_at": 0.0,
|
|
56
|
+
"idle_seconds": 0.0,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# Idle gaps longer than this are treated as "away from keyboard" and are not
|
|
60
|
+
# counted as typing time (prevents a coffee break from tanking the CPS rate).
|
|
61
|
+
_IDLE_GAP = 8.0
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def is_enabled() -> bool:
|
|
65
|
+
return os.environ.get("UTIM_TELEMETRY", "1") not in ("0", "false", "False", "no")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _now() -> float:
|
|
69
|
+
return time.monotonic()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _ensure_window(now: float) -> None:
|
|
73
|
+
"""Reset accumulators when the rolling window has elapsed."""
|
|
74
|
+
start = _state["window_start"]
|
|
75
|
+
if start <= 0.0 or (now - start) > _WINDOW_SECONDS:
|
|
76
|
+
_state["window_start"] = now
|
|
77
|
+
_state["typing_chars"] = 0
|
|
78
|
+
_state["typing_seconds"] = 0.0
|
|
79
|
+
_state["keystrokes"] = 0
|
|
80
|
+
_state["corrections"] = 0
|
|
81
|
+
_state["paste_events"] = 0
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _start_session() -> None:
|
|
85
|
+
if _state["session_start"] <= 0.0:
|
|
86
|
+
_state["session_start"] = _now()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def record_keystroke(chars: int = 1) -> None:
|
|
90
|
+
"""Record printable input. Call from the prompt input handler.
|
|
91
|
+
|
|
92
|
+
Kept deliberately trivial: this runs on every keystroke, so it must not
|
|
93
|
+
allocate, hash, or touch the filesystem.
|
|
94
|
+
"""
|
|
95
|
+
if not is_enabled():
|
|
96
|
+
return
|
|
97
|
+
now = _now()
|
|
98
|
+
with _lock:
|
|
99
|
+
_start_session()
|
|
100
|
+
_ensure_window(now)
|
|
101
|
+
|
|
102
|
+
last = _state["last_input_at"]
|
|
103
|
+
if last > 0.0:
|
|
104
|
+
gap = now - last
|
|
105
|
+
if gap <= _IDLE_GAP:
|
|
106
|
+
_state["typing_seconds"] += gap
|
|
107
|
+
else:
|
|
108
|
+
_state["idle_seconds"] += gap
|
|
109
|
+
_state["last_input_at"] = now
|
|
110
|
+
|
|
111
|
+
if chars > 0:
|
|
112
|
+
_state["typing_chars"] += chars
|
|
113
|
+
_state["keystrokes"] += chars
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def record_correction() -> None:
|
|
117
|
+
"""Record a backspace / delete. Humans correct; scripts rarely do."""
|
|
118
|
+
if not is_enabled():
|
|
119
|
+
return
|
|
120
|
+
now = _now()
|
|
121
|
+
with _lock:
|
|
122
|
+
_start_session()
|
|
123
|
+
_ensure_window(now)
|
|
124
|
+
_state["corrections"] += 1
|
|
125
|
+
_state["keystrokes"] += 1
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def record_paste(chars: int = 0) -> None:
|
|
129
|
+
"""Record a paste event (bulk input is machine-assisted, not typed)."""
|
|
130
|
+
if not is_enabled():
|
|
131
|
+
return
|
|
132
|
+
now = _now()
|
|
133
|
+
with _lock:
|
|
134
|
+
_start_session()
|
|
135
|
+
_ensure_window(now)
|
|
136
|
+
_state["paste_events"] += 1
|
|
137
|
+
# Pasted characters are NOT counted as typing_chars — that is the whole
|
|
138
|
+
# point: they inflate volume without demonstrating human typing effort.
|
|
139
|
+
_state["keystrokes"] += max(0, chars)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def snapshot(reset: bool = True) -> Optional[Dict[str, Any]]:
|
|
143
|
+
"""Return the current window snapshot for transmission.
|
|
144
|
+
|
|
145
|
+
Returns None when telemetry is disabled or there is nothing meaningful to
|
|
146
|
+
report (so we do not send empty headers on every request).
|
|
147
|
+
"""
|
|
148
|
+
if not is_enabled():
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
now = _now()
|
|
152
|
+
with _lock:
|
|
153
|
+
_ensure_window(now)
|
|
154
|
+
|
|
155
|
+
typing_chars = _state["typing_chars"]
|
|
156
|
+
typing_seconds = _state["typing_seconds"]
|
|
157
|
+
keystrokes = _state["keystrokes"]
|
|
158
|
+
corrections = _state["corrections"]
|
|
159
|
+
paste_events = _state["paste_events"]
|
|
160
|
+
session_start = _state["session_start"]
|
|
161
|
+
idle_seconds = _state["idle_seconds"]
|
|
162
|
+
|
|
163
|
+
if reset:
|
|
164
|
+
_state["typing_chars"] = 0
|
|
165
|
+
_state["typing_seconds"] = 0.0
|
|
166
|
+
_state["keystrokes"] = 0
|
|
167
|
+
_state["corrections"] = 0
|
|
168
|
+
_state["paste_events"] = 0
|
|
169
|
+
_state["window_start"] = now
|
|
170
|
+
|
|
171
|
+
# Nothing worth reporting — avoid sending a misleading all-zero header,
|
|
172
|
+
# which the server would score as "pristine input".
|
|
173
|
+
if keystrokes <= 0 and typing_chars <= 0 and paste_events <= 0:
|
|
174
|
+
return None
|
|
175
|
+
|
|
176
|
+
session_seconds = (now - session_start) if session_start > 0.0 else 0.0
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
"typing_chars": int(typing_chars),
|
|
180
|
+
"typing_seconds": round(float(typing_seconds), 2),
|
|
181
|
+
"keystrokes": int(keystrokes),
|
|
182
|
+
"corrections": int(corrections),
|
|
183
|
+
"paste_events": int(paste_events),
|
|
184
|
+
"session_seconds": round(float(session_seconds), 2),
|
|
185
|
+
"idle_seconds": round(float(idle_seconds), 2),
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def telemetry_header_value() -> Optional[str]:
|
|
190
|
+
"""Compact JSON header value, or None when disabled/empty."""
|
|
191
|
+
snap = snapshot(reset=True)
|
|
192
|
+
if snap is None:
|
|
193
|
+
return None
|
|
194
|
+
try:
|
|
195
|
+
return json.dumps(snap, separators=(",", ":"))
|
|
196
|
+
except Exception:
|
|
197
|
+
return None
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def reset() -> None:
|
|
201
|
+
"""Clear all counters (used by tests)."""
|
|
202
|
+
with _lock:
|
|
203
|
+
for k in ("typing_chars", "keystrokes", "corrections", "paste_events"):
|
|
204
|
+
_state[k] = 0
|
|
205
|
+
for k in ("typing_seconds", "idle_seconds", "window_start",
|
|
206
|
+
"session_start", "last_input_at"):
|
|
207
|
+
_state[k] = 0.0
|
|
@@ -81,13 +81,14 @@ BRAIN_TOP_K_FORWARD = 12 # MiniLM candidates from forward pass
|
|
|
81
81
|
BRAIN_TOP_K_REVERSE = 6 # MiniLM candidates per Qwen sub-query
|
|
82
82
|
BRAIN_FINAL_LIMIT = 6 # max memories injected into system prompt
|
|
83
83
|
BRAIN_RELEVANCE_FLOOR = 0.30 # cosine similarity floor for injection
|
|
84
|
-
QWEN_BRAIN_MODEL = "qwen/qwen2.5-1.5b-instruct" # fast reasoning model on server
|
|
85
84
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
def _background_models():
|
|
86
|
+
"""Dynamic, plan-aware background models (never hardcoded, never the main agent's)."""
|
|
87
|
+
try:
|
|
88
|
+
from utim_cli.background_models import get_background_models
|
|
89
|
+
return get_background_models(limit=3)
|
|
90
|
+
except Exception:
|
|
91
|
+
return []
|
|
91
92
|
|
|
92
93
|
# ── Paths ──────────────────────────────────────────────────────────────────────
|
|
93
94
|
|
|
@@ -253,7 +254,7 @@ def _call_qwen_brain(prompt: str, system: str = "", max_tokens: int = 400) -> st
|
|
|
253
254
|
import requests as _req
|
|
254
255
|
_system = system or "You are a precise AI assistant. Be concise and accurate."
|
|
255
256
|
|
|
256
|
-
for model in
|
|
257
|
+
for model in _background_models():
|
|
257
258
|
try:
|
|
258
259
|
resp = _req.post(
|
|
259
260
|
"https://openrouter.ai/api/v1/chat/completions",
|
|
@@ -933,10 +934,9 @@ def _call_decision_model(
|
|
|
933
934
|
if not prompt:
|
|
934
935
|
return ""
|
|
935
936
|
|
|
936
|
-
candidates = list(models) if models else
|
|
937
|
-
|
|
938
|
-
"
|
|
939
|
-
]
|
|
937
|
+
candidates = list(models) if models else _background_models()
|
|
938
|
+
if not candidates:
|
|
939
|
+
return ""
|
|
940
940
|
|
|
941
941
|
# 1) Prefer the in-process proxy (session reuse / rate-limit headroom).
|
|
942
942
|
try:
|