utim-cli 2.5.1__tar.gz → 2.5.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. {utim_cli-2.5.1 → utim_cli-2.5.3}/CHANGELOG.md +10 -2
  2. {utim_cli-2.5.1/utim_cli.egg-info → utim_cli-2.5.3}/PKG-INFO +1 -1
  3. {utim_cli-2.5.1 → utim_cli-2.5.3}/pyproject.toml +1 -1
  4. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim/__init__.py +1 -1
  5. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/__init__.py +1 -1
  6. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/_version.py +1 -1
  7. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/architecture_indexer.py +105 -82
  8. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/client_utils.py +91 -8
  9. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/context_pruner.py +563 -257
  10. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/orchestrator.py +141 -58
  11. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/orchestrator_helpers.py +55 -6
  12. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/orchestrator_llm_transport.py +79 -11
  13. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/orchestrator_turn_history.py +134 -41
  14. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/auth.py +11 -0
  15. utim_cli-2.5.3/utim_cli/server/crypto_utils.py +219 -0
  16. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/db.py +119 -17
  17. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/hobby_throttler.py +60 -30
  18. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/models.py +312 -78
  19. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/plan_integrity.py +94 -33
  20. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/router.py +73 -66
  21. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/admin_providers_routes.py +61 -0
  22. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/admin_separated_models_routes.py +989 -719
  23. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/api_models_public_routes.py +14 -32
  24. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/completion_routes.py +616 -173
  25. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/credit_routes.py +139 -0
  26. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/utils.py +871 -862
  27. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tools.py +5 -5
  28. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/model_dialog.py +35 -17
  29. utim_cli-2.5.3/utim_cli/tui/verbosity_dialog.py +221 -0
  30. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/utim.py +79 -30
  31. {utim_cli-2.5.1 → utim_cli-2.5.3/utim_cli.egg-info}/PKG-INFO +1 -1
  32. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli.egg-info/SOURCES.txt +3 -1
  33. {utim_cli-2.5.1 → utim_cli-2.5.3}/LICENSE +0 -0
  34. {utim_cli-2.5.1 → utim_cli-2.5.3}/MANIFEST.in +0 -0
  35. {utim_cli-2.5.1 → utim_cli-2.5.3}/README.md +0 -0
  36. {utim_cli-2.5.1 → utim_cli-2.5.3}/setup.cfg +0 -0
  37. {utim_cli-2.5.1 → utim_cli-2.5.3}/setup.py +0 -0
  38. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/_chip_test.py +0 -0
  39. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/_verify_repro.py +0 -0
  40. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/_verify_syntax.py +0 -0
  41. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/_wizard_smoke_test.py +0 -0
  42. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/agent.py +0 -0
  43. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/ask_helper.py +0 -0
  44. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/auth.py +0 -0
  45. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/backup.py +0 -0
  46. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/billing.py +0 -0
  47. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/blender_agent.py +0 -0
  48. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/bootstrap.py +0 -0
  49. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/brain.py +0 -0
  50. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/builtin/skills/utim-guide/SKILL.md +0 -0
  51. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/config.py +0 -0
  52. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/constants.py +0 -0
  53. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/context_inspector.py +0 -0
  54. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/crawler/__init__.py +0 -0
  55. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/crawler/engine.py +0 -0
  56. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/crawler/markdown_distiller.py +0 -0
  57. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/dependency_manager.py +0 -0
  58. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/doctor.py +0 -0
  59. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/evidence_registry.py +0 -0
  60. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/harbor.py +0 -0
  61. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/knowledge_graph.py +0 -0
  62. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/local_db.py +0 -0
  63. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/logger.py +0 -0
  64. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/mcp_clean_wrapper.py +0 -0
  65. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/mcp_client.py +0 -0
  66. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/mcp_registry.json +0 -0
  67. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/models.txt +0 -0
  68. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/orchestrator_tool_exec.py +0 -0
  69. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/personalities.py +0 -0
  70. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/platform_installer.py +0 -0
  71. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/plugins/__init__.py +0 -0
  72. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/plugins/hooks.py +0 -0
  73. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/plugins/loader.py +0 -0
  74. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/plugins/manager.py +0 -0
  75. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/plugins/registry.py +0 -0
  76. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/plugins/runtime.py +0 -0
  77. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/plugins/schema.py +0 -0
  78. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/plugins/tool_loader.py +0 -0
  79. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/reflection.py +0 -0
  80. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/report.py +0 -0
  81. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/scrapy_search.py +0 -0
  82. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/sdk/__init__.py +0 -0
  83. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/sdk/config.py +0 -0
  84. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/sdk/context.py +0 -0
  85. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/sdk/events.py +0 -0
  86. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/sdk/handlers.py +0 -0
  87. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/sdk/session.py +0 -0
  88. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/sdk/sidecar.py +0 -0
  89. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/__init__.py +0 -0
  90. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/admin_auth.py +0 -0
  91. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/api_batcher.py +0 -0
  92. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/api_models_db.py +0 -0
  93. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/attribution.py +0 -0
  94. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/audit_log.py +0 -0
  95. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/batch_processor.py +0 -0
  96. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/captcha.py +0 -0
  97. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/cli_auth.py +0 -0
  98. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/concurrency.py +0 -0
  99. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/docs_md/SECRET_PROVISIONING.md +0 -0
  100. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/docs_md/about.md +0 -0
  101. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/docs_md/changelog.md +0 -0
  102. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/docs_md/docs.md +0 -0
  103. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/docs_md/features.md +0 -0
  104. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/docs_md/license.md +0 -0
  105. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/docs_md/pricing.md +0 -0
  106. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/docs_md/privacy.md +0 -0
  107. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/docs_md/refund.md +0 -0
  108. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/docs_md/support.md +0 -0
  109. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/docs_md/terms.md +0 -0
  110. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/email_utils.py +0 -0
  111. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/exchange_rate.py +0 -0
  112. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/firebase.py +0 -0
  113. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/firewall.py +0 -0
  114. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/fix_duplicate_users.py +0 -0
  115. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/history.py +0 -0
  116. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/key_rotator.py +0 -0
  117. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/local_llm.py +0 -0
  118. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/logging_config.py +0 -0
  119. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/maintenance.py +0 -0
  120. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/micro_batcher.py +0 -0
  121. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/model_agent.py +0 -0
  122. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/platform_billing.py +0 -0
  123. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/pricing_updater.py +0 -0
  124. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/provider_balance.py +0 -0
  125. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/provider_discovery.py +0 -0
  126. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/provider_router.py +0 -0
  127. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/provision_build.py +0 -0
  128. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/rate_limit.py +0 -0
  129. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/request_batcher.py +0 -0
  130. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/request_queue.py +0 -0
  131. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/rewards_engine.py +0 -0
  132. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/__init__.py +0 -0
  133. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/admin_bonus_routes.py +0 -0
  134. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/admin_db_routes.py +0 -0
  135. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/api_gateway_extensions_routes.py +0 -0
  136. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/api_key_routes.py +0 -0
  137. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/api_models_admin_routes.py +0 -0
  138. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/auth_routes.py +0 -0
  139. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/billing_routes.py +0 -0
  140. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/feedback_routes.py +0 -0
  141. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/marketplace_routes.py +0 -0
  142. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/provider_compat_routes.py +0 -0
  143. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/quota_routes.py +0 -0
  144. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/quota_share_routes.py +0 -0
  145. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/referral_routes.py +0 -0
  146. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/rewards_routes.py +0 -0
  147. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/security_routes.py +0 -0
  148. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/session_routes.py +0 -0
  149. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/share_routes.py +0 -0
  150. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/wallet_routes.py +0 -0
  151. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/routes/webhook_routes.py +0 -0
  152. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/security_alerts.py +0 -0
  153. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/server.py +0 -0
  154. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/signup_guard.py +0 -0
  155. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/server/storage_nodes.py +0 -0
  156. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/share.py +0 -0
  157. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/share_tui.py +0 -0
  158. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/situational_scoring.py +0 -0
  159. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/state.py +0 -0
  160. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/subagent_manager.py +0 -0
  161. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/syntax_fix.py +0 -0
  162. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/task_dispatcher.py +0 -0
  163. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/task_logger.py +0 -0
  164. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tools_grep.py +0 -0
  165. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/__init__.py +0 -0
  166. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/compression_dialog.py +0 -0
  167. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/feedback_dialog.py +0 -0
  168. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/history_dialog.py +0 -0
  169. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/marketplace_app_state.py +0 -0
  170. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/marketplace_dialog.py +0 -0
  171. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/marketplace_layout_engine.py +0 -0
  172. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/mcp_dialog.py +0 -0
  173. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/miniagents_dialog.py +0 -0
  174. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/personality_dialog.py +0 -0
  175. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/plugins_dialog.py +0 -0
  176. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/publish_dialog.py +0 -0
  177. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/quota_dialog.py +0 -0
  178. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/quota_redeem_dialog.py +0 -0
  179. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/quota_share_dialog.py +0 -0
  180. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/resume_dialog.py +0 -0
  181. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/rewards_tui.py +0 -0
  182. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/skills_dialog.py +0 -0
  183. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/subagents_dialog.py +0 -0
  184. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/tasks_dialog.py +0 -0
  185. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/thinking_display.py +0 -0
  186. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/tools_dialog.py +0 -0
  187. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/tui/update_dialog.py +0 -0
  188. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/update_checker.py +0 -0
  189. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/utilities.py +0 -0
  190. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/utimmodel.txt +0 -0
  191. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/vector_memory.py +0 -0
  192. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/voice.py +0 -0
  193. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/voice_coding.py +0 -0
  194. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/voice_events.py +0 -0
  195. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/voice_handler.py +0 -0
  196. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/voice_instant.py +0 -0
  197. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/voice_orchestrator.py +0 -0
  198. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/voice_session.py +0 -0
  199. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/voice_stt.py +0 -0
  200. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/voice_tts.py +0 -0
  201. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/wheel.py +0 -0
  202. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli/workspace.py +0 -0
  203. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli.egg-info/dependency_links.txt +0 -0
  204. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli.egg-info/entry_points.txt +0 -0
  205. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli.egg-info/requires.txt +0 -0
  206. {utim_cli-2.5.1 → utim_cli-2.5.3}/utim_cli.egg-info/top_level.txt +0 -0
