alita-sdk 0.3.462__py3-none-any.whl → 0.3.627__py3-none-any.whl

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 (261) hide show
  1. alita_sdk/cli/agent/__init__.py +5 -0
  2. alita_sdk/cli/agent/default.py +258 -0
  3. alita_sdk/cli/agent_executor.py +15 -3
  4. alita_sdk/cli/agent_loader.py +56 -8
  5. alita_sdk/cli/agent_ui.py +93 -31
  6. alita_sdk/cli/agents.py +2274 -230
  7. alita_sdk/cli/callbacks.py +96 -25
  8. alita_sdk/cli/cli.py +10 -1
  9. alita_sdk/cli/config.py +162 -9
  10. alita_sdk/cli/context/__init__.py +30 -0
  11. alita_sdk/cli/context/cleanup.py +198 -0
  12. alita_sdk/cli/context/manager.py +731 -0
  13. alita_sdk/cli/context/message.py +285 -0
  14. alita_sdk/cli/context/strategies.py +289 -0
  15. alita_sdk/cli/context/token_estimation.py +127 -0
  16. alita_sdk/cli/input_handler.py +419 -0
  17. alita_sdk/cli/inventory.py +1073 -0
  18. alita_sdk/cli/testcases/__init__.py +94 -0
  19. alita_sdk/cli/testcases/data_generation.py +119 -0
  20. alita_sdk/cli/testcases/discovery.py +96 -0
  21. alita_sdk/cli/testcases/executor.py +84 -0
  22. alita_sdk/cli/testcases/logger.py +85 -0
  23. alita_sdk/cli/testcases/parser.py +172 -0
  24. alita_sdk/cli/testcases/prompts.py +91 -0
  25. alita_sdk/cli/testcases/reporting.py +125 -0
  26. alita_sdk/cli/testcases/setup.py +108 -0
  27. alita_sdk/cli/testcases/test_runner.py +282 -0
  28. alita_sdk/cli/testcases/utils.py +39 -0
  29. alita_sdk/cli/testcases/validation.py +90 -0
  30. alita_sdk/cli/testcases/workflow.py +196 -0
  31. alita_sdk/cli/toolkit.py +14 -17
  32. alita_sdk/cli/toolkit_loader.py +35 -5
  33. alita_sdk/cli/tools/__init__.py +36 -2
  34. alita_sdk/cli/tools/approval.py +224 -0
  35. alita_sdk/cli/tools/filesystem.py +910 -64
  36. alita_sdk/cli/tools/planning.py +389 -0
  37. alita_sdk/cli/tools/terminal.py +414 -0
  38. alita_sdk/community/__init__.py +72 -12
  39. alita_sdk/community/inventory/__init__.py +236 -0
  40. alita_sdk/community/inventory/config.py +257 -0
  41. alita_sdk/community/inventory/enrichment.py +2137 -0
  42. alita_sdk/community/inventory/extractors.py +1469 -0
  43. alita_sdk/community/inventory/ingestion.py +3172 -0
  44. alita_sdk/community/inventory/knowledge_graph.py +1457 -0
  45. alita_sdk/community/inventory/parsers/__init__.py +218 -0
  46. alita_sdk/community/inventory/parsers/base.py +295 -0
  47. alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
  48. alita_sdk/community/inventory/parsers/go_parser.py +851 -0
  49. alita_sdk/community/inventory/parsers/html_parser.py +389 -0
  50. alita_sdk/community/inventory/parsers/java_parser.py +593 -0
  51. alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
  52. alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
  53. alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
  54. alita_sdk/community/inventory/parsers/python_parser.py +604 -0
  55. alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
  56. alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
  57. alita_sdk/community/inventory/parsers/text_parser.py +322 -0
  58. alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
  59. alita_sdk/community/inventory/patterns/__init__.py +61 -0
  60. alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
  61. alita_sdk/community/inventory/patterns/loader.py +348 -0
  62. alita_sdk/community/inventory/patterns/registry.py +198 -0
  63. alita_sdk/community/inventory/presets.py +535 -0
  64. alita_sdk/community/inventory/retrieval.py +1403 -0
  65. alita_sdk/community/inventory/toolkit.py +173 -0
  66. alita_sdk/community/inventory/toolkit_utils.py +176 -0
  67. alita_sdk/community/inventory/visualize.py +1370 -0
  68. alita_sdk/configurations/__init__.py +1 -1
  69. alita_sdk/configurations/ado.py +141 -20
  70. alita_sdk/configurations/bitbucket.py +0 -3
  71. alita_sdk/configurations/confluence.py +76 -42
  72. alita_sdk/configurations/figma.py +76 -0
  73. alita_sdk/configurations/gitlab.py +17 -5
  74. alita_sdk/configurations/openapi.py +329 -0
  75. alita_sdk/configurations/qtest.py +72 -1
  76. alita_sdk/configurations/report_portal.py +96 -0
  77. alita_sdk/configurations/sharepoint.py +148 -0
  78. alita_sdk/configurations/testio.py +83 -0
  79. alita_sdk/runtime/clients/artifact.py +3 -3
  80. alita_sdk/runtime/clients/client.py +353 -48
  81. alita_sdk/runtime/clients/sandbox_client.py +0 -21
  82. alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
  83. alita_sdk/runtime/langchain/assistant.py +123 -26
  84. alita_sdk/runtime/langchain/constants.py +642 -1
  85. alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +103 -60
  86. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
  87. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +6 -3
  88. alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +226 -7
  89. alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +5 -2
  90. alita_sdk/runtime/langchain/document_loaders/constants.py +12 -7
  91. alita_sdk/runtime/langchain/langraph_agent.py +279 -73
  92. alita_sdk/runtime/langchain/utils.py +82 -15
  93. alita_sdk/runtime/llms/preloaded.py +2 -6
  94. alita_sdk/runtime/skills/__init__.py +91 -0
  95. alita_sdk/runtime/skills/callbacks.py +498 -0
  96. alita_sdk/runtime/skills/discovery.py +540 -0
  97. alita_sdk/runtime/skills/executor.py +610 -0
  98. alita_sdk/runtime/skills/input_builder.py +371 -0
  99. alita_sdk/runtime/skills/models.py +330 -0
  100. alita_sdk/runtime/skills/registry.py +355 -0
  101. alita_sdk/runtime/skills/skill_runner.py +330 -0
  102. alita_sdk/runtime/toolkits/__init__.py +7 -0
  103. alita_sdk/runtime/toolkits/application.py +21 -9
  104. alita_sdk/runtime/toolkits/artifact.py +15 -5
  105. alita_sdk/runtime/toolkits/datasource.py +13 -6
  106. alita_sdk/runtime/toolkits/mcp.py +139 -251
  107. alita_sdk/runtime/toolkits/mcp_config.py +1048 -0
  108. alita_sdk/runtime/toolkits/planning.py +178 -0
  109. alita_sdk/runtime/toolkits/skill_router.py +238 -0
  110. alita_sdk/runtime/toolkits/subgraph.py +251 -6
  111. alita_sdk/runtime/toolkits/tools.py +238 -32
  112. alita_sdk/runtime/toolkits/vectorstore.py +11 -5
  113. alita_sdk/runtime/tools/__init__.py +3 -1
  114. alita_sdk/runtime/tools/application.py +20 -6
  115. alita_sdk/runtime/tools/artifact.py +511 -28
  116. alita_sdk/runtime/tools/data_analysis.py +183 -0
  117. alita_sdk/runtime/tools/function.py +43 -15
  118. alita_sdk/runtime/tools/image_generation.py +50 -44
  119. alita_sdk/runtime/tools/llm.py +852 -67
  120. alita_sdk/runtime/tools/loop.py +3 -1
  121. alita_sdk/runtime/tools/loop_output.py +3 -1
  122. alita_sdk/runtime/tools/mcp_remote_tool.py +25 -10
  123. alita_sdk/runtime/tools/mcp_server_tool.py +7 -6
  124. alita_sdk/runtime/tools/planning/__init__.py +36 -0
  125. alita_sdk/runtime/tools/planning/models.py +246 -0
  126. alita_sdk/runtime/tools/planning/wrapper.py +607 -0
  127. alita_sdk/runtime/tools/router.py +2 -4
  128. alita_sdk/runtime/tools/sandbox.py +9 -6
  129. alita_sdk/runtime/tools/skill_router.py +776 -0
  130. alita_sdk/runtime/tools/tool.py +3 -1
  131. alita_sdk/runtime/tools/vectorstore.py +7 -2
  132. alita_sdk/runtime/tools/vectorstore_base.py +51 -11
  133. alita_sdk/runtime/utils/AlitaCallback.py +137 -21
  134. alita_sdk/runtime/utils/constants.py +5 -1
  135. alita_sdk/runtime/utils/mcp_client.py +492 -0
  136. alita_sdk/runtime/utils/mcp_oauth.py +202 -5
  137. alita_sdk/runtime/utils/mcp_sse_client.py +36 -7
  138. alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
  139. alita_sdk/runtime/utils/serialization.py +155 -0
  140. alita_sdk/runtime/utils/streamlit.py +6 -10
  141. alita_sdk/runtime/utils/toolkit_utils.py +16 -5
  142. alita_sdk/runtime/utils/utils.py +36 -0
  143. alita_sdk/tools/__init__.py +113 -29
  144. alita_sdk/tools/ado/repos/__init__.py +51 -33
  145. alita_sdk/tools/ado/repos/repos_wrapper.py +148 -89
  146. alita_sdk/tools/ado/test_plan/__init__.py +25 -9
  147. alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +23 -1
  148. alita_sdk/tools/ado/utils.py +1 -18
  149. alita_sdk/tools/ado/wiki/__init__.py +25 -8
  150. alita_sdk/tools/ado/wiki/ado_wrapper.py +291 -22
  151. alita_sdk/tools/ado/work_item/__init__.py +26 -9
  152. alita_sdk/tools/ado/work_item/ado_wrapper.py +56 -3
  153. alita_sdk/tools/advanced_jira_mining/__init__.py +11 -8
  154. alita_sdk/tools/aws/delta_lake/__init__.py +13 -9
  155. alita_sdk/tools/aws/delta_lake/tool.py +5 -1
  156. alita_sdk/tools/azure_ai/search/__init__.py +11 -8
  157. alita_sdk/tools/azure_ai/search/api_wrapper.py +1 -1
  158. alita_sdk/tools/base/tool.py +5 -1
  159. alita_sdk/tools/base_indexer_toolkit.py +170 -45
  160. alita_sdk/tools/bitbucket/__init__.py +17 -12
  161. alita_sdk/tools/bitbucket/api_wrapper.py +59 -11
  162. alita_sdk/tools/bitbucket/cloud_api_wrapper.py +49 -35
  163. alita_sdk/tools/browser/__init__.py +5 -4
  164. alita_sdk/tools/carrier/__init__.py +5 -6
  165. alita_sdk/tools/carrier/backend_reports_tool.py +6 -6
  166. alita_sdk/tools/carrier/run_ui_test_tool.py +6 -6
  167. alita_sdk/tools/carrier/ui_reports_tool.py +5 -5
  168. alita_sdk/tools/chunkers/__init__.py +3 -1
  169. alita_sdk/tools/chunkers/code/treesitter/treesitter.py +37 -13
  170. alita_sdk/tools/chunkers/sematic/json_chunker.py +1 -0
  171. alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
  172. alita_sdk/tools/chunkers/universal_chunker.py +270 -0
  173. alita_sdk/tools/cloud/aws/__init__.py +10 -7
  174. alita_sdk/tools/cloud/azure/__init__.py +10 -7
  175. alita_sdk/tools/cloud/gcp/__init__.py +10 -7
  176. alita_sdk/tools/cloud/k8s/__init__.py +10 -7
  177. alita_sdk/tools/code/linter/__init__.py +10 -8
  178. alita_sdk/tools/code/loaders/codesearcher.py +3 -2
  179. alita_sdk/tools/code/sonar/__init__.py +10 -7
  180. alita_sdk/tools/code_indexer_toolkit.py +73 -23
  181. alita_sdk/tools/confluence/__init__.py +21 -15
  182. alita_sdk/tools/confluence/api_wrapper.py +78 -23
  183. alita_sdk/tools/confluence/loader.py +4 -2
  184. alita_sdk/tools/custom_open_api/__init__.py +12 -5
  185. alita_sdk/tools/elastic/__init__.py +11 -8
  186. alita_sdk/tools/elitea_base.py +493 -30
  187. alita_sdk/tools/figma/__init__.py +58 -11
  188. alita_sdk/tools/figma/api_wrapper.py +1235 -143
  189. alita_sdk/tools/figma/figma_client.py +73 -0
  190. alita_sdk/tools/figma/toon_tools.py +2748 -0
  191. alita_sdk/tools/github/__init__.py +13 -14
  192. alita_sdk/tools/github/github_client.py +224 -100
  193. alita_sdk/tools/github/graphql_client_wrapper.py +119 -33
  194. alita_sdk/tools/github/schemas.py +14 -5
  195. alita_sdk/tools/github/tool.py +5 -1
  196. alita_sdk/tools/github/tool_prompts.py +9 -22
  197. alita_sdk/tools/gitlab/__init__.py +15 -11
  198. alita_sdk/tools/gitlab/api_wrapper.py +207 -41
  199. alita_sdk/tools/gitlab_org/__init__.py +10 -8
  200. alita_sdk/tools/gitlab_org/api_wrapper.py +63 -64
  201. alita_sdk/tools/google/bigquery/__init__.py +13 -12
  202. alita_sdk/tools/google/bigquery/tool.py +5 -1
  203. alita_sdk/tools/google_places/__init__.py +10 -8
  204. alita_sdk/tools/google_places/api_wrapper.py +1 -1
  205. alita_sdk/tools/jira/__init__.py +17 -11
  206. alita_sdk/tools/jira/api_wrapper.py +91 -40
  207. alita_sdk/tools/keycloak/__init__.py +11 -8
  208. alita_sdk/tools/localgit/__init__.py +9 -3
  209. alita_sdk/tools/localgit/local_git.py +62 -54
  210. alita_sdk/tools/localgit/tool.py +5 -1
  211. alita_sdk/tools/memory/__init__.py +11 -3
  212. alita_sdk/tools/non_code_indexer_toolkit.py +1 -0
  213. alita_sdk/tools/ocr/__init__.py +11 -8
  214. alita_sdk/tools/openapi/__init__.py +490 -114
  215. alita_sdk/tools/openapi/api_wrapper.py +1368 -0
  216. alita_sdk/tools/openapi/tool.py +20 -0
  217. alita_sdk/tools/pandas/__init__.py +20 -12
  218. alita_sdk/tools/pandas/api_wrapper.py +38 -25
  219. alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
  220. alita_sdk/tools/postman/__init__.py +11 -11
  221. alita_sdk/tools/pptx/__init__.py +10 -9
  222. alita_sdk/tools/pptx/pptx_wrapper.py +1 -1
  223. alita_sdk/tools/qtest/__init__.py +30 -10
  224. alita_sdk/tools/qtest/api_wrapper.py +430 -13
  225. alita_sdk/tools/rally/__init__.py +10 -8
  226. alita_sdk/tools/rally/api_wrapper.py +1 -1
  227. alita_sdk/tools/report_portal/__init__.py +12 -9
  228. alita_sdk/tools/salesforce/__init__.py +10 -9
  229. alita_sdk/tools/servicenow/__init__.py +17 -14
  230. alita_sdk/tools/servicenow/api_wrapper.py +1 -1
  231. alita_sdk/tools/sharepoint/__init__.py +10 -8
  232. alita_sdk/tools/sharepoint/api_wrapper.py +4 -4
  233. alita_sdk/tools/slack/__init__.py +10 -8
  234. alita_sdk/tools/slack/api_wrapper.py +2 -2
  235. alita_sdk/tools/sql/__init__.py +11 -9
  236. alita_sdk/tools/testio/__init__.py +10 -8
  237. alita_sdk/tools/testrail/__init__.py +11 -8
  238. alita_sdk/tools/testrail/api_wrapper.py +1 -1
  239. alita_sdk/tools/utils/__init__.py +9 -4
  240. alita_sdk/tools/utils/content_parser.py +77 -3
  241. alita_sdk/tools/utils/text_operations.py +410 -0
  242. alita_sdk/tools/utils/tool_prompts.py +79 -0
  243. alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +17 -13
  244. alita_sdk/tools/xray/__init__.py +12 -9
  245. alita_sdk/tools/yagmail/__init__.py +9 -3
  246. alita_sdk/tools/zephyr/__init__.py +9 -7
  247. alita_sdk/tools/zephyr_enterprise/__init__.py +11 -8
  248. alita_sdk/tools/zephyr_essential/__init__.py +10 -8
  249. alita_sdk/tools/zephyr_essential/api_wrapper.py +30 -13
  250. alita_sdk/tools/zephyr_essential/client.py +2 -2
  251. alita_sdk/tools/zephyr_scale/__init__.py +11 -9
  252. alita_sdk/tools/zephyr_scale/api_wrapper.py +2 -2
  253. alita_sdk/tools/zephyr_squad/__init__.py +10 -8
  254. {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/METADATA +147 -7
  255. alita_sdk-0.3.627.dist-info/RECORD +468 -0
  256. alita_sdk-0.3.627.dist-info/entry_points.txt +2 -0
  257. alita_sdk-0.3.462.dist-info/RECORD +0 -384
  258. alita_sdk-0.3.462.dist-info/entry_points.txt +0 -2
  259. {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/WHEEL +0 -0
  260. {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/licenses/LICENSE +0 -0
  261. {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,419 @@
1
+ """
2
+ Enhanced input handler with readline support.
3
+
4
+ Provides tab completion for commands, cursor movement, and input history.
5
+ """
6
+
7
+ import readline
8
+ import os
9
+ from pathlib import Path
10
+ from typing import Dict, List, Optional, Any, Callable
11
+
12
+ from rich.console import Console
13
+ from rich.text import Text
14
+
15
+ console = Console()
16
+
17
+ # Available commands for autocompletion
18
+ CHAT_COMMANDS = [
19
+ '/help',
20
+ '/clear',
21
+ '/history',
22
+ '/save',
23
+ '/agent',
24
+ '/model',
25
+ '/mode',
26
+ '/mode always',
27
+ '/mode auto',
28
+ '/mode yolo',
29
+ '/dir',
30
+ '/dir add',
31
+ '/dir rm',
32
+ '/dir remove',
33
+ '/inventory',
34
+ '/session',
35
+ '/session list',
36
+ '/session resume',
37
+ '/add_mcp',
38
+ '/add_toolkit',
39
+ '/rm_mcp',
40
+ '/rm_toolkit',
41
+ '/reload',
42
+ 'exit',
43
+ 'quit',
44
+ ]
45
+
46
+
47
+ # Callback to get dynamic toolkit names for completion
48
+ _toolkit_names_callback: Optional[Callable[[], List[str]]] = None
49
+
50
+ # Callback to get inventory .json files for completion
51
+ _inventory_files_callback: Optional[Callable[[], List[str]]] = None
52
+
53
+
54
+ def set_toolkit_names_callback(callback: Callable[[], List[str]]):
55
+ """
56
+ Set a callback function that returns available toolkit names.
57
+
58
+ This allows the input handler to provide dynamic completions
59
+ for /add_toolkit without having a direct dependency on config.
60
+ """
61
+ global _toolkit_names_callback
62
+ _toolkit_names_callback = callback
63
+
64
+
65
+ def set_inventory_files_callback(callback: Callable[[], List[str]]):
66
+ """
67
+ Set a callback function that returns available inventory .json files.
68
+
69
+ This allows the input handler to provide dynamic completions
70
+ for /inventory without having a direct dependency on agents.py.
71
+ """
72
+ global _inventory_files_callback
73
+ _inventory_files_callback = callback
74
+
75
+
76
+ def get_toolkit_names_for_completion() -> List[str]:
77
+ """Get toolkit names for tab completion."""
78
+ global _toolkit_names_callback
79
+ if _toolkit_names_callback:
80
+ try:
81
+ return _toolkit_names_callback()
82
+ except Exception:
83
+ return []
84
+ return []
85
+
86
+
87
+ def get_inventory_files_for_completion() -> List[str]:
88
+ """Get inventory .json files for tab completion."""
89
+ global _inventory_files_callback
90
+ if _inventory_files_callback:
91
+ try:
92
+ return _inventory_files_callback()
93
+ except Exception:
94
+ return []
95
+ return []
96
+
97
+
98
+ class ChatInputHandler:
99
+ """
100
+ Enhanced input handler with readline support for chat sessions.
101
+
102
+ Features:
103
+ - Tab completion for slash commands
104
+ - Arrow key navigation through input history
105
+ - Cursor movement with left/right arrows
106
+ - Ctrl+A (start of line), Ctrl+E (end of line)
107
+ - Persistent command history within session
108
+ - Material UI-style input prompt
109
+ """
110
+
111
+ def __init__(self):
112
+ self._setup_readline()
113
+ self._input_history: List[str] = []
114
+
115
+ def _setup_readline(self):
116
+ """Configure readline for enhanced input."""
117
+ # Set up tab completion
118
+ readline.set_completer(self._completer)
119
+
120
+ # Detect if we're using libedit (macOS) or GNU readline (Linux)
121
+ # libedit uses different syntax for parse_and_bind
122
+ if 'libedit' in readline.__doc__:
123
+ # macOS libedit syntax
124
+ readline.parse_and_bind('bind ^I rl_complete')
125
+ else:
126
+ # GNU readline syntax
127
+ readline.parse_and_bind('tab: complete')
128
+
129
+ # Enable emacs-style keybindings (Ctrl+A, Ctrl+E, etc.)
130
+ # This is usually the default on macOS/Linux
131
+ try:
132
+ if 'libedit' not in readline.__doc__:
133
+ readline.parse_and_bind('set editing-mode emacs')
134
+ except Exception:
135
+ pass # Some systems might not support this
136
+
137
+ # Set completion display style (GNU readline only)
138
+ try:
139
+ if 'libedit' not in readline.__doc__:
140
+ readline.parse_and_bind('set show-all-if-ambiguous on')
141
+ readline.parse_and_bind('set completion-ignore-case on')
142
+ except Exception:
143
+ pass
144
+
145
+ # Set delimiters for completion (space and common punctuation)
146
+ readline.set_completer_delims(' \t\n;')
147
+
148
+ def _completer(self, text: str, state: int) -> Optional[str]:
149
+ """
150
+ Readline completer function for slash commands.
151
+
152
+ Args:
153
+ text: The current text being completed
154
+ state: The state of completion (0 for first match, 1 for second, etc.)
155
+
156
+ Returns:
157
+ The next matching command or None
158
+ """
159
+ # Get the full line buffer
160
+ line = readline.get_line_buffer()
161
+
162
+ # Only complete at the start of the line or after whitespace
163
+ if line and not line.startswith('/') and text != line:
164
+ return None
165
+
166
+ matches = []
167
+
168
+ # Handle /add_toolkit <name> completion
169
+ if line.startswith('/add_toolkit '):
170
+ # Get partial toolkit name being typed
171
+ toolkit_prefix = text.lower()
172
+ toolkit_names = get_toolkit_names_for_completion()
173
+ matches = [f'/add_toolkit {name}' for name in toolkit_names
174
+ if name.lower().startswith(toolkit_prefix) or toolkit_prefix == '']
175
+ # Also match just the toolkit name if text doesn't start with /
176
+ if not text.startswith('/'):
177
+ matches = [name for name in toolkit_names if name.lower().startswith(toolkit_prefix)]
178
+ # Handle /inventory <path> completion
179
+ elif line.startswith('/inventory '):
180
+ # Get partial path being typed
181
+ path_prefix = text.lower()
182
+ inventory_files = get_inventory_files_for_completion()
183
+ matches = [f'/inventory {path}' for path in inventory_files
184
+ if path.lower().startswith(path_prefix) or path_prefix == '']
185
+ # Also match just the path if text doesn't start with /
186
+ if not text.startswith('/'):
187
+ matches = [path for path in inventory_files if path.lower().startswith(path_prefix)]
188
+ # Find matching commands
189
+ elif text.startswith('/'):
190
+ matches = [cmd for cmd in CHAT_COMMANDS if cmd.startswith(text)]
191
+ elif text == '' and line == '':
192
+ # Show all commands on empty tab
193
+ matches = [cmd for cmd in CHAT_COMMANDS if cmd.startswith('/')]
194
+ else:
195
+ matches = [cmd for cmd in CHAT_COMMANDS if cmd.startswith(text)]
196
+
197
+ # Return the match at the given state
198
+ if state < len(matches):
199
+ return matches[state]
200
+ return None
201
+
202
+ def get_input(self, prompt: str = "") -> str:
203
+ """
204
+ Get user input with enhanced readline features.
205
+
206
+ Args:
207
+ prompt: The prompt to display (note: for rich console, prompt is printed separately)
208
+
209
+ Returns:
210
+ The user's input string
211
+ """
212
+ try:
213
+ user_input = input(prompt)
214
+
215
+ # Add non-empty, non-duplicate inputs to history
216
+ if user_input.strip() and (not self._input_history or
217
+ self._input_history[-1] != user_input):
218
+ self._input_history.append(user_input)
219
+ readline.add_history(user_input)
220
+
221
+ return user_input
222
+ except (KeyboardInterrupt, EOFError):
223
+ raise
224
+
225
+ def clear_history(self):
226
+ """Clear the input history."""
227
+ self._input_history.clear()
228
+ readline.clear_history()
229
+
230
+ @property
231
+ def history(self) -> List[str]:
232
+ """Get the current input history."""
233
+ return self._input_history.copy()
234
+
235
+
236
+ # Global instance for use across the CLI
237
+ _input_handler: Optional[ChatInputHandler] = None
238
+
239
+
240
+ def get_input_handler() -> ChatInputHandler:
241
+ """Get or create the global input handler instance."""
242
+ global _input_handler
243
+ if _input_handler is None:
244
+ _input_handler = ChatInputHandler()
245
+ return _input_handler
246
+
247
+
248
+ def chat_input(prompt: str = "") -> str:
249
+ """
250
+ Convenience function for getting chat input with enhanced features.
251
+
252
+ Args:
253
+ prompt: The prompt to display
254
+
255
+ Returns:
256
+ The user's input string
257
+ """
258
+ return get_input_handler().get_input(prompt)
259
+
260
+
261
+ def styled_input(context_info: Optional[Dict[str, Any]] = None) -> str:
262
+ """
263
+ Get user input with a styled bordered prompt that works correctly with readline.
264
+
265
+ The prompt is passed directly to input() so readline can properly
266
+ handle cursor positioning and history navigation.
267
+
268
+ Args:
269
+ context_info: Optional context info dict with keys:
270
+ - used_tokens: Current tokens in context
271
+ - max_tokens: Maximum allowed tokens
272
+ - fill_ratio: Context fill ratio (0.0-1.0)
273
+ - pruned_count: Number of pruned messages
274
+
275
+ Returns:
276
+ The user's input string
277
+ """
278
+ # Get terminal width for the border
279
+ try:
280
+ width = console.width - 2
281
+ except Exception:
282
+ width = 78
283
+
284
+ # Build context indicator if provided
285
+ context_indicator = ""
286
+ if context_info and context_info.get('max_tokens', 0) > 0:
287
+ context_indicator = _format_context_indicator(
288
+ context_info.get('used_tokens', 0),
289
+ context_info.get('max_tokens', 8000),
290
+ context_info.get('fill_ratio', 0.0),
291
+ context_info.get('pruned_count', 0),
292
+ )
293
+
294
+ # Print the complete box frame first, then move cursor up to input line
295
+ console.print()
296
+ console.print(f"[dim]╭{'─' * width}╮[/dim]")
297
+ console.print(f"[dim]│[/dim]{' ' * width}[dim]│[/dim]")
298
+
299
+ # Bottom border with context indicator on the right
300
+ if context_indicator:
301
+ indicator_len = len(_strip_ansi(context_indicator))
302
+ padding = width - indicator_len - 1
303
+ console.print(f"[dim]╰{'─' * padding}[/dim]{context_indicator}[dim]─╯[/dim]")
304
+ else:
305
+ console.print(f"[dim]╰{'─' * width}╯[/dim]")
306
+
307
+ # Move cursor up 2 lines and to position after "│ > "
308
+ # \033[2A = move up 2 lines, \033[4C = move right 4 columns
309
+ prompt = "\033[2A\033[2C > "
310
+
311
+ user_input = get_input_handler().get_input(prompt)
312
+
313
+ # Move cursor down to after the box
314
+ console.print()
315
+
316
+ return user_input
317
+
318
+
319
+ def _format_context_indicator(
320
+ used_tokens: int,
321
+ max_tokens: int,
322
+ fill_ratio: float,
323
+ pruned_count: int = 0
324
+ ) -> str:
325
+ """
326
+ Format context usage indicator for display.
327
+
328
+ Shows: [1234/8000 ██████░░░░ 15%]
329
+ Color coded: green <70%, yellow 70-90%, red >90%
330
+
331
+ Args:
332
+ used_tokens: Current tokens in context
333
+ max_tokens: Maximum allowed tokens
334
+ fill_ratio: Context fill ratio (0.0-1.0)
335
+ pruned_count: Number of pruned messages
336
+
337
+ Returns:
338
+ Formatted indicator string with ANSI colors
339
+ """
340
+ # Determine color based on fill ratio
341
+ if fill_ratio < 0.7:
342
+ color = "green"
343
+ elif fill_ratio < 0.9:
344
+ color = "yellow"
345
+ else:
346
+ color = "red"
347
+
348
+ # Build progress bar (10 chars)
349
+ bar_width = 10
350
+ filled = int(fill_ratio * bar_width)
351
+ empty = bar_width - filled
352
+ bar = "█" * filled + "░" * empty
353
+
354
+ # Format percentage
355
+ percent = int(fill_ratio * 100)
356
+
357
+ # Build indicator
358
+ indicator = f"[{color}]{used_tokens}/{max_tokens} {bar} {percent}%[/{color}]"
359
+
360
+ # Add pruned count if any
361
+ if pruned_count > 0:
362
+ indicator += f" [dim]({pruned_count} pruned)[/dim]"
363
+
364
+ return indicator
365
+
366
+
367
+ def _strip_ansi(text: str) -> str:
368
+ """Strip ANSI escape codes and Rich markup from text for length calculation."""
369
+ import re
370
+ # Remove Rich markup tags like [green], [/green], [dim], etc.
371
+ text = re.sub(r'\[/?[^\]]+\]', '', text)
372
+ # Remove ANSI escape codes
373
+ text = re.sub(r'\x1b\[[0-9;]*m', '', text)
374
+ return text
375
+
376
+
377
+ def styled_selection_input(prompt_text: str = "Select") -> str:
378
+ """
379
+ Get user selection input with a styled bordered prompt.
380
+
381
+ Args:
382
+ prompt_text: The prompt text to show (e.g., "Select model number")
383
+
384
+ Returns:
385
+ The user's input string
386
+ """
387
+ # Get terminal width for the border
388
+ try:
389
+ width = console.width - 2
390
+ except Exception:
391
+ width = 78
392
+
393
+ # Print the complete box frame first, then move cursor up to input line
394
+ console.print()
395
+ console.print(f"[dim]╭{'─' * width}╮[/dim]")
396
+ console.print(f"[dim]│[/dim]{' ' * width}[dim]│[/dim]")
397
+ console.print(f"[dim]╰{'─' * width}╯[/dim]")
398
+
399
+ # Move cursor up 2 lines and to position after "│"
400
+ # \033[2A = move up 2 lines, \033[2C = move right 2 columns
401
+ prompt = f"\033[2A\033[2C {prompt_text}: "
402
+
403
+ try:
404
+ user_input = input(prompt)
405
+ except (KeyboardInterrupt, EOFError):
406
+ console.print()
407
+ raise
408
+
409
+ # Move cursor down to after the box
410
+ console.print()
411
+
412
+ return user_input.strip()
413
+
414
+
415
+ def print_input_prompt():
416
+ """Print a clean, modern input prompt."""
417
+ # Simple clean prompt with > indicator
418
+ console.print() # Empty line for spacing
419
+ console.print("[bold cyan]>[/bold cyan] ", end="")