janito 1.14.3__py3-none-any.whl → 2.0.0__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 (282) hide show
  1. janito/__init__.py +6 -1
  2. janito/__main__.py +1 -1
  3. janito/agent/setup_agent.py +139 -0
  4. janito/agent/templates/profiles/{system_prompt_template_base.txt.j2 → system_prompt_template_main.txt.j2} +1 -1
  5. janito/cli/__init__.py +9 -0
  6. janito/cli/chat_mode/bindings.py +37 -0
  7. janito/cli/chat_mode/chat_entry.py +23 -0
  8. janito/cli/chat_mode/prompt_style.py +19 -0
  9. janito/cli/chat_mode/session.py +272 -0
  10. janito/{shell/prompt/completer.py → cli/chat_mode/shell/autocomplete.py} +7 -6
  11. janito/cli/chat_mode/shell/commands/__init__.py +55 -0
  12. janito/cli/chat_mode/shell/commands/base.py +9 -0
  13. janito/cli/chat_mode/shell/commands/clear.py +12 -0
  14. janito/{shell → cli/chat_mode/shell}/commands/conversation_restart.py +34 -30
  15. janito/cli/chat_mode/shell/commands/edit.py +25 -0
  16. janito/cli/chat_mode/shell/commands/help.py +16 -0
  17. janito/cli/chat_mode/shell/commands/history_view.py +93 -0
  18. janito/cli/chat_mode/shell/commands/lang.py +25 -0
  19. janito/cli/chat_mode/shell/commands/last.py +137 -0
  20. janito/cli/chat_mode/shell/commands/livelogs.py +49 -0
  21. janito/cli/chat_mode/shell/commands/multi.py +51 -0
  22. janito/cli/chat_mode/shell/commands/prompt.py +64 -0
  23. janito/cli/chat_mode/shell/commands/role.py +36 -0
  24. janito/cli/chat_mode/shell/commands/session.py +40 -0
  25. janito/{shell → cli/chat_mode/shell}/commands/session_control.py +2 -2
  26. janito/cli/chat_mode/shell/commands/termweb_log.py +92 -0
  27. janito/cli/chat_mode/shell/commands/tools.py +32 -0
  28. janito/{shell → cli/chat_mode/shell}/commands/utility.py +4 -7
  29. janito/{shell → cli/chat_mode/shell}/commands/verbose.py +5 -5
  30. janito/cli/chat_mode/shell/session/__init__.py +1 -0
  31. janito/{shell → cli/chat_mode/shell}/session/manager.py +9 -1
  32. janito/cli/chat_mode/toolbar.py +90 -0
  33. janito/cli/cli_commands/list_models.py +35 -0
  34. janito/cli/cli_commands/list_providers.py +9 -0
  35. janito/cli/cli_commands/list_tools.py +53 -0
  36. janito/cli/cli_commands/model_selection.py +50 -0
  37. janito/cli/cli_commands/model_utils.py +84 -0
  38. janito/cli/cli_commands/set_api_key.py +19 -0
  39. janito/cli/cli_commands/show_config.py +51 -0
  40. janito/cli/cli_commands/show_system_prompt.py +62 -0
  41. janito/cli/config.py +28 -0
  42. janito/cli/console.py +3 -0
  43. janito/cli/core/__init__.py +4 -0
  44. janito/cli/core/event_logger.py +59 -0
  45. janito/cli/core/getters.py +31 -0
  46. janito/cli/core/runner.py +141 -0
  47. janito/cli/core/setters.py +174 -0
  48. janito/cli/core/unsetters.py +54 -0
  49. janito/cli/main.py +8 -196
  50. janito/cli/main_cli.py +312 -0
  51. janito/cli/prompt_core.py +230 -0
  52. janito/cli/prompt_handler.py +6 -0
  53. janito/cli/rich_terminal_reporter.py +101 -0
  54. janito/cli/single_shot_mode/__init__.py +6 -0
  55. janito/cli/single_shot_mode/handler.py +137 -0
  56. janito/cli/termweb_starter.py +73 -24
  57. janito/cli/utils.py +25 -0
  58. janito/cli/verbose_output.py +196 -0
  59. janito/config.py +5 -0
  60. janito/config_manager.py +110 -0
  61. janito/conversation_history.py +30 -0
  62. janito/{agent/tools_utils/dir_walk_utils.py → dir_walk_utils.py} +3 -2
  63. janito/driver_events.py +98 -0
  64. janito/drivers/anthropic/driver.py +113 -0
  65. janito/drivers/azure_openai/driver.py +36 -0
  66. janito/drivers/driver_registry.py +33 -0
  67. janito/drivers/google_genai/driver.py +54 -0
  68. janito/drivers/google_genai/schema_generator.py +67 -0
  69. janito/drivers/mistralai/driver.py +41 -0
  70. janito/drivers/openai/driver.py +334 -0
  71. janito/event_bus/__init__.py +2 -0
  72. janito/event_bus/bus.py +68 -0
  73. janito/event_bus/event.py +15 -0
  74. janito/event_bus/handler.py +31 -0
  75. janito/event_bus/queue_bus.py +57 -0
  76. janito/exceptions.py +23 -0
  77. janito/formatting_token.py +54 -0
  78. janito/i18n/pt.py +1 -0
  79. janito/llm/__init__.py +5 -0
  80. janito/llm/agent.py +443 -0
  81. janito/llm/auth.py +62 -0
  82. janito/llm/driver.py +239 -0
  83. janito/llm/driver_config.py +34 -0
  84. janito/llm/driver_config_builder.py +34 -0
  85. janito/llm/driver_input.py +12 -0
  86. janito/llm/message_parts.py +60 -0
  87. janito/llm/model.py +38 -0
  88. janito/llm/provider.py +187 -0
  89. janito/perf_singleton.py +3 -0
  90. janito/performance_collector.py +167 -0
  91. janito/provider_config.py +98 -0
  92. janito/provider_registry.py +152 -0
  93. janito/providers/__init__.py +7 -0
  94. janito/providers/anthropic/model_info.py +22 -0
  95. janito/providers/anthropic/provider.py +65 -0
  96. janito/providers/azure_openai/model_info.py +15 -0
  97. janito/providers/azure_openai/provider.py +72 -0
  98. janito/providers/deepseek/__init__.py +1 -0
  99. janito/providers/deepseek/model_info.py +16 -0
  100. janito/providers/deepseek/provider.py +91 -0
  101. janito/providers/google/__init__.py +1 -0
  102. janito/providers/google/model_info.py +40 -0
  103. janito/providers/google/provider.py +69 -0
  104. janito/providers/mistralai/model_info.py +37 -0
  105. janito/providers/mistralai/provider.py +69 -0
  106. janito/providers/openai/__init__.py +1 -0
  107. janito/providers/openai/model_info.py +137 -0
  108. janito/providers/openai/provider.py +107 -0
  109. janito/providers/openai/schema_generator.py +63 -0
  110. janito/providers/provider_static_info.py +21 -0
  111. janito/providers/registry.py +26 -0
  112. janito/report_events.py +38 -0
  113. janito/termweb/app.py +1 -1
  114. janito/tools/__init__.py +16 -0
  115. janito/tools/adapters/__init__.py +1 -0
  116. janito/tools/adapters/local/__init__.py +54 -0
  117. janito/tools/adapters/local/adapter.py +92 -0
  118. janito/{agent/tools → tools/adapters/local}/ask_user.py +30 -13
  119. janito/tools/adapters/local/copy_file.py +84 -0
  120. janito/{agent/tools → tools/adapters/local}/create_directory.py +11 -10
  121. janito/tools/adapters/local/create_file.py +82 -0
  122. janito/tools/adapters/local/delete_text_in_file.py +136 -0
  123. janito/{agent/tools → tools/adapters/local}/fetch_url.py +18 -19
  124. janito/tools/adapters/local/find_files.py +140 -0
  125. janito/tools/adapters/local/get_file_outline/core.py +151 -0
  126. janito/{agent/tools → tools/adapters/local}/get_file_outline/python_outline.py +125 -0
  127. janito/tools/adapters/local/get_file_outline/python_outline_v2.py +156 -0
  128. janito/{agent/tools → tools/adapters/local}/get_file_outline/search_outline.py +12 -7
  129. janito/{agent/tools → tools/adapters/local}/move_file.py +13 -9
  130. janito/{agent/tools → tools/adapters/local}/open_url.py +7 -5
  131. janito/tools/adapters/local/python_code_run.py +165 -0
  132. janito/tools/adapters/local/python_command_run.py +163 -0
  133. janito/tools/adapters/local/python_file_run.py +162 -0
  134. janito/{agent/tools → tools/adapters/local}/remove_directory.py +15 -9
  135. janito/{agent/tools → tools/adapters/local}/remove_file.py +17 -14
  136. janito/{agent/tools → tools/adapters/local}/replace_text_in_file.py +27 -22
  137. janito/tools/adapters/local/run_bash_command.py +176 -0
  138. janito/tools/adapters/local/run_powershell_command.py +219 -0
  139. janito/{agent/tools → tools/adapters/local}/search_text/core.py +32 -12
  140. janito/{agent/tools → tools/adapters/local}/search_text/match_lines.py +13 -4
  141. janito/{agent/tools → tools/adapters/local}/search_text/pattern_utils.py +12 -4
  142. janito/{agent/tools → tools/adapters/local}/search_text/traverse_directory.py +15 -2
  143. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/core.py +12 -11
  144. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/css_validator.py +1 -1
  145. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/html_validator.py +1 -1
  146. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/js_validator.py +1 -1
  147. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/json_validator.py +1 -1
  148. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/markdown_validator.py +1 -1
  149. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/ps1_validator.py +1 -1
  150. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/python_validator.py +1 -1
  151. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/xml_validator.py +1 -1
  152. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/yaml_validator.py +1 -1
  153. janito/{agent/tools/get_lines.py → tools/adapters/local/view_file.py} +45 -27
  154. janito/tools/inspect_registry.py +17 -0
  155. janito/tools/tool_base.py +105 -0
  156. janito/tools/tool_events.py +58 -0
  157. janito/tools/tool_run_exception.py +12 -0
  158. janito/{agent → tools}/tool_use_tracker.py +2 -4
  159. janito/{agent/tools_utils/utils.py → tools/tool_utils.py} +18 -9
  160. janito/tools/tools_adapter.py +207 -0
  161. janito/tools/tools_schema.py +104 -0
  162. janito/utils.py +11 -0
  163. janito/version.py +4 -0
  164. janito-2.0.0.dist-info/METADATA +232 -0
  165. janito-2.0.0.dist-info/RECORD +180 -0
  166. janito/agent/__init__.py +0 -0
  167. janito/agent/api_exceptions.py +0 -4
  168. janito/agent/config.py +0 -147
  169. janito/agent/config_defaults.py +0 -12
  170. janito/agent/config_utils.py +0 -0
  171. janito/agent/content_handler.py +0 -0
  172. janito/agent/conversation.py +0 -238
  173. janito/agent/conversation_api.py +0 -306
  174. janito/agent/conversation_exceptions.py +0 -18
  175. janito/agent/conversation_tool_calls.py +0 -39
  176. janito/agent/conversation_ui.py +0 -17
  177. janito/agent/event.py +0 -24
  178. janito/agent/event_dispatcher.py +0 -24
  179. janito/agent/event_handler_protocol.py +0 -5
  180. janito/agent/event_system.py +0 -15
  181. janito/agent/llm_conversation_history.py +0 -82
  182. janito/agent/message_handler.py +0 -20
  183. janito/agent/message_handler_protocol.py +0 -5
  184. janito/agent/openai_client.py +0 -149
  185. janito/agent/openai_schema_generator.py +0 -187
  186. janito/agent/profile_manager.py +0 -96
  187. janito/agent/queued_message_handler.py +0 -50
  188. janito/agent/rich_live.py +0 -32
  189. janito/agent/rich_message_handler.py +0 -115
  190. janito/agent/runtime_config.py +0 -36
  191. janito/agent/test_handler_protocols.py +0 -47
  192. janito/agent/test_openai_schema_generator.py +0 -93
  193. janito/agent/tests/__init__.py +0 -1
  194. janito/agent/tool_base.py +0 -63
  195. janito/agent/tool_executor.py +0 -122
  196. janito/agent/tool_registry.py +0 -49
  197. janito/agent/tools/__init__.py +0 -47
  198. janito/agent/tools/create_file.py +0 -59
  199. janito/agent/tools/delete_text_in_file.py +0 -97
  200. janito/agent/tools/find_files.py +0 -106
  201. janito/agent/tools/get_file_outline/core.py +0 -81
  202. janito/agent/tools/present_choices.py +0 -64
  203. janito/agent/tools/python_command_runner.py +0 -201
  204. janito/agent/tools/python_file_runner.py +0 -199
  205. janito/agent/tools/python_stdin_runner.py +0 -208
  206. janito/agent/tools/replace_file.py +0 -72
  207. janito/agent/tools/run_bash_command.py +0 -218
  208. janito/agent/tools/run_powershell_command.py +0 -251
  209. janito/agent/tools_utils/__init__.py +0 -1
  210. janito/agent/tools_utils/action_type.py +0 -7
  211. janito/agent/tools_utils/test_gitignore_utils.py +0 -46
  212. janito/cli/_livereload_log_utils.py +0 -13
  213. janito/cli/_print_config.py +0 -96
  214. janito/cli/_termweb_log_utils.py +0 -17
  215. janito/cli/_utils.py +0 -9
  216. janito/cli/arg_parser.py +0 -272
  217. janito/cli/cli_main.py +0 -281
  218. janito/cli/config_commands.py +0 -211
  219. janito/cli/config_runner.py +0 -35
  220. janito/cli/formatting_runner.py +0 -12
  221. janito/cli/livereload_starter.py +0 -60
  222. janito/cli/logging_setup.py +0 -38
  223. janito/cli/one_shot.py +0 -80
  224. janito/livereload/app.py +0 -25
  225. janito/rich_utils.py +0 -59
  226. janito/shell/__init__.py +0 -0
  227. janito/shell/commands/__init__.py +0 -61
  228. janito/shell/commands/config.py +0 -22
  229. janito/shell/commands/edit.py +0 -24
  230. janito/shell/commands/history_view.py +0 -18
  231. janito/shell/commands/lang.py +0 -19
  232. janito/shell/commands/livelogs.py +0 -42
  233. janito/shell/commands/prompt.py +0 -62
  234. janito/shell/commands/termweb_log.py +0 -94
  235. janito/shell/commands/tools.py +0 -26
  236. janito/shell/commands/track.py +0 -36
  237. janito/shell/main.py +0 -326
  238. janito/shell/prompt/load_prompt.py +0 -57
  239. janito/shell/prompt/session_setup.py +0 -57
  240. janito/shell/session/config.py +0 -109
  241. janito/shell/session/history.py +0 -0
  242. janito/shell/ui/interactive.py +0 -226
  243. janito/termweb/static/editor.css +0 -158
  244. janito/termweb/static/editor.css.bak +0 -145
  245. janito/termweb/static/editor.html +0 -46
  246. janito/termweb/static/editor.html.bak +0 -46
  247. janito/termweb/static/editor.js +0 -265
  248. janito/termweb/static/editor.js.bak +0 -259
  249. janito/termweb/static/explorer.html.bak +0 -59
  250. janito/termweb/static/favicon.ico +0 -0
  251. janito/termweb/static/favicon.ico.bak +0 -0
  252. janito/termweb/static/index.html +0 -53
  253. janito/termweb/static/index.html.bak +0 -54
  254. janito/termweb/static/index.html.bak.bak +0 -175
  255. janito/termweb/static/landing.html.bak +0 -36
  256. janito/termweb/static/termicon.svg +0 -1
  257. janito/termweb/static/termweb.css +0 -214
  258. janito/termweb/static/termweb.css.bak +0 -237
  259. janito/termweb/static/termweb.js +0 -162
  260. janito/termweb/static/termweb.js.bak +0 -168
  261. janito/termweb/static/termweb.js.bak.bak +0 -157
  262. janito/termweb/static/termweb_quickopen.js +0 -135
  263. janito/termweb/static/termweb_quickopen.js.bak +0 -125
  264. janito/tests/test_rich_utils.py +0 -44
  265. janito/web/__init__.py +0 -0
  266. janito/web/__main__.py +0 -25
  267. janito/web/app.py +0 -145
  268. janito-1.14.3.dist-info/METADATA +0 -313
  269. janito-1.14.3.dist-info/RECORD +0 -162
  270. janito-1.14.3.dist-info/licenses/LICENSE +0 -21
  271. /janito/{shell → cli/chat_mode/shell}/input_history.py +0 -0
  272. /janito/{shell/commands/session.py → cli/chat_mode/shell/session/history.py} +0 -0
  273. /janito/{agent/tools_utils/formatting.py → formatting.py} +0 -0
  274. /janito/{agent/tools_utils/gitignore_utils.py → gitignore_utils.py} +0 -0
  275. /janito/{agent/platform_discovery.py → platform_discovery.py} +0 -0
  276. /janito/{agent/tools → tools/adapters/local}/get_file_outline/__init__.py +0 -0
  277. /janito/{agent/tools → tools/adapters/local}/get_file_outline/markdown_outline.py +0 -0
  278. /janito/{agent/tools → tools/adapters/local}/search_text/__init__.py +0 -0
  279. /janito/{agent/tools → tools/adapters/local}/validate_file_syntax/__init__.py +0 -0
  280. {janito-1.14.3.dist-info → janito-2.0.0.dist-info}/WHEEL +0 -0
  281. {janito-1.14.3.dist-info → janito-2.0.0.dist-info}/entry_points.txt +0 -0
  282. {janito-1.14.3.dist-info → janito-2.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,196 @@
1
+ """
2
+ Verbose output formatting and error handling for janito CLI (shared for single and chat modes).
3
+ """
4
+
5
+ from rich import print as rich_print
6
+ from rich.align import Align
7
+ from rich.panel import Panel
8
+ from rich.text import Text
9
+ from janito.version import __version__ as VERSION
10
+ from janito.cli.utils import format_tokens
11
+
12
+
13
+ def print_verbose_header(agent, args):
14
+ if hasattr(args, "verbose") and args.verbose:
15
+ role = (
16
+ agent.template_vars.get("role") if hasattr(agent, "template_vars") else None
17
+ )
18
+ role_part = f" (Role: {role})" if role else ""
19
+ parts = [
20
+ f"Janito {VERSION}",
21
+ f"Provider: {agent.llm_provider.__class__.__name__}",
22
+ f"Model: {agent.llm_provider.model_name}{role_part}",
23
+ f"Driver: {agent.llm_provider.__class__.__module__.split('.')[-2] if len(agent.llm_provider.__class__.__module__.split('.')) > 1 else agent.llm_provider.__class__.__name__}",
24
+ ]
25
+ if hasattr(args, "think") and args.think:
26
+ parts.append("Thinking ON")
27
+ info_line = " | ".join(part.strip() for part in parts)
28
+ rich_print(
29
+ Panel(
30
+ Align(f"[cyan]{info_line}[/cyan]", align="center"),
31
+ style="on grey11",
32
+ expand=True,
33
+ )
34
+ )
35
+
36
+
37
+ def print_verbose_info(label, content, style="green", align_content=False):
38
+ icon = "[bold]●[/bold]" if style == "green" else "[bold]🔷[/bold]"
39
+ panel_title = f"{icon} [bold {style}]{label}[/bold {style}]"
40
+ from rich.console import Console
41
+ from rich.align import Align
42
+ from rich.text import Text
43
+
44
+ console = Console()
45
+ width = console.size.width
46
+ obfuscated_content = content
47
+ # Obfuscate api_key if LLMDriverConfig
48
+ if (
49
+ hasattr(content, "__dataclass_fields__")
50
+ and "api_key" in content.__dataclass_fields__
51
+ ):
52
+ # Copy and mask the api_key
53
+ from copy import deepcopy
54
+
55
+ obfuscated_content = deepcopy(content)
56
+ if hasattr(obfuscated_content, "api_key") and obfuscated_content.api_key:
57
+ val = obfuscated_content.api_key
58
+ if len(val) > 8:
59
+ masked = val[:2] + "***" + val[-2:]
60
+ else:
61
+ masked = "***"
62
+ obfuscated_content.api_key = masked
63
+ else:
64
+ obfuscated_content.api_key = None
65
+ if align_content:
66
+ rendered_content = Align.center(Text(str(obfuscated_content)))
67
+ else:
68
+ rendered_content = Text(str(obfuscated_content))
69
+ panel = Panel(
70
+ rendered_content,
71
+ title=panel_title,
72
+ border_style=style,
73
+ expand=False,
74
+ width=min(width - 8, 100),
75
+ )
76
+ console.print(Align.center(panel))
77
+
78
+
79
+ def print_performance(start_time, end_time, performance_collector, args):
80
+ if start_time is None or end_time is None:
81
+ generation_time_ns = None
82
+ else:
83
+ generation_time_ns = (end_time - start_time) * 1e9
84
+ if hasattr(args, "verbose") and args.verbose:
85
+ from rich.table import Table
86
+ from rich.style import Style
87
+ from rich import box
88
+
89
+ total_requests = performance_collector.get_total_requests()
90
+ avg_duration = performance_collector.get_average_duration()
91
+ status_counts = performance_collector.get_status_counts()
92
+ token_usage = performance_collector.get_token_usage()
93
+ error_count = performance_collector.get_error_count()
94
+ avg_turns = performance_collector.get_average_turns()
95
+ content_parts = performance_collector.get_content_part_count()
96
+
97
+ left = []
98
+ right = []
99
+ right.append(("[bold]Total Requests[/bold]", f"{total_requests}"))
100
+ left.append(("[bold]Avg Duration[/bold]", f"{avg_duration:.3f}s"))
101
+ right.append(
102
+ (
103
+ "[bold]Status Counts[/bold]",
104
+ (
105
+ ", ".join(f"{k}: {v}" for k, v in status_counts.items())
106
+ if status_counts
107
+ else "-"
108
+ ),
109
+ )
110
+ )
111
+ if token_usage:
112
+ usage_str = ", ".join(
113
+ f"{k.removesuffix('_token_count').removesuffix('_tokens')}: {format_tokens(v)}"
114
+ for k, v in token_usage.items()
115
+ )
116
+ else:
117
+ usage_str = "-"
118
+ left.append(("[bold]Token Usage[/bold]", usage_str))
119
+ right.append(
120
+ ("[bold]Avg Turns[/bold]", f"{avg_turns:.2f}" if avg_turns > 0 else "-")
121
+ )
122
+ left.append(
123
+ (
124
+ "[bold]Content Parts[/bold]",
125
+ f"{content_parts}" if content_parts > 0 else "-",
126
+ )
127
+ )
128
+ right.append(
129
+ ("[bold]Errors[/bold]", f"{error_count}" if error_count > 0 else "-")
130
+ )
131
+
132
+ total_tool_events = performance_collector.get_total_tool_events()
133
+ tool_names_counter = performance_collector.get_tool_names_counter()
134
+ tool_error_count = performance_collector.get_tool_error_count()
135
+ tool_error_messages = performance_collector.get_tool_error_messages()
136
+ tool_action_counter = performance_collector.get_tool_action_counter()
137
+ tool_subtype_counter = performance_collector.get_tool_subtype_counter()
138
+
139
+ tool_names_str = (
140
+ ", ".join(f"{k}: {v}" for k, v in tool_names_counter.items())
141
+ if tool_names_counter
142
+ else "-"
143
+ )
144
+ tool_actions_str = (
145
+ ", ".join(
146
+ f"{k.split('.')[-1]}: {v}" for k, v in tool_action_counter.items()
147
+ )
148
+ if tool_action_counter
149
+ else "-"
150
+ )
151
+ tool_subtypes_str = (
152
+ ", ".join(f"{k}: {v}" for k, v in tool_subtype_counter.items())
153
+ if tool_subtype_counter
154
+ else "-"
155
+ )
156
+ tool_errors_str = f"{tool_error_count}"
157
+ tool_error_msgs_str = (
158
+ "\n".join(tool_error_messages[:2])
159
+ + ("\n..." if len(tool_error_messages) > 2 else "")
160
+ if tool_error_count
161
+ else "-"
162
+ )
163
+
164
+ left.append(("[bold]Tool Events[/bold]", f"{total_tool_events}"))
165
+ right.append(("[bold]Tool Usage[/bold]", tool_names_str))
166
+ left.append(("[bold]Tool Errors[/bold]", tool_errors_str))
167
+
168
+ max_len = max(len(left), len(right))
169
+ while len(left) < max_len:
170
+ left.append(("", ""))
171
+ while len(right) < max_len:
172
+ right.append(("", ""))
173
+
174
+ from rich.table import Table
175
+
176
+ table = Table(
177
+ show_header=False,
178
+ box=box.SIMPLE,
179
+ pad_edge=False,
180
+ style="cyan",
181
+ expand=False,
182
+ )
183
+ table.add_column(justify="right")
184
+ table.add_column(justify="left")
185
+ table.add_column(justify="right")
186
+ table.add_column(justify="left")
187
+ for (l_key, l_val), (r_key, r_val) in zip(left, right):
188
+ table.add_row(l_key, l_val, r_key, r_val)
189
+ if total_requests == 0:
190
+ table.add_row("[bold]Info[/bold]", "No performance data available.", "", "")
191
+ rich_print(Panel(table, style="on grey11", expand=True))
192
+
193
+
194
+ def handle_exception(e):
195
+ rich_print(f"[bold red]Error:[/bold red] {e}")
196
+ return
janito/config.py ADDED
@@ -0,0 +1,5 @@
1
+ # Shared Janito ConfigManager singleton
2
+ from janito.config_manager import ConfigManager
3
+
4
+ # Only one global instance! Used by CLI, provider_config, others:
5
+ config = ConfigManager(config_path=None)
@@ -0,0 +1,110 @@
1
+ import json
2
+ from pathlib import Path
3
+ from threading import Lock
4
+
5
+
6
+ class ConfigManager:
7
+ """
8
+ Unified configuration manager supporting:
9
+ - Defaults
10
+ - File-based configuration
11
+ - Runtime overrides (e.g., CLI args)
12
+ """
13
+
14
+ _instance = None
15
+ _lock = Lock()
16
+
17
+ def __new__(cls, *args, **kwargs):
18
+ with cls._lock:
19
+ if not cls._instance:
20
+ cls._instance = super(ConfigManager, cls).__new__(cls)
21
+ return cls._instance
22
+
23
+ def __init__(self, config_path=None, defaults=None, runtime_overrides=None):
24
+ # Lazy single-init
25
+ if hasattr(self, "_initialized") and self._initialized:
26
+ return
27
+ self._initialized = True
28
+
29
+ self.config_path = Path(config_path or Path.home() / ".janito" / "config.json")
30
+ self.defaults = dict(defaults) if defaults else {}
31
+ self.file_config = {}
32
+ self.runtime_overrides = dict(runtime_overrides) if runtime_overrides else {}
33
+ self._load_file_config()
34
+
35
+ def _load_file_config(self):
36
+ if self.config_path.exists():
37
+ with open(self.config_path, "r", encoding="utf-8") as f:
38
+ try:
39
+ self.file_config = json.load(f)
40
+ except Exception:
41
+ self.file_config = {}
42
+ else:
43
+ self.file_config = {}
44
+
45
+ def save(self):
46
+ self.config_path.parent.mkdir(parents=True, exist_ok=True)
47
+ with open(self.config_path, "w", encoding="utf-8") as f:
48
+ json.dump(self.file_config, f, indent=2)
49
+
50
+ def get(self, key, default=None):
51
+ # Precedence: runtime_overrides > file_config > defaults
52
+ for layer in (self.runtime_overrides, self.file_config, self.defaults):
53
+ if key in layer and layer[key] is not None:
54
+ return layer[key]
55
+ return default
56
+
57
+ def runtime_set(self, key, value):
58
+ self.runtime_overrides[key] = value
59
+
60
+ def file_set(self, key, value):
61
+ # Always reload, update, and persist
62
+ self._load_file_config()
63
+ self.file_config[key] = value
64
+ with open(self.config_path, "w", encoding="utf-8") as f:
65
+ json.dump(self.file_config, f, indent=2)
66
+
67
+ def all(self, layered=False):
68
+ merged = dict(self.defaults)
69
+ merged.update(self.file_config)
70
+ merged.update(self.runtime_overrides)
71
+ if layered:
72
+ # Only file+runtime, i.e., what is saved to disk
73
+ d = dict(self.file_config)
74
+ d.update(self.runtime_overrides)
75
+ return d
76
+ return merged
77
+
78
+ # Namespaced provider/model config
79
+ def get_provider_config(self, provider, default=None):
80
+ providers = self.file_config.get("providers") or {}
81
+ return providers.get(provider) or (default or {})
82
+
83
+ def set_provider_config(self, provider, key, value):
84
+ if "providers" not in self.file_config:
85
+ self.file_config["providers"] = {}
86
+ if provider not in self.file_config["providers"]:
87
+ self.file_config["providers"][provider] = {}
88
+ self.file_config["providers"][provider][key] = value
89
+
90
+ def get_provider_model_config(self, provider, model, default=None):
91
+ return (
92
+ self.file_config.get("providers")
93
+ or {}.get(provider, {}).get("models", {}).get(model)
94
+ or (default or {})
95
+ )
96
+
97
+ def set_provider_model_config(self, provider, model, key, value):
98
+ if "providers" not in self.file_config:
99
+ self.file_config["providers"] = {}
100
+ if provider not in self.file_config["providers"]:
101
+ self.file_config["providers"][provider] = {}
102
+ if "models" not in self.file_config["providers"][provider]:
103
+ self.file_config["providers"][provider]["models"] = {}
104
+ if model not in self.file_config["providers"][provider]["models"]:
105
+ self.file_config["providers"][provider]["models"][model] = {}
106
+ self.file_config["providers"][provider]["models"][model][key] = value
107
+
108
+ # Support loading runtime overrides after init (e.g. after parsing CLI args)
109
+ def apply_runtime_overrides(self, overrides_dict):
110
+ self.runtime_overrides.update(overrides_dict)
@@ -0,0 +1,30 @@
1
+ import json
2
+ from typing import List, Dict, Optional
3
+
4
+
5
+ class LLMConversationHistory:
6
+ """
7
+ Stores the conversation history between user and LLM (assistant/system).
8
+ Each message is a dict with keys: 'role', 'content', and optional 'metadata'.
9
+ """
10
+
11
+ def __init__(self):
12
+ self._history: List[Dict] = []
13
+
14
+ def add_message(self, role: str, content: str, metadata: Optional[Dict] = None):
15
+ message = {"role": role, "content": content}
16
+ if metadata:
17
+ message["metadata"] = metadata
18
+ self._history.append(message)
19
+
20
+ def get_history(self) -> List[Dict]:
21
+ return list(self._history)
22
+
23
+ def clear(self):
24
+ self._history.clear()
25
+
26
+ def export_json(self) -> str:
27
+ return json.dumps(self._history, indent=2)
28
+
29
+ def import_json(self, json_str: str):
30
+ self._history = json.loads(json_str)
@@ -2,7 +2,7 @@ import os
2
2
  from .gitignore_utils import GitignoreFilter
3
3
 
4
4
 
5
- def walk_dir_with_gitignore(root_dir, max_depth=None):
5
+ def walk_dir_with_gitignore(root_dir, max_depth=None, include_gitignored=False):
6
6
  """
7
7
  Walks the directory tree starting at root_dir, yielding (root, dirs, files) tuples,
8
8
  with .gitignore rules applied.
@@ -20,5 +20,6 @@ def walk_dir_with_gitignore(root_dir, max_depth=None):
20
20
  # For max_depth=1, only root (depth=0). For max_depth=2, root and one level below (depth=0,1).
21
21
  if depth > 0:
22
22
  continue
23
- dirs, files = gitignore.filter_ignored(root, dirs, files)
23
+ if not include_gitignored:
24
+ dirs, files = gitignore.filter_ignored(root, dirs, files)
24
25
  yield root, dirs, files
@@ -0,0 +1,98 @@
1
+ import attr
2
+ from typing import Any, ClassVar
3
+ from enum import Enum
4
+ from janito.event_bus.event import Event
5
+
6
+
7
+ class RequestStatus(Enum):
8
+ SUCCESS = "success"
9
+ ERROR = "error"
10
+ CANCELLED = "cancelled"
11
+ EMPTY_RESPONSE = "empty_response"
12
+ TIMEOUT = "timeout"
13
+
14
+
15
+ @attr.s(auto_attribs=True, kw_only=True)
16
+ class DriverEvent(Event):
17
+ """
18
+ Base class for events related to a driver (e.g., LLM, API provider).
19
+ Includes driver name and request ID for correlation.
20
+ """
21
+
22
+ category: ClassVar[str] = "driver"
23
+ driver_name: str = None
24
+ request_id: str = None
25
+
26
+
27
+ @attr.s(auto_attribs=True, kw_only=True)
28
+ class GenerationStarted(DriverEvent):
29
+ conversation_history: Any = None
30
+
31
+
32
+ @attr.s(auto_attribs=True, kw_only=True)
33
+ class GenerationFinished(DriverEvent):
34
+ total_turns: int = 0
35
+
36
+
37
+ @attr.s(auto_attribs=True, kw_only=True)
38
+ class RequestStarted(DriverEvent):
39
+ payload: Any = None
40
+
41
+
42
+ @attr.s(auto_attribs=True, kw_only=True)
43
+ class RequestFinished(DriverEvent):
44
+ """
45
+ Used for all request completions: success, error, cancellation, empty response, or timeout.
46
+ status should be a RequestStatus value.
47
+ - For errors, fill error/exception/traceback fields.
48
+ - For cancellations, fill reason field.
49
+ - For empty response, fill error/details fields as appropriate.
50
+ - For timeout, fill error/details fields as appropriate.
51
+ """
52
+
53
+ response: Any = None
54
+ status: RequestStatus = (
55
+ None # RequestStatus.SUCCESS, ERROR, CANCELLED, EMPTY_RESPONSE, TIMEOUT
56
+ )
57
+ usage: dict = None
58
+ finish_type: str = None # 'success', 'error', 'cancelled', etc. (legacy)
59
+ error: str = None
60
+ exception: Exception = None
61
+ traceback: str = None
62
+ reason: str = None # for cancellations or empty/timeout reasons
63
+ details: dict = None # for additional info (empty response, timeout, etc.)
64
+
65
+
66
+ @attr.s(auto_attribs=True, kw_only=True)
67
+ class ContentPartFound(DriverEvent):
68
+ content_part: Any = None
69
+
70
+
71
+ @attr.s(auto_attribs=True, kw_only=True)
72
+ class ToolCallStarted(DriverEvent):
73
+ tool_call_id: str = None
74
+ name: str = None
75
+ arguments: Any = None
76
+
77
+ @property
78
+ def tool_name(self):
79
+ return self.name
80
+
81
+
82
+ @attr.s(auto_attribs=True, kw_only=True)
83
+ class ToolCallFinished(DriverEvent):
84
+ tool_call_id: str = None
85
+ name: str = None
86
+ result: Any = None
87
+
88
+ @property
89
+ def tool_name(self):
90
+ return self.name
91
+
92
+
93
+ @attr.s(auto_attribs=True, kw_only=True)
94
+ class ResponseReceived(DriverEvent):
95
+ parts: list = None
96
+ tool_results: list = None # each as dict or custom ToolResult dataclass
97
+ timestamp: float = None # UNIX epoch seconds, normalized
98
+ metadata: dict = None
@@ -0,0 +1,113 @@
1
+ from janito.llm.driver import LLMDriver
2
+ from janito.llm.driver_config import LLMDriverConfig
3
+ from janito.driver_events import (
4
+ GenerationStarted,
5
+ GenerationFinished,
6
+ RequestStarted,
7
+ RequestFinished,
8
+ ResponseReceived,
9
+ )
10
+ from janito.llm.message_parts import TextMessagePart
11
+ import uuid
12
+ import traceback
13
+ import time
14
+
15
+ # Safe import of anthropic SDK
16
+ try:
17
+ import anthropic
18
+
19
+ DRIVER_AVAILABLE = True
20
+ DRIVER_UNAVAILABLE_REASON = None
21
+ except ImportError:
22
+ DRIVER_AVAILABLE = False
23
+ DRIVER_UNAVAILABLE_REASON = "Missing dependency: anthropic (pip install anthropic)"
24
+
25
+
26
+ class AnthropicModelDriver(LLMDriver):
27
+ available = False
28
+ unavailable_reason = "AnthropicModelDriver is not implemented yet."
29
+
30
+ @classmethod
31
+ def is_available(cls):
32
+ return cls.available
33
+
34
+ """
35
+ LLMDriver for Anthropic's Claude API (v3), using the anthropic SDK.
36
+ """
37
+ required_config = ["api_key", "model"]
38
+
39
+ def __init__(self, tools_adapter=None):
40
+ raise ImportError(self.unavailable_reason)
41
+
42
+ def _create_client(self):
43
+ try:
44
+ import anthropic
45
+ except ImportError:
46
+ raise Exception(
47
+ "The 'anthropic' Python SDK is required. Please install via `pip install anthropic`."
48
+ )
49
+ return anthropic.Anthropic(api_key=self.api_key)
50
+
51
+ def _run_generation(
52
+ self, messages_or_prompt, system_prompt=None, tools=None, **kwargs
53
+ ):
54
+ request_id = str(uuid.uuid4())
55
+ client = self._create_client()
56
+ try:
57
+ prompt = ""
58
+ if isinstance(messages_or_prompt, str):
59
+ prompt = messages_or_prompt
60
+ elif isinstance(messages_or_prompt, list):
61
+ chat = []
62
+ for msg in messages_or_prompt:
63
+ if msg.get("role") == "user":
64
+ chat.append("Human: " + msg.get("content", ""))
65
+ elif msg.get("role") == "assistant":
66
+ chat.append("Assistant: " + msg.get("content", ""))
67
+ prompt = "\n".join(chat)
68
+ if system_prompt:
69
+ prompt = f"System: {system_prompt}\n{prompt}"
70
+
71
+ self.publish(
72
+ GenerationStarted,
73
+ request_id,
74
+ conversation_history=list(getattr(self, "_history", [])),
75
+ )
76
+ self.publish(RequestStarted, request_id, payload={})
77
+ start_time = time.time()
78
+ response = client.completions.create(
79
+ model=self.model_name,
80
+ max_tokens_to_sample=int(getattr(self.config, "max_response", 1024)),
81
+ prompt=prompt,
82
+ temperature=float(getattr(self.config, "default_temp", 0.7)),
83
+ )
84
+ duration = time.time() - start_time
85
+ content = response.completion if hasattr(response, "completion") else None
86
+ self.publish(
87
+ RequestFinished,
88
+ request_id,
89
+ response=content,
90
+ status=RequestStatus.SUCCESS,
91
+ usage={},
92
+ )
93
+ parts = []
94
+ if content:
95
+ parts.append(TextMessagePart(content=content))
96
+ self.publish(
97
+ ResponseReceived,
98
+ request_id=request_id,
99
+ parts=parts,
100
+ tool_results=[],
101
+ timestamp=time.time(),
102
+ metadata={"raw_response": response},
103
+ )
104
+ self.publish(GenerationFinished, request_id, total_turns=1)
105
+ except Exception as e:
106
+ self.publish(
107
+ RequestFinished,
108
+ request_id,
109
+ status=RequestStatus.ERROR,
110
+ error=str(e),
111
+ exception=e,
112
+ traceback=traceback.format_exc(),
113
+ )
@@ -0,0 +1,36 @@
1
+ from janito.drivers.openai.driver import OpenAIModelDriver
2
+
3
+ # Safe import of AzureOpenAI SDK
4
+ try:
5
+ from openai import AzureOpenAI
6
+
7
+ DRIVER_AVAILABLE = True
8
+ DRIVER_UNAVAILABLE_REASON = None
9
+ except ImportError:
10
+ DRIVER_AVAILABLE = False
11
+ DRIVER_UNAVAILABLE_REASON = "Missing dependency: openai (pip install openai)"
12
+
13
+ from janito.llm.driver_config import LLMDriverConfig
14
+
15
+
16
+ class AzureOpenAIModelDriver(OpenAIModelDriver):
17
+ available = DRIVER_AVAILABLE
18
+ unavailable_reason = DRIVER_UNAVAILABLE_REASON
19
+
20
+ @classmethod
21
+ def is_available(cls):
22
+ return cls.available
23
+
24
+ required_config = {"base_url"} # Update key as used in your config logic
25
+
26
+ def __init__(self, tools_adapter=None):
27
+ if not self.available:
28
+ raise ImportError(
29
+ f"AzureOpenAIModelDriver unavailable: {self.unavailable_reason}"
30
+ )
31
+ super().__init__(tools_adapter=tools_adapter)
32
+ self.azure_endpoint = None
33
+ self.api_version = None
34
+ self.api_key = None
35
+
36
+ # ... rest of the implementation ...
@@ -0,0 +1,33 @@
1
+ # janito/drivers/driver_registry.py
2
+ """
3
+ DriverRegistry: Maps driver string names to class objects for use by providers.
4
+ """
5
+
6
+ from typing import Dict, Type
7
+
8
+ # --- Import driver classes ---
9
+ from janito.drivers.anthropic.driver import AnthropicModelDriver
10
+ from janito.drivers.azure_openai.driver import AzureOpenAIModelDriver
11
+ from janito.drivers.google_genai.driver import GoogleGenaiModelDriver
12
+ from janito.drivers.mistralai.driver import MistralAIModelDriver
13
+ from janito.drivers.openai.driver import OpenAIModelDriver
14
+
15
+ _DRIVER_REGISTRY: Dict[str, Type] = {
16
+ "AnthropicModelDriver": AnthropicModelDriver,
17
+ "AzureOpenAIModelDriver": AzureOpenAIModelDriver,
18
+ "GoogleGenaiModelDriver": GoogleGenaiModelDriver,
19
+ "MistralAIModelDriver": MistralAIModelDriver,
20
+ "OpenAIModelDriver": OpenAIModelDriver,
21
+ }
22
+
23
+
24
+ def get_driver_class(name: str):
25
+ """Get the driver class by string name."""
26
+ try:
27
+ return _DRIVER_REGISTRY[name]
28
+ except KeyError:
29
+ raise ValueError(f"No driver found for name: {name}")
30
+
31
+
32
+ def register_driver(name: str, cls: type):
33
+ _DRIVER_REGISTRY[name] = cls