@@ -1,11 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.5.3] - 2026-10-20
4
+
5
+ ### ✨ Improvements & Fixes
6
+ - Stability, streaming, and rate-limit handling improvements.
7
+
8
+ ## [2.5.2] - 2026-10-18
9
+
10
+ ### 🐛 Fixes
11
+ - **Paid Plan Model Gating & Aliases**: Fixed preflight model registry check in `completion_routes.py` so paid subscribers (Hobby/Pro/Max/Ultra) are never blocked by free-plan error notices.
12
+
3
13
  ## [2.5.1] - 2026-10-18
4
14
 
5
15
  ### 🐛 Fixes
6
16
  - **Compression Empty-Response Spam**: `_call_compression_model_with_fallback` now always sets `last_error` on empty/stripped content and logs `model=… routing=… empty_hits=…` diagnostics — eliminates the endless `[COMPRESSION FALLBACK FAILURE] Last error: None` loop and surfaces the real reason (429 / empty content / bad shape).
7
- - **Admin Portal CORS**: `router.py` now always allows `Origin: null` (downloaded `file://` HTML) and `allow_headers=["*"]` + `max_age=600` so `X-UTIM-Master-Key` / `Authorization` preflights for `/api/admin/*` and `/models` no longer fail with `No 'Access-Control-Allow-Origin'`.
8
- - **Admin Firewall Bypass**: `firewall.py: _FIREWALL_SKIP_PREFIXES` and `cli_auth.py: PUBLIC_PREFIXES` now include `/admin`, `/admin.html`, `/admin_models.html`, `/static` so the browser-served admin panel bypasses proxy-abuse / burst throttling and CLI HMAC `401` (API auth via master-key session is still enforced).
9
17
 
