janito 1.14.3__py3-none-any.whl → 2.0.1__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 (283) 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/tools/adapters/local/open_html_in_browser.py +34 -0
  131. janito/{agent/tools → tools/adapters/local}/open_url.py +7 -5
  132. janito/tools/adapters/local/python_code_run.py +165 -0
  133. janito/tools/adapters/local/python_command_run.py +163 -0
  134. janito/tools/adapters/local/python_file_run.py +162 -0
  135. janito/{agent/tools → tools/adapters/local}/remove_directory.py +15 -9
  136. janito/{agent/tools → tools/adapters/local}/remove_file.py +17 -14
  137. janito/{agent/tools → tools/adapters/local}/replace_text_in_file.py +27 -22
  138. janito/tools/adapters/local/run_bash_command.py +176 -0
  139. janito/tools/adapters/local/run_powershell_command.py +219 -0
  140. janito/{agent/tools → tools/adapters/local}/search_text/core.py +32 -12
  141. janito/{agent/tools → tools/adapters/local}/search_text/match_lines.py +13 -4
  142. janito/{agent/tools → tools/adapters/local}/search_text/pattern_utils.py +12 -4
  143. janito/{agent/tools → tools/adapters/local}/search_text/traverse_directory.py +15 -2
  144. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/core.py +12 -11
  145. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/css_validator.py +1 -1
  146. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/html_validator.py +1 -1
  147. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/js_validator.py +1 -1
  148. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/json_validator.py +1 -1
  149. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/markdown_validator.py +1 -1
  150. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/ps1_validator.py +1 -1
  151. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/python_validator.py +1 -1
  152. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/xml_validator.py +1 -1
  153. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/yaml_validator.py +1 -1
  154. janito/{agent/tools/get_lines.py → tools/adapters/local/view_file.py} +45 -27
  155. janito/tools/inspect_registry.py +17 -0
  156. janito/tools/tool_base.py +105 -0
  157. janito/tools/tool_events.py +58 -0
  158. janito/tools/tool_run_exception.py +12 -0
  159. janito/{agent → tools}/tool_use_tracker.py +2 -4
  160. janito/{agent/tools_utils/utils.py → tools/tool_utils.py} +18 -9
  161. janito/tools/tools_adapter.py +207 -0
  162. janito/tools/tools_schema.py +104 -0
  163. janito/utils.py +11 -0
  164. janito/version.py +4 -0
  165. janito-2.0.1.dist-info/METADATA +232 -0
  166. janito-2.0.1.dist-info/RECORD +181 -0
  167. janito/agent/__init__.py +0 -0
  168. janito/agent/api_exceptions.py +0 -4
  169. janito/agent/config.py +0 -147
  170. janito/agent/config_defaults.py +0 -12
  171. janito/agent/config_utils.py +0 -0
  172. janito/agent/content_handler.py +0 -0
  173. janito/agent/conversation.py +0 -238
  174. janito/agent/conversation_api.py +0 -306
  175. janito/agent/conversation_exceptions.py +0 -18
  176. janito/agent/conversation_tool_calls.py +0 -39
  177. janito/agent/conversation_ui.py +0 -17
  178. janito/agent/event.py +0 -24
  179. janito/agent/event_dispatcher.py +0 -24
  180. janito/agent/event_handler_protocol.py +0 -5
  181. janito/agent/event_system.py +0 -15
  182. janito/agent/llm_conversation_history.py +0 -82
  183. janito/agent/message_handler.py +0 -20
  184. janito/agent/message_handler_protocol.py +0 -5
  185. janito/agent/openai_client.py +0 -149
  186. janito/agent/openai_schema_generator.py +0 -187
  187. janito/agent/profile_manager.py +0 -96
  188. janito/agent/queued_message_handler.py +0 -50
  189. janito/agent/rich_live.py +0 -32
  190. janito/agent/rich_message_handler.py +0 -115
  191. janito/agent/runtime_config.py +0 -36
  192. janito/agent/test_handler_protocols.py +0 -47
  193. janito/agent/test_openai_schema_generator.py +0 -93
  194. janito/agent/tests/__init__.py +0 -1
  195. janito/agent/tool_base.py +0 -63
  196. janito/agent/tool_executor.py +0 -122
  197. janito/agent/tool_registry.py +0 -49
  198. janito/agent/tools/__init__.py +0 -47
  199. janito/agent/tools/create_file.py +0 -59
  200. janito/agent/tools/delete_text_in_file.py +0 -97
  201. janito/agent/tools/find_files.py +0 -106
  202. janito/agent/tools/get_file_outline/core.py +0 -81
  203. janito/agent/tools/present_choices.py +0 -64
  204. janito/agent/tools/python_command_runner.py +0 -201
  205. janito/agent/tools/python_file_runner.py +0 -199
  206. janito/agent/tools/python_stdin_runner.py +0 -208
  207. janito/agent/tools/replace_file.py +0 -72
  208. janito/agent/tools/run_bash_command.py +0 -218
  209. janito/agent/tools/run_powershell_command.py +0 -251
  210. janito/agent/tools_utils/__init__.py +0 -1
  211. janito/agent/tools_utils/action_type.py +0 -7
  212. janito/agent/tools_utils/test_gitignore_utils.py +0 -46
  213. janito/cli/_livereload_log_utils.py +0 -13
  214. janito/cli/_print_config.py +0 -96
  215. janito/cli/_termweb_log_utils.py +0 -17
  216. janito/cli/_utils.py +0 -9
  217. janito/cli/arg_parser.py +0 -272
  218. janito/cli/cli_main.py +0 -281
  219. janito/cli/config_commands.py +0 -211
  220. janito/cli/config_runner.py +0 -35
  221. janito/cli/formatting_runner.py +0 -12
  222. janito/cli/livereload_starter.py +0 -60
  223. janito/cli/logging_setup.py +0 -38
  224. janito/cli/one_shot.py +0 -80
  225. janito/livereload/app.py +0 -25
  226. janito/rich_utils.py +0 -59
  227. janito/shell/__init__.py +0 -0
  228. janito/shell/commands/__init__.py +0 -61
  229. janito/shell/commands/config.py +0 -22
  230. janito/shell/commands/edit.py +0 -24
  231. janito/shell/commands/history_view.py +0 -18
  232. janito/shell/commands/lang.py +0 -19
  233. janito/shell/commands/livelogs.py +0 -42
  234. janito/shell/commands/prompt.py +0 -62
  235. janito/shell/commands/termweb_log.py +0 -94
  236. janito/shell/commands/tools.py +0 -26
  237. janito/shell/commands/track.py +0 -36
  238. janito/shell/main.py +0 -326
  239. janito/shell/prompt/load_prompt.py +0 -57
  240. janito/shell/prompt/session_setup.py +0 -57
  241. janito/shell/session/config.py +0 -109
  242. janito/shell/session/history.py +0 -0
  243. janito/shell/ui/interactive.py +0 -226
  244. janito/termweb/static/editor.css +0 -158
  245. janito/termweb/static/editor.css.bak +0 -145
  246. janito/termweb/static/editor.html +0 -46
  247. janito/termweb/static/editor.html.bak +0 -46
  248. janito/termweb/static/editor.js +0 -265
  249. janito/termweb/static/editor.js.bak +0 -259
  250. janito/termweb/static/explorer.html.bak +0 -59
  251. janito/termweb/static/favicon.ico +0 -0
  252. janito/termweb/static/favicon.ico.bak +0 -0
  253. janito/termweb/static/index.html +0 -53
  254. janito/termweb/static/index.html.bak +0 -54
  255. janito/termweb/static/index.html.bak.bak +0 -175
  256. janito/termweb/static/landing.html.bak +0 -36
  257. janito/termweb/static/termicon.svg +0 -1
  258. janito/termweb/static/termweb.css +0 -214
  259. janito/termweb/static/termweb.css.bak +0 -237
  260. janito/termweb/static/termweb.js +0 -162
  261. janito/termweb/static/termweb.js.bak +0 -168
  262. janito/termweb/static/termweb.js.bak.bak +0 -157
  263. janito/termweb/static/termweb_quickopen.js +0 -135
  264. janito/termweb/static/termweb_quickopen.js.bak +0 -125
  265. janito/tests/test_rich_utils.py +0 -44
  266. janito/web/__init__.py +0 -0
  267. janito/web/__main__.py +0 -25
  268. janito/web/app.py +0 -145
  269. janito-1.14.3.dist-info/METADATA +0 -313
  270. janito-1.14.3.dist-info/RECORD +0 -162
  271. janito-1.14.3.dist-info/licenses/LICENSE +0 -21
  272. /janito/{shell → cli/chat_mode/shell}/input_history.py +0 -0
  273. /janito/{shell/commands/session.py → cli/chat_mode/shell/session/history.py} +0 -0
  274. /janito/{agent/tools_utils/formatting.py → formatting.py} +0 -0
  275. /janito/{agent/tools_utils/gitignore_utils.py → gitignore_utils.py} +0 -0
  276. /janito/{agent/platform_discovery.py → platform_discovery.py} +0 -0
  277. /janito/{agent/tools → tools/adapters/local}/get_file_outline/__init__.py +0 -0
  278. /janito/{agent/tools → tools/adapters/local}/get_file_outline/markdown_outline.py +0 -0
  279. /janito/{agent/tools → tools/adapters/local}/search_text/__init__.py +0 -0
  280. /janito/{agent/tools → tools/adapters/local}/validate_file_syntax/__init__.py +0 -0
  281. {janito-1.14.3.dist-info → janito-2.0.1.dist-info}/WHEEL +0 -0
  282. {janito-1.14.3.dist-info → janito-2.0.1.dist-info}/entry_points.txt +0 -0
  283. {janito-1.14.3.dist-info → janito-2.0.1.dist-info}/top_level.txt +0 -0
