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.
- janito/__init__.py +6 -1
- janito/__main__.py +1 -1
- janito/agent/setup_agent.py +139 -0
- janito/agent/templates/profiles/{system_prompt_template_base.txt.j2 → system_prompt_template_main.txt.j2} +1 -1
- janito/cli/__init__.py +9 -0
- janito/cli/chat_mode/bindings.py +37 -0
- janito/cli/chat_mode/chat_entry.py +23 -0
- janito/cli/chat_mode/prompt_style.py +19 -0
- janito/cli/chat_mode/session.py +272 -0
- janito/{shell/prompt/completer.py → cli/chat_mode/shell/autocomplete.py} +7 -6
- janito/cli/chat_mode/shell/commands/__init__.py +55 -0
- janito/cli/chat_mode/shell/commands/base.py +9 -0
- janito/cli/chat_mode/shell/commands/clear.py +12 -0
- janito/{shell → cli/chat_mode/shell}/commands/conversation_restart.py +34 -30
- janito/cli/chat_mode/shell/commands/edit.py +25 -0
- janito/cli/chat_mode/shell/commands/help.py +16 -0
- janito/cli/chat_mode/shell/commands/history_view.py +93 -0
- janito/cli/chat_mode/shell/commands/lang.py +25 -0
- janito/cli/chat_mode/shell/commands/last.py +137 -0
- janito/cli/chat_mode/shell/commands/livelogs.py +49 -0
- janito/cli/chat_mode/shell/commands/multi.py +51 -0
- janito/cli/chat_mode/shell/commands/prompt.py +64 -0
- janito/cli/chat_mode/shell/commands/role.py +36 -0
- janito/cli/chat_mode/shell/commands/session.py +40 -0
- janito/{shell → cli/chat_mode/shell}/commands/session_control.py +2 -2
- janito/cli/chat_mode/shell/commands/termweb_log.py +92 -0
- janito/cli/chat_mode/shell/commands/tools.py +32 -0
- janito/{shell → cli/chat_mode/shell}/commands/utility.py +4 -7
- janito/{shell → cli/chat_mode/shell}/commands/verbose.py +5 -5
- janito/cli/chat_mode/shell/session/__init__.py +1 -0
- janito/{shell → cli/chat_mode/shell}/session/manager.py +9 -1
- janito/cli/chat_mode/toolbar.py +90 -0
- janito/cli/cli_commands/list_models.py +35 -0
- janito/cli/cli_commands/list_providers.py +9 -0
- janito/cli/cli_commands/list_tools.py +53 -0
- janito/cli/cli_commands/model_selection.py +50 -0
- janito/cli/cli_commands/model_utils.py +84 -0
- janito/cli/cli_commands/set_api_key.py +19 -0
- janito/cli/cli_commands/show_config.py +51 -0
- janito/cli/cli_commands/show_system_prompt.py +62 -0
- janito/cli/config.py +28 -0
- janito/cli/console.py +3 -0
- janito/cli/core/__init__.py +4 -0
- janito/cli/core/event_logger.py +59 -0
- janito/cli/core/getters.py +31 -0
- janito/cli/core/runner.py +141 -0
- janito/cli/core/setters.py +174 -0
- janito/cli/core/unsetters.py +54 -0
- janito/cli/main.py +8 -196
- janito/cli/main_cli.py +312 -0
- janito/cli/prompt_core.py +230 -0
- janito/cli/prompt_handler.py +6 -0
- janito/cli/rich_terminal_reporter.py +101 -0
- janito/cli/single_shot_mode/__init__.py +6 -0
- janito/cli/single_shot_mode/handler.py +137 -0
- janito/cli/termweb_starter.py +73 -24
- janito/cli/utils.py +25 -0
- janito/cli/verbose_output.py +196 -0
- janito/config.py +5 -0
- janito/config_manager.py +110 -0
- janito/conversation_history.py +30 -0
- janito/{agent/tools_utils/dir_walk_utils.py → dir_walk_utils.py} +3 -2
- janito/driver_events.py +98 -0
- janito/drivers/anthropic/driver.py +113 -0
- janito/drivers/azure_openai/driver.py +36 -0
- janito/drivers/driver_registry.py +33 -0
- janito/drivers/google_genai/driver.py +54 -0
- janito/drivers/google_genai/schema_generator.py +67 -0
- janito/drivers/mistralai/driver.py +41 -0
- janito/drivers/openai/driver.py +334 -0
- janito/event_bus/__init__.py +2 -0
- janito/event_bus/bus.py +68 -0
- janito/event_bus/event.py +15 -0
- janito/event_bus/handler.py +31 -0
- janito/event_bus/queue_bus.py +57 -0
- janito/exceptions.py +23 -0
- janito/formatting_token.py +54 -0
- janito/i18n/pt.py +1 -0
- janito/llm/__init__.py +5 -0
- janito/llm/agent.py +443 -0
- janito/llm/auth.py +62 -0
- janito/llm/driver.py +239 -0
- janito/llm/driver_config.py +34 -0
- janito/llm/driver_config_builder.py +34 -0
- janito/llm/driver_input.py +12 -0
- janito/llm/message_parts.py +60 -0
- janito/llm/model.py +38 -0
- janito/llm/provider.py +187 -0
- janito/perf_singleton.py +3 -0
- janito/performance_collector.py +167 -0
- janito/provider_config.py +98 -0
- janito/provider_registry.py +152 -0
- janito/providers/__init__.py +7 -0
- janito/providers/anthropic/model_info.py +22 -0
- janito/providers/anthropic/provider.py +65 -0
- janito/providers/azure_openai/model_info.py +15 -0
- janito/providers/azure_openai/provider.py +72 -0
- janito/providers/deepseek/__init__.py +1 -0
- janito/providers/deepseek/model_info.py +16 -0
- janito/providers/deepseek/provider.py +91 -0
- janito/providers/google/__init__.py +1 -0
- janito/providers/google/model_info.py +40 -0
- janito/providers/google/provider.py +69 -0
- janito/providers/mistralai/model_info.py +37 -0
- janito/providers/mistralai/provider.py +69 -0
- janito/providers/openai/__init__.py +1 -0
- janito/providers/openai/model_info.py +137 -0
- janito/providers/openai/provider.py +107 -0
- janito/providers/openai/schema_generator.py +63 -0
- janito/providers/provider_static_info.py +21 -0
- janito/providers/registry.py +26 -0
- janito/report_events.py +38 -0
- janito/termweb/app.py +1 -1
- janito/tools/__init__.py +16 -0
- janito/tools/adapters/__init__.py +1 -0
- janito/tools/adapters/local/__init__.py +54 -0
- janito/tools/adapters/local/adapter.py +92 -0
- janito/{agent/tools → tools/adapters/local}/ask_user.py +30 -13
- janito/tools/adapters/local/copy_file.py +84 -0
- janito/{agent/tools → tools/adapters/local}/create_directory.py +11 -10
- janito/tools/adapters/local/create_file.py +82 -0
- janito/tools/adapters/local/delete_text_in_file.py +136 -0
- janito/{agent/tools → tools/adapters/local}/fetch_url.py +18 -19
- janito/tools/adapters/local/find_files.py +140 -0
- janito/tools/adapters/local/get_file_outline/core.py +151 -0
- janito/{agent/tools → tools/adapters/local}/get_file_outline/python_outline.py +125 -0
- janito/tools/adapters/local/get_file_outline/python_outline_v2.py +156 -0
- janito/{agent/tools → tools/adapters/local}/get_file_outline/search_outline.py +12 -7
- janito/{agent/tools → tools/adapters/local}/move_file.py +13 -9
- janito/tools/adapters/local/open_html_in_browser.py +34 -0
- janito/{agent/tools → tools/adapters/local}/open_url.py +7 -5
- janito/tools/adapters/local/python_code_run.py +165 -0
- janito/tools/adapters/local/python_command_run.py +163 -0
- janito/tools/adapters/local/python_file_run.py +162 -0
- janito/{agent/tools → tools/adapters/local}/remove_directory.py +15 -9
- janito/{agent/tools → tools/adapters/local}/remove_file.py +17 -14
- janito/{agent/tools → tools/adapters/local}/replace_text_in_file.py +27 -22
- janito/tools/adapters/local/run_bash_command.py +176 -0
- janito/tools/adapters/local/run_powershell_command.py +219 -0
- janito/{agent/tools → tools/adapters/local}/search_text/core.py +32 -12
- janito/{agent/tools → tools/adapters/local}/search_text/match_lines.py +13 -4
- janito/{agent/tools → tools/adapters/local}/search_text/pattern_utils.py +12 -4
- janito/{agent/tools → tools/adapters/local}/search_text/traverse_directory.py +15 -2
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/core.py +12 -11
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/css_validator.py +1 -1
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/html_validator.py +1 -1
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/js_validator.py +1 -1
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/json_validator.py +1 -1
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/markdown_validator.py +1 -1
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/ps1_validator.py +1 -1
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/python_validator.py +1 -1
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/xml_validator.py +1 -1
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/yaml_validator.py +1 -1
- janito/{agent/tools/get_lines.py → tools/adapters/local/view_file.py} +45 -27
- janito/tools/inspect_registry.py +17 -0
- janito/tools/tool_base.py +105 -0
- janito/tools/tool_events.py +58 -0
- janito/tools/tool_run_exception.py +12 -0
- janito/{agent → tools}/tool_use_tracker.py +2 -4
- janito/{agent/tools_utils/utils.py → tools/tool_utils.py} +18 -9
- janito/tools/tools_adapter.py +207 -0
- janito/tools/tools_schema.py +104 -0
- janito/utils.py +11 -0
- janito/version.py +4 -0
- janito-2.0.1.dist-info/METADATA +232 -0
- janito-2.0.1.dist-info/RECORD +181 -0
- janito/agent/__init__.py +0 -0
- janito/agent/api_exceptions.py +0 -4
- janito/agent/config.py +0 -147
- janito/agent/config_defaults.py +0 -12
- janito/agent/config_utils.py +0 -0
- janito/agent/content_handler.py +0 -0
- janito/agent/conversation.py +0 -238
- janito/agent/conversation_api.py +0 -306
- janito/agent/conversation_exceptions.py +0 -18
- janito/agent/conversation_tool_calls.py +0 -39
- janito/agent/conversation_ui.py +0 -17
- janito/agent/event.py +0 -24
- janito/agent/event_dispatcher.py +0 -24
- janito/agent/event_handler_protocol.py +0 -5
- janito/agent/event_system.py +0 -15
- janito/agent/llm_conversation_history.py +0 -82
- janito/agent/message_handler.py +0 -20
- janito/agent/message_handler_protocol.py +0 -5
- janito/agent/openai_client.py +0 -149
- janito/agent/openai_schema_generator.py +0 -187
- janito/agent/profile_manager.py +0 -96
- janito/agent/queued_message_handler.py +0 -50
- janito/agent/rich_live.py +0 -32
- janito/agent/rich_message_handler.py +0 -115
- janito/agent/runtime_config.py +0 -36
- janito/agent/test_handler_protocols.py +0 -47
- janito/agent/test_openai_schema_generator.py +0 -93
- janito/agent/tests/__init__.py +0 -1
- janito/agent/tool_base.py +0 -63
- janito/agent/tool_executor.py +0 -122
- janito/agent/tool_registry.py +0 -49
- janito/agent/tools/__init__.py +0 -47
- janito/agent/tools/create_file.py +0 -59
- janito/agent/tools/delete_text_in_file.py +0 -97
- janito/agent/tools/find_files.py +0 -106
- janito/agent/tools/get_file_outline/core.py +0 -81
- janito/agent/tools/present_choices.py +0 -64
- janito/agent/tools/python_command_runner.py +0 -201
- janito/agent/tools/python_file_runner.py +0 -199
- janito/agent/tools/python_stdin_runner.py +0 -208
- janito/agent/tools/replace_file.py +0 -72
- janito/agent/tools/run_bash_command.py +0 -218
- janito/agent/tools/run_powershell_command.py +0 -251
- janito/agent/tools_utils/__init__.py +0 -1
- janito/agent/tools_utils/action_type.py +0 -7
- janito/agent/tools_utils/test_gitignore_utils.py +0 -46
- janito/cli/_livereload_log_utils.py +0 -13
- janito/cli/_print_config.py +0 -96
- janito/cli/_termweb_log_utils.py +0 -17
- janito/cli/_utils.py +0 -9
- janito/cli/arg_parser.py +0 -272
- janito/cli/cli_main.py +0 -281
- janito/cli/config_commands.py +0 -211
- janito/cli/config_runner.py +0 -35
- janito/cli/formatting_runner.py +0 -12
- janito/cli/livereload_starter.py +0 -60
- janito/cli/logging_setup.py +0 -38
- janito/cli/one_shot.py +0 -80
- janito/livereload/app.py +0 -25
- janito/rich_utils.py +0 -59
- janito/shell/__init__.py +0 -0
- janito/shell/commands/__init__.py +0 -61
- janito/shell/commands/config.py +0 -22
- janito/shell/commands/edit.py +0 -24
- janito/shell/commands/history_view.py +0 -18
- janito/shell/commands/lang.py +0 -19
- janito/shell/commands/livelogs.py +0 -42
- janito/shell/commands/prompt.py +0 -62
- janito/shell/commands/termweb_log.py +0 -94
- janito/shell/commands/tools.py +0 -26
- janito/shell/commands/track.py +0 -36
- janito/shell/main.py +0 -326
- janito/shell/prompt/load_prompt.py +0 -57
- janito/shell/prompt/session_setup.py +0 -57
- janito/shell/session/config.py +0 -109
- janito/shell/session/history.py +0 -0
- janito/shell/ui/interactive.py +0 -226
- janito/termweb/static/editor.css +0 -158
- janito/termweb/static/editor.css.bak +0 -145
- janito/termweb/static/editor.html +0 -46
- janito/termweb/static/editor.html.bak +0 -46
- janito/termweb/static/editor.js +0 -265
- janito/termweb/static/editor.js.bak +0 -259
- janito/termweb/static/explorer.html.bak +0 -59
- janito/termweb/static/favicon.ico +0 -0
- janito/termweb/static/favicon.ico.bak +0 -0
- janito/termweb/static/index.html +0 -53
- janito/termweb/static/index.html.bak +0 -54
- janito/termweb/static/index.html.bak.bak +0 -175
- janito/termweb/static/landing.html.bak +0 -36
- janito/termweb/static/termicon.svg +0 -1
- janito/termweb/static/termweb.css +0 -214
- janito/termweb/static/termweb.css.bak +0 -237
- janito/termweb/static/termweb.js +0 -162
- janito/termweb/static/termweb.js.bak +0 -168
- janito/termweb/static/termweb.js.bak.bak +0 -157
- janito/termweb/static/termweb_quickopen.js +0 -135
- janito/termweb/static/termweb_quickopen.js.bak +0 -125
- janito/tests/test_rich_utils.py +0 -44
- janito/web/__init__.py +0 -0
- janito/web/__main__.py +0 -25
- janito/web/app.py +0 -145
- janito-1.14.3.dist-info/METADATA +0 -313
- janito-1.14.3.dist-info/RECORD +0 -162
- janito-1.14.3.dist-info/licenses/LICENSE +0 -21
- /janito/{shell → cli/chat_mode/shell}/input_history.py +0 -0
- /janito/{shell/commands/session.py → cli/chat_mode/shell/session/history.py} +0 -0
- /janito/{agent/tools_utils/formatting.py → formatting.py} +0 -0
- /janito/{agent/tools_utils/gitignore_utils.py → gitignore_utils.py} +0 -0
- /janito/{agent/platform_discovery.py → platform_discovery.py} +0 -0
- /janito/{agent/tools → tools/adapters/local}/get_file_outline/__init__.py +0 -0
- /janito/{agent/tools → tools/adapters/local}/get_file_outline/markdown_outline.py +0 -0
- /janito/{agent/tools → tools/adapters/local}/search_text/__init__.py +0 -0
- /janito/{agent/tools → tools/adapters/local}/validate_file_syntax/__init__.py +0 -0
- {janito-1.14.3.dist-info → janito-2.0.1.dist-info}/WHEEL +0 -0
- {janito-1.14.3.dist-info → janito-2.0.1.dist-info}/entry_points.txt +0 -0
- {janito-1.14.3.dist-info → janito-2.0.1.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
|
janito/agent/message_handler.py
DELETED
@@ -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))
|
janito/agent/openai_client.py
DELETED
@@ -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
|
-
}
|
janito/agent/profile_manager.py
DELETED
@@ -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))
|