10
18
  ## [2.5.0] - 2026-10-18
11
19
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: utim-cli
3
- Version: 2.5.1
3
+ Version: 2.5.3
4
4
  Summary: UTIM – Universal Terminal Intelligence Manager. A powerful agentic AI coding assistant for your terminal.
5
5
  License: Emend AI Proprietary EULA
6
6
  Project-URL: Homepage, https://utim.dev
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "utim-cli"
7
- version = "2.5.1"
7
+ version = "2.5.3"
8
8
 
9
9
  description = "UTIM – Universal Terminal Intelligence Manager. A powerful agentic AI coding assistant for your terminal."
10
10
  readme = "README.md"
@@ -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.1"
17
+ __version__ = "2.5.3"
18
18
 
19
19
  # Developer-friendly core classes (similar to `import genai` / `import openai`)
20
20
  from utim_cli.sdk.session import UtimSession as Session
@@ -5,7 +5,7 @@ try:
5
5
  from utim_cli._version import VERSION as __version__
6
6
  except ImportError:
7
7
  # Fallback during first-install / editable-install edge cases
8
- __version__ = "2.5.1"
8
+ __version__ = "2.5.3"
9
9
 
10
10
 
11
11
  import builtins, os, pathlib, urllib.parse, re
@@ -11,6 +11,6 @@
11
11
  # from here so every surface stays in sync automatically.
12
12
  # ─────────────────────────────────────────────────────────────────────────────
13
13
 
