utim-cli 2.3.25__tar.gz → 2.3.26__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.3.25 → utim_cli-2.3.26}/CHANGELOG.md +7 -0
- {utim_cli-2.3.25/utim_cli.egg-info → utim_cli-2.3.26}/PKG-INFO +1 -1
- {utim_cli-2.3.25 → utim_cli-2.3.26}/pyproject.toml +1 -1
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim/__init__.py +1 -1
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/__init__.py +1 -1
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/_version.py +1 -1
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/constants.py +12 -1
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/orchestrator.py +33 -29
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/orchestrator_helpers.py +12 -12
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/orchestrator_llm_transport.py +39 -42
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/orchestrator_tool_exec.py +17 -5
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/key_rotator.py +103 -80
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/models.py +88 -2
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/router.py +131 -137
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/routes/completion_routes.py +588 -297
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/tools.py +90 -10
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/utim.py +3 -1
- {utim_cli-2.3.25 → utim_cli-2.3.26/utim_cli.egg-info}/PKG-INFO +1 -1
- {utim_cli-2.3.25 → utim_cli-2.3.26}/LICENSE +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/MANIFEST.in +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/README.md +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/setup.cfg +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/setup.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/_chip_test.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/_verify_repro.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/_verify_syntax.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/_wizard_smoke_test.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/agent.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/ask_helper.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/auth.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/backup.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/billing.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/blender_agent.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/bootstrap.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/brain.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/builtin/skills/utim-guide/SKILL.md +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/client_utils.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/config.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/context_pruner.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/crawler/__init__.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/crawler/engine.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/crawler/markdown_distiller.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/dependency_manager.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/doctor.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/evidence_registry.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/harbor.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/knowledge_graph.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/local_db.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/logger.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/mcp_clean_wrapper.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/mcp_client.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/mcp_registry.json +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/models.txt +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/orchestrator_turn_history.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/personalities.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/platform_installer.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/plugins/__init__.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/plugins/hooks.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/plugins/loader.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/plugins/manager.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/plugins/registry.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/plugins/runtime.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/plugins/schema.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/plugins/tool_loader.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/reflection.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/report.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/scrapy_search.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/sdk/__init__.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/sdk/config.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/sdk/context.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/sdk/events.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/sdk/handlers.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/sdk/session.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/sdk/sidecar.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/__init__.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/admin_auth.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/attribution.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/audit_log.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/auth.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/batch_processor.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/captcha.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/cli_auth.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/concurrency.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/db.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/docs_md/SECRET_PROVISIONING.md +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/docs_md/about.md +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/docs_md/changelog.md +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/docs_md/docs.md +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/docs_md/features.md +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/docs_md/license.md +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/docs_md/pricing.md +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/docs_md/privacy.md +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/docs_md/refund.md +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/docs_md/support.md +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/docs_md/terms.md +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/email_utils.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/exchange_rate.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/firebase.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/fix_duplicate_users.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/history.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/local_llm.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/logging_config.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/micro_batcher.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/model_agent.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/plan_integrity.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/pricing_updater.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/provision_build.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/rate_limit.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/rewards_engine.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/routes/__init__.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/routes/admin_bonus_routes.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/routes/admin_db_routes.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/routes/auth_routes.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/routes/billing_routes.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/routes/credit_routes.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/routes/feedback_routes.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/routes/marketplace_routes.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/routes/quota_routes.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/routes/quota_share_routes.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/routes/referral_routes.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/routes/rewards_routes.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/routes/security_routes.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/routes/session_routes.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/routes/share_routes.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/routes/webhook_routes.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/security_alerts.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/server.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/storage_nodes.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/server/utils.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/share.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/share_tui.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/situational_scoring.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/state.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/subagent_manager.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/task_dispatcher.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/task_logger.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/tools_grep.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/tui/__init__.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/tui/compression_dialog.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/tui/feedback_dialog.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/tui/history_dialog.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/tui/marketplace_app_state.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/tui/marketplace_dialog.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/tui/marketplace_layout_engine.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/tui/mcp_dialog.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/tui/miniagents_dialog.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/tui/model_dialog.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/tui/personality_dialog.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/tui/plugins_dialog.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/tui/publish_dialog.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/tui/quota_dialog.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/tui/quota_redeem_dialog.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/tui/quota_share_dialog.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/tui/resume_dialog.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/tui/rewards_tui.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/tui/skills_dialog.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/tui/subagents_dialog.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/tui/tasks_dialog.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/tui/thinking_display.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/tui/tools_dialog.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/tui/update_dialog.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/update_checker.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/utilities.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/utimmodel.txt +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/vector_memory.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/wheel.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli/workspace.py +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli.egg-info/SOURCES.txt +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli.egg-info/dependency_links.txt +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli.egg-info/entry_points.txt +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli.egg-info/requires.txt +0 -0
- {utim_cli-2.3.25 → utim_cli-2.3.26}/utim_cli.egg-info/top_level.txt +0 -0
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.3.26] - 2026-09-08
|
|
4
|
+
|
|
5
|
+
### 🛡️ Zero Rate-Limit Failover & Low-Latency Key Rotation
|
|
6
|
+
- **Deterministic APINEX Failover Chain**: Upstream APINEX failures (429/402/exhaustion) seamlessly cascade through `openrouter/free` -> `upstage/solar-pro4` -> `qwen/qwen3.7-flash` -> `deepseek/deepseek-v4-flash-0731` with zero wait latency and direct key pool acquisition.
|
|
7
|
+
- **Silent Rate-Limit Recovery**: Completely eliminated user-facing rate-limit interruptions, warnings, and 24-hour key lockout bans on transient provider 429s.
|
|
8
|
+
- **Zero-Latency Paid 402/429 Key Rotation**: OpenRouter keys returning 402 or 429 immediately rotate across candidate keys and seamlessly fail over to backup chain models without aborting active turns.
|
|
9
|
+
|
|
3
10
|
## [2.3.25] - 2026-09-07
|
|
4
11
|
|
|
5
12
|
### 🛠️ Bug Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: utim-cli
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.26
|
|
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.3.
|
|
17
|
+
__version__ = "2.3.26"
|
|
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
|
|
@@ -11,7 +11,17 @@ SUBAGENT_DEFAULTS = {
|
|
|
11
11
|
"analyze_image": "google/gemma-4-31b-it:free",
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
ORCAROUTER_MODEL_IDS = {
|
|
15
|
+
"orcarouter/free",
|
|
16
|
+
"orcarouter/fusion",
|
|
17
|
+
"orcarouter/fusion-flash",
|
|
18
|
+
"orcarouter/fusion-mini",
|
|
19
|
+
"anthropic/claude-sonnet-5",
|
|
20
|
+
"anthropic/claude-opus-5",
|
|
21
|
+
"deepseek/deepseek-v4-flash-0731",
|
|
22
|
+
"deepseek/deepseek-v4-flash-free",
|
|
23
|
+
"deepseek/deepseek-v4-pro-0813",
|
|
24
|
+
"google/gemini-2.5-flash",
|
|
15
25
|
"claude/opus-5",
|
|
16
26
|
"claude/sonnet-5",
|
|
17
27
|
"deepseek/v4-flash-0731",
|
|
@@ -33,6 +43,7 @@ APINEX_MODEL_IDS = {
|
|
|
33
43
|
"qwen/3.8-max",
|
|
34
44
|
"free/qwen-3.8-max",
|
|
35
45
|
}
|
|
46
|
+
APINEX_MODEL_IDS = ORCAROUTER_MODEL_IDS
|
|
36
47
|
|
|
37
48
|
|
|
38
49
|
import sys
|
|
@@ -854,24 +854,6 @@ class Orchestrator(LLMTransportMixin, TurnHistoryMixin, ToolExecutionMixin):
|
|
|
854
854
|
|
|
855
855
|
break # success
|
|
856
856
|
except _ServerUnavailableError as exc:
|
|
857
|
-
_err_text = str(exc).lower()
|
|
858
|
-
if "rate limit" in _err_text or "rate-limit" in _err_text or "429" in _err_text or "too many requests" in _err_text:
|
|
859
|
-
# Mark the current API key as exhausted for 24 hours
|
|
860
|
-
from utim_cli.config import config
|
|
861
|
-
current_key = config.get("api_key", "")
|
|
862
|
-
if current_key:
|
|
863
|
-
config.mark_api_key_exhausted(current_key, duration_hours=24)
|
|
864
|
-
self.console.print(f"\n[bold yellow]⏳ Rate-limited on API key {current_key[:12]}... — excluded for 24 hours.[/bold yellow]")
|
|
865
|
-
else:
|
|
866
|
-
self.console.print(f"\n[bold yellow]⏳ Model is temporarily rate-limited by the provider.[/bold yellow]")
|
|
867
|
-
self.console.print(" [dim]Please wait a moment before retrying, or switch models using [bold]/model[/bold] (Ctrl+M).[/dim]\n")
|
|
868
|
-
if len(self.messages) <= turn_msg_start + 1:
|
|
869
|
-
self.messages.append({
|
|
870
|
-
"role": "assistant",
|
|
871
|
-
"content": "⏳ **Rate Limited**: The API key has been temporarily exhausted (24h cooldown). Please wait and retry, or use `/model` to switch models."
|
|
872
|
-
})
|
|
873
|
-
self._persist_messages()
|
|
874
|
-
return
|
|
875
857
|
if self.cancel_event.is_set():
|
|
876
858
|
del self.messages[turn_msg_start + 1:]
|
|
877
859
|
self._persist_messages()
|
|
@@ -918,17 +900,6 @@ class Orchestrator(LLMTransportMixin, TurnHistoryMixin, ToolExecutionMixin):
|
|
|
918
900
|
self.messages.append({"role": "assistant", "content": "[Aborted by user]"})
|
|
919
901
|
break
|
|
920
902
|
except Exception as exc:
|
|
921
|
-
_err_text = str(exc).lower()
|
|
922
|
-
if "rate limit" in _err_text or "rate-limit" in _err_text or "429" in _err_text or "too many requests" in _err_text:
|
|
923
|
-
self.console.print(f"\n[bold yellow]⏳ Model is temporarily rate-limited by the provider.[/bold yellow]")
|
|
924
|
-
self.console.print(" [dim]Please wait a moment before retrying, or switch models using [bold]/model[/bold] (Ctrl+M).[/dim]\n")
|
|
925
|
-
if len(self.messages) <= turn_msg_start + 1:
|
|
926
|
-
self.messages.append({
|
|
927
|
-
"role": "assistant",
|
|
928
|
-
"content": "⏳ **Model Rate Limited**: The model is temporarily rate-limited by the provider. Please wait a short moment and retry, or use `/model` to switch models."
|
|
929
|
-
})
|
|
930
|
-
self._persist_messages()
|
|
931
|
-
return
|
|
932
903
|
if self.cancel_event.is_set():
|
|
933
904
|
if len(self.messages) <= turn_msg_start + 1:
|
|
934
905
|
self.messages.append({"role": "assistant", "content": "[Aborted by user]"})
|
|
@@ -1234,6 +1205,39 @@ class Orchestrator(LLMTransportMixin, TurnHistoryMixin, ToolExecutionMixin):
|
|
|
1234
1205
|
# Got a valid response — reset streak counter
|
|
1235
1206
|
_empty_response_streak = 0
|
|
1236
1207
|
|
|
1208
|
+
# ── Nudge if model mistakenly claims it lacks web_search ──────────
|
|
1209
|
+
_no_web_search_claims = [
|
|
1210
|
+
"don't have a web_search",
|
|
1211
|
+
"do not have a web_search",
|
|
1212
|
+
"don't have a web search",
|
|
1213
|
+
"do not have a web search",
|
|
1214
|
+
"no web search tool",
|
|
1215
|
+
"don't have access to a web search",
|
|
1216
|
+
"do not have access to a web search",
|
|
1217
|
+
"cannot search the web",
|
|
1218
|
+
"unable to search the web",
|
|
1219
|
+
"don't have internet access",
|
|
1220
|
+
"do not have internet access",
|
|
1221
|
+
"cannot access the internet",
|
|
1222
|
+
"cannot browse the web",
|
|
1223
|
+
"don't have browsing",
|
|
1224
|
+
]
|
|
1225
|
+
if (
|
|
1226
|
+
not tool_calls
|
|
1227
|
+
and iteration < max_iterations - 1
|
|
1228
|
+
and any(claim in _lower_content for claim in _no_web_search_claims)
|
|
1229
|
+
and "web_search" not in (config.get("disabled_tools") or [])
|
|
1230
|
+
):
|
|
1231
|
+
self.messages.append({"role": "assistant", "content": content})
|
|
1232
|
+
self.messages.append({
|
|
1233
|
+
"role": "user",
|
|
1234
|
+
"content": (
|
|
1235
|
+
"SYSTEM REMINDER: You DO have the 'web_search' tool in your active tool registry. "
|
|
1236
|
+
"Call the web_search tool directly now using web_search(prompt=...) to perform the requested web search."
|
|
1237
|
+
)
|
|
1238
|
+
})
|
|
1239
|
+
continue
|
|
1240
|
+
|
|
1237
1241
|
# ── Universal Required Deliverables Checklist ──────────────────────────────
|
|
1238
1242
|
if getattr(self, "_deliverable_check_attempts", 0) < 2:
|
|
1239
1243
|
_deliverable_matches = re.findall(
|
|
@@ -350,11 +350,11 @@ If a workspace skill or rule is relevant to your task (or mentioned by the user)
|
|
|
350
350
|
1. EXECUTE
|
|
351
351
|
Take the shortest reliable path. Inspect → modify → verify. Avoid redundant exploration.
|
|
352
352
|
|
|
353
|
-
2. SEARCH
|
|
354
|
-
For known symbols/errors, use grep_search. For known files, use scoped read_file. Avoid large unsliced reads and redundant listings.
|
|
353
|
+
2. SEARCH & WEB RESEARCH
|
|
354
|
+
For known symbols/errors in the codebase, use grep_search. For known files, use scoped read_file. For live internet research, external library documentation, API references, latest package details, or web queries, use web_search. Avoid large unsliced reads and redundant listings.
|
|
355
355
|
|
|
356
356
|
3. PLAN
|
|
357
|
-
Use plan_project for new features, architectural designs, and complex multi-file refactors. Skip planning for localized edits and direct fixes.
|
|
357
|
+
Use plan_project for new features, architectural designs, and complex multi-file refactors. Generated plans are saved under .utim_tmp/plans/ and can be read with read_file. Skip planning for localized edits and direct fixes.
|
|
358
358
|
|
|
359
359
|
4. IMAGES
|
|
360
360
|
If the user provides an image/screenshot path, read that exact path with read_file on Turn 1.
|
|
@@ -366,7 +366,7 @@ After modifications, verify with run_command using the appropriate build/test/li
|
|
|
366
366
|
Use is_background=True or schedule for long-running commands, tests, or servers, then conclude the turn.
|
|
367
367
|
|
|
368
368
|
7. TOOLS
|
|
369
|
-
Use native function calls directly. Never emit raw tool JSON, <think> tags, or filler before tool calls.
|
|
369
|
+
Use native function calls directly. You have full access to tools including read_file, edit_file, write_file, run_command, grep_search, list_directory, web_search, plan_project, schedule, and background_tasks. Never emit raw tool JSON, <think> tags, or filler before tool calls. Never claim you lack web search or terminal execution capabilities when these tools are present in your registry.
|
|
370
370
|
|
|
371
371
|
8. OUTPUT
|
|
372
372
|
After execution, give a concise result and relevant verification status.
|
|
@@ -460,11 +460,11 @@ If a workspace skill or rule is relevant to your task (or mentioned by the user)
|
|
|
460
460
|
1. EXECUTE
|
|
461
461
|
Take the shortest reliable path. Inspect → modify → verify. Avoid redundant exploration.
|
|
462
462
|
|
|
463
|
-
2. SEARCH
|
|
464
|
-
For known symbols/errors, use grep_search. For known files, use scoped read_file. Avoid large unsliced reads and redundant listings.
|
|
463
|
+
2. SEARCH & WEB RESEARCH
|
|
464
|
+
For known symbols/errors in the codebase, use grep_search. For known files, use scoped read_file. For live internet research, external library documentation, API references, latest package details, or web queries, use web_search. Avoid large unsliced reads and redundant listings.
|
|
465
465
|
|
|
466
466
|
3. PLAN
|
|
467
|
-
Use plan_project for new features, architectural designs, and complex multi-file refactors. Skip planning for localized edits and direct fixes.
|
|
467
|
+
Use plan_project for new features, architectural designs, and complex multi-file refactors. Generated plans are saved under .utim_tmp/plans/ and can be read with read_file. Skip planning for localized edits and direct fixes.
|
|
468
468
|
|
|
469
469
|
4. IMAGES
|
|
470
470
|
If the user provides an image/screenshot path, read that exact path with read_file on Turn 1.
|
|
@@ -476,7 +476,7 @@ After modifications, verify with run_command using the appropriate build/test/li
|
|
|
476
476
|
Use is_background=True or schedule for long-running commands, tests, or servers, then conclude the turn.
|
|
477
477
|
|
|
478
478
|
7. TOOLS
|
|
479
|
-
Use native function calls directly. Never emit raw tool JSON, <think> tags, or filler before tool calls.
|
|
479
|
+
Use native function calls directly. You have full access to tools including read_file, edit_file, write_file, run_command, grep_search, list_directory, web_search, plan_project, schedule, and background_tasks. Never emit raw tool JSON, <think> tags, or filler before tool calls. Never claim you lack web search or terminal execution capabilities when these tools are present in your registry.
|
|
480
480
|
|
|
481
481
|
8. OUTPUT
|
|
482
482
|
After execution, give a concise result and relevant verification status.
|
|
@@ -1136,11 +1136,11 @@ def _get_dynamic_directives(is_compressed: bool, messages: Optional[List[Dict]]
|
|
|
1136
1136
|
"1. EXECUTE",
|
|
1137
1137
|
"Take the shortest reliable path. Inspect → modify → verify. Avoid redundant exploration.",
|
|
1138
1138
|
"",
|
|
1139
|
-
"2. SEARCH",
|
|
1140
|
-
"For known symbols/errors, use grep_search. For known files, use scoped read_file. Avoid large unsliced reads and redundant listings.",
|
|
1139
|
+
"2. SEARCH & WEB RESEARCH",
|
|
1140
|
+
"For known symbols/errors in the codebase, use grep_search. For known files, use scoped read_file. For live internet research, external library documentation, API references, latest package details, or web queries, use web_search. Avoid large unsliced reads and redundant listings.",
|
|
1141
1141
|
"",
|
|
1142
1142
|
"3. PLAN",
|
|
1143
|
-
"Use plan_project for new features, architectural designs, and complex multi-file refactors. Skip planning for localized edits and direct fixes.",
|
|
1143
|
+
"Use plan_project for new features, architectural designs, and complex multi-file refactors. Generated plans are saved under .utim_tmp/plans/ and can be read with read_file. Skip planning for localized edits and direct fixes.",
|
|
1144
1144
|
"",
|
|
1145
1145
|
"4. IMAGES",
|
|
1146
1146
|
"If the user provides an image/screenshot path, read that exact path with read_file on Turn 1.",
|
|
@@ -1152,7 +1152,7 @@ def _get_dynamic_directives(is_compressed: bool, messages: Optional[List[Dict]]
|
|
|
1152
1152
|
"Use is_background=True or schedule for long-running commands, tests, or servers, then conclude the turn.",
|
|
1153
1153
|
"",
|
|
1154
1154
|
"7. TOOLS",
|
|
1155
|
-
"Use native function calls directly. Never emit raw tool JSON, <think> tags, or filler before tool calls.",
|
|
1155
|
+
"Use native function calls directly. You have full access to tools including read_file, edit_file, write_file, run_command, grep_search, list_directory, web_search, plan_project, schedule, and background_tasks. Never emit raw tool JSON, <think> tags, or filler before tool calls. Never claim you lack web search or terminal execution capabilities when these tools are present in your registry.",
|
|
1156
1156
|
"",
|
|
1157
1157
|
"8. OUTPUT",
|
|
1158
1158
|
"After execution, give a concise result and relevant verification status.",
|
|
@@ -827,8 +827,8 @@ class LLMTransportMixin:
|
|
|
827
827
|
is_custom = self._is_custom_model_selection(chosen_model, is_primary=not override_model)
|
|
828
828
|
is_free_user = (quota.get("plan") == "free" or str(quota.get("display_name", "")).lower() == "free")
|
|
829
829
|
|
|
830
|
-
from utim_cli.constants import APINEX_MODEL_IDS
|
|
831
|
-
is_apinex = (chosen_model in APINEX_MODEL_IDS)
|
|
830
|
+
from utim_cli.constants import APINEX_MODEL_IDS, ORCAROUTER_MODEL_IDS
|
|
831
|
+
is_apinex = (chosen_model in APINEX_MODEL_IDS or chosen_model in ORCAROUTER_MODEL_IDS or chosen_model.startswith("orcarouter/"))
|
|
832
832
|
is_free_model = (chosen_model.endswith(":free") or chosen_model.startswith("free/"))
|
|
833
833
|
|
|
834
834
|
if not is_custom:
|
|
@@ -1604,24 +1604,19 @@ class LLMTransportMixin:
|
|
|
1604
1604
|
# Insert paid slug as the very next model, then break
|
|
1605
1605
|
models_to_try[model_idx + 1:model_idx + 1] = [_paid_slug]
|
|
1606
1606
|
break # inner attempt loop → outer loop picks up injected paid slug
|
|
1607
|
-
#
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
return {
|
|
1621
|
-
"role": "assistant",
|
|
1622
|
-
"content": rate_limit_display,
|
|
1623
|
-
"tool_calls": None,
|
|
1624
|
-
}, False
|
|
1607
|
+
# Never display rate limit warnings to the user.
|
|
1608
|
+
# Seamlessly append the failover chain to models_to_try and continue trying models.
|
|
1609
|
+
_failover_chain = [
|
|
1610
|
+
"openrouter/free",
|
|
1611
|
+
"upstage/solar-pro4",
|
|
1612
|
+
"qwen/qwen3.7-flash",
|
|
1613
|
+
"deepseek/deepseek-v4-flash-0731",
|
|
1614
|
+
]
|
|
1615
|
+
_remaining = models_to_try[model_idx + 1:]
|
|
1616
|
+
_to_inject = [m for m in _failover_chain if m not in _remaining and m != current_model]
|
|
1617
|
+
if _to_inject:
|
|
1618
|
+
models_to_try.extend(_to_inject)
|
|
1619
|
+
break # inner attempt loop → outer loop picks up next failover model
|
|
1625
1620
|
elif _is_provider_upstream:
|
|
1626
1621
|
raise RuntimeError(f"__PROVIDER_ERROR__:{current_model}")
|
|
1627
1622
|
else:
|
|
@@ -2015,7 +2010,14 @@ class LLMTransportMixin:
|
|
|
2015
2010
|
self.console.print(f"\n[red]HTTP 400 Error Details from {current_model}: {escape(err_msg)}[/red]")
|
|
2016
2011
|
except Exception:
|
|
2017
2012
|
from rich.markup import escape
|
|
2018
|
-
|
|
2013
|
+
if code == 404:
|
|
2014
|
+
_not_avail_msg = 'This model isn\'t currently available on UTIM in free plan please enter "/model" and select model'
|
|
2015
|
+
self.console.print(f"\n[bold yellow]{_not_avail_msg}[/bold yellow]\n")
|
|
2016
|
+
return {
|
|
2017
|
+
"role": "assistant",
|
|
2018
|
+
"content": _not_avail_msg,
|
|
2019
|
+
"tool_calls": None,
|
|
2020
|
+
}, False
|
|
2019
2021
|
if code == 403:
|
|
2020
2022
|
# Invalidate cached quota immediately to force fresh check
|
|
2021
2023
|
global _QUOTA_CACHE
|
|
@@ -2039,13 +2041,15 @@ class LLMTransportMixin:
|
|
|
2039
2041
|
break
|
|
2040
2042
|
# Bonus exhausted (or plan-gated): auto-degrade to the free
|
|
2041
2043
|
# default model instead of aborting the user's turn.
|
|
2042
|
-
from utim_cli.constants import APINEX_MODEL_IDS
|
|
2044
|
+
from utim_cli.constants import APINEX_MODEL_IDS, ORCAROUTER_MODEL_IDS
|
|
2043
2045
|
_free_fallback = DEFAULT_MODEL
|
|
2044
2046
|
_is_cur_free_or_apx = (
|
|
2045
2047
|
current_model == _free_fallback
|
|
2046
2048
|
or current_model.endswith(":free")
|
|
2047
2049
|
or current_model.startswith("free/")
|
|
2050
|
+
or current_model.startswith("orcarouter/")
|
|
2048
2051
|
or current_model in APINEX_MODEL_IDS
|
|
2052
|
+
or current_model in ORCAROUTER_MODEL_IDS
|
|
2049
2053
|
)
|
|
2050
2054
|
if current_model != _free_fallback and not _is_cur_free_or_apx:
|
|
2051
2055
|
self.console.print(f"\n[bold yellow]⚠ Model '{current_model}' needs bonus credits and your bonus balance is empty.[/bold yellow]")
|
|
@@ -2165,26 +2169,19 @@ class LLMTransportMixin:
|
|
|
2165
2169
|
if current_is_primary and fallback_list:
|
|
2166
2170
|
raise _ServerUnavailableError(f"Rate-limited on model '{current_model}'")
|
|
2167
2171
|
|
|
2168
|
-
#
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
return {
|
|
2182
|
-
"role": "assistant",
|
|
2183
|
-
"content": rate_limit_display,
|
|
2184
|
-
"tool_calls": None,
|
|
2185
|
-
}, False
|
|
2186
|
-
# When silent=True (free model flow), raise exception to continue to next model
|
|
2187
|
-
raise _ServerUnavailableError(f"Rate-limited on model '{current_model}'")
|
|
2172
|
+
# Never display rate limit warnings to the user.
|
|
2173
|
+
# Seamlessly append the failover chain to models_to_try and continue trying models.
|
|
2174
|
+
_failover_chain = [
|
|
2175
|
+
"openrouter/free",
|
|
2176
|
+
"upstage/solar-pro4",
|
|
2177
|
+
"qwen/qwen3.7-flash",
|
|
2178
|
+
"deepseek/deepseek-v4-flash-0731",
|
|
2179
|
+
]
|
|
2180
|
+
_remaining = models_to_try[model_idx + 1:]
|
|
2181
|
+
_to_inject = [m for m in _failover_chain if m not in _remaining and m != current_model]
|
|
2182
|
+
if _to_inject:
|
|
2183
|
+
models_to_try.extend(_to_inject)
|
|
2184
|
+
break # outer loop will immediately pick up next failover model
|
|
2188
2185
|
if attempt < model_retries:
|
|
2189
2186
|
delay = 3 * (attempt + 1)
|
|
2190
2187
|
# Interruptible wait — so Ctrl+C can abort immediately
|
|
@@ -803,9 +803,20 @@ class ToolExecutionMixin:
|
|
|
803
803
|
v_strip = v.strip()
|
|
804
804
|
v_norm = v_strip.replace("\\", "/")
|
|
805
805
|
|
|
806
|
+
# 0. Prioritize workspace .utim_tmp and plans normalization so model paths like
|
|
807
|
+
# /.utim/_tmp/plans, /.utim.tmp/plans, etc. resolve to workspace .utim_tmp, NOT ~/.utim
|
|
808
|
+
import re as _re_path
|
|
809
|
+
if _re_path.search(
|
|
810
|
+
r'(?:^|[/\\])\.utim(?:[._/]tmp|[/\\]_tmp|[/\\]tmp|[._/]plans|[/\\]plans)($|[/\\])',
|
|
811
|
+
v_norm,
|
|
812
|
+
_re_path.IGNORECASE
|
|
813
|
+
):
|
|
814
|
+
from utim_cli.tools import resolve_project_path
|
|
815
|
+
arguments[k] = resolve_project_path(v_strip)
|
|
816
|
+
continue
|
|
817
|
+
|
|
806
818
|
# 1. First prioritize dynamic .utim mapping for all users across any virtual prefix
|
|
807
819
|
# NOTE: use path-component regex to avoid matching .utim_tmp (which contains .utim as substring)
|
|
808
|
-
import re as _re_path
|
|
809
820
|
if _re_path.search(r'(^|[/\\])\.utim($|[/\\])', v_norm):
|
|
810
821
|
from utim_cli.config import get_utim_dir
|
|
811
822
|
utim_dir = get_utim_dir()
|
|
@@ -1356,10 +1367,11 @@ class ToolExecutionMixin:
|
|
|
1356
1367
|
("run_command", "input_text"): "input",
|
|
1357
1368
|
("run_command", "enter"): "press_enter",
|
|
1358
1369
|
("run_command", "target_process_id"):"process_id",
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
("web_search", "
|
|
1362
|
-
("web_search", "
|
|
1370
|
+
# web_search: model sometimes uses 'query', 'search_query', 'q', or 'text' instead of 'prompt'
|
|
1371
|
+
("web_search", "query"): "prompt",
|
|
1372
|
+
("web_search", "search_query"): "prompt",
|
|
1373
|
+
("web_search", "q"): "prompt",
|
|
1374
|
+
("web_search", "text"): "prompt",
|
|
1363
1375
|
}
|
|
1364
1376
|
# Apply aliasing before filtering (don't overwrite existing correct keys)
|
|
1365
1377
|
remapped = dict(call_args)
|