janito 1.8.1__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 -3
- janito/agent/config_utils.py +0 -9
- janito/agent/conversation.py +177 -114
- janito/agent/conversation_api.py +179 -159
- janito/agent/conversation_tool_calls.py +11 -8
- janito/agent/llm_conversation_history.py +70 -0
- janito/agent/openai_client.py +44 -21
- janito/agent/openai_schema_generator.py +164 -128
- 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 +9 -8
- janito/agent/test_openai_schema_generator.py +93 -0
- janito/agent/tool_base.py +7 -2
- janito/agent/tool_executor.py +63 -50
- janito/agent/tool_registry.py +5 -2
- janito/agent/tool_use_tracker.py +42 -5
- janito/agent/tools/__init__.py +13 -12
- janito/agent/tools/create_directory.py +9 -6
- janito/agent/tools/create_file.py +35 -54
- janito/agent/tools/delete_text_in_file.py +97 -0
- janito/agent/tools/fetch_url.py +50 -5
- janito/agent/tools/find_files.py +40 -26
- janito/agent/tools/get_file_outline/__init__.py +1 -0
- janito/agent/tools/{outline_file/__init__.py → get_file_outline/core.py} +14 -18
- janito/agent/tools/get_file_outline/python_outline.py +134 -0
- janito/agent/tools/{search_outline.py → get_file_outline/search_outline.py} +11 -0
- janito/agent/tools/get_lines.py +21 -12
- janito/agent/tools/move_file.py +13 -12
- 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 +4 -2
- janito/agent/tools/remove_file.py +15 -13
- janito/agent/tools/replace_file.py +72 -0
- janito/agent/tools/replace_text_in_file.py +7 -5
- janito/agent/tools/run_bash_command.py +29 -72
- janito/agent/tools/run_powershell_command.py +142 -102
- janito/agent/tools/search_text.py +177 -131
- janito/agent/tools/validate_file_syntax/__init__.py +1 -0
- janito/agent/tools/validate_file_syntax/core.py +94 -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/action_type.py +7 -0
- janito/agent/tools_utils/dir_walk_utils.py +24 -0
- janito/agent/tools_utils/formatting.py +49 -0
- janito/agent/tools_utils/gitignore_utils.py +69 -0
- janito/agent/tools_utils/test_gitignore_utils.py +46 -0
- janito/agent/tools_utils/utils.py +30 -0
- janito/cli/_livereload_log_utils.py +13 -0
- janito/cli/_print_config.py +63 -61
- janito/cli/arg_parser.py +57 -14
- janito/cli/cli_main.py +270 -0
- janito/cli/livereload_starter.py +60 -0
- janito/cli/main.py +166 -99
- janito/cli/one_shot.py +80 -0
- janito/cli/termweb_starter.py +2 -2
- janito/i18n/__init__.py +1 -1
- janito/livereload/app.py +25 -0
- janito/rich_utils.py +41 -25
- janito/{cli_chat_shell → shell}/commands/__init__.py +19 -14
- janito/{cli_chat_shell → shell}/commands/config.py +4 -4
- janito/shell/commands/conversation_restart.py +74 -0
- janito/shell/commands/edit.py +24 -0
- janito/shell/commands/history_view.py +18 -0
- janito/{cli_chat_shell → shell}/commands/lang.py +3 -0
- janito/shell/commands/livelogs.py +42 -0
- janito/{cli_chat_shell → shell}/commands/prompt.py +16 -6
- janito/shell/commands/session.py +35 -0
- janito/{cli_chat_shell → shell}/commands/session_control.py +3 -5
- janito/{cli_chat_shell → shell}/commands/termweb_log.py +18 -10
- janito/shell/commands/tools.py +26 -0
- janito/shell/commands/track.py +36 -0
- janito/shell/commands/utility.py +28 -0
- janito/{cli_chat_shell → shell}/commands/verbose.py +4 -5
- janito/shell/commands.py +40 -0
- janito/shell/input_history.py +62 -0
- janito/shell/main.py +257 -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/shell/session/manager.py +101 -0
- janito/{cli_chat_shell/ui.py → shell/ui/interactive.py} +23 -17
- janito/termweb/app.py +3 -3
- janito/termweb/static/editor.css +142 -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/tests/test_rich_utils.py +44 -0
- janito/web/app.py +0 -75
- {janito-1.8.1.dist-info → janito-1.10.0.dist-info}/METADATA +62 -42
- janito-1.10.0.dist-info/RECORD +158 -0
- {janito-1.8.1.dist-info → janito-1.10.0.dist-info}/WHEEL +1 -1
- janito/agent/tools/dir_walk_utils.py +0 -16
- janito/agent/tools/gitignore_utils.py +0 -46
- janito/agent/tools/memory.py +0 -48
- janito/agent/tools/outline_file/formatting.py +0 -20
- 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/run_python_command.py +0 -163
- 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/runner/cli_main.py +0 -180
- 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/cli_chat_shell/commands/sum.py +0 -49
- janito/cli_chat_shell/commands/utility.py +0 -32
- janito/cli_chat_shell/session_manager.py +0 -72
- janito-1.8.1.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/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.1.dist-info → janito-1.10.0.dist-info}/entry_points.txt +0 -0
- {janito-1.8.1.dist-info → janito-1.10.0.dist-info}/licenses/LICENSE +0 -0
- {janito-1.8.1.dist-info → janito-1.10.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,26 @@
|
|
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)
|
24
|
+
|
25
|
+
|
26
|
+
handle_tools.help_text = "List available tools"
|
@@ -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)
|
@@ -0,0 +1,28 @@
|
|
1
|
+
def handle_help(console, **kwargs):
|
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}")
|
9
|
+
|
10
|
+
|
11
|
+
def handle_clear(console, **kwargs):
|
12
|
+
import os
|
13
|
+
|
14
|
+
os.system("cls" if os.name == "nt" else "clear")
|
15
|
+
|
16
|
+
|
17
|
+
handle_clear.help_text = "Clear the terminal screen"
|
18
|
+
|
19
|
+
|
20
|
+
def handle_multi(console, shell_state=None, **kwargs):
|
21
|
+
console.print(
|
22
|
+
"[bold yellow]Multiline mode activated. Provide or write your text and press Esc + Enter to submit.[/bold yellow]"
|
23
|
+
)
|
24
|
+
if shell_state:
|
25
|
+
shell_state.paste_mode = True
|
26
|
+
|
27
|
+
|
28
|
+
handle_multi.help_text = "Provide multiline input as next message"
|
@@ -1,11 +1,7 @@
|
|
1
1
|
from janito.agent.runtime_config import runtime_config
|
2
2
|
|
3
3
|
|
4
|
-
def handle_verbose(console,
|
5
|
-
"""
|
6
|
-
/verbose [on|off]
|
7
|
-
Shows or sets verbose mode for the current shell session.
|
8
|
-
"""
|
4
|
+
def handle_verbose(console, shell_state=None, **kwargs):
|
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, state, **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"
|
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}")
|
@@ -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
ADDED
@@ -0,0 +1,257 @@
|
|
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.api_exceptions import ApiError
|
12
|
+
from janito.agent.llm_conversation_history import LLMConversationHistory
|
13
|
+
import janito.i18n as i18n
|
14
|
+
from janito.agent.runtime_config import runtime_config
|
15
|
+
from rich.console import Console
|
16
|
+
import os
|
17
|
+
from janito.shell.session.manager import get_session_id
|
18
|
+
from prompt_toolkit.formatted_text import HTML
|
19
|
+
import time
|
20
|
+
from janito.shell.input_history import UserInputHistory
|
21
|
+
|
22
|
+
|
23
|
+
@dataclass
|
24
|
+
class ShellState:
|
25
|
+
mem_history: Any = field(default_factory=InMemoryHistory)
|
26
|
+
conversation_history: Any = field(default_factory=lambda: LLMConversationHistory())
|
27
|
+
last_usage_info: Dict[str, int] = field(
|
28
|
+
default_factory=lambda: {
|
29
|
+
"prompt_tokens": 0,
|
30
|
+
"completion_tokens": 0,
|
31
|
+
"total_tokens": 0,
|
32
|
+
}
|
33
|
+
)
|
34
|
+
last_elapsed: Optional[float] = None
|
35
|
+
termweb_stdout_path: Optional[str] = None
|
36
|
+
termweb_stderr_path: Optional[str] = None
|
37
|
+
livereload_stdout_path: Optional[str] = None
|
38
|
+
livereload_stderr_path: Optional[str] = None
|
39
|
+
paste_mode: bool = False
|
40
|
+
profile_manager: Optional[Any] = None
|
41
|
+
user_input_history: Optional[Any] = None
|
42
|
+
|
43
|
+
|
44
|
+
# Track the active prompt session for cleanup
|
45
|
+
active_prompt_session = None
|
46
|
+
|
47
|
+
|
48
|
+
def load_session(shell_state, continue_session, session_id, profile_manager):
|
49
|
+
from janito.shell.session.manager import load_conversation_by_session_id
|
50
|
+
|
51
|
+
if continue_session and session_id:
|
52
|
+
try:
|
53
|
+
messages, prompts, usage = load_conversation_by_session_id(session_id)
|
54
|
+
except FileNotFoundError as e:
|
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)
|
60
|
+
conversation_history = shell_state.conversation_history
|
61
|
+
found = False
|
62
|
+
for msg in conversation_history.get_messages():
|
63
|
+
if msg.get("role") == "system":
|
64
|
+
msg["content"] = profile_manager.system_prompt_template
|
65
|
+
found = True
|
66
|
+
break
|
67
|
+
if not found:
|
68
|
+
conversation_history.set_system_message(
|
69
|
+
profile_manager.system_prompt_template
|
70
|
+
)
|
71
|
+
shell_state.last_usage_info = usage or {}
|
72
|
+
else:
|
73
|
+
conversation_history = shell_state.conversation_history
|
74
|
+
if (
|
75
|
+
profile_manager.system_prompt_template
|
76
|
+
and (
|
77
|
+
not runtime_config.get("vanilla_mode", False)
|
78
|
+
or runtime_config.get("system_prompt_template")
|
79
|
+
)
|
80
|
+
and not any(
|
81
|
+
m.get("role") == "system" for m in conversation_history.get_messages()
|
82
|
+
)
|
83
|
+
):
|
84
|
+
conversation_history.set_system_message(
|
85
|
+
profile_manager.system_prompt_template
|
86
|
+
)
|
87
|
+
return True
|
88
|
+
|
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
|
97
|
+
while True:
|
98
|
+
try:
|
99
|
+
if shell_state.paste_mode:
|
100
|
+
user_input = session.prompt("Multiline> ", multiline=True)
|
101
|
+
was_paste_mode = True
|
102
|
+
shell_state.paste_mode = False
|
103
|
+
else:
|
104
|
+
user_input = session.prompt(
|
105
|
+
HTML("<inputline>💬 </inputline>"), multiline=False
|
106
|
+
)
|
107
|
+
was_paste_mode = False
|
108
|
+
except EOFError:
|
109
|
+
console.print("\n[bold red]Exiting...[/bold red]")
|
110
|
+
break
|
111
|
+
except KeyboardInterrupt:
|
112
|
+
console.print()
|
113
|
+
try:
|
114
|
+
confirm = (
|
115
|
+
session.prompt(
|
116
|
+
HTML(
|
117
|
+
"<inputline>Do you really want to exit? (y/n): </inputline>"
|
118
|
+
)
|
119
|
+
)
|
120
|
+
.strip()
|
121
|
+
.lower()
|
122
|
+
)
|
123
|
+
except KeyboardInterrupt:
|
124
|
+
message_handler.handle_message(
|
125
|
+
{"type": "error", "message": "Exiting..."}
|
126
|
+
)
|
127
|
+
break
|
128
|
+
if confirm == "y":
|
129
|
+
message_handler.handle_message(
|
130
|
+
{"type": "error", "message": "Exiting..."}
|
131
|
+
)
|
132
|
+
conversation_history.add_message(
|
133
|
+
{"role": "system", "content": "[Session ended by user]"}
|
134
|
+
)
|
135
|
+
break
|
136
|
+
else:
|
137
|
+
continue
|
138
|
+
cmd_input = user_input.strip().lower()
|
139
|
+
if not was_paste_mode and (cmd_input.startswith("/") or cmd_input == "exit"):
|
140
|
+
result = handle_command(
|
141
|
+
user_input.strip(),
|
142
|
+
console,
|
143
|
+
shell_state=shell_state,
|
144
|
+
)
|
145
|
+
if result == "exit":
|
146
|
+
conversation_history.add_message(
|
147
|
+
{"role": "system", "content": "[Session ended by user]"}
|
148
|
+
)
|
149
|
+
break
|
150
|
+
continue
|
151
|
+
if not user_input.strip():
|
152
|
+
continue
|
153
|
+
shell_state.mem_history.append_string(user_input)
|
154
|
+
shell_state.user_input_history.append(user_input)
|
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_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
|
204
|
+
|
205
|
+
history_dir = os.path.join(os.path.expanduser("~"), ".janito", "chat_history")
|
206
|
+
os.makedirs(history_dir, exist_ok=True)
|
207
|
+
session_id_to_save = session_id if session_id else get_session_id()
|
208
|
+
history_path = os.path.join(history_dir, f"{session_id_to_save}.json")
|
209
|
+
conversation_history.to_json_file(history_path)
|
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
|
+
)
|
@@ -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)
|
@@ -0,0 +1,101 @@
|
|
1
|
+
import os
|
2
|
+
import json
|
3
|
+
from datetime import datetime
|
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
|
+
|
34
|
+
|
35
|
+
def load_last_summary(path=".janito/last_conversation.json"):
|
36
|
+
if not os.path.exists(path):
|
37
|
+
return None
|
38
|
+
with open(path, "r", encoding="utf-8") as f:
|
39
|
+
data = json.load(f)
|
40
|
+
return data
|
41
|
+
|
42
|
+
|
43
|
+
def load_last_conversation(path=".janito/last_conversation.json"):
|
44
|
+
if not os.path.exists(path):
|
45
|
+
return [], [], None
|
46
|
+
with open(path, "r", encoding="utf-8") as f:
|
47
|
+
data = json.load(f)
|
48
|
+
messages = data.get("messages", [])
|
49
|
+
prompts = data.get("prompts", [])
|
50
|
+
usage = data.get("last_usage_info")
|
51
|
+
return messages, prompts, usage
|
52
|
+
|
53
|
+
|
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")
|
78
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
79
|
+
data = {"messages": messages, "prompts": prompts, "last_usage_info": usage_info}
|
80
|
+
|
81
|
+
def usage_serializer(obj):
|
82
|
+
if hasattr(obj, "to_dict"):
|
83
|
+
return obj.to_dict()
|
84
|
+
if hasattr(obj, "__dict__"):
|
85
|
+
return obj.__dict__
|
86
|
+
return str(obj)
|
87
|
+
|
88
|
+
with open(path, "w", encoding="utf-8") as f:
|
89
|
+
json.dump(data, f, indent=2, default=usage_serializer)
|
90
|
+
|
91
|
+
|
92
|
+
def last_conversation_exists(path=".janito/last_conversation.json"):
|
93
|
+
if not os.path.exists(path):
|
94
|
+
return False
|
95
|
+
try:
|
96
|
+
with open(path, "r", encoding="utf-8") as f:
|
97
|
+
data = json.load(f)
|
98
|
+
messages = data.get("messages", [])
|
99
|
+
return bool(messages)
|
100
|
+
except Exception:
|
101
|
+
return False
|