14
- VERSION = "2.5.1"
14
+ VERSION = "2.5.3"
15
15
 
16
16
 
@@ -140,66 +140,53 @@ def architecture_lock(project_path: str = ".") -> str:
140
140
  _now = _time.time()
141
141
  _mem = _ARCH_LOCK_MEM_CACHE.get(proj_id)
142
142
  if _mem and (_now - _mem.get("ts", 0) < _ARCH_LOCK_COOLDOWN_S):
143
- _cached_dir = _mem.get("arch_dir")
143
+ _cached_file = _mem.get("arch_file")
144
144
  _cached_result = _mem.get("result")
145
- if _cached_dir and Path(_cached_dir).exists() and _cached_result:
145
+ if _cached_file and Path(_cached_file).is_file() and _cached_result:
146
146
  # Fresh within the session — return cached output instantly.
147
147
  return (
148
148
  "[CACHED — architecture lock already generated this session; NOT re-indexed]\n"
149
149
  + _cached_result
150
- + "\n\n⚠️ Do NOT call architecture_lock again this session. "
151
- "Use `read_file` on the component_<domain>.md blueprints listed above."
150
+ + f"\n\n⚠️ Do NOT call architecture_lock again this session. Read the file at: `{_cached_file}`"
152
151
  )
153
152
 
154
153
  # ── 2. Stable single directory (replaces per-call arch_{uuid} spam) ───────
155
154
  arch_dir = proj_dir / "arch_lock"
156
155
  arch_dir.mkdir(parents=True, exist_ok=True)
157
156
  brain_file = arch_dir / "00_architecture_map.json"
157
+ arch_file = arch_dir / "architecture.md"
158
158
  meta_file = arch_dir / ".arch_lock_meta.json"
159
159
 
160
- # ── 3. Disk cache: reuse if the graph is fresh AND file tree unchanged ───
160
+ # ── 3. Disk cache: reuse if the single-file index is fresh AND file tree unchanged
161
161
  _tree_hash = _quick_file_tree_hash(abs_path)
162
162
  try:
163
- if brain_file.exists() and meta_file.exists():
163
+ if arch_file.is_file() and brain_file.is_file() and meta_file.is_file():
164
164
  _meta = json.loads(meta_file.read_text(encoding="utf-8"))
165
165
  _age = _now - float(_meta.get("ts", 0))
166
166
  _same_tree = _meta.get("tree_hash") == _tree_hash
167
167
  if _same_tree and _age < _ARCH_LOCK_DISK_TTL_S:
168
- # Rebuild the lightweight return string from the cached dir
169
- # without re-walking / re-parsing the entire codebase.
170
- _cached_mds = sorted([p.name for p in arch_dir.glob("component_*.md")])
171
- if _cached_mds:
172
- # Count files from cached graph
173
- try:
174
- _g = json.loads(brain_file.read_text(encoding="utf-8"))
175
- _n_files = _g.get("total_files", "?")
176
- _ptype = ", ".join(_g.get("project_type", [])) or "Unknown"
177
- except Exception:
178
- _n_files, _ptype = "?", "Unknown"
179
- _bullets = "\n".join(
180
- f"- {m}: [file:///{str((arch_dir / m).absolute()).replace(chr(92), '/')}]"
181
- for m in _cached_mds
182
- )
183
- _result = (
184
- f"✓ Architecture lock (CACHED — reused, not re-indexed) at: {arch_dir}\n"
185
- f"Identified stack: {_ptype} | Indexed files: {_n_files} | Age: {int(_age)}s\n\n"
186
- f"WHAT WAS PROVIDED:\n"
187
- f"1. Structural Dependency Graph: `00_architecture_map.json`\n"
188
- f"2. Architecture Conventions: `conventions_summary.txt`\n"
189
- f"3. Modular Component Blueprints ({len(_cached_mds)} domain modules):\n{_bullets}\n\n"
190
- f"WHY THIS IS NEEDED & NEXT ACTION:\n"
191
- f"- Zero-token structural grounding eliminates hallucinations and token-wasting blind exploration.\n"
192
- f"- NEXT STEP: Use `read_file` on the relevant `component_<domain>.md` blueprint above "
193
- f"matching your task to inspect exact function/class signatures BEFORE editing.\n"
194
- f"- Do NOT call architecture_lock again unless the project file tree has changed."
195
- )
196
- _ARCH_LOCK_MEM_CACHE[proj_id] = {"ts": _now, "arch_dir": str(arch_dir), "result": _result}
197
- return (
198
- "[CACHED — architecture lock reused from disk; NOT re-indexed]\n"
199
- + _result
200
- + "\n\n⚠️ Do NOT call architecture_lock again. "
201
- "Use `read_file` on the component blueprints listed above."
202
- )
168
+ try:
169
+ _g = json.loads(brain_file.read_text(encoding="utf-8"))
170
+ _n_files = _g.get("total_files", "?")
171
+ _ptype = ", ".join(_g.get("project_type", [])) or "Unknown"
172
+ except Exception:
173
+ _n_files, _ptype = "?", "Unknown"
174
+ _arch_uri = f"file:///{str(arch_file.absolute()).replace(chr(92), '/')}"
175
+ _result = (
176
+ f"✓ Architecture lock (CACHED — reused, not re-indexed)\n"
177
+ f"Identified stack: {_ptype} | Indexed files: {_n_files} | Age: {int(_age)}s\n"
178
+ f"Architecture file: `{arch_file}`\n"
179
+ f"URI: {_arch_uri}\n\n"
180
+ f"NEXT STEP: Read the architecture with `read_file(\"{arch_file}\")` "
181
+ f"to inspect exact function/class signatures BEFORE editing.\n"
182
+ f"Do NOT call architecture_lock again unless the project file tree has changed."
183
+ )
184
+ _ARCH_LOCK_MEM_CACHE[proj_id] = {"ts": _now, "arch_file": str(arch_file), "result": _result}
185
+ return (
186
+ "[CACHED — architecture lock reused from disk; NOT re-indexed]\n"
187
+ + _result
188
+ + f"\n\n⚠️ Do NOT call architecture_lock again. Read the file at: `{arch_file}`"
189
+ )
203
190
  except Exception:
