utim-cli 2.3.24__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.24 → utim_cli-2.3.26}/CHANGELOG.md +12 -0
- {utim_cli-2.3.24/utim_cli.egg-info → utim_cli-2.3.26}/PKG-INFO +1 -1
- {utim_cli-2.3.24 → utim_cli-2.3.26}/pyproject.toml +1 -1
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim/__init__.py +1 -1
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/__init__.py +1 -1
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/_version.py +1 -1
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/constants.py +12 -1
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/orchestrator.py +33 -29
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/orchestrator_helpers.py +12 -12
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/orchestrator_llm_transport.py +40 -86
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/orchestrator_tool_exec.py +24 -5
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/key_rotator.py +103 -80
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/models.py +88 -2
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/router.py +131 -137
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/routes/completion_routes.py +588 -297
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/state.py +1 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/tools.py +90 -10
- utim_cli-2.3.26/utim_cli/tui/compression_dialog.py +188 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/utim.py +154 -167
- {utim_cli-2.3.24 → utim_cli-2.3.26/utim_cli.egg-info}/PKG-INFO +1 -1
- utim_cli-2.3.24/utim_cli/tui/compression_dialog.py +0 -230
- {utim_cli-2.3.24 → utim_cli-2.3.26}/LICENSE +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/MANIFEST.in +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/README.md +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/setup.cfg +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/setup.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/_chip_test.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/_verify_repro.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/_verify_syntax.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/_wizard_smoke_test.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/agent.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/ask_helper.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/auth.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/backup.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/billing.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/blender_agent.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/bootstrap.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/brain.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/builtin/skills/utim-guide/SKILL.md +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/client_utils.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/config.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/context_pruner.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/crawler/__init__.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/crawler/engine.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/crawler/markdown_distiller.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/dependency_manager.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/doctor.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/evidence_registry.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/harbor.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/knowledge_graph.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/local_db.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/logger.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/mcp_clean_wrapper.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/mcp_client.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/mcp_registry.json +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/models.txt +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/orchestrator_turn_history.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/personalities.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/platform_installer.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/plugins/__init__.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/plugins/hooks.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/plugins/loader.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/plugins/manager.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/plugins/registry.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/plugins/runtime.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/plugins/schema.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/plugins/tool_loader.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/reflection.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/report.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/scrapy_search.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/sdk/__init__.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/sdk/config.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/sdk/context.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/sdk/events.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/sdk/handlers.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/sdk/session.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/sdk/sidecar.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/__init__.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/admin_auth.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/attribution.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/audit_log.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/auth.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/batch_processor.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/captcha.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/cli_auth.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/concurrency.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/db.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/docs_md/SECRET_PROVISIONING.md +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/docs_md/about.md +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/docs_md/changelog.md +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/docs_md/docs.md +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/docs_md/features.md +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/docs_md/license.md +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/docs_md/pricing.md +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/docs_md/privacy.md +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/docs_md/refund.md +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/docs_md/support.md +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/docs_md/terms.md +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/email_utils.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/exchange_rate.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/firebase.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/fix_duplicate_users.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/history.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/local_llm.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/logging_config.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/micro_batcher.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/model_agent.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/plan_integrity.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/pricing_updater.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/provision_build.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/rate_limit.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/rewards_engine.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/routes/__init__.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/routes/admin_bonus_routes.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/routes/admin_db_routes.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/routes/auth_routes.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/routes/billing_routes.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/routes/credit_routes.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/routes/feedback_routes.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/routes/marketplace_routes.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/routes/quota_routes.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/routes/quota_share_routes.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/routes/referral_routes.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/routes/rewards_routes.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/routes/security_routes.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/routes/session_routes.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/routes/share_routes.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/routes/webhook_routes.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/security_alerts.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/server.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/storage_nodes.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/server/utils.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/share.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/share_tui.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/situational_scoring.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/subagent_manager.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/task_dispatcher.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/task_logger.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/tools_grep.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/tui/__init__.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/tui/feedback_dialog.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/tui/history_dialog.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/tui/marketplace_app_state.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/tui/marketplace_dialog.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/tui/marketplace_layout_engine.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/tui/mcp_dialog.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/tui/miniagents_dialog.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/tui/model_dialog.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/tui/personality_dialog.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/tui/plugins_dialog.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/tui/publish_dialog.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/tui/quota_dialog.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/tui/quota_redeem_dialog.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/tui/quota_share_dialog.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/tui/resume_dialog.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/tui/rewards_tui.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/tui/skills_dialog.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/tui/subagents_dialog.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/tui/tasks_dialog.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/tui/thinking_display.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/tui/tools_dialog.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/tui/update_dialog.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/update_checker.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/utilities.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/utimmodel.txt +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/vector_memory.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/wheel.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli/workspace.py +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli.egg-info/SOURCES.txt +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli.egg-info/dependency_links.txt +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli.egg-info/entry_points.txt +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli.egg-info/requires.txt +0 -0
- {utim_cli-2.3.24 → utim_cli-2.3.26}/utim_cli.egg-info/top_level.txt +0 -0
|
@@ -1,5 +1,17 @@
|
|
|
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
|
+
|
|
10
|
+
## [2.3.25] - 2026-09-07
|
|
11
|
+
|
|
12
|
+
### 🛠️ Bug Fixes
|
|
13
|
+
- Fixed usage dialog glitches
|
|
14
|
+
|
|
3
15
|
## [2.3.24] - 2026-09-07
|
|
4
16
|
|
|
5
17
|
### ⚡ Performance
|
|
@@ -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.",
|
|
@@ -268,11 +268,6 @@ class LLMTransportMixin:
|
|
|
268
268
|
if not race_candidates:
|
|
269
269
|
return None
|
|
270
270
|
|
|
271
|
-
if not silent:
|
|
272
|
-
self.console.print(
|
|
273
|
-
f"\n[dim yellow]↪ Primary model unavailable — racing {len(race_candidates)} fallback models for lowest latency response…[/dim yellow]\n"
|
|
274
|
-
)
|
|
275
|
-
|
|
276
271
|
winner_lock = threading.Lock()
|
|
277
272
|
winner_model = [None]
|
|
278
273
|
winner_result = [None]
|
|
@@ -832,8 +827,8 @@ class LLMTransportMixin:
|
|
|
832
827
|
is_custom = self._is_custom_model_selection(chosen_model, is_primary=not override_model)
|
|
833
828
|
is_free_user = (quota.get("plan") == "free" or str(quota.get("display_name", "")).lower() == "free")
|
|
834
829
|
|
|
835
|
-
from utim_cli.constants import APINEX_MODEL_IDS
|
|
836
|
-
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/"))
|
|
837
832
|
is_free_model = (chosen_model.endswith(":free") or chosen_model.startswith("free/"))
|
|
838
833
|
|
|
839
834
|
if not is_custom:
|
|
@@ -1135,10 +1130,6 @@ class LLMTransportMixin:
|
|
|
1135
1130
|
_exhausted_key = _api_key
|
|
1136
1131
|
|
|
1137
1132
|
if _exhausted_key:
|
|
1138
|
-
remaining = config.get_exhaustion_countdown(_exhausted_key)
|
|
1139
|
-
remaining_str = f"{remaining // 3600}h {remaining % 3600 // 60}m" if remaining else "24h"
|
|
1140
|
-
if not silent:
|
|
1141
|
-
self.console.print(f"[dim]Skipping exhausted API key {_exhausted_key[:12]}... (cooldown: {remaining_str})[/dim]")
|
|
1142
1133
|
raise _ServerUnavailableError(f"API key exhausted: {_exhausted_key[:12]}...")
|
|
1143
1134
|
|
|
1144
1135
|
# Build auth headers or query params depending on provider
|
|
@@ -1610,52 +1601,26 @@ class LLMTransportMixin:
|
|
|
1610
1601
|
_paid_slug = current_model.rsplit(":free", 1)[0]
|
|
1611
1602
|
_remaining = models_to_try[model_idx + 1:]
|
|
1612
1603
|
if _paid_slug not in _remaining:
|
|
1613
|
-
if not silent:
|
|
1614
|
-
self.console.print(
|
|
1615
|
-
f"\n[dim yellow]↪ Free model rate-limited — trying paid version '{_paid_slug}'…[/dim yellow]"
|
|
1616
|
-
)
|
|
1617
1604
|
# Insert paid slug as the very next model, then break
|
|
1618
1605
|
models_to_try[model_idx + 1:model_idx + 1] = [_paid_slug]
|
|
1619
|
-
else:
|
|
1620
|
-
if not silent:
|
|
1621
|
-
self.console.print(
|
|
1622
|
-
f"\n[dim yellow]↪ Free model rate-limited — paid version already in queue, skipping…[/dim yellow]"
|
|
1623
|
-
)
|
|
1624
1606
|
break # inner attempt loop → outer loop picks up injected paid slug
|
|
1625
|
-
#
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
return {
|
|
1639
|
-
"role": "assistant",
|
|
1640
|
-
"content": rate_limit_display,
|
|
1641
|
-
"tool_calls": None,
|
|
1642
|
-
}, 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
|
|
1643
1620
|
elif _is_provider_upstream:
|
|
1644
|
-
if not silent:
|
|
1645
|
-
self.console.print(
|
|
1646
|
-
f"\n[dim yellow]↪ {_friendly_name}'s provider is having trouble — "
|
|
1647
|
-
f"switching to the next available model…[/dim yellow]"
|
|
1648
|
-
)
|
|
1649
1621
|
raise RuntimeError(f"__PROVIDER_ERROR__:{current_model}")
|
|
1650
1622
|
else:
|
|
1651
|
-
|
|
1652
|
-
_short = err_msg.split("{'error'")[0].strip() or err_msg[:120]
|
|
1653
|
-
if not silent:
|
|
1654
|
-
self.console.print(
|
|
1655
|
-
f"\n[bold red]Model error on {_friendly_name}: {_short}[/bold red]\n"
|
|
1656
|
-
f"[dim]Trying the next available model…[/dim]"
|
|
1657
|
-
)
|
|
1658
|
-
raise RuntimeError(f"Server completion error ({current_model}): {err_msg}")
|
|
1623
|
+
raise RuntimeError(f"Server completion error ({current_model}): {err_msg}")
|
|
1659
1624
|
if "finish_reason" in chunk and chunk["finish_reason"]:
|
|
1660
1625
|
_stream_finish_reason = chunk["finish_reason"]
|
|
1661
1626
|
elif not _stream_finish_reason:
|
|
@@ -2045,7 +2010,14 @@ class LLMTransportMixin:
|
|
|
2045
2010
|
self.console.print(f"\n[red]HTTP 400 Error Details from {current_model}: {escape(err_msg)}[/red]")
|
|
2046
2011
|
except Exception:
|
|
2047
2012
|
from rich.markup import escape
|
|
2048
|
-
|
|
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
|
|
2049
2021
|
if code == 403:
|
|
2050
2022
|
# Invalidate cached quota immediately to force fresh check
|
|
2051
2023
|
global _QUOTA_CACHE
|
|
@@ -2069,13 +2041,15 @@ class LLMTransportMixin:
|
|
|
2069
2041
|
break
|
|
2070
2042
|
# Bonus exhausted (or plan-gated): auto-degrade to the free
|
|
2071
2043
|
# default model instead of aborting the user's turn.
|
|
2072
|
-
from utim_cli.constants import APINEX_MODEL_IDS
|
|
2044
|
+
from utim_cli.constants import APINEX_MODEL_IDS, ORCAROUTER_MODEL_IDS
|
|
2073
2045
|
_free_fallback = DEFAULT_MODEL
|
|
2074
2046
|
_is_cur_free_or_apx = (
|
|
2075
2047
|
current_model == _free_fallback
|
|
2076
2048
|
or current_model.endswith(":free")
|
|
2077
2049
|
or current_model.startswith("free/")
|
|
2050
|
+
or current_model.startswith("orcarouter/")
|
|
2078
2051
|
or current_model in APINEX_MODEL_IDS
|
|
2052
|
+
or current_model in ORCAROUTER_MODEL_IDS
|
|
2079
2053
|
)
|
|
2080
2054
|
if current_model != _free_fallback and not _is_cur_free_or_apx:
|
|
2081
2055
|
self.console.print(f"\n[bold yellow]⚠ Model '{current_model}' needs bonus credits and your bonus balance is empty.[/bold yellow]")
|
|
@@ -2188,46 +2162,26 @@ class LLMTransportMixin:
|
|
|
2188
2162
|
_paid_slug = current_model.rsplit(":free", 1)[0]
|
|
2189
2163
|
_remaining = models_to_try[model_idx + 1:]
|
|
2190
2164
|
if _paid_slug not in _remaining:
|
|
2191
|
-
if not silent:
|
|
2192
|
-
self.console.print(
|
|
2193
|
-
f"\n[dim yellow]↪ Free model rate-limited — trying paid version '{_paid_slug}'…[/dim yellow]"
|
|
2194
|
-
)
|
|
2195
2165
|
# Insert paid slug as the very next model, then break
|
|
2196
2166
|
models_to_try[model_idx + 1:model_idx + 1] = [_paid_slug]
|
|
2197
|
-
else:
|
|
2198
|
-
if not silent:
|
|
2199
|
-
self.console.print(
|
|
2200
|
-
f"\n[dim yellow]↪ Free model rate-limited — paid version already in queue, skipping…[/dim yellow]"
|
|
2201
|
-
)
|
|
2202
2167
|
break # inner attempt loop → outer loop picks up injected paid slug
|
|
2203
2168
|
# If primary model is rate-limited and fallback models exist, race them
|
|
2204
2169
|
if current_is_primary and fallback_list:
|
|
2205
|
-
if not silent:
|
|
2206
|
-
self.console.print(
|
|
2207
|
-
f"\n[dim yellow]⏳ Model '{current_model}' is temporarily rate-limited by the provider — racing fallback models…[/dim yellow]\n"
|
|
2208
|
-
)
|
|
2209
2170
|
raise _ServerUnavailableError(f"Rate-limited on model '{current_model}'")
|
|
2210
2171
|
|
|
2211
|
-
#
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
return {
|
|
2225
|
-
"role": "assistant",
|
|
2226
|
-
"content": rate_limit_display,
|
|
2227
|
-
"tool_calls": None,
|
|
2228
|
-
}, False
|
|
2229
|
-
# When silent=True (free model flow), raise exception to continue to next model
|
|
2230
|
-
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
|
|
2231
2185
|
if attempt < model_retries:
|
|
2232
2186
|
delay = 3 * (attempt + 1)
|
|
2233
2187
|
# Interruptible wait — so Ctrl+C can abort immediately
|
|
@@ -659,6 +659,13 @@ class ToolExecutionMixin:
|
|
|
659
659
|
self.console.print(bl)
|
|
660
660
|
rendered_text = capture.get()
|
|
661
661
|
if print_to_console and getattr(self, "_subagent_depth", 0) == 0:
|
|
662
|
+
try:
|
|
663
|
+
from utim_cli.state import STATE
|
|
664
|
+
if STATE.get("dialog_active"):
|
|
665
|
+
self.console.print(rendered_text, end="", markup=False, highlight=False)
|
|
666
|
+
return rendered_text
|
|
667
|
+
except Exception:
|
|
668
|
+
pass
|
|
662
669
|
if getattr(self.console, "file", None) is not sys.stdout and getattr(self.console, "file", None) is not sys.__stdout__:
|
|
663
670
|
# IMPORTANT: rendered_text is a *plain string* that was already
|
|
664
671
|
# produced by Rich (via capture.get()). It can legitimately
|
|
@@ -796,9 +803,20 @@ class ToolExecutionMixin:
|
|
|
796
803
|
v_strip = v.strip()
|
|
797
804
|
v_norm = v_strip.replace("\\", "/")
|
|
798
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
|
+
|
|
799
818
|
# 1. First prioritize dynamic .utim mapping for all users across any virtual prefix
|
|
800
819
|
# NOTE: use path-component regex to avoid matching .utim_tmp (which contains .utim as substring)
|
|
801
|
-
import re as _re_path
|
|
802
820
|
if _re_path.search(r'(^|[/\\])\.utim($|[/\\])', v_norm):
|
|
803
821
|
from utim_cli.config import get_utim_dir
|
|
804
822
|
utim_dir = get_utim_dir()
|
|
@@ -1349,10 +1367,11 @@ class ToolExecutionMixin:
|
|
|
1349
1367
|
("run_command", "input_text"): "input",
|
|
1350
1368
|
("run_command", "enter"): "press_enter",
|
|
1351
1369
|
("run_command", "target_process_id"):"process_id",
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
("web_search", "
|
|
1355
|
-
("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",
|
|
1356
1375
|
}
|
|
1357
1376
|
# Apply aliasing before filtering (don't overwrite existing correct keys)
|
|
1358
1377
|
remapped = dict(call_args)
|