janito 1.9.0__py3-none-any.whl → 1.10.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 +149 -159
- janito/agent/{conversation_history.py → llm_conversation_history.py} +18 -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 +5 -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 +6 -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 +3 -1
- janito/agent/tools/get_file_outline/core.py +6 -7
- janito/agent/tools/get_file_outline/search_outline.py +3 -1
- janito/agent/tools/get_lines.py +7 -2
- janito/agent/tools/move_file.py +3 -1
- janito/agent/tools/present_choices.py +3 -1
- janito/agent/tools/python_command_runner.py +150 -0
- janito/agent/tools/python_file_runner.py +148 -0
- janito/agent/tools/python_stdin_runner.py +154 -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 +4 -2
- janito/agent/tools/run_bash_command.py +30 -69
- janito/agent/tools/run_powershell_command.py +134 -105
- janito/agent/tools/search_text.py +172 -122
- janito/agent/tools/validate_file_syntax/core.py +3 -1
- 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 +66 -40
- janito/agent/tools_utils/test_gitignore_utils.py +46 -0
- janito/cli/_print_config.py +63 -61
- janito/cli/arg_parser.py +13 -12
- janito/cli/cli_main.py +137 -147
- 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 +117 -181
- janito/shell/session/manager.py +0 -21
- janito/shell/ui/interactive.py +0 -2
- janito/termweb/static/editor.css +0 -4
- janito/tests/test_rich_utils.py +44 -0
- janito/web/app.py +0 -75
- {janito-1.9.0.dist-info → janito-1.10.0.dist-info}/METADATA +61 -42
- {janito-1.9.0.dist-info → janito-1.10.0.dist-info}/RECORD +78 -71
- {janito-1.9.0.dist-info → janito-1.10.0.dist-info}/WHEEL +1 -1
- janito/agent/providers.py +0 -77
- janito/agent/tools/run_python_command.py +0 -161
- janito/shell/commands/sum.py +0 -49
- {janito-1.9.0.dist-info → janito-1.10.0.dist-info}/entry_points.txt +0 -0
- {janito-1.9.0.dist-info → janito-1.10.0.dist-info}/licenses/LICENSE +0 -0
- {janito-1.9.0.dist-info → janito-1.10.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
|
-
|
171
|
-
|
172
|
-
def last_usage_info_ref():
|
173
|
-
return shell_state.last_usage_info
|
87
|
+
return True
|
174
88
|
|
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,110 @@ 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)
|
254
159
|
|
255
|
-
start_time = time.time()
|
256
|
-
|
257
|
-
# No need to propagate verbose; ToolExecutor and others fetch from runtime_config
|
258
160
|
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
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
|
-
|
313
|
-
# After exiting the main loop, print restart info if conversation has >1 message
|
161
|
+
def handle_chat(shell_state, profile_manager, agent, max_rounds, session_id):
|
162
|
+
conversation_history = shell_state.conversation_history
|
163
|
+
message_handler = RichMessageHandler()
|
164
|
+
console = message_handler.console
|
165
|
+
start_time = time.time()
|
166
|
+
try:
|
167
|
+
response = profile_manager.agent.chat(
|
168
|
+
conversation_history,
|
169
|
+
max_rounds=max_rounds,
|
170
|
+
message_handler=message_handler,
|
171
|
+
spinner=True,
|
172
|
+
)
|
173
|
+
except KeyboardInterrupt:
|
174
|
+
message_handler.handle_message(
|
175
|
+
{"type": "info", "message": "Request interrupted. Returning to prompt."}
|
176
|
+
)
|
177
|
+
return
|
178
|
+
except ProviderError as e:
|
179
|
+
message_handler.handle_message(
|
180
|
+
{"type": "error", "message": f"Provider error: {e}"}
|
181
|
+
)
|
182
|
+
return
|
183
|
+
except EmptyResponseError as e:
|
184
|
+
message_handler.handle_message({"type": "error", "message": f"Error: {e}"})
|
185
|
+
return
|
186
|
+
except ApiError as e:
|
187
|
+
message_handler.handle_message({"type": "error", "message": str(e)})
|
188
|
+
return
|
189
|
+
shell_state.last_elapsed = time.time() - start_time
|
190
|
+
usage = response.get("usage")
|
191
|
+
if usage:
|
192
|
+
for k in ("prompt_tokens", "completion_tokens", "total_tokens"):
|
193
|
+
shell_state.last_usage_info[k] = usage.get(k, 0)
|
194
|
+
content = response.get("content")
|
195
|
+
if content and (
|
196
|
+
len(conversation_history) == 0
|
197
|
+
or conversation_history.get_messages()[-1].get("role") != "assistant"
|
198
|
+
):
|
199
|
+
conversation_history.add_message({"role": "assistant", "content": content})
|
200
|
+
|
201
|
+
|
202
|
+
def save_conversation_history(conversation_history, session_id):
|
203
|
+
from janito.shell.session.manager import get_session_id
|
314
204
|
|
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
205
|
history_dir = os.path.join(os.path.expanduser("~"), ".janito", "chat_history")
|
318
206
|
os.makedirs(history_dir, exist_ok=True)
|
207
|
+
session_id_to_save = session_id if session_id else get_session_id()
|
319
208
|
history_path = os.path.join(history_dir, f"{session_id_to_save}.json")
|
320
209
|
conversation_history.to_json_file(history_path)
|
321
|
-
|
210
|
+
|
211
|
+
|
212
|
+
def start_chat_shell(
|
213
|
+
profile_manager,
|
214
|
+
continue_session=False,
|
215
|
+
session_id=None,
|
216
|
+
max_rounds=100,
|
217
|
+
termweb_stdout_path=None,
|
218
|
+
termweb_stderr_path=None,
|
219
|
+
livereload_stdout_path=None,
|
220
|
+
livereload_stderr_path=None,
|
221
|
+
):
|
222
|
+
i18n.set_locale(runtime_config.get("lang", "en"))
|
223
|
+
global active_prompt_session
|
224
|
+
agent = profile_manager.agent
|
225
|
+
message_handler = RichMessageHandler()
|
226
|
+
console = message_handler.console
|
227
|
+
console.clear()
|
228
|
+
shell_state = ShellState()
|
229
|
+
shell_state.profile_manager = profile_manager
|
230
|
+
user_input_history = UserInputHistory()
|
231
|
+
user_input_dicts = user_input_history.load()
|
232
|
+
mem_history = shell_state.mem_history
|
233
|
+
for item in user_input_dicts:
|
234
|
+
if isinstance(item, dict) and "input" in item:
|
235
|
+
mem_history.append_string(item["input"])
|
236
|
+
shell_state.user_input_history = user_input_history
|
237
|
+
if not load_session(shell_state, continue_session, session_id, profile_manager):
|
238
|
+
return
|
239
|
+
|
240
|
+
def last_usage_info_ref():
|
241
|
+
return shell_state.last_usage_info
|
242
|
+
|
243
|
+
last_elapsed = shell_state.last_elapsed
|
244
|
+
print_welcome_message(console, continue_id=session_id if continue_session else None)
|
245
|
+
session = setup_prompt_session(
|
246
|
+
lambda: shell_state.conversation_history.get_messages(),
|
247
|
+
last_usage_info_ref,
|
248
|
+
last_elapsed,
|
249
|
+
mem_history,
|
250
|
+
profile_manager,
|
251
|
+
agent,
|
252
|
+
lambda: shell_state.conversation_history,
|
253
|
+
)
|
254
|
+
active_prompt_session = session
|
255
|
+
handle_prompt_loop(
|
256
|
+
shell_state, session, profile_manager, agent, max_rounds, session_id
|
257
|
+
)
|
janito/shell/session/manager.py
CHANGED
@@ -89,27 +89,6 @@ def save_conversation(messages, prompts, usage_info=None, path=None):
|
|
89
89
|
json.dump(data, f, indent=2, default=usage_serializer)
|
90
90
|
|
91
91
|
|
92
|
-
def load_input_history():
|
93
|
-
history_dir = os.path.join(".janito", "input_history")
|
94
|
-
os.makedirs(history_dir, exist_ok=True)
|
95
|
-
today_str = datetime.now().strftime("%y%m%d")
|
96
|
-
history_file = os.path.join(history_dir, f"{today_str}.json")
|
97
|
-
try:
|
98
|
-
with open(history_file, "r", encoding="utf-8") as f:
|
99
|
-
return json.load(f)
|
100
|
-
except (FileNotFoundError, json.JSONDecodeError):
|
101
|
-
return []
|
102
|
-
|
103
|
-
|
104
|
-
def save_input_history(history_list):
|
105
|
-
history_dir = os.path.join(".janito", "input_history")
|
106
|
-
os.makedirs(history_dir, exist_ok=True)
|
107
|
-
today_str = datetime.now().strftime("%y%m%d")
|
108
|
-
history_file = os.path.join(history_dir, f"{today_str}.json")
|
109
|
-
with open(history_file, "w", encoding="utf-8") as f:
|
110
|
-
json.dump(history_list, f, indent=2)
|
111
|
-
|
112
|
-
|
113
92
|
def last_conversation_exists(path=".janito/last_conversation.json"):
|
114
93
|
if not os.path.exists(path):
|
115
94
|
return False
|
janito/shell/ui/interactive.py
CHANGED
@@ -17,12 +17,10 @@ def print_welcome(console, version=None, continue_id=None):
|
|
17
17
|
if runtime_config.get("vanilla_mode", False):
|
18
18
|
console.print(
|
19
19
|
f"[bold magenta]{tr('Welcome to Janito{version_str} in [white on magenta]VANILLA MODE[/white on magenta]! Tools, system prompt, and temperature are disabled unless overridden.', version_str=version_str)}[/bold magenta]\n"
|
20
|
-
f"[cyan]{tr('F12 = Quick Action (follows the recommended action)')}[/cyan]"
|
21
20
|
)
|
22
21
|
else:
|
23
22
|
console.print(
|
24
23
|
f"[bold green]{tr('Welcome to Janito{version_str}! Entering chat mode. Type /exit to exit.', version_str=version_str)}[/bold green]\n"
|
25
|
-
f"[cyan]{tr('F12 = Quick Action (follows the recommended action)')}[/cyan]"
|
26
24
|
)
|
27
25
|
|
28
26
|
|
janito/termweb/static/editor.css
CHANGED
@@ -0,0 +1,44 @@
|
|
1
|
+
import io
|
2
|
+
from rich.console import Console
|
3
|
+
from janito.rich_utils import RichPrinter
|
4
|
+
|
5
|
+
|
6
|
+
def test_print_info(capsys=None):
|
7
|
+
buf = io.StringIO()
|
8
|
+
printer = RichPrinter(
|
9
|
+
console=Console(file=buf, force_terminal=True, color_system=None)
|
10
|
+
)
|
11
|
+
printer.print_info("info message")
|
12
|
+
output = buf.getvalue()
|
13
|
+
assert "info message" in output
|
14
|
+
assert "cyan" in output or output # Style is present if rich renders ANSI
|
15
|
+
|
16
|
+
|
17
|
+
def test_print_error():
|
18
|
+
buf = io.StringIO()
|
19
|
+
printer = RichPrinter(
|
20
|
+
console=Console(file=buf, force_terminal=True, color_system=None)
|
21
|
+
)
|
22
|
+
printer.print_error("error message")
|
23
|
+
output = buf.getvalue()
|
24
|
+
assert "error message" in output
|
25
|
+
|
26
|
+
|
27
|
+
def test_print_warning():
|
28
|
+
buf = io.StringIO()
|
29
|
+
printer = RichPrinter(
|
30
|
+
console=Console(file=buf, force_terminal=True, color_system=None)
|
31
|
+
)
|
32
|
+
printer.print_warning("warning message")
|
33
|
+
output = buf.getvalue()
|
34
|
+
assert "warning message" in output
|
35
|
+
|
36
|
+
|
37
|
+
def test_print_magenta():
|
38
|
+
buf = io.StringIO()
|
39
|
+
printer = RichPrinter(
|
40
|
+
console=Console(file=buf, force_terminal=True, color_system=None)
|
41
|
+
)
|
42
|
+
printer.print_magenta("magenta message")
|
43
|
+
output = buf.getvalue()
|
44
|
+
assert "magenta message" in output
|