204
191
  pass # any cache failure → fall through to full index
205
192
 
@@ -310,41 +297,84 @@ def architecture_lock(project_path: str = ".") -> str:
310
297
  md_block += f"```text\n{snippet}\n```\n"
311
298
  clusters[cluster_name].append(md_block)
312
299
 
313
- # Dynamically generate markdown files based on organic clusters
314
- generated_mds = []
315
- for cname, blocks in clusters.items():
316
- if not blocks: continue
317
- safe_cname = re.sub(r'[^a-z0-9_]', '_', cname)
318
- md_content = f"# 🧩 Architecture Component: {cname.title()}\n\n" + "\n".join(blocks)
319
- md_filename = f"component_{safe_cname}.md"
320
- (arch_dir / md_filename).write_text(md_content, encoding="utf-8")
321
- generated_mds.append(md_filename)
322
-
323
- domain_links = ", ".join([
324
- "[file:///{}]".format(str(arch_dir.absolute() / m).replace(chr(92), "/"))
325
- for m in generated_mds
326
- ])
327
- conventions = [
328
- "## Innate Project Architecture Rules",
329
- "- **Organic Component Isolation**: The project is structured organically. Do not bloat central routers or core files.",
330
- "- **Multifile Designs**: When adding large new features, ALWAYS create new standalone component files based on the existing directory structure.",
331
- "- **MANDATORY ARCHITECTURE REVIEW**: You MUST use `read_file` on the relevant domain files below BEFORE exploring the source code manually. They contain the exact structural blueprints you need.",
332
- "- **Available Domain Files**: " + domain_links
300
+ # ── Build single consolidated architecture.md (TOC + all clusters) ──────
301
+ # Remove stale split files from previous multi-file runs
302
+ for _stale in arch_dir.glob("component_*.md"):
303
+ try:
304
+ _stale.unlink()
305
+ except Exception:
306
+ pass
307
+ for _stale in (arch_dir / "conventions_summary.txt",):
308
+ try:
309
+ if _stale.is_file():
310
+ _stale.unlink()
311
+ except Exception:
312
+ pass
313
+
314
+ # Stable ordering for reproducible output
315
+ _ordered_clusters = sorted(clusters.items(), key=lambda kv: kv[0].lower())
316
+ # TOC entries — link to per-cluster anchors inside the single file
317
+ _toc_lines: List[str] = []
318
+ for cname, blocks in _ordered_clusters:
319
+ if not blocks:
320
+ continue
321
+ safe_cname = re.sub(r'[^a-z0-9_]', '_', cname.lower())
322
+ anchor = f"component-{safe_cname}"
323
+ _toc_lines.append(f"- [{cname.title()}](#{anchor}) — {len(blocks)} symbol(s)/note(s)")
324
+
325
+ _header = [
326
+ f"# Project Architecture — `{Path(abs_path).name}`",
327
+ "",
328
+ f"- **Stack:** {', '.join(project_type) or 'Unknown'}",
329
+ f"- **Root:** `{abs_path}`",
330
+ f"- **Indexed files:** {len(inventory)}",
331
+ f"- **Generated:** {arch_file.name} (single consolidated file — all domains inline)",
332
+ "",
333
+ "## Table of Contents",
334
+ "- [Conventions](#conventions)",
335
+ ]
336
+ if _toc_lines:
337
+ _header.extend(_toc_lines)
338
+ _header.extend(["", "---", ""])
339
+
340
+ _conventions = [
341
+ "## Conventions",
342
+ "- **Component isolation**: Keep domains isolated. Do not bloat central routers.",
343
+ "- **Multifile expansion**: For large features, create new standalone files following existing directory structure.",
344
+ "- **Grounding**: This document is the zero-token structural ground truth. Prefer it over blind directory walks.",
345
+ "",
346
+ "---",
347
+ "",
333
348
  ]
334
- (arch_dir / "conventions_summary.txt").write_text("\n".join(conventions), encoding="utf-8")
335
349
 
350
+ _body_parts: List[str] = _header + _conventions
351
+ for cname, blocks in _ordered_clusters:
352
+ if not blocks:
353
+ continue
354
+ safe_cname = re.sub(r'[^a-z0-9_]', '_', cname.lower())
355
+ anchor = f"component-{safe_cname}"
356
+ _body_parts.append(f'<a id="{anchor}"></a>')
357
+ _body_parts.append(f"## 🧩 {cname.title()}")
358
+ _body_parts.append("")
359
+ _body_parts.extend(blocks)
360
+ _body_parts.append("")
361
+ _body_parts.append("---")
362
+ _body_parts.append("")
363
+
364
+ _arch_content = "\n".join(_body_parts).rstrip() + "\n"
365
+ arch_file.write_text(_arch_content, encoding="utf-8")
366
+
367
+ # ── Structural dependency graph (kept for tooling; not the LLM-facing artifact)
336
368
  unique_nodes = {n["id"]: n for n in nodes}.values()
337
-
338
369
  graph_data = {
339
370
  "project_path": abs_path,
340
371
  "project_type": project_type,
341
372
  "total_files": len(inventory),
342
373
  "nodes": list(unique_nodes),
343
374
  "edges": edges,
344
- "capabilities": ["Project orientation", "Graph traversal", "Symbol resolution"]
375
+ "capabilities": ["Project orientation", "Graph traversal", "Symbol resolution"],
345
376
  }
346
-
347
- with open(brain_file, 'w', encoding='utf-8') as bf:
377
+ with open(brain_file, "w", encoding="utf-8") as bf:
348
378
  json.dump(graph_data, bf, indent=2)
349
379
 
350
380
  # Write meta sidecar for disk-cache invalidation (tree hash + timestamp)
@@ -353,24 +383,17 @@ def architecture_lock(project_path: str = ".") -> str:
353
383
  except Exception:
354
384
  pass
355
385
 
356
- component_bullets = "\n".join([
357
- f"- {m}: [file:///{str(arch_dir.absolute() / m).replace(chr(92), '/')}]"
358
- for m in generated_mds
359
- ]) if generated_mds else "- None"
360
-
386
+ _arch_uri = f"file:///{str(arch_file.absolute()).replace(chr(92), '/')}"
361
387
  _result_str = (
362
- f"✓ Architecture lock successfully generated at: {arch_dir}\n"
363
- f"Identified stack: {', '.join(project_type) or 'Unknown'} | Indexed files: {len(inventory)}\n\n"
364
- f"WHAT WAS PROVIDED:\n"
365
- f"1. Structural Dependency Graph: `00_architecture_map.json` (all file nodes, module imports, and symbol definitions)\n"
366
- f"2. Architecture Conventions: `conventions_summary.txt`\n"
367
- f"3. Modular Component Blueprints ({len(generated_mds)} domain modules):\n{component_bullets}\n\n"
368
- f"WHY THIS IS NEEDED & NEXT ACTION:\n"
369
- f"- Zero-token structural grounding eliminates hallucinations and token-wasting blind repo exploration.\n"
370
- f"- NEXT STEP: Use `read_file` on the relevant `component_<domain>.md` blueprint above matching your task to inspect exact function/class signatures and dependencies before reading or editing source code.\n"
371
- f"- Do NOT call architecture_lock again this session — reuse the blueprints above."
388
+ f"✓ Architecture lock successfully generated\n"
389
+ f"Identified stack: {', '.join(project_type) or 'Unknown'} | Indexed files: {len(inventory)}\n"
390
+ f"Architecture file: `{arch_file}`\n"
391
+ f"URI: {_arch_uri}\n\n"
392
+ f"This single file contains ALL domain sections inline (TOC + conventions + {len([b for _, b in _ordered_clusters if b])} component groups).\n"
393
+ f"NEXT STEP: Read it with `read_file(\"{arch_file}\")` to inspect exact function/class signatures and dependencies before editing source code.\n"
394
+ f"Do NOT call architecture_lock again this session — re-use this file."
372
395
  )
373
396
  # Populate in-memory cache so a second call this session is instant
374
- _ARCH_LOCK_MEM_CACHE[proj_id] = {"ts": _now, "arch_dir": str(arch_dir), "result": _result_str}
397
+ _ARCH_LOCK_MEM_CACHE[proj_id] = {"ts": _now, "arch_file": str(arch_file), "result": _result_str}
375
398
  return _result_str
376
399
 
@@ -166,7 +166,7 @@ def get_server_model_catalog(timeout: float = 5.0) -> dict | None:
166
166
  )
