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
@@ -1,82 +0,0 @@
1
- from typing import List, Dict, Optional
2
- import json
3
- import sys
4
- import traceback
5
-
6
-
7
- class LLMConversationHistory:
8
- """
9
- Manages the message history for a conversation, supporting OpenAI-style roles.
10
- Intended to be used by ConversationHandler and chat loop for all history operations.
11
- """
12
-
13
- def __init__(self, messages: Optional[List[Dict]] = None):
14
- self._messages = messages.copy() if messages else []
15
-
16
- def add_message(self, message: Dict):
17
- """Append a message dict to the history."""
18
- content = message.get("content")
19
- if isinstance(content, str) and any(
20
- 0xD800 <= ord(ch) <= 0xDFFF for ch in content
21
- ):
22
- print(
23
- f"Surrogate code point detected in message content: {content!r}\nStack trace:\n{''.join(traceback.format_stack())}",
24
- file=sys.stderr,
25
- )
26
- self._messages.append(message)
27
-
28
- def get_messages(self, role: Optional[str] = None) -> List[Dict]:
29
- """
30
- Return all messages, or only those matching a given role/type (e.g., 'assistant', 'user', 'tool').
31
- If role is None, returns all messages.
32
- """
33
- if role is None:
34
- return self._messages.copy()
35
- return [msg for msg in self._messages if msg.get("role") == role]
36
-
37
- def clear(self):
38
- """Remove all messages from history."""
39
- self._messages.clear()
40
-
41
- def set_system_message(self, content: str):
42
- """
43
- Replace the first system prompt message, or insert if not present.
44
- """
45
- system_idx = next(
46
- (i for i, m in enumerate(self._messages) if m.get("role") == "system"), None
47
- )
48
- system_msg = {"role": "system", "content": content}
49
- if isinstance(content, str) and any(
50
- 0xD800 <= ord(ch) <= 0xDFFF for ch in content
51
- ):
52
- print(
53
- f"Surrogate code point detected in system message content: {content!r}\nStack trace:\n{''.join(traceback.format_stack())}",
54
- file=sys.stderr,
55
- )
56
- if system_idx is not None:
57
- self._messages[system_idx] = system_msg
58
- else:
59
- self._messages.insert(0, system_msg)
60
-
61
- def to_json_file(self, path: str):
62
- """Save the conversation history as a JSON file to the given path."""
63
- with open(path, "w", encoding="utf-8") as f:
64
- json.dump(self.get_messages(), f, indent=2, ensure_ascii=False)
65
-
66
- def __len__(self):
67
- return len(self._messages)
68
-
69
- def __getitem__(self, idx):
70
- return self._messages[idx]
71
-
72
- def remove_last_message(self):
73
- """Remove and return the last message in the history, or None if empty."""
74
- if self._messages:
75
- return self._messages.pop()
76
- return None
77
-
78
- def last_message(self):
79
- """Return the last message in the history, or None if empty."""
80
- if self._messages:
81
- return self._messages[-1]
82
- return None
@@ -1,20 +0,0 @@
1
- from janito.agent.message_handler_protocol import MessageHandlerProtocol
2
-
3
-
4
- class QueueMessageHandler(MessageHandlerProtocol):
5
- def __init__(self, queue, *args, **kwargs):
6
- self._queue = queue
7
-
8
- def handle_tool_call(self, tool_call):
9
- # All output is routed through the unified message handler and queue
10
- return super().handle_tool_call(tool_call)
11
-
12
- def handle_message(self, msg, msg_type=None):
13
- # Unified: send content (agent/LLM) messages to the frontend
14
- if not isinstance(msg, dict):
15
- raise TypeError(
16
- f"QueueMessageHandler.handle_message expects a dict with 'type' and 'message', got {type(msg)}: {msg!r}"
17
- )
18
- msg_type = msg.get("type", "info")
19
- message = msg.get("message", "")
20
- self._queue.put(("message", message, msg_type))
@@ -1,5 +0,0 @@
1
- from typing import Protocol
2
-
3
-
4
- class MessageHandlerProtocol(Protocol):
5
- def handle_message(self, msg: dict, msg_type: str = None) -> None: ...
@@ -1,149 +0,0 @@
1
- """Agent module: defines the core LLM agent with tool and conversation handling."""
2
-
3
- import time
4
- from openai import OpenAI
5
- from janito.agent.conversation import ConversationHandler
6
- from janito.agent.conversation_exceptions import ProviderError
7
- from janito.agent.llm_conversation_history import LLMConversationHistory
8
-
9
-
10
- class Agent:
11
- """Agent capable of handling conversations and tool calls."""
12
-
13
- REFERER = "www.janito.dev"
14
- TITLE = "Janito"
15
-
16
- def __init__(
17
- self,
18
- api_key: str,
19
- model: str = None,
20
- system_prompt_template: str | None = None,
21
- verbose_tools: bool = False,
22
- base_url: str = None,
23
- azure_openai_api_version: str = "2023-05-15",
24
- use_azure_openai: bool = False,
25
- ):
26
- """
27
- Initialize Agent.
28
-
29
- Args:
30
- api_key: API key for OpenAI-compatible service.
31
- model: Model name to use.
32
- system_prompt_template: Optional system prompt override.
33
- verbose_tools: Enable verbose tool call logging.
34
- base_url: API base URL.
35
- azure_openai_api_version: Azure OpenAI API version (default: "2023-05-15").
36
- use_azure_openai: Whether to use Azure OpenAI client (default: False).
37
- """
38
- self.api_key = api_key
39
- self.model = model
40
- self.system_prompt_template = system_prompt_template
41
- if use_azure_openai:
42
- # Import inside conditional to avoid requiring AzureOpenAI unless needed
43
- from openai import AzureOpenAI
44
-
45
- if base_url:
46
- self.client = AzureOpenAI(
47
- api_key=api_key,
48
- azure_endpoint=base_url,
49
- api_version=azure_openai_api_version,
50
- )
51
- else:
52
- self.client = AzureOpenAI(
53
- api_key=api_key,
54
- api_version=azure_openai_api_version,
55
- )
56
- else:
57
- if base_url:
58
- self.client = OpenAI(
59
- base_url=base_url,
60
- api_key=api_key,
61
- default_headers={
62
- "HTTP-Referer": self.REFERER,
63
- "X-Title": self.TITLE,
64
- },
65
- )
66
- else:
67
- self.client = OpenAI(
68
- api_key=api_key,
69
- default_headers={
70
- "HTTP-Referer": self.REFERER,
71
- "X-Title": self.TITLE,
72
- },
73
- )
74
-
75
- self.conversation_handler = ConversationHandler(
76
- self.client,
77
- self.model,
78
- )
79
-
80
- @property
81
- def usage_history(self):
82
- return self.conversation_handler.usage_history
83
-
84
- def chat(
85
- self,
86
- messages=None,
87
- message_handler=None,
88
- spinner=False,
89
- max_tokens=None,
90
- max_rounds=100,
91
- tool_user=False,
92
- ):
93
- """
94
- Start a chat conversation with the agent.
95
-
96
- Args:
97
- messages: LLMConversationHistory instance or None.
98
- message_handler: Optional handler for event messages.
99
- spinner: Show spinner during request.
100
- max_tokens: Max tokens for completion.
101
- max_rounds: Max conversation rounds.
102
- Returns:
103
- dict with 'content', 'usage', and 'usage_history'.
104
- """
105
- from janito.agent.runtime_config import runtime_config
106
-
107
- if messages is None:
108
- messages = LLMConversationHistory()
109
- elif not isinstance(messages, LLMConversationHistory):
110
- raise TypeError(
111
- "Agent.chat expects a LLMConversationHistory instance or None."
112
- )
113
-
114
- max_retries = 5
115
- for attempt in range(1, max_retries + 1):
116
- try:
117
- return self.conversation_handler.handle_conversation(
118
- messages,
119
- max_rounds=max_rounds,
120
- message_handler=message_handler,
121
- verbose_response=runtime_config.get("verbose_response", False),
122
- spinner=spinner,
123
- max_tokens=max_tokens,
124
- verbose_events=runtime_config.get("verbose_events", False),
125
- tool_user=tool_user,
126
- )
127
- except ProviderError as e:
128
- error_data = getattr(e, "error_data", {}) or {}
129
- code = error_data.get("code", "")
130
- # Retry only on 5xx errors
131
- if isinstance(code, int) and 500 <= code < 600:
132
- pass
133
- elif (
134
- isinstance(code, str) and code.isdigit() and 500 <= int(code) < 600
135
- ):
136
- code = int(code)
137
- else:
138
- raise
139
-
140
- if attempt < max_retries:
141
- print(
142
- f"ProviderError with 5xx code encountered (attempt {attempt}/{max_retries}). Retrying in 5 seconds..."
143
- )
144
- time.sleep(5)
145
- else:
146
- print("Max retries reached. Raising error.")
147
- raise
148
-
149
- raise
@@ -1,187 +0,0 @@
1
- """
2
- Generates OpenAI-compatible function schemas from Python callables and class docstrings.
3
-
4
- - Ensures all parameters are documented and type-annotated.
5
- - Integrates return documentation into the schema description.
6
- - Supports Google, NumPy, and relaxed docstring formats.
7
- """
8
-
9
- import inspect
10
- import re
11
- import typing
12
- from collections import OrderedDict
13
-
14
- PYTHON_TYPE_TO_JSON = {
15
- str: "string",
16
- int: "integer",
17
- float: "number",
18
- bool: "boolean",
19
- list: "array",
20
- dict: "object",
21
- }
22
-
23
-
24
- class OpenAISchemaGenerator:
25
- """
26
- Generates OpenAI-compatible function schemas from Python callables and class docstrings.
27
-
28
- - Ensures all parameters are documented and type-annotated.
29
- - Integrates return documentation into the schema description.
30
- - Supports Google, NumPy, and relaxed docstring formats.
31
- """
32
-
33
- def __init__(self):
34
- pass
35
-
36
- def type_to_json_schema(self, annotation):
37
- if hasattr(annotation, "__origin__"):
38
- if annotation.__origin__ is list or annotation.__origin__ is typing.List:
39
- return {
40
- "type": "array",
41
- "items": self.type_to_json_schema(annotation.__args__[0]),
42
- }
43
- if annotation.__origin__ is dict or annotation.__origin__ is typing.Dict:
44
- return {"type": "object"}
45
- return {"type": PYTHON_TYPE_TO_JSON.get(annotation, "string")}
46
-
47
- def parse_param_section(self, lines, param_section_headers):
48
- param_descs = {}
49
- in_params = False
50
- for line in lines:
51
- stripped_line = line.strip()
52
- if any(
53
- stripped_line.lower().startswith(h + ":") or stripped_line.lower() == h
54
- for h in param_section_headers
55
- ):
56
- in_params = True
57
- continue
58
- if in_params:
59
- m = re.match(
60
- r"([a-zA-Z_][a-zA-Z0-9_]*)\s*(?:\(([^)]+)\))?\s*[:\-]?\s*(.+)",
61
- stripped_line,
62
- )
63
- if m:
64
- param, _, desc = m.groups()
65
- param_descs[param] = desc.strip()
66
- elif stripped_line and stripped_line[0] != "-":
67
- if param_descs:
68
- last = list(param_descs)[-1]
69
- param_descs[last] += " " + stripped_line
70
- if (
71
- stripped_line.lower().startswith("returns:")
72
- or stripped_line.lower() == "returns"
73
- ):
74
- break
75
- return param_descs
76
-
77
- def parse_return_section(self, lines):
78
- in_returns = False
79
- return_desc = ""
80
- for line in lines:
81
- stripped_line = line.strip()
82
- if (
83
- stripped_line.lower().startswith("returns:")
84
- or stripped_line.lower() == "returns"
85
- ):
86
- in_returns = True
87
- continue
88
- if in_returns:
89
- if stripped_line:
90
- return_desc += (" " if return_desc else "") + stripped_line
91
- return return_desc
92
-
93
- def parse_docstring(self, docstring: str):
94
- """
95
- Parses a docstring to extract summary, parameter descriptions, and return description.
96
- Accepts Google, NumPy, and relaxed formats.
97
- Returns: summary, {param: description}, return_description
98
- """
99
- if not docstring:
100
- return "", {}, ""
101
- lines = docstring.strip().split("\n")
102
- summary = lines[0].strip()
103
- param_section_headers = ("args", "arguments", "params", "parameters")
104
- param_descs = self.parse_param_section(lines[1:], param_section_headers)
105
- return_desc = self.parse_return_section(lines[1:])
106
- return summary, param_descs, return_desc
107
-
108
- def generate_schema(self, tool_class):
109
- """
110
- Generates an OpenAI-compatible function schema for a tool class.
111
- The tool class must have _tool_run_method and _tool_name attributes set by the tool registration decorator.
112
- Raises ValueError if the return type is not explicitly str or if any parameter is missing a type hint.
113
- """
114
- if not hasattr(tool_class, "_tool_run_method") or not hasattr(
115
- tool_class, "_tool_name"
116
- ):
117
- raise ValueError(
118
- "Tool class must have _tool_run_method and _tool_name attributes (set by @register_tool)."
119
- )
120
- func = tool_class._tool_run_method
121
- tool_name = tool_class._tool_name
122
- sig = inspect.signature(func)
123
- # Enforce explicit str return type
124
- if sig.return_annotation is inspect._empty or sig.return_annotation is not str:
125
- raise ValueError(
126
- f"Tool '{tool_name}' must have an explicit return type of 'str'. Found: {sig.return_annotation}"
127
- )
128
- # Enforce type hints for all parameters (except self)
129
- missing_type_hints = [
130
- name
131
- for name, param in sig.parameters.items()
132
- if name != "self" and param.annotation is inspect._empty
133
- ]
134
- if missing_type_hints:
135
- raise ValueError(
136
- f"Tool '{tool_name}' is missing type hints for parameter(s): {', '.join(missing_type_hints)}.\n"
137
- f"All parameters must have explicit type hints for schema generation."
138
- )
139
- # Only use the class docstring for schema generation
140
- class_doc = (
141
- tool_class.__doc__.strip() if tool_class and tool_class.__doc__ else ""
142
- )
143
- summary, param_descs, return_desc = self.parse_docstring(class_doc)
144
- description = summary
145
- if return_desc:
146
- description += f"\n\nReturns: {return_desc}"
147
- # Check that all parameters in the signature have documentation
148
- undocumented = [
149
- name
150
- for name, param in sig.parameters.items()
151
- if name != "self" and name not in param_descs
152
- ]
153
- if undocumented:
154
- raise ValueError(
155
- f"Tool '{tool_name}' is missing docstring documentation for parameter(s): {', '.join(undocumented)}.\n"
156
- f"Parameter documentation must be provided in the Tool class docstring, not the method docstring."
157
- )
158
- properties = OrderedDict()
159
- required = []
160
- # Inject tool_call_reason as the first required parameter, unless --ntt is set
161
- from janito.agent.runtime_config import runtime_config
162
-
163
- if not runtime_config.get("no_tools_tracking", False):
164
- properties["tool_call_reason"] = {
165
- "type": "string",
166
- "description": "The reason or context for why this tool is being called. This is required for traceability.",
167
- }
168
- required.append("tool_call_reason")
169
- for name, param in sig.parameters.items():
170
- if name == "self":
171
- continue
172
- annotation = param.annotation
173
- pdesc = param_descs.get(name, "")
174
- schema = self.type_to_json_schema(annotation)
175
- schema["description"] = pdesc
176
- properties[name] = schema
177
- if param.default == inspect._empty:
178
- required.append(name)
179
- return {
180
- "name": tool_name,
181
- "description": description,
182
- "parameters": {
183
- "type": "object",
184
- "properties": properties,
185
- "required": required,
186
- },
187
- }
@@ -1,96 +0,0 @@
1
- from openai import OpenAI
2
- from jinja2 import Environment, FileSystemLoader, select_autoescape
3
- from pathlib import Path
4
- from janito.agent.platform_discovery import PlatformDiscovery
5
-
6
-
7
- class AgentProfileManager:
8
- REFERER = "www.janito.dev"
9
- TITLE = "Janito"
10
-
11
- def set_role(self, new_role):
12
- """Set the agent's role and force prompt re-rendering."""
13
- self.role = new_role
14
- self.refresh_prompt()
15
-
16
- def render_prompt(self):
17
- base_dir = Path(__file__).parent / "templates"
18
- profiles_dir = base_dir / "profiles"
19
- if getattr(self, "lang", "en") == "pt":
20
- main_template_name = "system_prompt_template_base_pt.txt.j2"
21
- else:
22
- main_template_name = "system_prompt_template_base.txt.j2"
23
- pd = PlatformDiscovery()
24
- platform_name = pd.get_platform_name()
25
- python_version = pd.get_python_version()
26
- shell_info = pd.detect_shell()
27
-
28
- context = {
29
- "role": self.role,
30
- "interaction_mode": self.interaction_mode,
31
- "platform": platform_name,
32
- "python_version": python_version,
33
- "shell_info": shell_info,
34
- }
35
- env = Environment(
36
- loader=FileSystemLoader(str(profiles_dir)),
37
- autoescape=select_autoescape(["txt", "j2"]),
38
- )
39
- template = env.get_template(main_template_name)
40
- prompt = template.render(**context)
41
- return prompt
42
-
43
- def __init__(
44
- self,
45
- api_key,
46
- model,
47
- role,
48
- profile_name,
49
- interaction_mode,
50
- verbose_tools,
51
- base_url,
52
- azure_openai_api_version,
53
- use_azure_openai,
54
- lang="en",
55
- ):
56
- self.api_key = api_key
57
- self.model = model
58
- self.role = role
59
- self.profile_name = "base"
60
- self.interaction_mode = interaction_mode
61
- self.verbose_tools = verbose_tools
62
- self.base_url = base_url
63
- self.azure_openai_api_version = azure_openai_api_version
64
- self.use_azure_openai = use_azure_openai
65
- self.lang = lang
66
- if use_azure_openai:
67
- from openai import AzureOpenAI
68
-
69
- self.client = AzureOpenAI(
70
- api_key=api_key,
71
- azure_endpoint=base_url,
72
- api_version=azure_openai_api_version,
73
- )
74
- else:
75
- self.client = OpenAI(
76
- base_url=base_url,
77
- api_key=api_key,
78
- default_headers={"HTTP-Referer": self.REFERER, "X-Title": self.TITLE},
79
- )
80
- from janito.agent.openai_client import Agent
81
-
82
- self.agent = Agent(
83
- api_key=api_key,
84
- model=model,
85
- base_url=base_url,
86
- use_azure_openai=use_azure_openai,
87
- azure_openai_api_version=azure_openai_api_version,
88
- )
89
- self.system_prompt_template = None
90
-
91
- def refresh_prompt(self):
92
- self.system_prompt_template = self.render_prompt()
93
- self.agent.system_prompt_template = self.system_prompt_template
94
-
95
-
96
- # All prompt rendering is now handled by AgentProfileManager.
@@ -1,50 +0,0 @@
1
- from janito.i18n import tr
2
-
3
-
4
- class QueuedMessageHandler:
5
- def __init__(self, queue, *args, **kwargs):
6
- self._queue = queue
7
-
8
- def handle_message(self, msg, msg_type=None):
9
- # Unified: send content (agent/LLM) messages to the frontend via queue
10
- if not isinstance(msg, dict):
11
- raise TypeError(
12
- tr(
13
- "QueuedMessageHandler.handle_message expects a dict with 'type' and 'message', got {msg_type}: {msg!r}",
14
- msg_type=type(msg),
15
- msg=msg,
16
- )
17
- )
18
- msg_type = msg.get("type", "info")
19
- # For tool_call and tool_result, print and forward the full dict
20
- if msg_type in ("tool_call", "tool_result"):
21
- print(
22
- tr(
23
- "[QueuedMessageHandler] {msg_type}: {msg}",
24
- msg_type=msg_type,
25
- msg=msg,
26
- )
27
- )
28
- self._queue.put(msg)
29
- return
30
- message = msg.get("message", "")
31
- # For normal agent/user/info messages, emit type 'content' for frontend compatibility
32
- print(
33
- tr(
34
- "[QueuedMessageHandler] {msg_type}: {message}",
35
- msg_type=msg_type,
36
- message=message,
37
- )
38
- )
39
- if msg_type == "content":
40
- self._queue.put({"type": "content", "content": message})
41
- elif msg_type == "info":
42
- out = {"type": "info", "message": message}
43
- if "tool" in msg:
44
- out["tool"] = msg["tool"]
45
- self._queue.put(out)
46
- else:
47
- out = {"type": msg_type, "message": message}
48
- if "tool" in msg:
49
- out["tool"] = msg["tool"]
50
- self._queue.put(out)
janito/agent/rich_live.py DELETED
@@ -1,32 +0,0 @@
1
- from rich.live import Live
2
- from rich.markdown import Markdown
3
- from rich.console import Console
4
-
5
-
6
- class LiveMarkdownDisplay:
7
- def __init__(self, console=None):
8
- self.console = console or Console()
9
- self._accumulated = ""
10
- self._live = None
11
-
12
- def start(self):
13
- self._live = Live(
14
- Markdown(self._accumulated), console=self.console, refresh_per_second=8
15
- )
16
- self._live.__enter__()
17
-
18
- def update(self, part):
19
- self._accumulated += part
20
- # Only re-render on newlines for efficiency
21
- if "\n" in part:
22
- self._live.update(Markdown(self._accumulated))
23
-
24
- def stop(self):
25
- if self._live:
26
- self._live.__exit__(None, None, None)
27
- self._live = None
28
-
29
- def reset(self):
30
- self._accumulated = ""
31
- if self._live:
32
- self._live.update(Markdown(self._accumulated))