janito 1.8.0__py3-none-any.whl → 1.9.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/config_defaults.py +23 -0
- janito/agent/config_utils.py +0 -9
- janito/agent/conversation.py +31 -9
- janito/agent/conversation_api.py +32 -2
- janito/agent/conversation_history.py +53 -0
- janito/agent/conversation_tool_calls.py +11 -8
- janito/agent/openai_client.py +11 -3
- janito/agent/openai_schema_generator.py +9 -6
- janito/agent/providers.py +77 -0
- janito/agent/rich_message_handler.py +1 -1
- janito/agent/templates/profiles/system_prompt_template_base.txt.j2 +8 -8
- janito/agent/tool_executor.py +18 -10
- janito/agent/tool_use_tracker.py +16 -0
- janito/agent/tools/__init__.py +7 -9
- janito/agent/tools/create_directory.py +7 -6
- janito/agent/tools/create_file.py +29 -54
- janito/agent/tools/delete_text_in_file.py +97 -0
- janito/agent/tools/fetch_url.py +11 -3
- janito/agent/tools/find_files.py +37 -25
- janito/agent/tools/get_file_outline/__init__.py +1 -0
- janito/agent/tools/{outline_file/__init__.py → get_file_outline/core.py} +12 -15
- janito/agent/tools/get_file_outline/python_outline.py +134 -0
- janito/agent/tools/{search_outline.py → get_file_outline/search_outline.py} +9 -0
- janito/agent/tools/get_lines.py +15 -11
- janito/agent/tools/move_file.py +10 -11
- janito/agent/tools/remove_directory.py +2 -2
- janito/agent/tools/remove_file.py +11 -13
- janito/agent/tools/replace_file.py +62 -0
- janito/agent/tools/replace_text_in_file.py +3 -3
- janito/agent/tools/run_bash_command.py +3 -7
- janito/agent/tools/run_powershell_command.py +39 -28
- janito/agent/tools/run_python_command.py +3 -5
- janito/agent/tools/search_text.py +10 -14
- janito/agent/tools/validate_file_syntax/__init__.py +1 -0
- janito/agent/tools/validate_file_syntax/core.py +92 -0
- janito/agent/tools/validate_file_syntax/css_validator.py +35 -0
- janito/agent/tools/validate_file_syntax/html_validator.py +77 -0
- janito/agent/tools/validate_file_syntax/js_validator.py +27 -0
- janito/agent/tools/validate_file_syntax/json_validator.py +6 -0
- janito/agent/tools/validate_file_syntax/markdown_validator.py +66 -0
- janito/agent/tools/validate_file_syntax/ps1_validator.py +32 -0
- janito/agent/tools/validate_file_syntax/python_validator.py +5 -0
- janito/agent/tools/validate_file_syntax/xml_validator.py +11 -0
- janito/agent/tools/validate_file_syntax/yaml_validator.py +6 -0
- janito/agent/tools_utils/__init__.py +1 -0
- janito/agent/tools_utils/dir_walk_utils.py +23 -0
- janito/agent/{tools/outline_file → tools_utils}/formatting.py +5 -2
- janito/agent/{tools → tools_utils}/gitignore_utils.py +0 -3
- janito/agent/tools_utils/utils.py +30 -0
- janito/cli/_livereload_log_utils.py +13 -0
- janito/cli/arg_parser.py +45 -3
- janito/cli/{runner/cli_main.py → cli_main.py} +120 -20
- janito/cli/livereload_starter.py +60 -0
- janito/cli/main.py +110 -21
- janito/cli/one_shot.py +66 -0
- janito/cli/termweb_starter.py +2 -2
- janito/livereload/app.py +25 -0
- janito/rich_utils.py +0 -22
- janito/{cli_chat_shell → shell}/commands/__init__.py +18 -11
- janito/{cli_chat_shell → shell}/commands/config.py +4 -4
- janito/shell/commands/conversation_restart.py +72 -0
- janito/shell/commands/edit.py +21 -0
- janito/shell/commands/history_view.py +18 -0
- janito/shell/commands/livelogs.py +40 -0
- janito/{cli_chat_shell → shell}/commands/prompt.py +10 -6
- janito/shell/commands/session.py +32 -0
- janito/{cli_chat_shell → shell}/commands/session_control.py +2 -7
- janito/{cli_chat_shell → shell}/commands/sum.py +6 -6
- janito/{cli_chat_shell → shell}/commands/termweb_log.py +10 -10
- janito/shell/commands/tools.py +23 -0
- janito/{cli_chat_shell → shell}/commands/utility.py +5 -4
- janito/{cli_chat_shell → shell}/commands/verbose.py +1 -1
- janito/shell/commands.py +40 -0
- janito/shell/main.py +321 -0
- janito/{cli_chat_shell/shell_command_completer.py → shell/prompt/completer.py} +1 -1
- janito/{cli_chat_shell/chat_ui.py → shell/prompt/session_setup.py} +19 -5
- janito/{cli_chat_shell/session_manager.py → shell/session/manager.py} +53 -3
- janito/{cli_chat_shell/ui.py → shell/ui/interactive.py} +23 -15
- janito/termweb/app.py +3 -3
- janito/termweb/static/editor.css +146 -0
- janito/termweb/static/editor.css.bak +27 -0
- janito/termweb/static/editor.html +15 -213
- janito/termweb/static/editor.html.bak +16 -215
- janito/termweb/static/editor.js +209 -0
- janito/termweb/static/editor.js.bak +227 -0
- janito/termweb/static/index.html +2 -3
- janito/termweb/static/index.html.bak +2 -3
- janito/termweb/static/termweb.css.bak +33 -84
- janito/termweb/static/termweb.js +15 -34
- janito/termweb/static/termweb.js.bak +18 -36
- {janito-1.8.0.dist-info → janito-1.9.0.dist-info}/METADATA +6 -3
- janito-1.9.0.dist-info/RECORD +151 -0
- {janito-1.8.0.dist-info → janito-1.9.0.dist-info}/WHEEL +1 -1
- janito/agent/tools/dir_walk_utils.py +0 -16
- janito/agent/tools/memory.py +0 -48
- janito/agent/tools/outline_file/python_outline.py +0 -71
- janito/agent/tools/present_choices_test.py +0 -18
- janito/agent/tools/rich_live.py +0 -44
- janito/agent/tools/tools_utils.py +0 -56
- janito/agent/tools/utils.py +0 -33
- janito/agent/tools/validate_file_syntax.py +0 -163
- janito/cli_chat_shell/chat_loop.py +0 -163
- janito/cli_chat_shell/chat_state.py +0 -38
- janito/cli_chat_shell/commands/history_start.py +0 -37
- janito/cli_chat_shell/commands/session.py +0 -48
- janito-1.8.0.dist-info/RECORD +0 -127
- /janito/agent/tools/{outline_file → get_file_outline}/markdown_outline.py +0 -0
- /janito/cli/{runner/_termweb_log_utils.py → _termweb_log_utils.py} +0 -0
- /janito/cli/{runner/config.py → config_runner.py} +0 -0
- /janito/cli/{runner/formatting.py → formatting_runner.py} +0 -0
- /janito/{cli/runner → shell}/__init__.py +0 -0
- /janito/{cli_chat_shell → shell}/commands/lang.py +0 -0
- /janito/{cli_chat_shell → shell/prompt}/load_prompt.py +0 -0
- /janito/{cli_chat_shell/config_shell.py → shell/session/config.py} +0 -0
- /janito/{cli_chat_shell/__init__.py → shell/session/history.py} +0 -0
- {janito-1.8.0.dist-info → janito-1.9.0.dist-info}/entry_points.txt +0 -0
- {janito-1.8.0.dist-info → janito-1.9.0.dist-info}/licenses/LICENSE +0 -0
- {janito-1.8.0.dist-info → janito-1.9.0.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
|
|
1
|
-
def handle_sum(console,
|
1
|
+
def handle_sum(console, shell_state=None, **kwargs):
|
2
2
|
"""
|
3
3
|
Summarize the current chat history and replace it with a summary message.
|
4
4
|
"""
|
@@ -7,15 +7,15 @@ def handle_sum(console, state, **kwargs):
|
|
7
7
|
console.print("[bold red]Agent not provided to /sum command.[/bold red]")
|
8
8
|
return
|
9
9
|
|
10
|
-
|
11
|
-
if not
|
10
|
+
history = shell_state.conversation_history.get_messages()
|
11
|
+
if not history or len(history) < 2:
|
12
12
|
console.print(
|
13
13
|
"[bold yellow]Not enough conversation to summarize.[/bold yellow]"
|
14
14
|
)
|
15
15
|
return
|
16
16
|
|
17
17
|
# Find the system message if present
|
18
|
-
system_msg = next((m for m in
|
18
|
+
system_msg = next((m for m in history if m.get("role") == "system"), None)
|
19
19
|
|
20
20
|
# Prepare summary prompt
|
21
21
|
summary_prompt = {
|
@@ -23,7 +23,7 @@ def handle_sum(console, state, **kwargs):
|
|
23
23
|
"content": "Summarize the following conversation in a concise paragraph for context. Only output the summary, do not include any tool calls or formatting.",
|
24
24
|
}
|
25
25
|
# Exclude system messages for the summary context
|
26
|
-
convo_for_summary = [m for m in
|
26
|
+
convo_for_summary = [m for m in history if m.get("role") != "system"]
|
27
27
|
summary_messages = [summary_prompt] + convo_for_summary
|
28
28
|
|
29
29
|
try:
|
@@ -42,7 +42,7 @@ def handle_sum(console, state, **kwargs):
|
|
42
42
|
if system_msg:
|
43
43
|
new_history.append(system_msg)
|
44
44
|
new_history.append({"role": "assistant", "content": summary_text})
|
45
|
-
|
45
|
+
shell_state.conversation_history.set_messages(new_history)
|
46
46
|
|
47
47
|
console.print(
|
48
48
|
"[bold green]Conversation summarized and history replaced with summary.[/bold green]"
|
@@ -14,12 +14,12 @@ def is_termweb_running(port):
|
|
14
14
|
return False
|
15
15
|
|
16
16
|
|
17
|
-
def handle_termweb_log_tail(console: Console, *args,
|
17
|
+
def handle_termweb_log_tail(console: Console, *args, shell_state=None, **kwargs):
|
18
18
|
lines = 20
|
19
19
|
if args and args[0].isdigit():
|
20
20
|
lines = int(args[0])
|
21
|
-
stdout_path =
|
22
|
-
stderr_path =
|
21
|
+
stdout_path = shell_state.termweb_stdout_path if shell_state else None
|
22
|
+
stderr_path = shell_state.termweb_stderr_path if shell_state else None
|
23
23
|
if not stdout_path and not stderr_path:
|
24
24
|
console.print(
|
25
25
|
"[yellow][termweb] No termweb log files found for this session.[/yellow]"
|
@@ -51,20 +51,20 @@ def handle_termweb_log_tail(console: Console, *args, state=None, **kwargs):
|
|
51
51
|
console.print("[termweb] No output or errors captured in logs.")
|
52
52
|
|
53
53
|
|
54
|
-
def handle_termweb_status(console: Console, *args,
|
55
|
-
if
|
54
|
+
def handle_termweb_status(console: Console, *args, shell_state=None, **kwargs):
|
55
|
+
if shell_state is None:
|
56
56
|
console.print(
|
57
57
|
"[red]No shell state available. Cannot determine termweb status.[/red]"
|
58
58
|
)
|
59
59
|
return
|
60
|
-
port =
|
61
|
-
port_source = "
|
60
|
+
port = getattr(shell_state, "termweb_port", None)
|
61
|
+
port_source = "shell_state"
|
62
62
|
if not port:
|
63
63
|
port = runtime_config.get("termweb_port")
|
64
64
|
port_source = "runtime_config"
|
65
|
-
pid =
|
66
|
-
stdout_path =
|
67
|
-
stderr_path =
|
65
|
+
pid = getattr(shell_state, "termweb_pid", None)
|
66
|
+
stdout_path = getattr(shell_state, "termweb_stdout_path", None)
|
67
|
+
stderr_path = getattr(shell_state, "termweb_stderr_path", None)
|
68
68
|
running = False
|
69
69
|
if port:
|
70
70
|
running = is_termweb_running(port)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
from janito.agent.tool_registry import get_tool_schemas
|
2
|
+
from rich.table import Table
|
3
|
+
|
4
|
+
|
5
|
+
def handle_tools(console, args=None, shell_state=None):
|
6
|
+
table = Table(title="Available Tools", show_lines=True, style="bold magenta")
|
7
|
+
table.add_column("Name", style="cyan", no_wrap=True)
|
8
|
+
table.add_column("Description", style="green")
|
9
|
+
table.add_column("Parameters", style="yellow")
|
10
|
+
try:
|
11
|
+
for schema in get_tool_schemas():
|
12
|
+
fn = schema["function"]
|
13
|
+
params = "\n".join(
|
14
|
+
[
|
15
|
+
f"[bold]{k}[/]: {v['type']}"
|
16
|
+
for k, v in fn["parameters"].get("properties", {}).items()
|
17
|
+
]
|
18
|
+
)
|
19
|
+
table.add_row(f"[b]{fn['name']}[/b]", fn["description"], params or "-")
|
20
|
+
except Exception as e:
|
21
|
+
console.print(f"[red]Error loading tools: {e}[/red]")
|
22
|
+
return
|
23
|
+
console.print(table)
|
@@ -3,16 +3,16 @@ def handle_help(console, **kwargs):
|
|
3
3
|
"""
|
4
4
|
[bold green]Available commands:[/bold green]
|
5
5
|
/exit, exit - Exit chat mode
|
6
|
-
/restart -
|
6
|
+
/restart - Start a new conversation
|
7
7
|
/help - Show this help message
|
8
8
|
/continue - Restore last saved conversation
|
9
|
-
/start - Reset conversation history
|
10
9
|
/prompt - Show the system prompt
|
11
10
|
/role - Change the system role
|
12
11
|
/clear - Clear the terminal screen
|
13
12
|
/multi - Provide multiline input as next message
|
14
13
|
/config - Show or set configuration (see: /config show, /config set local|global key=value)
|
15
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
16
|
/termweb-status - Show status information about the running termweb server
|
17
17
|
/verbose [on|off] - Show or set verbose mode for this session
|
18
18
|
"""
|
@@ -25,8 +25,9 @@ def handle_clear(console, **kwargs):
|
|
25
25
|
os.system("cls" if os.name == "nt" else "clear")
|
26
26
|
|
27
27
|
|
28
|
-
def handle_multi(console,
|
28
|
+
def handle_multi(console, shell_state=None, **kwargs):
|
29
29
|
console.print(
|
30
30
|
"[bold yellow]Multiline mode activated. Provide or write your text and press Esc + Enter to submit.[/bold yellow]"
|
31
31
|
)
|
32
|
-
|
32
|
+
if shell_state:
|
33
|
+
shell_state.paste_mode = True
|
janito/shell/commands.py
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
from janito.agent.tool_registry import get_tool_schemas
|
2
|
+
from rich.console import Console
|
3
|
+
|
4
|
+
|
5
|
+
def handle_command(cmd, console: Console, shell_state=None):
|
6
|
+
cmd = cmd.strip().lower()
|
7
|
+
if cmd in ("/exit", "exit"):
|
8
|
+
return "exit"
|
9
|
+
if cmd in ("/help", "help"):
|
10
|
+
console.print("[bold cyan]/help[/]: Show this help message")
|
11
|
+
console.print("[bold cyan]/exit[/]: Exit the shell")
|
12
|
+
console.print("[bold cyan]/tools[/]: List available tools")
|
13
|
+
return
|
14
|
+
if cmd in ("/tools", "tools"):
|
15
|
+
table = None
|
16
|
+
try:
|
17
|
+
from rich.table import Table
|
18
|
+
|
19
|
+
table = Table(
|
20
|
+
title="Available Tools", show_lines=True, style="bold magenta"
|
21
|
+
)
|
22
|
+
table.add_column("Name", style="cyan", no_wrap=True)
|
23
|
+
table.add_column("Description", style="green")
|
24
|
+
table.add_column("Parameters", style="yellow")
|
25
|
+
for schema in get_tool_schemas():
|
26
|
+
fn = schema["function"]
|
27
|
+
params = "\n".join(
|
28
|
+
[
|
29
|
+
f"[bold]{k}[/]: {v['type']}"
|
30
|
+
for k, v in fn["parameters"].get("properties", {}).items()
|
31
|
+
]
|
32
|
+
)
|
33
|
+
table.add_row(f"[b]{fn['name']}[/b]", fn["description"], params or "-")
|
34
|
+
except Exception as e:
|
35
|
+
console.print(f"[red]Error loading tools: {e}[/red]")
|
36
|
+
if table:
|
37
|
+
console.print(table)
|
38
|
+
return
|
39
|
+
# Unknown command
|
40
|
+
console.print(f"[yellow]Unknown command:[/] {cmd}")
|
janito/shell/main.py
ADDED
@@ -0,0 +1,321 @@
|
|
1
|
+
from janito.agent.rich_message_handler import RichMessageHandler
|
2
|
+
from prompt_toolkit.history import InMemoryHistory
|
3
|
+
from dataclasses import dataclass, field
|
4
|
+
from typing import Optional, Any, Dict
|
5
|
+
from janito.shell.prompt.session_setup import (
|
6
|
+
setup_prompt_session,
|
7
|
+
print_welcome_message,
|
8
|
+
)
|
9
|
+
from janito.shell.commands import handle_command
|
10
|
+
from janito.agent.conversation_exceptions import EmptyResponseError, ProviderError
|
11
|
+
from janito.agent.conversation_history import ConversationHistory
|
12
|
+
from janito.agent.tool_use_tracker import ToolUseTracker
|
13
|
+
import janito.i18n as i18n
|
14
|
+
from janito.agent.runtime_config import runtime_config
|
15
|
+
from rich.console import Console
|
16
|
+
from collections import Counter
|
17
|
+
import os
|
18
|
+
from janito.shell.session.manager import get_session_id
|
19
|
+
from prompt_toolkit.formatted_text import HTML
|
20
|
+
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))
|
83
|
+
|
84
|
+
|
85
|
+
@dataclass
|
86
|
+
class ShellState:
|
87
|
+
mem_history: Any = field(default_factory=InMemoryHistory)
|
88
|
+
conversation_history: Any = field(default_factory=lambda: ConversationHistory())
|
89
|
+
last_usage_info: Dict[str, int] = field(
|
90
|
+
default_factory=lambda: {
|
91
|
+
"prompt_tokens": 0,
|
92
|
+
"completion_tokens": 0,
|
93
|
+
"total_tokens": 0,
|
94
|
+
}
|
95
|
+
)
|
96
|
+
last_elapsed: Optional[float] = None
|
97
|
+
termweb_stdout_path: Optional[str] = None
|
98
|
+
termweb_stderr_path: Optional[str] = None
|
99
|
+
livereload_stdout_path: Optional[str] = None
|
100
|
+
livereload_stderr_path: Optional[str] = None
|
101
|
+
paste_mode: bool = False
|
102
|
+
profile_manager: Optional[Any] = None
|
103
|
+
|
104
|
+
|
105
|
+
# Track the active prompt session for cleanup
|
106
|
+
active_prompt_session = None
|
107
|
+
|
108
|
+
|
109
|
+
def start_chat_shell(
|
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
|
127
|
+
from janito.shell.session.manager import load_conversation_by_session_id
|
128
|
+
|
129
|
+
shell_state = ShellState()
|
130
|
+
shell_state.profile_manager = profile_manager
|
131
|
+
if continue_session and session_id:
|
132
|
+
try:
|
133
|
+
messages, prompts, usage = load_conversation_by_session_id(session_id)
|
134
|
+
except FileNotFoundError as e:
|
135
|
+
console.print(f"[bold red]{str(e)}[/bold red]")
|
136
|
+
return
|
137
|
+
# Initialize ConversationHistory with loaded messages
|
138
|
+
shell_state.conversation_history = ConversationHistory(messages)
|
139
|
+
conversation_history = shell_state.conversation_history
|
140
|
+
# Always refresh the system prompt in the loaded history
|
141
|
+
found = False
|
142
|
+
for msg in conversation_history.get_messages():
|
143
|
+
if msg.get("role") == "system":
|
144
|
+
msg["content"] = profile_manager.system_prompt_template
|
145
|
+
found = True
|
146
|
+
break
|
147
|
+
if not found:
|
148
|
+
conversation_history.set_system_message(
|
149
|
+
profile_manager.system_prompt_template
|
150
|
+
)
|
151
|
+
# Optionally set prompts/usage if needed
|
152
|
+
shell_state.last_usage_info = usage or {}
|
153
|
+
else:
|
154
|
+
conversation_history = shell_state.conversation_history
|
155
|
+
# Add system prompt if needed (skip in vanilla mode)
|
156
|
+
|
157
|
+
if (
|
158
|
+
profile_manager.system_prompt_template
|
159
|
+
and (
|
160
|
+
not runtime_config.get("vanilla_mode", False)
|
161
|
+
or runtime_config.get("system_prompt_template")
|
162
|
+
)
|
163
|
+
and not any(
|
164
|
+
m.get("role") == "system" for m in conversation_history.get_messages()
|
165
|
+
)
|
166
|
+
):
|
167
|
+
conversation_history.set_system_message(
|
168
|
+
profile_manager.system_prompt_template
|
169
|
+
)
|
170
|
+
mem_history = shell_state.mem_history
|
171
|
+
|
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
|
+
|
190
|
+
while True:
|
191
|
+
try:
|
192
|
+
|
193
|
+
if shell_state.paste_mode:
|
194
|
+
user_input = session.prompt("Multiline> ", multiline=True)
|
195
|
+
was_paste_mode = True
|
196
|
+
shell_state.paste_mode = False
|
197
|
+
else:
|
198
|
+
user_input = session.prompt(
|
199
|
+
HTML("<inputline>💬 </inputline>"), multiline=False
|
200
|
+
)
|
201
|
+
was_paste_mode = False
|
202
|
+
except EOFError:
|
203
|
+
console.print("\n[bold red]Exiting...[/bold red]")
|
204
|
+
break
|
205
|
+
except KeyboardInterrupt:
|
206
|
+
console.print() # Move to next line
|
207
|
+
try:
|
208
|
+
confirm = (
|
209
|
+
session.prompt(
|
210
|
+
# Use <inputline> for full-line blue background, <prompt> for icon only
|
211
|
+
HTML(
|
212
|
+
"<inputline>Do you really want to exit? (y/n): </inputline>"
|
213
|
+
)
|
214
|
+
)
|
215
|
+
.strip()
|
216
|
+
.lower()
|
217
|
+
)
|
218
|
+
except KeyboardInterrupt:
|
219
|
+
message_handler.handle_message(
|
220
|
+
{"type": "error", "message": "Exiting..."}
|
221
|
+
)
|
222
|
+
break
|
223
|
+
if confirm == "y":
|
224
|
+
message_handler.handle_message(
|
225
|
+
{"type": "error", "message": "Exiting..."}
|
226
|
+
)
|
227
|
+
conversation_history.add_message(
|
228
|
+
{"role": "system", "content": "[Session ended by user]"}
|
229
|
+
)
|
230
|
+
break
|
231
|
+
else:
|
232
|
+
continue
|
233
|
+
|
234
|
+
cmd_input = user_input.strip().lower()
|
235
|
+
if not was_paste_mode and (cmd_input.startswith("/") or cmd_input == "exit"):
|
236
|
+
# Treat both '/exit' and 'exit' as commands
|
237
|
+
result = handle_command(
|
238
|
+
user_input.strip(),
|
239
|
+
console,
|
240
|
+
shell_state=shell_state,
|
241
|
+
)
|
242
|
+
if result == "exit":
|
243
|
+
conversation_history.add_message(
|
244
|
+
{"role": "system", "content": "[Session ended by user]"}
|
245
|
+
)
|
246
|
+
break
|
247
|
+
continue
|
248
|
+
|
249
|
+
if not user_input.strip():
|
250
|
+
continue
|
251
|
+
|
252
|
+
mem_history.append_string(user_input)
|
253
|
+
conversation_history.add_message({"role": "user", "content": user_input})
|
254
|
+
|
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
|
+
|
313
|
+
# After exiting the main loop, print restart info if conversation has >1 message
|
314
|
+
|
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
|
+
history_dir = os.path.join(os.path.expanduser("~"), ".janito", "chat_history")
|
318
|
+
os.makedirs(history_dir, exist_ok=True)
|
319
|
+
history_path = os.path.join(history_dir, f"{session_id_to_save}.json")
|
320
|
+
conversation_history.to_json_file(history_path)
|
321
|
+
# -------------------------------------------------------------------------
|
@@ -4,7 +4,7 @@ from prompt_toolkit.completion import Completer, Completion
|
|
4
4
|
class ShellCommandCompleter(Completer):
|
5
5
|
def __init__(self):
|
6
6
|
# Import here to avoid circular import at module level
|
7
|
-
from janito.
|
7
|
+
from janito.shell.commands import COMMAND_HANDLERS
|
8
8
|
|
9
9
|
# Only commands starting with '/'
|
10
10
|
self.commands = sorted(
|
@@ -1,19 +1,31 @@
|
|
1
|
-
from .ui import
|
1
|
+
from janito.shell.ui.interactive import (
|
2
|
+
print_welcome,
|
3
|
+
get_toolbar_func,
|
4
|
+
get_prompt_session,
|
5
|
+
)
|
2
6
|
from janito import __version__
|
3
7
|
from janito.agent.config import effective_config
|
4
8
|
from janito.agent.runtime_config import runtime_config
|
9
|
+
from janito.shell.session.manager import get_session_id
|
5
10
|
|
6
11
|
|
7
12
|
def setup_prompt_session(
|
8
|
-
messages,
|
13
|
+
messages,
|
14
|
+
last_usage_info_ref,
|
15
|
+
last_elapsed,
|
16
|
+
mem_history,
|
17
|
+
profile_manager,
|
18
|
+
agent,
|
19
|
+
history_ref,
|
9
20
|
):
|
10
21
|
model_name = getattr(agent, "model", None)
|
22
|
+
session_id = get_session_id()
|
11
23
|
|
12
24
|
def get_messages():
|
13
25
|
return messages
|
14
26
|
|
15
27
|
def get_usage():
|
16
|
-
return last_usage_info_ref
|
28
|
+
return last_usage_info_ref()
|
17
29
|
|
18
30
|
def get_elapsed():
|
19
31
|
return last_elapsed
|
@@ -32,11 +44,13 @@ def setup_prompt_session(
|
|
32
44
|
)
|
33
45
|
else (runtime_config.get("role") or effective_config.get("role"))
|
34
46
|
),
|
47
|
+
session_id=session_id,
|
48
|
+
history_ref=history_ref,
|
35
49
|
),
|
36
50
|
mem_history,
|
37
51
|
)
|
38
52
|
return session
|
39
53
|
|
40
54
|
|
41
|
-
def print_welcome_message(console,
|
42
|
-
print_welcome(console, version=__version__,
|
55
|
+
def print_welcome_message(console, continue_id=None):
|
56
|
+
print_welcome(console, version=__version__, continue_id=continue_id)
|
@@ -2,6 +2,35 @@ import os
|
|
2
2
|
import json
|
3
3
|
from datetime import datetime
|
4
4
|
|
5
|
+
# --- Session ID generation ---
|
6
|
+
_current_session_id = None
|
7
|
+
|
8
|
+
|
9
|
+
def generate_session_id():
|
10
|
+
# Use seconds since start of year, encode as base36 for shortness
|
11
|
+
now = datetime.now()
|
12
|
+
start_of_year = datetime(now.year, 1, 1)
|
13
|
+
seconds = int((now - start_of_year).total_seconds())
|
14
|
+
chars = "0123456789abcdefghijklmnopqrstuvwxyz"
|
15
|
+
out = ""
|
16
|
+
n = seconds
|
17
|
+
while n:
|
18
|
+
n, r = divmod(n, 36)
|
19
|
+
out = chars[r] + out
|
20
|
+
return out or "0"
|
21
|
+
|
22
|
+
|
23
|
+
def reset_session_id():
|
24
|
+
global _current_session_id
|
25
|
+
_current_session_id = None
|
26
|
+
|
27
|
+
|
28
|
+
def get_session_id():
|
29
|
+
global _current_session_id
|
30
|
+
if _current_session_id is None:
|
31
|
+
_current_session_id = generate_session_id()
|
32
|
+
return _current_session_id
|
33
|
+
|
5
34
|
|
6
35
|
def load_last_summary(path=".janito/last_conversation.json"):
|
7
36
|
if not os.path.exists(path):
|
@@ -22,9 +51,30 @@ def load_last_conversation(path=".janito/last_conversation.json"):
|
|
22
51
|
return messages, prompts, usage
|
23
52
|
|
24
53
|
|
25
|
-
def
|
26
|
-
|
27
|
-
):
|
54
|
+
def load_conversation_by_session_id(session_id):
|
55
|
+
path = os.path.join(".janito", "chat_history", f"{session_id}.json")
|
56
|
+
if not os.path.exists(path):
|
57
|
+
raise FileNotFoundError(f"Session file not found: {path}")
|
58
|
+
with open(path, "r", encoding="utf-8") as f:
|
59
|
+
data = json.load(f)
|
60
|
+
messages = data.get("messages", [])
|
61
|
+
prompts = data.get("prompts", [])
|
62
|
+
usage = data.get("last_usage_info")
|
63
|
+
return messages, prompts, usage
|
64
|
+
|
65
|
+
|
66
|
+
def save_conversation(messages, prompts, usage_info=None, path=None):
|
67
|
+
# Do not save if only one message and it is a system message (noop session)
|
68
|
+
if (
|
69
|
+
isinstance(messages, list)
|
70
|
+
and len(messages) == 1
|
71
|
+
and messages[0].get("role") == "system"
|
72
|
+
):
|
73
|
+
return
|
74
|
+
|
75
|
+
if path is None:
|
76
|
+
session_id = get_session_id()
|
77
|
+
path = os.path.join(".janito", "chat_history", f"{session_id}.json")
|
28
78
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
29
79
|
data = {"messages": messages, "prompts": prompts, "last_usage_info": usage_info}
|
30
80
|
|