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
@@ -0,0 +1,230 @@
|
|
1
|
+
"""
|
2
|
+
Core PromptHandler: Handles prompt submission and response formatting for janito CLI (shared by single and chat modes).
|
3
|
+
"""
|
4
|
+
|
5
|
+
import time
|
6
|
+
from janito.version import __version__ as VERSION
|
7
|
+
from janito.performance_collector import PerformanceCollector
|
8
|
+
from rich.status import Status
|
9
|
+
from rich.console import Console
|
10
|
+
from typing import Any, Optional, Callable
|
11
|
+
from janito.driver_events import RequestStarted, RequestFinished, RequestStatus
|
12
|
+
from janito.tools.tool_events import ToolCallError
|
13
|
+
import threading
|
14
|
+
from janito.cli.verbose_output import print_verbose_header
|
15
|
+
from janito.event_bus import event_bus as global_event_bus
|
16
|
+
|
17
|
+
|
18
|
+
class StatusRef:
|
19
|
+
def __init__(self):
|
20
|
+
self.status = None
|
21
|
+
|
22
|
+
|
23
|
+
class PromptHandler:
|
24
|
+
args: Any
|
25
|
+
agent: Any
|
26
|
+
performance_collector: PerformanceCollector
|
27
|
+
console: Console
|
28
|
+
provider_instance: Any
|
29
|
+
|
30
|
+
def __init__(self, args: Any, conversation_history, provider_instance) -> None:
|
31
|
+
self.temperature = args.temperature if hasattr(args, "temperature") else None
|
32
|
+
"""
|
33
|
+
Initialize PromptHandler.
|
34
|
+
:param args: CLI or programmatic arguments for provider/model selection, etc.
|
35
|
+
:param conversation_history: LLMConversationHistory object for multi-turn chat mode.
|
36
|
+
:param provider_instance: An initialized provider instance.
|
37
|
+
"""
|
38
|
+
self.args = args
|
39
|
+
self.conversation_history = conversation_history
|
40
|
+
self.provider_instance = provider_instance
|
41
|
+
self.agent = None
|
42
|
+
from janito.perf_singleton import performance_collector
|
43
|
+
|
44
|
+
self.performance_collector = performance_collector
|
45
|
+
self.console = Console()
|
46
|
+
|
47
|
+
def _handle_inner_event(self, inner_event, on_event, status):
|
48
|
+
if on_event:
|
49
|
+
on_event(inner_event)
|
50
|
+
from janito.tools.tool_events import ToolCallFinished
|
51
|
+
|
52
|
+
# Print tool result if ToolCallFinished event is received
|
53
|
+
if isinstance(inner_event, ToolCallFinished):
|
54
|
+
# Print result if verbose_tools is enabled or always for user visibility
|
55
|
+
if hasattr(self.args, "verbose_tools") and self.args.verbose_tools:
|
56
|
+
self.console.print(
|
57
|
+
f"[cyan][tools-adapter] Tool '{inner_event.tool_name}' result:[/cyan] {inner_event.result}"
|
58
|
+
)
|
59
|
+
else:
|
60
|
+
self.console.print(inner_event.result)
|
61
|
+
return None
|
62
|
+
if isinstance(inner_event, RequestFinished):
|
63
|
+
status.update("[bold green]Received response![bold green]")
|
64
|
+
return "break"
|
65
|
+
elif (
|
66
|
+
isinstance(inner_event, RequestFinished)
|
67
|
+
and getattr(inner_event, "status", None) == "error"
|
68
|
+
):
|
69
|
+
error_msg = (
|
70
|
+
inner_event.error if hasattr(inner_event, "error") else "Unknown error"
|
71
|
+
)
|
72
|
+
if (
|
73
|
+
"Status 429" in error_msg
|
74
|
+
and "Service tier capacity exceeded for this model" in error_msg
|
75
|
+
):
|
76
|
+
status.update(
|
77
|
+
"[yellow]Service tier capacity exceeded, retrying...[yellow]"
|
78
|
+
)
|
79
|
+
return "break"
|
80
|
+
status.update(f"[bold red]Error: {error_msg}[bold red]")
|
81
|
+
self.console.print(f"[red]Error: {error_msg}[red]")
|
82
|
+
return "break"
|
83
|
+
elif isinstance(inner_event, ToolCallError):
|
84
|
+
error_msg = (
|
85
|
+
inner_event.error
|
86
|
+
if hasattr(inner_event, "error")
|
87
|
+
else "Unknown tool error"
|
88
|
+
)
|
89
|
+
tool_name = (
|
90
|
+
inner_event.tool_name
|
91
|
+
if hasattr(inner_event, "tool_name")
|
92
|
+
else "unknown"
|
93
|
+
)
|
94
|
+
status.update(
|
95
|
+
f"[bold red]Tool Error in '{tool_name}': {error_msg}[bold red]"
|
96
|
+
)
|
97
|
+
self.console.print(f"[red]Tool Error in '{tool_name}': {error_msg}[red]")
|
98
|
+
return "break"
|
99
|
+
elif isinstance(inner_event, RequestFinished) and getattr(
|
100
|
+
inner_event, "status", None
|
101
|
+
) in (RequestStatus.EMPTY_RESPONSE, RequestStatus.TIMEOUT):
|
102
|
+
details = getattr(inner_event, "details", None) or {}
|
103
|
+
block_reason = details.get("block_reason")
|
104
|
+
block_msg = details.get("block_reason_message")
|
105
|
+
msg = details.get(
|
106
|
+
"message", "LLM returned an empty or incomplete response."
|
107
|
+
)
|
108
|
+
driver_name = getattr(inner_event, "driver_name", "unknown driver")
|
109
|
+
if block_reason or block_msg:
|
110
|
+
status.update(
|
111
|
+
f"[bold yellow]Blocked by driver: {driver_name} | {block_reason or ''} {block_msg or ''}[bold yellow]"
|
112
|
+
)
|
113
|
+
self.console.print(
|
114
|
+
f"[yellow]Blocked by driver: {driver_name} (empty response): {block_reason or ''}\n{block_msg or ''}[/yellow]"
|
115
|
+
)
|
116
|
+
else:
|
117
|
+
status.update(
|
118
|
+
f"[yellow]LLM produced no output for this request (driver: {driver_name}).[/yellow]"
|
119
|
+
)
|
120
|
+
self.console.print(
|
121
|
+
f"[yellow]Warning: {msg} (driver: {driver_name})[/yellow]"
|
122
|
+
)
|
123
|
+
return "break"
|
124
|
+
# Report unknown event types
|
125
|
+
event_type = type(inner_event).__name__
|
126
|
+
self.console.print(
|
127
|
+
f"[yellow]Warning: Unknown event type encountered: {event_type}[yellow]"
|
128
|
+
)
|
129
|
+
return None
|
130
|
+
|
131
|
+
def _process_event_iter(self, event_iter, on_event):
|
132
|
+
for event in event_iter:
|
133
|
+
# Handle exceptions from generation thread
|
134
|
+
if isinstance(event, dict) and event.get("type") == "exception":
|
135
|
+
self.console.print("[red]Exception in generation thread:[red]")
|
136
|
+
self.console.print(event.get("traceback", "No traceback available"))
|
137
|
+
break
|
138
|
+
if on_event:
|
139
|
+
on_event(event)
|
140
|
+
if isinstance(event, RequestStarted):
|
141
|
+
pass # No change needed for started event
|
142
|
+
elif isinstance(event, RequestFinished) and getattr(
|
143
|
+
event, "status", None
|
144
|
+
) in ("error", "cancelled"):
|
145
|
+
# Handle error/cancelled as needed
|
146
|
+
for inner_event in event_iter:
|
147
|
+
result = self._handle_inner_event(inner_event, on_event, None)
|
148
|
+
if result == "break":
|
149
|
+
break
|
150
|
+
# After exiting, continue with next events (if any)
|
151
|
+
# Handle other event types outside the spinner if needed
|
152
|
+
elif isinstance(event, RequestFinished) and getattr(
|
153
|
+
event, "status", None
|
154
|
+
) in (RequestStatus.EMPTY_RESPONSE, RequestStatus.TIMEOUT):
|
155
|
+
details = getattr(event, "details", None) or {}
|
156
|
+
block_reason = details.get("block_reason")
|
157
|
+
block_msg = details.get("block_reason_message")
|
158
|
+
msg = details.get(
|
159
|
+
"message", "LLM returned an empty or incomplete response."
|
160
|
+
)
|
161
|
+
driver_name = getattr(event, "driver_name", "unknown driver")
|
162
|
+
if block_reason or block_msg:
|
163
|
+
self.console.print(
|
164
|
+
f"[yellow]Blocked by driver: {driver_name} (empty response): {block_reason or ''}\n{block_msg or ''}[/yellow]"
|
165
|
+
)
|
166
|
+
else:
|
167
|
+
self.console.print(
|
168
|
+
f"[yellow]Warning: {msg} (driver: {driver_name})[/yellow]"
|
169
|
+
)
|
170
|
+
else:
|
171
|
+
pass
|
172
|
+
|
173
|
+
def handle_prompt(
|
174
|
+
self, user_prompt, args=None, print_header=True, raw=False, on_event=None
|
175
|
+
):
|
176
|
+
# args defaults to self.args for compatibility in interactive mode
|
177
|
+
args = (
|
178
|
+
args if args is not None else self.args if hasattr(self, "args") else None
|
179
|
+
)
|
180
|
+
# Join/cleanup prompt
|
181
|
+
if isinstance(user_prompt, list):
|
182
|
+
user_prompt = " ".join(user_prompt).strip()
|
183
|
+
else:
|
184
|
+
user_prompt = str(user_prompt).strip() if user_prompt is not None else ""
|
185
|
+
if not user_prompt:
|
186
|
+
raise ValueError("No user prompt was provided!")
|
187
|
+
if print_header and hasattr(self, "agent") and args is not None:
|
188
|
+
print_verbose_header(self.agent, args)
|
189
|
+
self.run_prompt(user_prompt, raw=raw, on_event=on_event)
|
190
|
+
|
191
|
+
def run_prompt(
|
192
|
+
self, user_prompt: str, raw: bool = False, on_event: Optional[Callable] = None
|
193
|
+
) -> None:
|
194
|
+
"""
|
195
|
+
Handles a single prompt, using the blocking event-driven chat interface.
|
196
|
+
Optionally takes an on_event callback for custom event handling.
|
197
|
+
"""
|
198
|
+
try:
|
199
|
+
self._print_verbose_debug("Calling agent.chat()...")
|
200
|
+
final_event = self.agent.chat(prompt=user_prompt)
|
201
|
+
if hasattr(self.agent, "set_latest_event"):
|
202
|
+
self.agent.set_latest_event(final_event)
|
203
|
+
self.agent.last_event = final_event
|
204
|
+
self._print_verbose_debug(f"agent.chat() returned: {final_event}")
|
205
|
+
self._print_verbose_final_event(final_event)
|
206
|
+
if on_event and final_event is not None:
|
207
|
+
on_event(final_event)
|
208
|
+
global_event_bus.publish(final_event)
|
209
|
+
except KeyboardInterrupt:
|
210
|
+
self.console.print("[red]Request interrupted.[red]")
|
211
|
+
|
212
|
+
def _print_verbose_debug(self, message):
|
213
|
+
if hasattr(self.args, "verbose_agent") and self.args.verbose_agent:
|
214
|
+
print(f"[prompt_core][DEBUG] {message}")
|
215
|
+
|
216
|
+
def _print_verbose_final_event(self, final_event):
|
217
|
+
if hasattr(self.args, "verbose_agent") and self.args.verbose_agent:
|
218
|
+
print("[prompt_core][DEBUG] Received final_event from agent.chat:")
|
219
|
+
print(f" [prompt_core][DEBUG] type={type(final_event)}")
|
220
|
+
print(f" [prompt_core][DEBUG] content={final_event}")
|
221
|
+
|
222
|
+
def run_prompts(
|
223
|
+
self, prompts: list, raw: bool = False, on_event: Optional[Callable] = None
|
224
|
+
) -> None:
|
225
|
+
"""
|
226
|
+
Handles multiple prompts in sequence, collecting performance data for each.
|
227
|
+
"""
|
228
|
+
for prompt in prompts:
|
229
|
+
self.run_prompt(prompt, raw=raw, on_event=on_event)
|
230
|
+
# No return value
|
@@ -0,0 +1,101 @@
|
|
1
|
+
from rich.console import Console
|
2
|
+
from rich.markdown import Markdown
|
3
|
+
from rich.pretty import Pretty
|
4
|
+
from rich.panel import Panel
|
5
|
+
from rich.text import Text
|
6
|
+
from janito.event_bus.handler import EventHandlerBase
|
7
|
+
import janito.driver_events as driver_events
|
8
|
+
from janito.report_events import ReportSubtype, ReportAction
|
9
|
+
from janito.event_bus.bus import event_bus
|
10
|
+
from janito.llm import message_parts
|
11
|
+
|
12
|
+
|
13
|
+
class RichTerminalReporter(EventHandlerBase):
|
14
|
+
"""
|
15
|
+
Handles UI rendering for janito events using Rich.
|
16
|
+
|
17
|
+
- For ResponseReceived events, iterates over the 'parts' field and displays each part appropriately:
|
18
|
+
- TextMessagePart: rendered as Markdown (uses 'content' field)
|
19
|
+
- Other MessageParts: displayed using Pretty or a suitable Rich representation
|
20
|
+
- For RequestFinished events, output is printed only if raw mode is enabled (using Pretty formatting).
|
21
|
+
- Report events (info, success, error, etc.) are always printed with appropriate styling.
|
22
|
+
"""
|
23
|
+
|
24
|
+
def __init__(self, raw_mode=False):
|
25
|
+
from janito.cli.console import shared_console
|
26
|
+
|
27
|
+
self.console = shared_console
|
28
|
+
self.raw_mode = raw_mode
|
29
|
+
import janito.report_events as report_events
|
30
|
+
|
31
|
+
super().__init__(driver_events, report_events)
|
32
|
+
self._waiting_printed = False
|
33
|
+
|
34
|
+
def on_RequestStarted(self, event):
|
35
|
+
# Print waiting message with provider name
|
36
|
+
provider = None
|
37
|
+
if hasattr(event, "payload") and isinstance(event.payload, dict):
|
38
|
+
provider = event.payload.get("provider_name")
|
39
|
+
if not provider:
|
40
|
+
provider = getattr(event, "provider_name", None)
|
41
|
+
if not provider:
|
42
|
+
provider = getattr(event, "driver_name", None)
|
43
|
+
if not provider:
|
44
|
+
provider = "LLM"
|
45
|
+
self.console.print(f"[bold cyan]Waiting for {provider}...[/bold cyan]", end="")
|
46
|
+
|
47
|
+
def on_ResponseReceived(self, event):
|
48
|
+
parts = event.parts if hasattr(event, "parts") else None
|
49
|
+
if not parts:
|
50
|
+
self.console.print("[No response parts to display]")
|
51
|
+
self.console.file.flush()
|
52
|
+
return
|
53
|
+
for part in parts:
|
54
|
+
if isinstance(part, message_parts.TextMessagePart):
|
55
|
+
self.console.print(Markdown(part.content))
|
56
|
+
self.console.file.flush()
|
57
|
+
|
58
|
+
def on_RequestFinished(self, event):
|
59
|
+
self.console.print("") # Print end of line after waiting message
|
60
|
+
self._waiting_printed = False
|
61
|
+
response = event.response if hasattr(event, "response") else None
|
62
|
+
if response is not None:
|
63
|
+
if self.raw_mode:
|
64
|
+
self.console.print(Pretty(response, expand_all=True))
|
65
|
+
self.console.file.flush()
|
66
|
+
# Check for 'code' and 'event' fields in the response
|
67
|
+
code = None
|
68
|
+
event_field = None
|
69
|
+
if isinstance(response, dict):
|
70
|
+
code = response.get("code")
|
71
|
+
event_field = response.get("event")
|
72
|
+
if event_field is not None:
|
73
|
+
self.console.print(f"[bold yellow]Event:[/] {event_field}")
|
74
|
+
self.console.file.flush()
|
75
|
+
# No output if not raw_mode or if response is None
|
76
|
+
|
77
|
+
def on_ReportEvent(self, event):
|
78
|
+
msg = event.message if hasattr(event, "message") else None
|
79
|
+
subtype = event.subtype if hasattr(event, "subtype") else None
|
80
|
+
if not msg or not subtype:
|
81
|
+
return
|
82
|
+
if subtype == ReportSubtype.ACTION_INFO:
|
83
|
+
if getattr(event, "action", None) in (
|
84
|
+
getattr(ReportAction, "UPDATE", None),
|
85
|
+
getattr(ReportAction, "WRITE", None),
|
86
|
+
getattr(ReportAction, "DELETE", None),
|
87
|
+
):
|
88
|
+
self.console.print(f"[magenta]{msg}[/magenta]", end="")
|
89
|
+
else:
|
90
|
+
self.console.print(msg, end="")
|
91
|
+
self.console.file.flush()
|
92
|
+
elif subtype in (
|
93
|
+
ReportSubtype.SUCCESS,
|
94
|
+
ReportSubtype.ERROR,
|
95
|
+
ReportSubtype.WARNING,
|
96
|
+
):
|
97
|
+
self.console.print(msg)
|
98
|
+
self.console.file.flush()
|
99
|
+
else:
|
100
|
+
self.console.print(msg)
|
101
|
+
self.console.file.flush()
|
@@ -0,0 +1,137 @@
|
|
1
|
+
"""
|
2
|
+
PromptHandler: Handles prompt submission and response formatting for janito CLI (one-shot prompt execution).
|
3
|
+
"""
|
4
|
+
|
5
|
+
import time
|
6
|
+
from janito.version import __version__ as VERSION
|
7
|
+
from janito.cli.prompt_core import PromptHandler as GenericPromptHandler
|
8
|
+
from janito.cli.verbose_output import (
|
9
|
+
print_verbose_header,
|
10
|
+
print_performance,
|
11
|
+
handle_exception,
|
12
|
+
)
|
13
|
+
import janito.tools # Ensure all tools are registered
|
14
|
+
from janito.cli.console import shared_console
|
15
|
+
|
16
|
+
|
17
|
+
class PromptHandler:
|
18
|
+
def __init__(self, args, provider_instance, llm_driver_config, role=None):
|
19
|
+
self.args = args
|
20
|
+
self.provider_instance = provider_instance
|
21
|
+
self.llm_driver_config = llm_driver_config
|
22
|
+
self.role = role
|
23
|
+
from janito.agent.setup_agent import create_configured_agent
|
24
|
+
|
25
|
+
self.agent = create_configured_agent(
|
26
|
+
provider_instance=provider_instance,
|
27
|
+
llm_driver_config=llm_driver_config,
|
28
|
+
role=role,
|
29
|
+
verbose_tools=getattr(args, "verbose_tools", False),
|
30
|
+
verbose_agent=getattr(args, "verbose_agent", False),
|
31
|
+
)
|
32
|
+
# Setup conversation/history if needed
|
33
|
+
self.generic_handler = GenericPromptHandler(
|
34
|
+
args, [], provider_instance=provider_instance
|
35
|
+
)
|
36
|
+
self.generic_handler.agent = self.agent
|
37
|
+
|
38
|
+
def handle(self) -> None:
|
39
|
+
import traceback
|
40
|
+
|
41
|
+
user_prompt = " ".join(getattr(self.args, "user_prompt", [])).strip()
|
42
|
+
# UTF-8 sanitize user_prompt
|
43
|
+
sanitized = user_prompt
|
44
|
+
try:
|
45
|
+
sanitized.encode("utf-8")
|
46
|
+
except UnicodeEncodeError:
|
47
|
+
sanitized = sanitized.encode("utf-8", errors="replace").decode("utf-8")
|
48
|
+
shared_console.print(
|
49
|
+
"[yellow]Warning: Some characters in your input were not valid UTF-8 and have been replaced.[/yellow]"
|
50
|
+
)
|
51
|
+
try:
|
52
|
+
self.generic_handler.handle_prompt(
|
53
|
+
sanitized,
|
54
|
+
args=self.args,
|
55
|
+
print_header=True,
|
56
|
+
raw=getattr(self.args, "raw", False),
|
57
|
+
)
|
58
|
+
if hasattr(self.args, "verbose_agent") and self.args.verbose_agent:
|
59
|
+
print("[debug] handle_prompt() completed without exception.")
|
60
|
+
except Exception as e:
|
61
|
+
print(
|
62
|
+
f"[error] Exception occurred in handle_prompt: {type(e).__name__}: {e}"
|
63
|
+
)
|
64
|
+
traceback.print_exc()
|
65
|
+
self._post_prompt_actions()
|
66
|
+
|
67
|
+
def _post_prompt_actions(self):
|
68
|
+
final_event = getattr(self.agent, "last_event", None)
|
69
|
+
if final_event is not None:
|
70
|
+
self._print_exit_reason_and_parts(final_event)
|
71
|
+
# --- BEGIN: Print token info in rich rule if --verbose is set ---
|
72
|
+
if hasattr(self.args, "verbose") and self.args.verbose:
|
73
|
+
from janito.perf_singleton import performance_collector
|
74
|
+
|
75
|
+
token_info = performance_collector.get_last_request_usage()
|
76
|
+
from rich.rule import Rule
|
77
|
+
from rich import print as rich_print
|
78
|
+
from janito.cli.utils import format_tokens
|
79
|
+
|
80
|
+
if token_info:
|
81
|
+
if isinstance(token_info, dict):
|
82
|
+
token_str = " | ".join(
|
83
|
+
f"{k}: {format_tokens(v) if isinstance(v, int) else v}"
|
84
|
+
for k, v in token_info.items()
|
85
|
+
)
|
86
|
+
else:
|
87
|
+
token_str = str(token_info)
|
88
|
+
rich_print(Rule(f"[bold cyan]Token Usage[/bold cyan] {token_str}"))
|
89
|
+
else:
|
90
|
+
rich_print(Rule("[cyan]No token usage info available.[/cyan]"))
|
91
|
+
else:
|
92
|
+
shared_console.print("[yellow]No output produced by the model.[/yellow]")
|
93
|
+
self._cleanup_driver_and_console()
|
94
|
+
|
95
|
+
def _print_exit_reason_and_parts(self, final_event):
|
96
|
+
exit_reason = (
|
97
|
+
getattr(final_event, "metadata", {}).get("exit_reason")
|
98
|
+
if hasattr(final_event, "metadata")
|
99
|
+
else None
|
100
|
+
)
|
101
|
+
if exit_reason:
|
102
|
+
print(f"[bold yellow]Exit reason: {exit_reason}[/bold yellow]")
|
103
|
+
parts = getattr(final_event, "parts", None)
|
104
|
+
if not exit_reason:
|
105
|
+
if parts is None or len(parts) == 0:
|
106
|
+
shared_console.print(
|
107
|
+
"[yellow]No output produced by the model.[/yellow]"
|
108
|
+
)
|
109
|
+
else:
|
110
|
+
if hasattr(self.args, "verbose_agent") and self.args.verbose_agent:
|
111
|
+
print(
|
112
|
+
"[yellow]No user-visible output. Model returned the following parts:"
|
113
|
+
)
|
114
|
+
for idx, part in enumerate(parts):
|
115
|
+
print(
|
116
|
+
f" [part {idx}] type: {type(part).__name__} | content: {getattr(part, 'content', repr(part))}"
|
117
|
+
)
|
118
|
+
|
119
|
+
def _cleanup_driver_and_console(self):
|
120
|
+
if hasattr(self.agent, "join_driver"):
|
121
|
+
if (
|
122
|
+
hasattr(self.agent, "input_queue")
|
123
|
+
and self.agent.input_queue is not None
|
124
|
+
):
|
125
|
+
self.agent.input_queue.put(None)
|
126
|
+
self.agent.join_driver()
|
127
|
+
try:
|
128
|
+
shared_console.file.flush()
|
129
|
+
except Exception:
|
130
|
+
pass
|
131
|
+
try:
|
132
|
+
import sys
|
133
|
+
|
134
|
+
sys.stdout.flush()
|
135
|
+
except Exception:
|
136
|
+
pass
|
137
|
+
# If event logger is active, flush event log
|
janito/cli/termweb_starter.py
CHANGED
@@ -4,9 +4,11 @@ import tempfile
|
|
4
4
|
import time
|
5
5
|
import http.client
|
6
6
|
import os
|
7
|
+
import threading
|
8
|
+
import queue
|
7
9
|
from rich.console import Console
|
8
|
-
from janito.cli._termweb_log_utils import print_termweb_logs
|
9
10
|
from janito.i18n import tr
|
11
|
+
from janito.cli.config import get_termweb_port
|
10
12
|
|
11
13
|
|
12
14
|
def wait_for_termweb(port, timeout=3.0):
|
@@ -25,49 +27,96 @@ def wait_for_termweb(port, timeout=3.0):
|
|
25
27
|
return False
|
26
28
|
|
27
29
|
|
28
|
-
def
|
30
|
+
def termweb_start_and_watch(shell_state, shellstate_lock, selected_port=None):
|
29
31
|
"""
|
30
|
-
Start the termweb server on the given port
|
31
|
-
|
32
|
+
Start the termweb server on the given port in a background thread.
|
33
|
+
Communicates (termweb_proc, started: bool, stdout_path, stderr_path) via result_queue if provided.
|
34
|
+
Returns the Thread object.
|
32
35
|
"""
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
+
|
37
|
+
def termweb_worker(shell_state, shellstate_lock, selected_port, check_interval=2.0):
|
38
|
+
"""
|
39
|
+
Worker to start termweb process, then monitor its running state/health, and update shell_state fields in a thread-safe manner.
|
40
|
+
- shell_state: context or state object to update (must provide .termweb_pid, .termweb_status, etc.)
|
41
|
+
- shellstate_lock: a threading.Lock or similar to synchronize access
|
42
|
+
"""
|
43
|
+
console = Console()
|
44
|
+
# Try to locate app.py
|
36
45
|
app_py_path = os.path.join(os.path.dirname(__file__), "..", "termweb", "app.py")
|
37
46
|
app_py_path = os.path.abspath(app_py_path)
|
38
47
|
if not os.path.isfile(app_py_path):
|
39
|
-
# Step 2: Try installed package
|
40
48
|
try:
|
41
49
|
import janito_termweb
|
42
50
|
|
43
51
|
app_py_path = janito_termweb.__file__.replace("__init__.py", "app.py")
|
44
52
|
except ImportError:
|
45
|
-
|
46
|
-
|
53
|
+
with shellstate_lock:
|
54
|
+
shell_state.termweb_status = "notfound"
|
55
|
+
shell_state.termweb_pid = None
|
56
|
+
shell_state.termweb_stdout_path = None
|
57
|
+
shell_state.termweb_stderr_path = None
|
58
|
+
return
|
47
59
|
termweb_stdout = tempfile.NamedTemporaryFile(
|
48
60
|
delete=False, mode="w+", encoding="utf-8"
|
49
61
|
)
|
50
62
|
termweb_stderr = tempfile.NamedTemporaryFile(
|
51
63
|
delete=False, mode="w+", encoding="utf-8"
|
52
64
|
)
|
65
|
+
port_to_use = selected_port if selected_port is not None else get_termweb_port()
|
53
66
|
termweb_proc = subprocess.Popen(
|
54
|
-
[sys.executable, app_py_path, "--port", str(
|
67
|
+
[sys.executable, app_py_path, "--port", str(port_to_use)],
|
55
68
|
stdout=termweb_stdout,
|
56
69
|
stderr=termweb_stderr,
|
57
70
|
)
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
71
|
+
|
72
|
+
# Step 1: Wait for server to become healthy (initial check)
|
73
|
+
if wait_for_termweb(port_to_use, timeout=3.0):
|
74
|
+
with shellstate_lock:
|
75
|
+
shell_state.termweb_status = "running"
|
76
|
+
shell_state.termweb_pid = termweb_proc.pid
|
77
|
+
shell_state.termweb_stdout_path = termweb_stdout.name
|
78
|
+
shell_state.termweb_stderr_path = termweb_stderr.name
|
79
|
+
shell_state.termweb_port = port_to_use
|
66
80
|
else:
|
67
81
|
termweb_proc.terminate()
|
68
82
|
termweb_proc.wait()
|
69
|
-
console.print(
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
83
|
+
# console.print(f"[red]Failed to start TermWeb on port {port_to_use}. Check logs for details.[/red]")
|
84
|
+
with shellstate_lock:
|
85
|
+
shell_state.termweb_status = "failed-start"
|
86
|
+
shell_state.termweb_pid = None
|
87
|
+
shell_state.termweb_stdout_path = termweb_stdout.name
|
88
|
+
shell_state.termweb_stderr_path = termweb_stderr.name
|
89
|
+
shell_state.termweb_port = None
|
90
|
+
return
|
91
|
+
|
92
|
+
# Step 2: Run watcher loop; exit and set fields if process or health fails
|
93
|
+
import time
|
94
|
+
from http.client import HTTPConnection
|
95
|
+
|
96
|
+
while True:
|
97
|
+
if termweb_proc.poll() is not None: # means process exited
|
98
|
+
with shellstate_lock:
|
99
|
+
shell_state.termweb_status = "terminated"
|
100
|
+
shell_state.termweb_pid = None
|
101
|
+
break
|
102
|
+
try:
|
103
|
+
conn = HTTPConnection("localhost", port_to_use, timeout=0.5)
|
104
|
+
conn.request("GET", "/")
|
105
|
+
resp = conn.getresponse()
|
106
|
+
if resp.status != 200:
|
107
|
+
raise Exception("Bad status")
|
108
|
+
except Exception:
|
109
|
+
# console.print(f"[red]TermWeb on port {port_to_use} appears to have stopped responding![/red]")
|
110
|
+
with shellstate_lock:
|
111
|
+
shell_state.termweb_status = "unresponsive"
|
112
|
+
break
|
113
|
+
time.sleep(check_interval)
|
114
|
+
|
115
|
+
# Launch background thread
|
116
|
+
t = threading.Thread(
|
117
|
+
target=termweb_worker,
|
118
|
+
args=(shell_state, shellstate_lock, selected_port),
|
119
|
+
daemon=True,
|
120
|
+
)
|
121
|
+
t.start()
|
122
|
+
return t
|
janito/cli/utils.py
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
"""
|
2
|
+
Utility functions for janito CLI (shared).
|
3
|
+
"""
|
4
|
+
|
5
|
+
|
6
|
+
def format_tokens(n):
|
7
|
+
if n >= 1_000_000:
|
8
|
+
return f"{n/1_000_000:.2f}m"
|
9
|
+
elif n >= 1_000:
|
10
|
+
return f"{n/1_000:.2f}k"
|
11
|
+
else:
|
12
|
+
return str(n)
|
13
|
+
|
14
|
+
|
15
|
+
def format_generation_time(generation_time_ms):
|
16
|
+
minutes = int(generation_time_ms // 60000)
|
17
|
+
seconds = int((generation_time_ms % 60000) // 1000)
|
18
|
+
milliseconds = int(generation_time_ms % 1000)
|
19
|
+
formatted_time = ""
|
20
|
+
if minutes > 0:
|
21
|
+
formatted_time += f"{minutes}m "
|
22
|
+
if seconds > 0:
|
23
|
+
formatted_time += f"{seconds}s "
|
24
|
+
formatted_time += f"[{int(generation_time_ms)} ms]"
|
25
|
+
return formatted_time
|