janito 1.9.0__py3-none-any.whl → 1.11.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 +1 -1
- janito/agent/api_exceptions.py +4 -0
- janito/agent/config.py +1 -1
- janito/agent/config_defaults.py +2 -26
- janito/agent/conversation.py +163 -122
- janito/agent/conversation_api.py +246 -168
- janito/agent/conversation_ui.py +1 -1
- janito/agent/{conversation_history.py → llm_conversation_history.py} +30 -1
- janito/agent/openai_client.py +38 -23
- janito/agent/openai_schema_generator.py +162 -129
- janito/agent/platform_discovery.py +134 -77
- janito/agent/profile_manager.py +5 -5
- janito/agent/rich_message_handler.py +80 -31
- janito/agent/templates/profiles/system_prompt_template_base.txt.j2 +20 -4
- janito/agent/test_openai_schema_generator.py +93 -0
- janito/agent/tool_base.py +7 -2
- janito/agent/tool_executor.py +54 -49
- janito/agent/tool_registry.py +5 -2
- janito/agent/tool_use_tracker.py +26 -5
- janito/agent/tools/__init__.py +8 -3
- janito/agent/tools/create_directory.py +3 -1
- janito/agent/tools/create_file.py +7 -1
- janito/agent/tools/fetch_url.py +40 -3
- janito/agent/tools/find_files.py +29 -14
- janito/agent/tools/get_file_outline/core.py +7 -8
- janito/agent/tools/get_file_outline/python_outline.py +139 -95
- janito/agent/tools/get_file_outline/search_outline.py +3 -1
- janito/agent/tools/get_lines.py +98 -64
- janito/agent/tools/move_file.py +59 -31
- janito/agent/tools/open_url.py +31 -0
- janito/agent/tools/present_choices.py +3 -1
- janito/agent/tools/python_command_runner.py +149 -0
- janito/agent/tools/python_file_runner.py +147 -0
- janito/agent/tools/python_stdin_runner.py +153 -0
- janito/agent/tools/remove_directory.py +3 -1
- janito/agent/tools/remove_file.py +5 -1
- janito/agent/tools/replace_file.py +12 -2
- janito/agent/tools/replace_text_in_file.py +195 -149
- janito/agent/tools/run_bash_command.py +30 -69
- janito/agent/tools/run_powershell_command.py +138 -105
- janito/agent/tools/search_text/__init__.py +1 -0
- janito/agent/tools/search_text/core.py +176 -0
- janito/agent/tools/search_text/match_lines.py +58 -0
- janito/agent/tools/search_text/pattern_utils.py +65 -0
- janito/agent/tools/search_text/traverse_directory.py +127 -0
- janito/agent/tools/validate_file_syntax/core.py +43 -30
- janito/agent/tools/validate_file_syntax/html_validator.py +21 -5
- janito/agent/tools/validate_file_syntax/markdown_validator.py +77 -34
- janito/agent/tools_utils/action_type.py +7 -0
- janito/agent/tools_utils/dir_walk_utils.py +3 -2
- janito/agent/tools_utils/formatting.py +47 -21
- janito/agent/tools_utils/gitignore_utils.py +89 -40
- janito/agent/tools_utils/test_gitignore_utils.py +46 -0
- janito/agent/tools_utils/utils.py +7 -1
- janito/cli/_print_config.py +63 -61
- janito/cli/arg_parser.py +13 -12
- janito/cli/cli_main.py +137 -147
- janito/cli/config_commands.py +112 -109
- janito/cli/main.py +152 -174
- janito/cli/one_shot.py +40 -26
- janito/i18n/__init__.py +1 -1
- janito/rich_utils.py +46 -8
- janito/shell/commands/__init__.py +2 -4
- janito/shell/commands/conversation_restart.py +3 -1
- janito/shell/commands/edit.py +3 -0
- janito/shell/commands/history_view.py +3 -3
- janito/shell/commands/lang.py +3 -0
- janito/shell/commands/livelogs.py +5 -3
- janito/shell/commands/prompt.py +6 -0
- janito/shell/commands/session.py +3 -0
- janito/shell/commands/session_control.py +3 -0
- janito/shell/commands/termweb_log.py +8 -0
- janito/shell/commands/tools.py +3 -0
- janito/shell/commands/track.py +36 -0
- janito/shell/commands/utility.py +13 -18
- janito/shell/commands/verbose.py +3 -4
- janito/shell/input_history.py +62 -0
- janito/shell/main.py +160 -181
- janito/shell/session/config.py +83 -75
- janito/shell/session/manager.py +0 -21
- janito/shell/ui/interactive.py +97 -75
- janito/termweb/static/editor.css +32 -33
- janito/termweb/static/editor.css.bak +140 -22
- janito/termweb/static/editor.html +12 -7
- janito/termweb/static/editor.html.bak +16 -11
- janito/termweb/static/editor.js +94 -40
- janito/termweb/static/editor.js.bak +97 -65
- janito/termweb/static/index.html +1 -2
- janito/termweb/static/index.html.bak +1 -1
- janito/termweb/static/termweb.css +1 -22
- janito/termweb/static/termweb.css.bak +6 -4
- janito/termweb/static/termweb.js +0 -6
- janito/termweb/static/termweb.js.bak +1 -2
- janito/tests/test_rich_utils.py +44 -0
- janito/web/app.py +0 -75
- {janito-1.9.0.dist-info → janito-1.11.0.dist-info}/METADATA +61 -42
- janito-1.11.0.dist-info/RECORD +163 -0
- {janito-1.9.0.dist-info → janito-1.11.0.dist-info}/WHEEL +1 -1
- janito/agent/providers.py +0 -77
- janito/agent/tools/run_python_command.py +0 -161
- janito/agent/tools/search_text.py +0 -204
- janito/shell/commands/sum.py +0 -49
- janito-1.9.0.dist-info/RECORD +0 -151
- {janito-1.9.0.dist-info → janito-1.11.0.dist-info}/entry_points.txt +0 -0
- {janito-1.9.0.dist-info → janito-1.11.0.dist-info}/licenses/LICENSE +0 -0
- {janito-1.9.0.dist-info → janito-1.11.0.dist-info}/top_level.txt +0 -0
janito/shell/commands/tools.py
CHANGED
@@ -0,0 +1,36 @@
|
|
1
|
+
from janito.agent.tool_use_tracker import ToolUseTracker
|
2
|
+
from rich.console import Console
|
3
|
+
from rich.table import Table
|
4
|
+
|
5
|
+
|
6
|
+
def handle_track(console: Console, args=None, shell_state=None):
|
7
|
+
tracker = ToolUseTracker.instance()
|
8
|
+
history = tracker.get_history()
|
9
|
+
if not history:
|
10
|
+
console.print("[bold yellow]No tool usage history found.[/bold yellow]")
|
11
|
+
return
|
12
|
+
table = Table(show_header=True, header_style="bold magenta")
|
13
|
+
table.add_column("#", style="dim", width=4)
|
14
|
+
table.add_column("Tool")
|
15
|
+
table.add_column("Params/Result")
|
16
|
+
for idx, entry in enumerate(history, 1):
|
17
|
+
tool = entry["tool"]
|
18
|
+
params = entry["params"].copy()
|
19
|
+
result = entry.get("result", "")
|
20
|
+
# For create/replace file, trim content in params only
|
21
|
+
if tool in ("create_file", "replace_file") and "content" in params:
|
22
|
+
content = params["content"]
|
23
|
+
if isinstance(content, str):
|
24
|
+
lines = content.splitlines()
|
25
|
+
if len(lines) > 3:
|
26
|
+
params["content"] = (
|
27
|
+
"\n".join(lines[:2])
|
28
|
+
+ f"\n... (trimmed, {len(lines)} lines total)"
|
29
|
+
)
|
30
|
+
elif len(content) > 120:
|
31
|
+
params["content"] = content[:120] + "... (trimmed)"
|
32
|
+
else:
|
33
|
+
params["content"] = content
|
34
|
+
param_result = f"{params}\n--- Result ---\n{result}" if result else str(params)
|
35
|
+
table.add_row(str(idx), tool, param_result)
|
36
|
+
console.print(table)
|
janito/shell/commands/utility.py
CHANGED
@@ -1,22 +1,11 @@
|
|
1
1
|
def handle_help(console, **kwargs):
|
2
|
-
|
3
|
-
|
4
|
-
[bold green]Available commands:[/bold green]
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
/prompt - Show the system prompt
|
10
|
-
/role - Change the system role
|
11
|
-
/clear - Clear the terminal screen
|
12
|
-
/multi - Provide multiline input as next message
|
13
|
-
/config - Show or set configuration (see: /config show, /config set local|global key=value)
|
14
|
-
/termweb-logs - Show the last lines of the latest termweb logs
|
15
|
-
/livelogs - Show live updates from the server log file (default: server.log)
|
16
|
-
/termweb-status - Show status information about the running termweb server
|
17
|
-
/verbose [on|off] - Show or set verbose mode for this session
|
18
|
-
"""
|
19
|
-
)
|
2
|
+
from janito.shell.commands import COMMAND_HANDLERS
|
3
|
+
|
4
|
+
console.print("[bold green]Available commands:[/bold green]")
|
5
|
+
for cmd, handler in COMMAND_HANDLERS.items():
|
6
|
+
help_text = getattr(handler, "help_text", None)
|
7
|
+
if help_text:
|
8
|
+
console.print(f" {cmd} - {help_text}")
|
20
9
|
|
21
10
|
|
22
11
|
def handle_clear(console, **kwargs):
|
@@ -25,9 +14,15 @@ def handle_clear(console, **kwargs):
|
|
25
14
|
os.system("cls" if os.name == "nt" else "clear")
|
26
15
|
|
27
16
|
|
17
|
+
handle_clear.help_text = "Clear the terminal screen"
|
18
|
+
|
19
|
+
|
28
20
|
def handle_multi(console, shell_state=None, **kwargs):
|
29
21
|
console.print(
|
30
22
|
"[bold yellow]Multiline mode activated. Provide or write your text and press Esc + Enter to submit.[/bold yellow]"
|
31
23
|
)
|
32
24
|
if shell_state:
|
33
25
|
shell_state.paste_mode = True
|
26
|
+
|
27
|
+
|
28
|
+
handle_multi.help_text = "Provide multiline input as next message"
|
janito/shell/commands/verbose.py
CHANGED
@@ -2,10 +2,6 @@ from janito.agent.runtime_config import runtime_config
|
|
2
2
|
|
3
3
|
|
4
4
|
def handle_verbose(console, shell_state=None, **kwargs):
|
5
|
-
"""
|
6
|
-
/verbose [on|off]
|
7
|
-
Shows or sets verbose mode for the current shell session.
|
8
|
-
"""
|
9
5
|
args = kwargs.get("args", [])
|
10
6
|
verbose = runtime_config.get("verbose", False)
|
11
7
|
if not args:
|
@@ -27,3 +23,6 @@ def handle_verbose(console, shell_state=None, **kwargs):
|
|
27
23
|
)
|
28
24
|
else:
|
29
25
|
console.print("[bold red]Usage:[/bold red] /verbose [on|off]")
|
26
|
+
|
27
|
+
|
28
|
+
handle_verbose.help_text = "Show or set verbose mode for this session"
|
@@ -0,0 +1,62 @@
|
|
1
|
+
import os
|
2
|
+
import json
|
3
|
+
from datetime import datetime
|
4
|
+
from typing import List, Any, Dict
|
5
|
+
|
6
|
+
|
7
|
+
class UserInputHistory:
|
8
|
+
"""
|
9
|
+
Handles loading, saving, and appending of user input history for the shell.
|
10
|
+
Each day's history is stored in a line-delimited JSON file (.json.log) under .janito/input_history/.
|
11
|
+
Each line is a JSON dict, e.g., {"input": ..., "ts": ...}
|
12
|
+
"""
|
13
|
+
|
14
|
+
def __init__(self, history_dir=None):
|
15
|
+
self.history_dir = history_dir or os.path.join(".janito", "input_history")
|
16
|
+
os.makedirs(self.history_dir, exist_ok=True)
|
17
|
+
|
18
|
+
def _get_today_file(self):
|
19
|
+
today_str = datetime.now().strftime("%y%m%d")
|
20
|
+
return os.path.join(self.history_dir, f"{today_str}.json.log")
|
21
|
+
|
22
|
+
def load(self) -> List[Dict[str, Any]]:
|
23
|
+
"""Load today's input history as a list of dicts."""
|
24
|
+
history_file = self._get_today_file()
|
25
|
+
history = []
|
26
|
+
try:
|
27
|
+
with open(history_file, "r", encoding="utf-8") as f:
|
28
|
+
for line in f:
|
29
|
+
line = line.strip()
|
30
|
+
if not line:
|
31
|
+
continue
|
32
|
+
try:
|
33
|
+
history.append(json.loads(line))
|
34
|
+
except json.JSONDecodeError:
|
35
|
+
continue
|
36
|
+
except FileNotFoundError:
|
37
|
+
pass
|
38
|
+
return history
|
39
|
+
|
40
|
+
def sanitize_surrogates(self, s):
|
41
|
+
if isinstance(s, str):
|
42
|
+
return s.encode("utf-8", errors="replace").decode("utf-8")
|
43
|
+
return s
|
44
|
+
|
45
|
+
def append(self, input_str: str):
|
46
|
+
"""Append a new input as a JSON dict to today's history file."""
|
47
|
+
history_file = self._get_today_file()
|
48
|
+
input_str = self.sanitize_surrogates(input_str)
|
49
|
+
entry = {"input": input_str, "ts": datetime.now().isoformat()}
|
50
|
+
with open(history_file, "a", encoding="utf-8") as f:
|
51
|
+
f.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
52
|
+
|
53
|
+
def save(self, history_list: List[Any]):
|
54
|
+
"""Overwrite today's history file with the given list (for compatibility)."""
|
55
|
+
history_file = self._get_today_file()
|
56
|
+
with open(history_file, "w", encoding="utf-8") as f:
|
57
|
+
for item in history_list:
|
58
|
+
if isinstance(item, dict):
|
59
|
+
f.write(json.dumps(item, ensure_ascii=False) + "\n")
|
60
|
+
else:
|
61
|
+
entry = {"input": str(item), "ts": datetime.now().isoformat()}
|
62
|
+
f.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
janito/shell/main.py
CHANGED
@@ -8,84 +8,22 @@ from janito.shell.prompt.session_setup import (
|
|
8
8
|
)
|
9
9
|
from janito.shell.commands import handle_command
|
10
10
|
from janito.agent.conversation_exceptions import EmptyResponseError, ProviderError
|
11
|
-
from janito.agent.
|
12
|
-
from janito.agent.
|
11
|
+
from janito.agent.api_exceptions import ApiError
|
12
|
+
from janito.agent.llm_conversation_history import LLMConversationHistory
|
13
13
|
import janito.i18n as i18n
|
14
14
|
from janito.agent.runtime_config import runtime_config
|
15
15
|
from rich.console import Console
|
16
|
-
from collections import Counter
|
17
16
|
import os
|
18
17
|
from janito.shell.session.manager import get_session_id
|
19
18
|
from prompt_toolkit.formatted_text import HTML
|
20
19
|
import time
|
21
|
-
|
22
|
-
|
23
|
-
def chat_start_summary(conversation_history, console, last_usage_info):
|
24
|
-
def format_tokens(n):
|
25
|
-
if n is None:
|
26
|
-
return "-"
|
27
|
-
if n >= 1_000_000:
|
28
|
-
return f"{n/1_000_000:.2f}m"
|
29
|
-
elif n >= 1_000:
|
30
|
-
return f"{n/1_000:.2f}k"
|
31
|
-
return str(n)
|
32
|
-
|
33
|
-
num_messages = len(conversation_history)
|
34
|
-
roles = [m.get("role") for m in conversation_history.get_messages()]
|
35
|
-
role_counts = {role: roles.count(role) for role in set(roles)}
|
36
|
-
roles_str = ", ".join(
|
37
|
-
f"[bold]{role}[/]: {count}" for role, count in role_counts.items()
|
38
|
-
)
|
39
|
-
stats_lines = [
|
40
|
-
f"[cyan]Messages:[/] [bold]{num_messages}[/]",
|
41
|
-
f"[cyan]Roles:[/] {roles_str}",
|
42
|
-
]
|
43
|
-
# Use last_usage_info for tokens
|
44
|
-
if last_usage_info:
|
45
|
-
prompt_tokens = last_usage_info.get("prompt_tokens")
|
46
|
-
completion_tokens = last_usage_info.get("completion_tokens")
|
47
|
-
total_tokens = last_usage_info.get("total_tokens")
|
48
|
-
tokens_parts = []
|
49
|
-
if prompt_tokens is not None:
|
50
|
-
tokens_parts.append(f"Prompt: [bold]{format_tokens(prompt_tokens)}[/]")
|
51
|
-
if completion_tokens is not None:
|
52
|
-
tokens_parts.append(
|
53
|
-
f"Completion: [bold]{format_tokens(completion_tokens)}[/]"
|
54
|
-
)
|
55
|
-
if total_tokens is not None:
|
56
|
-
tokens_parts.append(f"Total: [bold]{format_tokens(total_tokens)}[/]")
|
57
|
-
if tokens_parts:
|
58
|
-
stats_lines.append(f"[cyan]Tokens:[/] {', '.join(tokens_parts)}")
|
59
|
-
|
60
|
-
# Add global tool usage stats
|
61
|
-
try:
|
62
|
-
tool_history = ToolUseTracker().get_history()
|
63
|
-
if tool_history:
|
64
|
-
tool_counts = Counter(
|
65
|
-
entry["tool"] for entry in tool_history if "tool" in entry
|
66
|
-
)
|
67
|
-
tools_str = ", ".join(
|
68
|
-
f"[bold]{tool}[/]: {count}" for tool, count in tool_counts.items()
|
69
|
-
)
|
70
|
-
stats_lines.append(f"[cyan]Tools used:[/] {tools_str}")
|
71
|
-
except Exception:
|
72
|
-
pass # Fail silently if tracker is unavailable
|
73
|
-
|
74
|
-
# Print all stats in a single line, no panel
|
75
|
-
# Print stats in a single line, but tokens info on a separate line if present
|
76
|
-
if len(stats_lines) > 2:
|
77
|
-
console.print(" | ".join(stats_lines[:2]))
|
78
|
-
console.print(stats_lines[2])
|
79
|
-
if len(stats_lines) > 3:
|
80
|
-
console.print(" | ".join(stats_lines[3:]))
|
81
|
-
else:
|
82
|
-
console.print(" | ".join(stats_lines))
|
20
|
+
from janito.shell.input_history import UserInputHistory
|
83
21
|
|
84
22
|
|
85
23
|
@dataclass
|
86
24
|
class ShellState:
|
87
25
|
mem_history: Any = field(default_factory=InMemoryHistory)
|
88
|
-
conversation_history: Any = field(default_factory=lambda:
|
26
|
+
conversation_history: Any = field(default_factory=lambda: LLMConversationHistory())
|
89
27
|
last_usage_info: Dict[str, int] = field(
|
90
28
|
default_factory=lambda: {
|
91
29
|
"prompt_tokens": 0,
|
@@ -100,44 +38,26 @@ class ShellState:
|
|
100
38
|
livereload_stderr_path: Optional[str] = None
|
101
39
|
paste_mode: bool = False
|
102
40
|
profile_manager: Optional[Any] = None
|
41
|
+
user_input_history: Optional[Any] = None
|
103
42
|
|
104
43
|
|
105
44
|
# Track the active prompt session for cleanup
|
106
45
|
active_prompt_session = None
|
107
46
|
|
108
47
|
|
109
|
-
def
|
110
|
-
profile_manager,
|
111
|
-
continue_session=False,
|
112
|
-
session_id=None,
|
113
|
-
max_rounds=100,
|
114
|
-
termweb_stdout_path=None,
|
115
|
-
termweb_stderr_path=None,
|
116
|
-
livereload_stdout_path=None,
|
117
|
-
livereload_stderr_path=None,
|
118
|
-
):
|
119
|
-
|
120
|
-
i18n.set_locale(runtime_config.get("lang", "en"))
|
121
|
-
global active_prompt_session
|
122
|
-
agent = profile_manager.agent
|
123
|
-
message_handler = RichMessageHandler()
|
124
|
-
console = message_handler.console
|
125
|
-
|
126
|
-
# Print session id at start
|
48
|
+
def load_session(shell_state, continue_session, session_id, profile_manager):
|
127
49
|
from janito.shell.session.manager import load_conversation_by_session_id
|
128
50
|
|
129
|
-
shell_state = ShellState()
|
130
|
-
shell_state.profile_manager = profile_manager
|
131
51
|
if continue_session and session_id:
|
132
52
|
try:
|
133
53
|
messages, prompts, usage = load_conversation_by_session_id(session_id)
|
134
54
|
except FileNotFoundError as e:
|
135
|
-
console.print(
|
136
|
-
|
137
|
-
|
138
|
-
|
55
|
+
shell_state.profile_manager.agent.message_handler.console.print(
|
56
|
+
f"[bold red]{str(e)}[/bold red]"
|
57
|
+
)
|
58
|
+
return False
|
59
|
+
shell_state.conversation_history = LLMConversationHistory(messages)
|
139
60
|
conversation_history = shell_state.conversation_history
|
140
|
-
# Always refresh the system prompt in the loaded history
|
141
61
|
found = False
|
142
62
|
for msg in conversation_history.get_messages():
|
143
63
|
if msg.get("role") == "system":
|
@@ -148,12 +68,9 @@ def start_chat_shell(
|
|
148
68
|
conversation_history.set_system_message(
|
149
69
|
profile_manager.system_prompt_template
|
150
70
|
)
|
151
|
-
# Optionally set prompts/usage if needed
|
152
71
|
shell_state.last_usage_info = usage or {}
|
153
72
|
else:
|
154
73
|
conversation_history = shell_state.conversation_history
|
155
|
-
# Add system prompt if needed (skip in vanilla mode)
|
156
|
-
|
157
74
|
if (
|
158
75
|
profile_manager.system_prompt_template
|
159
76
|
and (
|
@@ -167,29 +84,18 @@ def start_chat_shell(
|
|
167
84
|
conversation_history.set_system_message(
|
168
85
|
profile_manager.system_prompt_template
|
169
86
|
)
|
170
|
-
|
87
|
+
return True
|
171
88
|
|
172
|
-
def last_usage_info_ref():
|
173
|
-
return shell_state.last_usage_info
|
174
|
-
|
175
|
-
last_elapsed = shell_state.last_elapsed
|
176
|
-
|
177
|
-
print_welcome_message(console, continue_id=session_id if continue_session else None)
|
178
|
-
|
179
|
-
session = setup_prompt_session(
|
180
|
-
lambda: conversation_history.get_messages(),
|
181
|
-
last_usage_info_ref,
|
182
|
-
last_elapsed,
|
183
|
-
mem_history,
|
184
|
-
profile_manager,
|
185
|
-
agent,
|
186
|
-
lambda: conversation_history,
|
187
|
-
)
|
188
|
-
active_prompt_session = session
|
189
89
|
|
90
|
+
def handle_prompt_loop(
|
91
|
+
shell_state, session, profile_manager, agent, max_rounds, session_id
|
92
|
+
):
|
93
|
+
global active_prompt_session
|
94
|
+
conversation_history = shell_state.conversation_history
|
95
|
+
message_handler = RichMessageHandler()
|
96
|
+
console = message_handler.console
|
190
97
|
while True:
|
191
98
|
try:
|
192
|
-
|
193
99
|
if shell_state.paste_mode:
|
194
100
|
user_input = session.prompt("Multiline> ", multiline=True)
|
195
101
|
was_paste_mode = True
|
@@ -203,11 +109,10 @@ def start_chat_shell(
|
|
203
109
|
console.print("\n[bold red]Exiting...[/bold red]")
|
204
110
|
break
|
205
111
|
except KeyboardInterrupt:
|
206
|
-
console.print()
|
112
|
+
console.print()
|
207
113
|
try:
|
208
114
|
confirm = (
|
209
115
|
session.prompt(
|
210
|
-
# Use <inputline> for full-line blue background, <prompt> for icon only
|
211
116
|
HTML(
|
212
117
|
"<inputline>Do you really want to exit? (y/n): </inputline>"
|
213
118
|
)
|
@@ -230,10 +135,8 @@ def start_chat_shell(
|
|
230
135
|
break
|
231
136
|
else:
|
232
137
|
continue
|
233
|
-
|
234
138
|
cmd_input = user_input.strip().lower()
|
235
139
|
if not was_paste_mode and (cmd_input.startswith("/") or cmd_input == "exit"):
|
236
|
-
# Treat both '/exit' and 'exit' as commands
|
237
140
|
result = handle_command(
|
238
141
|
user_input.strip(),
|
239
142
|
console,
|
@@ -245,77 +148,153 @@ def start_chat_shell(
|
|
245
148
|
)
|
246
149
|
break
|
247
150
|
continue
|
248
|
-
|
249
151
|
if not user_input.strip():
|
250
152
|
continue
|
251
|
-
|
252
|
-
|
153
|
+
shell_state.mem_history.append_string(user_input)
|
154
|
+
shell_state.user_input_history.append(user_input)
|
253
155
|
conversation_history.add_message({"role": "user", "content": user_input})
|
156
|
+
handle_chat(shell_state, profile_manager, agent, max_rounds, session_id)
|
157
|
+
# Save conversation history after exiting
|
158
|
+
save_conversation_history(conversation_history, session_id)
|
159
|
+
|
160
|
+
|
161
|
+
def handle_keyboard_interrupt(conversation_history, message_handler):
|
162
|
+
removed_count = 0
|
163
|
+
while (
|
164
|
+
conversation_history.last_message()
|
165
|
+
and conversation_history.last_message().get("role") != "assistant"
|
166
|
+
):
|
167
|
+
conversation_history.remove_last_message()
|
168
|
+
removed_count += 1
|
169
|
+
# Remove the assistant message itself, if present
|
170
|
+
if (
|
171
|
+
conversation_history.last_message()
|
172
|
+
and conversation_history.last_message().get("role") == "assistant"
|
173
|
+
):
|
174
|
+
conversation_history.remove_last_message()
|
175
|
+
removed_count += 1
|
176
|
+
message_handler.handle_message(
|
177
|
+
{
|
178
|
+
"type": "info",
|
179
|
+
"message": f"\nLast turn cleared due to interruption. Returning to prompt. (removed {removed_count} last msgs)\n",
|
180
|
+
}
|
181
|
+
)
|
254
182
|
|
255
|
-
start_time = time.time()
|
256
|
-
|
257
|
-
# No need to propagate verbose; ToolExecutor and others fetch from runtime_config
|
258
|
-
|
259
|
-
# Clear the screen before starting LLM conversation
|
260
|
-
console = Console()
|
261
|
-
console.clear()
|
262
|
-
|
263
|
-
# Print a summary of the current conversation history
|
264
|
-
|
265
|
-
chat_start_summary(conversation_history, console, shell_state.last_usage_info)
|
266
|
-
|
267
|
-
try:
|
268
|
-
response = profile_manager.agent.chat(
|
269
|
-
conversation_history,
|
270
|
-
max_rounds=max_rounds,
|
271
|
-
message_handler=message_handler,
|
272
|
-
spinner=True,
|
273
|
-
)
|
274
|
-
except KeyboardInterrupt:
|
275
|
-
message_handler.handle_message(
|
276
|
-
{"type": "info", "message": "Request interrupted. Returning to prompt."}
|
277
|
-
)
|
278
|
-
continue
|
279
|
-
except ProviderError as e:
|
280
|
-
message_handler.handle_message(
|
281
|
-
{"type": "error", "message": f"Provider error: {e}"}
|
282
|
-
)
|
283
|
-
continue
|
284
|
-
except EmptyResponseError as e:
|
285
|
-
message_handler.handle_message({"type": "error", "message": f"Error: {e}"})
|
286
|
-
continue
|
287
|
-
last_elapsed = time.time() - start_time
|
288
|
-
|
289
|
-
usage = response.get("usage")
|
290
|
-
if usage:
|
291
|
-
for k in ("prompt_tokens", "completion_tokens", "total_tokens"):
|
292
|
-
shell_state.last_usage_info[k] = usage.get(k, 0)
|
293
|
-
|
294
|
-
# --- Ensure assistant and tool messages are added to ConversationHistory ---
|
295
|
-
# If the last message is not an assistant/tool, add the response content
|
296
|
-
content = response.get("content")
|
297
|
-
if content and (
|
298
|
-
len(conversation_history) == 0
|
299
|
-
or conversation_history.get_messages()[-1].get("role") != "assistant"
|
300
|
-
):
|
301
|
-
conversation_history.add_message({"role": "assistant", "content": content})
|
302
|
-
# Optionally, add tool messages if present in response (extend here if needed)
|
303
|
-
# ---------------------------------------------------------------------------
|
304
|
-
|
305
|
-
# --- Save conversation history after each assistant reply ---
|
306
|
-
session_id_to_save = session_id if session_id else get_session_id()
|
307
|
-
history_dir = os.path.join(os.path.expanduser("~"), ".janito", "chat_history")
|
308
|
-
os.makedirs(history_dir, exist_ok=True)
|
309
|
-
history_path = os.path.join(history_dir, f"{session_id_to_save}.json")
|
310
|
-
conversation_history.to_json_file(history_path)
|
311
|
-
# -----------------------------------------------------------
|
312
183
|
|
313
|
-
|
184
|
+
def handle_chat_error(message_handler, error_type, error):
|
185
|
+
if error_type == "ProviderError":
|
186
|
+
message_handler.handle_message(
|
187
|
+
{"type": "error", "message": f"Provider error: {error}"}
|
188
|
+
)
|
189
|
+
elif error_type == "EmptyResponseError":
|
190
|
+
message_handler.handle_message({"type": "error", "message": f"Error: {error}"})
|
191
|
+
elif error_type == "ApiError":
|
192
|
+
message_handler.handle_message({"type": "error", "message": str(error)})
|
193
|
+
elif error_type == "Exception":
|
194
|
+
import traceback
|
195
|
+
|
196
|
+
tb = traceback.format_exc()
|
197
|
+
message_handler.handle_message(
|
198
|
+
{
|
199
|
+
"type": "error",
|
200
|
+
"message": f"Unexpected error: {error}\n{tb}\nReturning to prompt.",
|
201
|
+
}
|
202
|
+
)
|
203
|
+
|
204
|
+
|
205
|
+
def handle_chat(shell_state, profile_manager, agent, max_rounds, session_id):
|
206
|
+
conversation_history = shell_state.conversation_history
|
207
|
+
message_handler = RichMessageHandler()
|
208
|
+
console = message_handler.console
|
209
|
+
start_time = time.time()
|
210
|
+
try:
|
211
|
+
response = profile_manager.agent.chat(
|
212
|
+
conversation_history,
|
213
|
+
max_rounds=max_rounds,
|
214
|
+
message_handler=message_handler,
|
215
|
+
spinner=True,
|
216
|
+
)
|
217
|
+
except KeyboardInterrupt:
|
218
|
+
handle_keyboard_interrupt(conversation_history, message_handler)
|
219
|
+
return
|
220
|
+
except ProviderError as e:
|
221
|
+
handle_chat_error(message_handler, "ProviderError", e)
|
222
|
+
return
|
223
|
+
except EmptyResponseError as e:
|
224
|
+
handle_chat_error(message_handler, "EmptyResponseError", e)
|
225
|
+
return
|
226
|
+
except ApiError as e:
|
227
|
+
handle_chat_error(message_handler, "ApiError", e)
|
228
|
+
return
|
229
|
+
except Exception as e:
|
230
|
+
handle_chat_error(message_handler, "Exception", e)
|
231
|
+
return
|
232
|
+
shell_state.last_elapsed = time.time() - start_time
|
233
|
+
usage = response.get("usage")
|
234
|
+
if usage:
|
235
|
+
for k in ("prompt_tokens", "completion_tokens", "total_tokens"):
|
236
|
+
shell_state.last_usage_info[k] = usage.get(k, 0)
|
237
|
+
content = response.get("content")
|
238
|
+
if content and (
|
239
|
+
len(conversation_history) == 0
|
240
|
+
or conversation_history.get_messages()[-1].get("role") != "assistant"
|
241
|
+
):
|
242
|
+
conversation_history.add_message({"role": "assistant", "content": content})
|
243
|
+
|
244
|
+
|
245
|
+
def save_conversation_history(conversation_history, session_id):
|
246
|
+
from janito.shell.session.manager import get_session_id
|
314
247
|
|
315
|
-
# --- Save conversation history to .janito/chat_history/(session_id).json ---
|
316
|
-
session_id_to_save = session_id if session_id else get_session_id()
|
317
248
|
history_dir = os.path.join(os.path.expanduser("~"), ".janito", "chat_history")
|
318
249
|
os.makedirs(history_dir, exist_ok=True)
|
250
|
+
session_id_to_save = session_id if session_id else get_session_id()
|
319
251
|
history_path = os.path.join(history_dir, f"{session_id_to_save}.json")
|
320
252
|
conversation_history.to_json_file(history_path)
|
321
|
-
|
253
|
+
|
254
|
+
|
255
|
+
def start_chat_shell(
|
256
|
+
profile_manager,
|
257
|
+
continue_session=False,
|
258
|
+
session_id=None,
|
259
|
+
max_rounds=100,
|
260
|
+
termweb_stdout_path=None,
|
261
|
+
termweb_stderr_path=None,
|
262
|
+
livereload_stdout_path=None,
|
263
|
+
livereload_stderr_path=None,
|
264
|
+
):
|
265
|
+
i18n.set_locale(runtime_config.get("lang", "en"))
|
266
|
+
global active_prompt_session
|
267
|
+
agent = profile_manager.agent
|
268
|
+
message_handler = RichMessageHandler()
|
269
|
+
console = message_handler.console
|
270
|
+
console.clear()
|
271
|
+
shell_state = ShellState()
|
272
|
+
shell_state.profile_manager = profile_manager
|
273
|
+
user_input_history = UserInputHistory()
|
274
|
+
user_input_dicts = user_input_history.load()
|
275
|
+
mem_history = shell_state.mem_history
|
276
|
+
for item in user_input_dicts:
|
277
|
+
if isinstance(item, dict) and "input" in item:
|
278
|
+
mem_history.append_string(item["input"])
|
279
|
+
shell_state.user_input_history = user_input_history
|
280
|
+
if not load_session(shell_state, continue_session, session_id, profile_manager):
|
281
|
+
return
|
282
|
+
|
283
|
+
def last_usage_info_ref():
|
284
|
+
return shell_state.last_usage_info
|
285
|
+
|
286
|
+
last_elapsed = shell_state.last_elapsed
|
287
|
+
print_welcome_message(console, continue_id=session_id if continue_session else None)
|
288
|
+
session = setup_prompt_session(
|
289
|
+
lambda: shell_state.conversation_history.get_messages(),
|
290
|
+
last_usage_info_ref,
|
291
|
+
last_elapsed,
|
292
|
+
mem_history,
|
293
|
+
profile_manager,
|
294
|
+
agent,
|
295
|
+
lambda: shell_state.conversation_history,
|
296
|
+
)
|
297
|
+
active_prompt_session = session
|
298
|
+
handle_prompt_loop(
|
299
|
+
shell_state, session, profile_manager, agent, max_rounds, session_id
|
300
|
+
)
|