janito 1.14.2__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.
- 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/{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.0.dist-info/METADATA +232 -0
- janito-2.0.0.dist-info/RECORD +180 -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.2.dist-info/METADATA +0 -306
- janito-1.14.2.dist-info/RECORD +0 -162
- janito-1.14.2.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.2.dist-info → janito-2.0.0.dist-info}/WHEEL +0 -0
- {janito-1.14.2.dist-info → janito-2.0.0.dist-info}/entry_points.txt +0 -0
- {janito-1.14.2.dist-info → janito-2.0.0.dist-info}/top_level.txt +0 -0
janito/agent/conversation.py
DELETED
@@ -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})")
|
janito/agent/conversation_api.py
DELETED
@@ -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
|
janito/agent/conversation_ui.py
DELETED
@@ -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
|
janito/agent/event_dispatcher.py
DELETED
@@ -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)
|
janito/agent/event_system.py
DELETED
@@ -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)
|