@@ -1,238 +0,0 @@
1
- from janito.agent.conversation_api import (
2
- get_openai_response,
3
- retry_api_call,
4
- )
5
- from janito.agent.conversation_tool_calls import handle_tool_calls
6
- from janito.agent.conversation_ui import show_spinner, print_verbose_event
7
- from janito.agent.conversation_exceptions import (
8
- MaxRoundsExceededError,
9
- EmptyResponseError,
10
- NoToolSupportError,
11
- )
12
- from janito.agent.runtime_config import unified_config, runtime_config
13
- from janito.agent.api_exceptions import ApiError
14
- import pprint
15
- from janito.agent.llm_conversation_history import LLMConversationHistory
16
-
17
-
18
- def get_openai_response_with_content_check(client, model, messages, max_tokens):
19
- response = get_openai_response(client, model, messages, max_tokens)
20
- # Check for empty assistant message content, but allow tool/function calls
21
- if not hasattr(response, "choices") or not response.choices:
22
- return response # Let normal error handling occur
23
- choice = response.choices[0]
24
- content = getattr(choice.message, "content", None)
25
- # Check for function_call (legacy OpenAI) or tool_calls (OpenAI v2 and others)
26
- has_function_call = (
27
- hasattr(choice.message, "function_call") and choice.message.function_call
28
- )
29
- has_tool_calls = hasattr(choice.message, "tool_calls") and choice.message.tool_calls
30
- if (content is None or str(content).strip() == "") and not (
31
- has_function_call or has_tool_calls
32
- ):
33
- print(
34
- "[DEBUG] Empty assistant message detected with no tool/function call. Will retry. Raw response:"
35
- )
36
- print(repr(response))
37
- raise EmptyResponseError("Empty assistant message content.")
38
- return response
39
-
40
-
41
- class ConversationHandler:
42
- def __init__(self, client, model):
43
- self.client = client
44
- self.model = model
45
- self.usage_history = []
46
-
47
- @staticmethod
48
- def remove_system_prompt(messages):
49
- """
50
- Return a new messages list with all system prompts removed.
51
- """
52
- return [msg for msg in messages if msg.get("role") != "system"]
53
-
54
- def _resolve_max_tokens(self, max_tokens):
55
- resolved_max_tokens = max_tokens
56
- if resolved_max_tokens is None:
57
- resolved_max_tokens = unified_config.get("max_tokens", 32000)
58
- try:
59
- resolved_max_tokens = int(resolved_max_tokens)
60
- except (TypeError, ValueError):
61
- raise ValueError(
62
- "max_tokens must be an integer, got: {resolved_max_tokens!r}".format(
63
- resolved_max_tokens=resolved_max_tokens
64
- )
65
- )
66
- if runtime_config.get("vanilla_mode", False) and max_tokens is None:
67
- resolved_max_tokens = 8000
68
- return resolved_max_tokens
69
-
70
- def _call_openai_api(self, history, resolved_max_tokens, spinner):
71
- def api_call():
72
- return get_openai_response_with_content_check(
73
- self.client,
74
- self.model,
75
- history.get_messages(),
76
- resolved_max_tokens,
77
- )
78
-
79
- user_message_on_empty = "Received an empty message from you. Please try again."
80
- if spinner:
81
- response = show_spinner(
82
- "Waiting for AI response...",
83
- retry_api_call,
84
- api_call,
85
- history=history,
86
- user_message_on_empty=user_message_on_empty,
87
- )
88
- else:
89
- response = retry_api_call(
90
- api_call, history=history, user_message_on_empty=user_message_on_empty
91
- )
92
- return response
93
-
94
- def _handle_no_tool_support(self, messages, max_tokens, spinner):
95
- print(
96
- "⚠️ Endpoint does not support tool use. Proceeding in vanilla mode (tools disabled)."
97
- )
98
- runtime_config.set("vanilla_mode", True)
99
- resolved_max_tokens = 8000
100
- if max_tokens is None:
101
- runtime_config.set("max_tokens", 8000)
102
-
103
- def api_call_vanilla():
104
- return get_openai_response_with_content_check(
105
- self.client, self.model, messages, resolved_max_tokens
106
- )
107
-
108
- user_message_on_empty = "Received an empty message from you. Please try again."
109
- if spinner:
110
- response = show_spinner(
111
- "Waiting for AI response (tools disabled)...",
112
- retry_api_call,
113
- api_call_vanilla,
114
- history=None,
115
- user_message_on_empty=user_message_on_empty,
116
- )
117
- else:
118
- response = retry_api_call(
119
- api_call_vanilla,
120
- history=None,
121
- user_message_on_empty=user_message_on_empty,
122
- )
123
- print(
124
- "[DEBUG] OpenAI API raw response (tools disabled):",
125
- repr(response),
126
- )
127
- return response, resolved_max_tokens
128
-
129
- def _process_response(self, response):
130
- if runtime_config.get("verbose_response", False):
131
- pprint.pprint(response)
132
- if response is None or not getattr(response, "choices", None):
133
- error = getattr(response, "error", None)
134
- if error:
135
- print(f"ApiError: {error.get('message', error)}")
136
- raise ApiError(error.get("message", str(error)))
137
- raise EmptyResponseError(
138
- f"No choices in response; possible API or LLM error. Raw response: {response!r}"
139
- )
140
- choice = response.choices[0]
141
- usage = getattr(response, "usage", None)
142
- usage_info = (
143
- {
144
- "_debug_raw_usage": getattr(response, "usage", None),
145
- "prompt_tokens": getattr(usage, "prompt_tokens", None),
146
- "completion_tokens": getattr(usage, "completion_tokens", None),
147
- "total_tokens": getattr(usage, "total_tokens", None),
148
- }
149
- if usage
150
- else None
151
- )
152
- return choice, usage_info
153
-
154
- def _handle_tool_calls(
155
- self, choice, history, message_handler, usage_info, tool_user=False
156
- ):
157
- tool_responses = handle_tool_calls(
158
- choice.message.tool_calls, message_handler=message_handler
159
- )
160
- agent_idx = len([m for m in history.get_messages() if m.get("role") == "agent"])
161
- self.usage_history.append({"agent_index": agent_idx, "usage": usage_info})
162
- history.add_message(
163
- {
164
- "role": "assistant",
165
- "content": choice.message.content,
166
- "tool_calls": [tc.to_dict() for tc in choice.message.tool_calls],
167
- }
168
- )
169
- for tool_response in tool_responses:
170
- history.add_message(
171
- {
172
- "role": "user" if tool_user else "tool",
173
- "tool_call_id": tool_response["tool_call_id"],
174
- "content": tool_response["content"],
175
- }
176
- )
177
-
178
- def handle_conversation(
179
- self,
180
- messages,
181
- max_rounds=100,
182
- message_handler=None,
183
- verbose_response=False,
184
- spinner=False,
185
- max_tokens=None,
186
- verbose_events=False,
187
- tool_user=False,
188
- ):
189
-
190
- if isinstance(messages, LLMConversationHistory):
191
- history = messages
192
- else:
193
- history = LLMConversationHistory(messages)
194
-
195
- if len(history) == 0:
196
- raise ValueError("No prompt provided in messages")
197
-
198
- resolved_max_tokens = self._resolve_max_tokens(max_tokens)
199
-
200
- for _ in range(max_rounds):
201
- try:
202
- response = self._call_openai_api(history, resolved_max_tokens, spinner)
203
- error = getattr(response, "error", None)
204
- if error:
205
- print(f"ApiError: {error.get('message', error)}")
206
- raise ApiError(error.get("message", str(error)))
207
- except NoToolSupportError:
208
- response, resolved_max_tokens = self._handle_no_tool_support(
209
- messages, max_tokens, spinner
210
- )
211
- choice, usage_info = self._process_response(response)
212
- event = {"type": "content", "message": choice.message.content}
213
- if runtime_config.get("verbose_events", False):
214
- print_verbose_event(event)
215
- if message_handler is not None and choice.message.content:
216
- message_handler.handle_message(event)
217
- if not choice.message.tool_calls:
218
- agent_idx = len(
219
- [m for m in history.get_messages() if m.get("role") == "agent"]
220
- )
221
- self.usage_history.append(
222
- {"agent_index": agent_idx, "usage": usage_info}
223
- )
224
- history.add_message(
225
- {
226
- "role": "assistant",
227
- "content": choice.message.content,
228
- }
229
- )
230
- return {
231
- "content": choice.message.content,
232
- "usage": usage_info,
233
- "usage_history": self.usage_history,
234
- }
235
- self._handle_tool_calls(
236
- choice, history, message_handler, usage_info, tool_user=tool_user
237
- )
238
- raise MaxRoundsExceededError(f"Max conversation rounds exceeded ({max_rounds})")
@@ -1,306 +0,0 @@
1
- """
2
- Handles OpenAI API calls and retry logic for conversation.
3
- """
4
-
5
- import time
6
- from janito.i18n import tr
7
- import json
8
- from janito.agent.runtime_config import runtime_config
9
- from janito.agent.tool_registry import get_tool_schemas
10
- from janito.agent.conversation_exceptions import NoToolSupportError, EmptyResponseError
11
- from janito.agent.api_exceptions import ApiError
12
- from rich.console import Console
13
- from rich.status import Status
14
-
15
- console = Console()
16
-
17
-
18
- def _sanitize_utf8_surrogates(obj):
19
- if isinstance(obj, dict):
20
- return {k: _sanitize_utf8_surrogates(v) for k, v in obj.items()}
21
- elif isinstance(obj, list):
22
- return [_sanitize_utf8_surrogates(i) for i in obj]
23
- elif isinstance(obj, str):
24
- return obj.encode("utf-8", "surrogatepass").decode("utf-8", "ignore")
25
- else:
26
- return obj
27
-
28
-
29
- def get_openai_response(
30
- client, model, messages, max_tokens, tools=None, tool_choice=None, temperature=None
31
- ):
32
- """OpenAI API call."""
33
- messages = _sanitize_utf8_surrogates(messages)
34
- from janito.agent.conversation_exceptions import ProviderError
35
-
36
- if runtime_config.get("vanilla_mode", False):
37
- response = client.chat.completions.create(
38
- model=model,
39
- messages=messages,
40
- max_tokens=max_tokens,
41
- )
42
- else:
43
- response = client.chat.completions.create(
44
- model=model,
45
- messages=messages,
46
- tools=tools or get_tool_schemas(),
47
- tool_choice=tool_choice or "auto",
48
- temperature=temperature if temperature is not None else 0.2,
49
- max_tokens=max_tokens,
50
- )
51
- # Explicitly check for missing or empty choices (API/LLM error)
52
- if (
53
- not hasattr(response, "choices")
54
- or response.choices is None
55
- or len(response.choices) == 0
56
- ):
57
- # Always check for error before raising ProviderError
58
- error = getattr(response, "error", None)
59
- if error:
60
- print(f"ApiError: {error.get('message', error)}")
61
- print(f"Full error object: {error}")
62
- print(f"Raw response: {response}")
63
- raise ApiError(error.get("message", str(error)))
64
- raise ProviderError(
65
- f"No choices in response; possible API or LLM error. Raw response: {response!r}",
66
- {"code": 502, "raw_response": str(response)},
67
- )
68
- return response
69
-
70
-
71
- def _extract_status_and_retry_after(e, error_message):
72
- status_code = None
73
- retry_after = None
74
- if hasattr(e, "status_code"):
75
- status_code = getattr(e, "status_code")
76
- elif hasattr(e, "response") and hasattr(e.response, "status_code"):
77
- status_code = getattr(e.response, "status_code")
78
- elif "429" in error_message:
79
- status_code = 429
80
- import re
81
-
82
- match = re.search(r"status[ _]?code[=: ]+([0-9]+)", error_message)
83
- if match:
84
- status_code = int(match.group(1))
85
- match_retry = re.search(r"retry[-_ ]?after[=: ]+([0-9]+)", error_message)
86
- if match_retry:
87
- retry_after = int(match_retry.group(1))
88
- return status_code, retry_after
89
-
90
-
91
- def _calculate_wait_time(status_code, retry_after, attempt):
92
- if status_code == 429 and retry_after:
93
- return max(retry_after, 2**attempt)
94
- return 2**attempt
95
-
96
-
97
- def _log_and_sleep(
98
- message,
99
- attempt,
100
- max_retries,
101
- e=None,
102
- wait_time=None,
103
- status=None,
104
- waiting_message=None,
105
- restore_message=None,
106
- ):
107
- status_message = tr(
108
- message,
109
- attempt=attempt,
110
- max_retries=max_retries,
111
- e=e,
112
- wait_time=wait_time,
113
- )
114
- if (
115
- status is not None
116
- and waiting_message is not None
117
- and restore_message is not None
118
- ):
119
- original_message = status.status
120
- status.update(waiting_message)
121
- time.sleep(wait_time)
122
- status.update(restore_message)
123
- else:
124
- with Status(status_message, console=console, spinner="dots"):
125
- time.sleep(wait_time)
126
-
127
-
128
- def _handle_json_decode_error(e, attempt, max_retries, status=None):
129
- if attempt < max_retries:
130
- wait_time = 2**attempt
131
- if status is not None:
132
- _log_and_sleep(
133
- "Invalid/malformed response from OpenAI (attempt {attempt}/{max_retries}). Retrying in {wait_time} seconds...",
134
- attempt,
135
- max_retries,
136
- wait_time=wait_time,
137
- status=status,
138
- waiting_message="Waiting after error...",
139
- restore_message="Waiting for AI response...",
140
- )
141
- else:
142
- _log_and_sleep(
143
- "Invalid/malformed response from OpenAI (attempt {attempt}/{max_retries}). Retrying in {wait_time} seconds...",
144
- attempt,
145
- max_retries,
146
- wait_time=wait_time,
147
- )
148
- return None
149
- else:
150
- print(tr("Max retries for invalid response reached. Raising error."))
151
- raise e
152
-
153
-
154
- def _handle_no_tool_support(error_message):
155
- if "No endpoints found that support tool use" in error_message:
156
- print(tr("API does not support tool use."))
157
- raise NoToolSupportError(error_message)
158
-
159
-
160
- def _handle_rate_limit(e, attempt, max_retries, status, status_code, retry_after):
161
- wait_time = _calculate_wait_time(status_code, retry_after, attempt)
162
- if attempt < max_retries:
163
- if status is not None:
164
- _log_and_sleep(
165
- "OpenAI API rate limit (429) (attempt {attempt}/{max_retries}): {e}. Retrying in {wait_time} seconds...",
166
- attempt,
167
- max_retries,
168
- e=e,
169
- wait_time=wait_time,
170
- status=status,
171
- waiting_message="Waiting after rate limit reached...",
172
- restore_message="Waiting for AI response...",
173
- )
174
- else:
175
- _log_and_sleep(
176
- "OpenAI API rate limit (429) (attempt {attempt}/{max_retries}): {e}. Retrying in {wait_time} seconds...",
177
- attempt,
178
- max_retries,
179
- e=e,
180
- wait_time=wait_time,
181
- )
182
- return None
183
- else:
184
- raise e
185
-
186
-
187
- def _handle_server_error(e, attempt, max_retries, status, status_code):
188
- wait_time = 2**attempt
189
- if attempt < max_retries:
190
- if status is not None:
191
- _log_and_sleep(
192
- "OpenAI API server error (attempt {attempt}/{max_retries}): {e}. Retrying in {wait_time} seconds...",
193
- attempt,
194
- max_retries,
195
- e=e,
196
- wait_time=wait_time,
197
- status=status,
198
- waiting_message="Waiting after server error...",
199
- restore_message="Waiting for AI response...",
200
- )
201
- else:
202
- _log_and_sleep(
203
- "OpenAI API server error (attempt {attempt}/{max_retries}): {e}. Retrying in {wait_time} seconds...",
204
- attempt,
205
- max_retries,
206
- e=e,
207
- wait_time=wait_time,
208
- )
209
- return None
210
- else:
211
- print("Max retries for OpenAI API server error reached. Raising error.")
212
- raise e
213
-
214
-
215
- def _handle_client_error(e, status_code):
216
- print(
217
- tr(
218
- "OpenAI API client error {status_code}: {e}. Not retrying.",
219
- status_code=status_code,
220
- e=e,
221
- )
222
- )
223
- raise e
224
-
225
-
226
- def _handle_generic_error(e, attempt, max_retries, status):
227
- wait_time = 2**attempt
228
- if attempt < max_retries:
229
- if status is not None:
230
- _log_and_sleep(
231
- "OpenAI API error (attempt {attempt}/{max_retries}): {e}. Retrying in {wait_time} seconds...",
232
- attempt,
233
- max_retries,
234
- e=e,
235
- wait_time=wait_time,
236
- status=status,
237
- waiting_message="Waiting after error...",
238
- restore_message="Waiting for AI response...",
239
- )
240
- else:
241
- _log_and_sleep(
242
- "OpenAI API error (attempt {attempt}/{max_retries}): {e}. Retrying in {wait_time} seconds...",
243
- attempt,
244
- max_retries,
245
- e=e,
246
- wait_time=wait_time,
247
- )
248
- print(f"[DEBUG] Exception repr: {repr(e)}")
249
- return None
250
- else:
251
- print(tr("Max retries for OpenAI API error reached. Raising error."))
252
- raise e
253
-
254
-
255
- def _handle_general_exception(e, attempt, max_retries, status=None):
256
- error_message = str(e)
257
- _handle_no_tool_support(error_message)
258
- status_code, retry_after = _extract_status_and_retry_after(e, error_message)
259
- if status_code is not None:
260
- if status_code == 429:
261
- return _handle_rate_limit(
262
- e, attempt, max_retries, status, status_code, retry_after
263
- )
264
- elif 500 <= status_code < 600:
265
- return _handle_server_error(e, attempt, max_retries, status, status_code)
266
- elif 400 <= status_code < 500:
267
- _handle_client_error(e, status_code)
268
- return _handle_generic_error(e, attempt, max_retries, status)
269
-
270
-
271
- def retry_api_call(
272
- api_func,
273
- max_retries=5,
274
- *args,
275
- history=None,
276
- user_message_on_empty=None,
277
- status=None,
278
- **kwargs,
279
- ):
280
- for attempt in range(1, max_retries + 1):
281
- try:
282
- response = api_func(*args, **kwargs)
283
- error = getattr(response, "error", None)
284
- if error:
285
- print(f"ApiError: {error.get('message', error)}")
286
- raise ApiError(error.get("message", str(error)))
287
- return response
288
- except ApiError:
289
- raise
290
- except EmptyResponseError:
291
- if history is not None and user_message_on_empty is not None:
292
- print(
293
- f"[DEBUG] Adding user message to history: {user_message_on_empty}"
294
- )
295
- history.add_message({"role": "user", "content": user_message_on_empty})
296
- continue # Retry with updated history
297
- else:
298
- raise
299
- except json.JSONDecodeError as e:
300
- result = _handle_json_decode_error(e, attempt, max_retries, status=status)
301
- if result is not None:
302
- return result
303
- except Exception as e:
304
- result = _handle_general_exception(e, attempt, max_retries, status=status)
305
- if result is not None:
306
- return result
@@ -1,18 +0,0 @@
1
- class MaxRoundsExceededError(Exception):
2
- pass
3
-
4
-
5
- class EmptyResponseError(Exception):
6
- pass
7
-
8
-
9
- class ProviderError(Exception):
10
- def __init__(self, message, error_data):
11
- self.error_data = error_data
12
- super().__init__(message)
13
-
14
-
15
- class NoToolSupportError(Exception):
16
- """Raised when the API endpoint does not support tool use."""
17
-
18
- pass
@@ -1,39 +0,0 @@
1
- """
2
- Helpers for handling tool calls in conversation.
3
- """
4
-
5
- import json
6
- from janito.agent.tool_executor import ToolExecutor
7
- from janito.agent import tool_registry
8
- from .conversation_exceptions import MaxRoundsExceededError
9
- from janito.agent.runtime_config import runtime_config
10
-
11
-
12
- def handle_tool_calls(tool_calls, message_handler=None):
13
- max_tools = runtime_config.get("max_tools", None)
14
- tool_calls_made = 0
15
- tool_responses = []
16
- for tool_call in tool_calls:
17
- if max_tools is not None and tool_calls_made >= max_tools:
18
- raise MaxRoundsExceededError(
19
- f"Maximum number of tool calls ({max_tools}) reached in this chat session."
20
- )
21
- tool_entry = tool_registry._tool_registry[tool_call.function.name]
22
- try:
23
- arguments = json.loads(tool_call.function.arguments)
24
- except (TypeError, AttributeError, json.JSONDecodeError) as e:
25
- error_msg = f"Invalid/malformed function parameters: {e}. Please retry with valid JSON arguments."
26
- tool_responses.append(
27
- {
28
- "tool_call_id": tool_call.id,
29
- "content": error_msg,
30
- }
31
- )
32
- tool_calls_made += 1
33
- continue
34
- result = ToolExecutor(message_handler=message_handler).execute(
35
- tool_entry, tool_call, arguments
36
- )
37
- tool_responses.append({"tool_call_id": tool_call.id, "content": result})
38
- tool_calls_made += 1
39
- return tool_responses
@@ -1,17 +0,0 @@
1
- """
2
- UI helpers for conversation (spinner, verbose output).
3
- """
4
-
5
- from rich.console import Console
6
-
7
-
8
- def show_spinner(message, func, *args, **kwargs):
9
- console = Console()
10
- with console.status(message, spinner="dots") as status:
11
- result = func(*args, status=status, **kwargs)
12
- status.stop()
13
- return result
14
-
15
-
16
- def print_verbose_event(event):
17
- print(f"[EVENT] {event}")
janito/agent/event.py DELETED
@@ -1,24 +0,0 @@
1
- from enum import Enum, auto
2
- from typing import Any
3
-
4
-
5
- class EventType(Enum):
6
- CONTENT = auto()
7
- INFO = auto()
8
- SUCCESS = auto()
9
- ERROR = auto()
10
- PROGRESS = auto()
11
- WARNING = auto()
12
- STDOUT = auto()
13
- STDERR = auto()
14
- STREAM = auto()
15
- STREAM_TOOL_CALL = auto()
16
- STREAM_END = auto()
17
- TOOL_CALL = auto()
18
- TOOL_RESULT = auto()
19
-
20
-
21
- class Event:
22
- def __init__(self, type: EventType, payload: Any = None):
23
- self.type = type
24
- self.payload = payload
@@ -1,24 +0,0 @@
1
- from typing import Callable, Dict, List
2
- from janito.agent.event import Event, EventType
3
-
4
-
5
- from janito.agent.event_handler_protocol import EventHandlerProtocol
6
-
7
-
8
- class EventDispatcher:
9
- def __init__(self):
10
- self._handlers: Dict[EventType, List[Callable[[Event], None]]] = {}
11
-
12
- def register(self, event_type: EventType, handler: EventHandlerProtocol):
13
- if event_type not in self._handlers:
14
- self._handlers[event_type] = []
15
- self._handlers[event_type].append(handler)
16
-
17
- def register_all(self, handler: EventHandlerProtocol):
18
- # Register handler for all event types
19
- for event_type in EventType:
20
- self.register(event_type, handler)
21
-
22
- def dispatch(self, event: Event):
23
- for handler in self._handlers.get(event.type, []):
24
- handler.handle_event(event)
@@ -1,5 +0,0 @@
1
- from typing import Protocol, Any
2
-
3
-
4
- class EventHandlerProtocol(Protocol):
5
- def handle_event(self, event: Any) -> None: ...
@@ -1,15 +0,0 @@
1
- from janito.agent.event_dispatcher import EventDispatcher
2
- from janito.agent.rich_message_handler import RichMessageHandler
3
-
4
- # Singleton dispatcher
5
- shared_event_dispatcher = EventDispatcher()
6
-
7
- # Register handlers (example: RichMessageHandler for all events)
8
- rich_handler = RichMessageHandler()
9
- shared_event_dispatcher.register_all(rich_handler)
10
-
11
- # You can register other handlers as needed, e.g.:
12
- # queued_handler = QueuedMessageHandler(...)
13
- # shared_event_dispatcher.register_all(queued_handler)
14
- # queue_handler = QueueMessageHandler(...)
15
- # shared_event_dispatcher.register_all(queue_handler)