167
167
  if resp.status_code == 200:
168
168
  data = resp.json()
169
- if isinstance(data, dict) and data:
169
+ if isinstance(data, dict):
170
170
  _catalog_cache = data
171
171
  _catalog_cache_ts = now
172
172
  return _catalog_cache
@@ -201,7 +201,7 @@ def fetch_remote_models_catalog(timeout: float = 3.0) -> dict:
201
201
  resp = requests.get(url, timeout=timeout)
202
202
  if resp.status_code == 200:
203
203
  models_list = resp.json()
204
- if models_list and isinstance(models_list, list):
204
+ if isinstance(models_list, list):
205
205
  from utim_cli.constants import APINEX_MODEL_IDS
206
206
  valid_models = [
207
207
  m for m in models_list
@@ -261,7 +261,7 @@ def fetch_remote_models_catalog(timeout: float = 3.0) -> dict:
261
261
  resp = requests.get(url, params=params, headers=get_auth_headers(), timeout=timeout)
262
262
  if resp.status_code == 200:
263
263
  data = resp.json()
264
- if data and isinstance(data, dict):
264
+ if isinstance(data, dict):
265
265
  from utim_cli.constants import APINEX_MODEL_IDS
266
266
  filtered_cat = {
267
267
  k: [
@@ -281,7 +281,7 @@ def fetch_remote_models_catalog(timeout: float = 3.0) -> dict:
281
281
  try:
282
282
  from utim_cli.server.models import get_model_catalog
283
283
  data = get_model_catalog()
284
- if data:
284
+ if isinstance(data, dict):
285
285
  _REMOTE_CATALOG_CACHE = data
286
286
  _REMOTE_CATALOG_CACHE_TIME = now
287
287
  return data
@@ -312,7 +312,7 @@ def _build_free_models_fallback_chain(now: float) -> list[str]:
312
312
  resp = _req.get(f"{SERVER_URL.rstrip('/')}/models", timeout=3.0)
313
313
  if resp.status_code == 200:
314
314
  models_list = resp.json()
315
- if models_list and isinstance(models_list, list):
315
+ if isinstance(models_list, list):
316
316
  from utim_cli.constants import APINEX_MODEL_IDS
317
317
  for m in models_list:
318
318
  mid = str(m.get("model_id") or "")
@@ -407,6 +407,86 @@ def resolve_model_provider_endpoint(model_id: Optional[str]) -> Optional[Dict[st
407
407
  except Exception:
408
408
  pass
409
409
 
410
+ # 1b. Server ModelEndpointDB and Separated Models (configured via admin_models.html)
411
+ try:
412
+ from utim_cli.server.db import SessionLocal, ModelEndpointDB, SEPARATED_MODEL_CLASSES
413
+ sdb = SessionLocal()
414
+ try:
415
+ cand_ids = {clean_id, clean_id.lower()}
416
+ stripped = clean_id
417
+ for pref in ("am/", "api/", "ag/"):
418
+ if clean_id.startswith(pref):
419
+ stripped = clean_id[len(pref):].strip()
420
+ break
421
+ cand_ids.add(stripped)
422
+ cand_ids.add(stripped.lower())
423
+ for pref in ("am/", "api/", "ag/"):
424
+ cand_ids.add(f"{pref}{stripped}")
425
+ cand_ids.add(f"{pref}{stripped}".lower())
426
+ if "/" in stripped:
427
+ base_part = stripped.split("/")[-1].strip()
428
+ cand_ids.add(base_part)
429
+ cand_ids.add(base_part.lower())
430
+ for pref in ("am/", "api/", "ag/"):
431
+ cand_ids.add(f"{pref}{base_part}")
432
+ cand_ids.add(f"{pref}{base_part}".lower())
433
+ if "/" in clean_id:
434
+ base_raw = clean_id.split("/")[-1].strip()
435
+ cand_ids.add(base_raw)
436
+ cand_ids.add(base_raw.lower())
437
+ for pref in ("am/", "api/", "ag/"):
438
+ cand_ids.add(f"{pref}{base_raw}")
439
+ cand_ids.add(f"{pref}{base_raw}".lower())
440
+
441
+ ep = sdb.query(ModelEndpointDB).filter(
442
+ ModelEndpointDB.model_id.in_(cand_ids),
443
+ (ModelEndpointDB.is_active == True) | (ModelEndpointDB.is_active.is_(None)),
444
+ ModelEndpointDB.endpoint_url.isnot(None),
445
+ ModelEndpointDB.endpoint_url != ""
446
+ ).order_by(ModelEndpointDB.priority.asc()).first()
447
+ if ep and ep.endpoint_url:
448
+ base = ep.endpoint_url.rstrip("/")
449
+ url = base if base.endswith("/chat/completions") else f"{base}/chat/completions"
450
+ keys = ep.get_api_keys()
451
+ key = keys[0] if keys else ""
452
+ raw_up = (getattr(ep, "upstream_model_id", None) or "").strip()
453
+ target_m = raw_up if raw_up else (ep.model_id or clean_id)
454
+ return {
455
+ "url": url,
456
+ "base_url": base,
457
+ "api_key": key,
458
+ "auth_style": "bearer",
459
+ "target_model": target_m,
460
+ "provider_name": ep.provider_id or "custom",
461
+ }
462
+
463
+ for s_cls in SEPARATED_MODEL_CLASSES:
464
+ m_row = sdb.query(s_cls).filter(
465
+ s_cls.model_id.in_(cand_ids),
466
+ (s_cls.is_active == True) | (s_cls.is_active.is_(None)),
467
+ s_cls.endpoint_url.isnot(None),
468
+ s_cls.endpoint_url != ""
469
+ ).first()
470
+ if m_row and getattr(m_row, "endpoint_url", None):
471
+ base = m_row.endpoint_url.rstrip("/")
472
+ url = base if base.endswith("/chat/completions") else f"{base}/chat/completions"
473
+ keys = m_row.get_api_keys() if hasattr(m_row, "get_api_keys") else []
474
+ key = keys[0] if keys else ""
475
+ raw_up = (getattr(m_row, "upstream_model_id", None) or "").strip()
476
+ target_m = raw_up if raw_up else (m_row.model_id or clean_id)
477
+ return {
478
+ "url": url,
479
+ "base_url": base,
480
+ "api_key": key,
481
+ "auth_style": "bearer",
482
+ "target_model": target_m,
483
+ "provider_name": getattr(m_row, "provider", "custom") or "custom",
484
+ }
485
+ finally:
486
+ sdb.close()
487
+ except Exception:
488
+ pass
489
+
410
490
  # 2. Server ApiModel database (upstream provider attached in admin_models.html)
411
491
  try:
412
492
  from utim_cli.server.api_models_db import ApiSessionLocal, ApiModel, find_canonical_model
@@ -902,12 +982,15 @@ def sanitize_tools_for_api(tools: Optional[List[Dict[str, Any]]], model_id: Opti
902
982
  if k in disallowed_keys:
903
983
  continue
904
984
 
905
- # Standardize JSON schema type names to lowercase
985
+ _valid = {"string", "number", "integer", "boolean", "array", "object", "null"}
906
986
  if k == "type":
907
987
  if isinstance(v, str):
908
- v = TYPE_MAP.get(v, v.lower())
988
+ _t = TYPE_MAP.get(v, v.lower())
989
+ v = _t if _t in _valid else "string"
909
990
  elif isinstance(v, list):
910
- v = [TYPE_MAP.get(item, item.lower()) if isinstance(item, str) else item for item in v]
991
+ _lst = [TYPE_MAP.get(item, item.lower()) if isinstance(item, str) else item for item in v]
992
+ _lst = [x for x in _lst if x in _valid]
993
+ v = _lst[0] if len(_lst)==1 else (_lst if _lst else "string")
911
994
 
912
995
  if isinstance(v, dict):
913
996
  cleaned[k] = _clean_schema_node(v)