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.
Files changed (119) hide show
  1. janito/__init__.py +1 -1
  2. janito/agent/config_defaults.py +23 -0
  3. janito/agent/config_utils.py +0 -9
  4. janito/agent/conversation.py +31 -9
  5. janito/agent/conversation_api.py +32 -2
  6. janito/agent/conversation_history.py +53 -0
  7. janito/agent/conversation_tool_calls.py +11 -8
  8. janito/agent/openai_client.py +11 -3
  9. janito/agent/openai_schema_generator.py +9 -6
  10. janito/agent/providers.py +77 -0
  11. janito/agent/rich_message_handler.py +1 -1
  12. janito/agent/templates/profiles/system_prompt_template_base.txt.j2 +8 -8
  13. janito/agent/tool_executor.py +18 -10
  14. janito/agent/tool_use_tracker.py +16 -0
  15. janito/agent/tools/__init__.py +7 -9
  16. janito/agent/tools/create_directory.py +7 -6
  17. janito/agent/tools/create_file.py +29 -54
  18. janito/agent/tools/delete_text_in_file.py +97 -0
  19. janito/agent/tools/fetch_url.py +11 -3
  20. janito/agent/tools/find_files.py +37 -25
  21. janito/agent/tools/get_file_outline/__init__.py +1 -0
  22. janito/agent/tools/{outline_file/__init__.py → get_file_outline/core.py} +12 -15
  23. janito/agent/tools/get_file_outline/python_outline.py +134 -0
  24. janito/agent/tools/{search_outline.py → get_file_outline/search_outline.py} +9 -0
  25. janito/agent/tools/get_lines.py +15 -11
  26. janito/agent/tools/move_file.py +10 -11
  27. janito/agent/tools/remove_directory.py +2 -2
  28. janito/agent/tools/remove_file.py +11 -13
  29. janito/agent/tools/replace_file.py +62 -0
  30. janito/agent/tools/replace_text_in_file.py +3 -3
  31. janito/agent/tools/run_bash_command.py +3 -7
  32. janito/agent/tools/run_powershell_command.py +39 -28
  33. janito/agent/tools/run_python_command.py +3 -5
  34. janito/agent/tools/search_text.py +10 -14
  35. janito/agent/tools/validate_file_syntax/__init__.py +1 -0
  36. janito/agent/tools/validate_file_syntax/core.py +92 -0
  37. janito/agent/tools/validate_file_syntax/css_validator.py +35 -0
  38. janito/agent/tools/validate_file_syntax/html_validator.py +77 -0
  39. janito/agent/tools/validate_file_syntax/js_validator.py +27 -0
  40. janito/agent/tools/validate_file_syntax/json_validator.py +6 -0
  41. janito/agent/tools/validate_file_syntax/markdown_validator.py +66 -0
  42. janito/agent/tools/validate_file_syntax/ps1_validator.py +32 -0
  43. janito/agent/tools/validate_file_syntax/python_validator.py +5 -0
  44. janito/agent/tools/validate_file_syntax/xml_validator.py +11 -0
  45. janito/agent/tools/validate_file_syntax/yaml_validator.py +6 -0
  46. janito/agent/tools_utils/__init__.py +1 -0
  47. janito/agent/tools_utils/dir_walk_utils.py +23 -0
  48. janito/agent/{tools/outline_file → tools_utils}/formatting.py +5 -2
  49. janito/agent/{tools → tools_utils}/gitignore_utils.py +0 -3
  50. janito/agent/tools_utils/utils.py +30 -0
  51. janito/cli/_livereload_log_utils.py +13 -0
  52. janito/cli/arg_parser.py +45 -3
  53. janito/cli/{runner/cli_main.py → cli_main.py} +120 -20
  54. janito/cli/livereload_starter.py +60 -0
  55. janito/cli/main.py +110 -21
  56. janito/cli/one_shot.py +66 -0
  57. janito/cli/termweb_starter.py +2 -2
  58. janito/livereload/app.py +25 -0
  59. janito/rich_utils.py +0 -22
  60. janito/{cli_chat_shell → shell}/commands/__init__.py +18 -11
  61. janito/{cli_chat_shell → shell}/commands/config.py +4 -4
  62. janito/shell/commands/conversation_restart.py +72 -0
  63. janito/shell/commands/edit.py +21 -0
  64. janito/shell/commands/history_view.py +18 -0
  65. janito/shell/commands/livelogs.py +40 -0
  66. janito/{cli_chat_shell → shell}/commands/prompt.py +10 -6
  67. janito/shell/commands/session.py +32 -0
  68. janito/{cli_chat_shell → shell}/commands/session_control.py +2 -7
  69. janito/{cli_chat_shell → shell}/commands/sum.py +6 -6
  70. janito/{cli_chat_shell → shell}/commands/termweb_log.py +10 -10
  71. janito/shell/commands/tools.py +23 -0
  72. janito/{cli_chat_shell → shell}/commands/utility.py +5 -4
  73. janito/{cli_chat_shell → shell}/commands/verbose.py +1 -1
  74. janito/shell/commands.py +40 -0
  75. janito/shell/main.py +321 -0
  76. janito/{cli_chat_shell/shell_command_completer.py → shell/prompt/completer.py} +1 -1
  77. janito/{cli_chat_shell/chat_ui.py → shell/prompt/session_setup.py} +19 -5
  78. janito/{cli_chat_shell/session_manager.py → shell/session/manager.py} +53 -3
  79. janito/{cli_chat_shell/ui.py → shell/ui/interactive.py} +23 -15
  80. janito/termweb/app.py +3 -3
  81. janito/termweb/static/editor.css +146 -0
  82. janito/termweb/static/editor.css.bak +27 -0
  83. janito/termweb/static/editor.html +15 -213
  84. janito/termweb/static/editor.html.bak +16 -215
  85. janito/termweb/static/editor.js +209 -0
  86. janito/termweb/static/editor.js.bak +227 -0
  87. janito/termweb/static/index.html +2 -3
  88. janito/termweb/static/index.html.bak +2 -3
  89. janito/termweb/static/termweb.css.bak +33 -84
  90. janito/termweb/static/termweb.js +15 -34
  91. janito/termweb/static/termweb.js.bak +18 -36
  92. {janito-1.8.0.dist-info → janito-1.9.0.dist-info}/METADATA +6 -3
  93. janito-1.9.0.dist-info/RECORD +151 -0
  94. {janito-1.8.0.dist-info → janito-1.9.0.dist-info}/WHEEL +1 -1
  95. janito/agent/tools/dir_walk_utils.py +0 -16
  96. janito/agent/tools/memory.py +0 -48
  97. janito/agent/tools/outline_file/python_outline.py +0 -71
  98. janito/agent/tools/present_choices_test.py +0 -18
  99. janito/agent/tools/rich_live.py +0 -44
  100. janito/agent/tools/tools_utils.py +0 -56
  101. janito/agent/tools/utils.py +0 -33
  102. janito/agent/tools/validate_file_syntax.py +0 -163
  103. janito/cli_chat_shell/chat_loop.py +0 -163
  104. janito/cli_chat_shell/chat_state.py +0 -38
  105. janito/cli_chat_shell/commands/history_start.py +0 -37
  106. janito/cli_chat_shell/commands/session.py +0 -48
  107. janito-1.8.0.dist-info/RECORD +0 -127
  108. /janito/agent/tools/{outline_file → get_file_outline}/markdown_outline.py +0 -0
  109. /janito/cli/{runner/_termweb_log_utils.py → _termweb_log_utils.py} +0 -0
  110. /janito/cli/{runner/config.py → config_runner.py} +0 -0
  111. /janito/cli/{runner/formatting.py → formatting_runner.py} +0 -0
  112. /janito/{cli/runner → shell}/__init__.py +0 -0
  113. /janito/{cli_chat_shell → shell}/commands/lang.py +0 -0
  114. /janito/{cli_chat_shell → shell/prompt}/load_prompt.py +0 -0
  115. /janito/{cli_chat_shell/config_shell.py → shell/session/config.py} +0 -0
  116. /janito/{cli_chat_shell/__init__.py → shell/session/history.py} +0 -0
  117. {janito-1.8.0.dist-info → janito-1.9.0.dist-info}/entry_points.txt +0 -0
  118. {janito-1.8.0.dist-info → janito-1.9.0.dist-info}/licenses/LICENSE +0 -0
  119. {janito-1.8.0.dist-info → janito-1.9.0.dist-info}/top_level.txt +0 -0
@@ -1,48 +0,0 @@
1
- from janito.agent.tool_base import ToolBase
2
- from janito.agent.tool_registry import register_tool
3
- from janito.i18n import tr
4
-
5
-
6
- @register_tool(name="memory")
7
- class MemoryTool(ToolBase):
8
- """
9
- Simple in-memory key-value store for demonstration purposes.
10
- """
11
-
12
- def __init__(self):
13
- super().__init__()
14
- self.memory = {}
15
-
16
- def run(self, action: str, key: str, value: str = None) -> str:
17
- if action == "set":
18
- self.report_info(tr("ℹ️ Storing value for key: '{key}' ...", key=key))
19
- self.memory[key] = value
20
- msg = tr("Value stored for key: '{key}'.", key=key)
21
- self.report_success(msg)
22
- return msg
23
- elif action == "get":
24
- self.report_info(tr("ℹ️ Retrieving value for key: '{key}' ...", key=key))
25
- if key in self.memory:
26
- msg = tr(
27
- "Value for key '{key}': {value}", key=key, value=self.memory[key]
28
- )
29
- self.report_success(msg)
30
- return msg
31
- else:
32
- msg = tr("Key '{key}' not found.", key=key)
33
- self.report_warning(msg)
34
- return msg
35
- elif action == "delete":
36
- if key in self.memory:
37
- del self.memory[key]
38
- msg = tr("Key '{key}' deleted.", key=key)
39
- self.report_success(msg)
40
- return msg
41
- else:
42
- msg = tr("Key '{key}' not found.", key=key)
43
- self.report_error(msg)
44
- return msg
45
- else:
46
- msg = tr("Unknown action: {action}", action=action)
47
- self.report_error(msg)
48
- return msg
@@ -1,71 +0,0 @@
1
- import re
2
- from typing import List
3
-
4
-
5
- def parse_python_outline(lines: List[str]):
6
- class_pat = re.compile(r"^(\s*)class\s+(\w+)")
7
- func_pat = re.compile(r"^(\s*)def\s+(\w+)")
8
- assign_pat = re.compile(r"^(\s*)([A-Za-z_][A-Za-z0-9_]*)\s*=.*")
9
- outline = []
10
- stack = [] # (type, name, indent, start, parent)
11
- for idx, line in enumerate(lines):
12
- class_match = class_pat.match(line)
13
- func_match = func_pat.match(line)
14
- assign_match = assign_pat.match(line)
15
- indent = len(line) - len(line.lstrip())
16
- if class_match:
17
- name = class_match.group(2)
18
- parent = stack[-1][1] if stack and stack[-1][0] == "class" else ""
19
- stack.append(("class", name, indent, idx + 1, parent))
20
- elif func_match:
21
- name = func_match.group(2)
22
- parent = (
23
- stack[-1][1]
24
- if stack
25
- and stack[-1][0] in ("class", "function")
26
- and indent > stack[-1][2]
27
- else ""
28
- )
29
- stack.append(("function", name, indent, idx + 1, parent))
30
- elif assign_match and indent == 0:
31
- var_name = assign_match.group(2)
32
- var_type = "const" if var_name.isupper() else "var"
33
- outline.append(
34
- {
35
- "type": var_type,
36
- "name": var_name,
37
- "start": idx + 1,
38
- "end": idx + 1,
39
- "parent": "",
40
- }
41
- )
42
- while stack and indent < stack[-1][2]:
43
- popped = stack.pop()
44
- outline.append(
45
- {
46
- "type": (
47
- popped[0]
48
- if popped[0] != "function" or popped[3] == 1
49
- else ("method" if popped[4] else "function")
50
- ),
51
- "name": popped[1],
52
- "start": popped[3],
53
- "end": idx,
54
- "parent": popped[4],
55
- }
56
- )
57
- for popped in stack:
58
- outline.append(
59
- {
60
- "type": (
61
- popped[0]
62
- if popped[0] != "function" or popped[3] == 1
63
- else ("method" if popped[4] else "function")
64
- ),
65
- "name": popped[1],
66
- "start": popped[3],
67
- "end": len(lines),
68
- "parent": popped[4],
69
- }
70
- )
71
- return outline
@@ -1,18 +0,0 @@
1
- """
2
- Test for present_choices tool.
3
-
4
- Note: This test is for manual/interactive verification only, as prompt_toolkit dialogs require user interaction.
5
- """
6
-
7
- from present_choices import present_choices
8
-
9
- if __name__ == "__main__":
10
- prompt = "Select your favorite fruits:"
11
- choices = ["Apple", "Banana", "Cherry", "Date"]
12
- print("Single-select test:")
13
- selected = present_choices(prompt, choices, multi_select=False)
14
- print(f"Selected: {selected}")
15
-
16
- print("\nMulti-select test:")
17
- selected_multi = present_choices(prompt, choices, multi_select=True)
18
- print(f"Selected: {selected_multi}")
@@ -1,44 +0,0 @@
1
- from contextlib import contextmanager
2
- from rich.live import Live
3
- from rich.panel import Panel
4
- from rich.console import Console
5
-
6
- console = Console()
7
-
8
- _global_live = None
9
-
10
-
11
- @contextmanager
12
- def global_live_panel(title="Working..."):
13
- global _global_live
14
- if _global_live is None:
15
- _global_live = Live(
16
- Panel("", title=title), console=console, refresh_per_second=4
17
- )
18
- _global_live.start()
19
- try:
20
- yield _global_live
21
- finally:
22
- pass # Do not stop here; stopping is handled explicitly
23
-
24
-
25
- def stop_global_live_panel():
26
- global _global_live
27
- if _global_live is not None:
28
- _global_live.stop()
29
- _global_live = None
30
-
31
-
32
- @contextmanager
33
- def live_panel(title="Working..."):
34
- global _global_live
35
- if _global_live is not None:
36
- # Update the global panel content instead of creating a nested panel
37
- _global_live.update(Panel("", title=title))
38
- yield _global_live
39
- else:
40
- # Fallback: create a temporary panel if no global panel is running
41
- with Live(
42
- Panel("", title=title), console=console, refresh_per_second=4
43
- ) as live:
44
- yield live
@@ -1,56 +0,0 @@
1
- import os
2
- import urllib.parse
3
- from janito.agent.tools.gitignore_utils import filter_ignored
4
- from janito.agent.runtime_config import runtime_config
5
-
6
-
7
- def display_path(path):
8
- """
9
- Returns a display-friendly path. If runtime_config['termweb_port'] is set, injects an ANSI hyperlink to the local web file viewer.
10
- Args:
11
- path (str): Path to display.
12
- Returns:
13
- str: Display path, optionally as an ANSI hyperlink.
14
- """
15
- if os.path.isabs(path):
16
- disp = path
17
- else:
18
- disp = os.path.relpath(path)
19
- port = runtime_config.get("termweb_port")
20
- if port:
21
- url = f"http://localhost:{port}/?path={urllib.parse.quote(path)}"
22
- # Use Rich markup for hyperlinks
23
- return f"[link={url}]{disp}[/link]"
24
- return disp
25
-
26
-
27
- def pluralize(word: str, count: int) -> str:
28
- """Return the pluralized form of word if count != 1, unless word already ends with 's'."""
29
- if count == 1 or word.endswith("s"):
30
- return word
31
- return word + "s"
32
-
33
-
34
- def find_files_with_extensions(directories, extensions, max_depth=0):
35
- """
36
- Find files in given directories with specified extensions, respecting .gitignore.
37
-
38
- Args:
39
- directories (list[str]): Directories to search.
40
- extensions (list[str]): File extensions to include (e.g., ['.py', '.md']).
41
- max_depth (int, optional): Maximum directory depth to search. If 0, unlimited.
42
- Returns:
43
- list[str]: List of matching file paths.
44
- """
45
- output = []
46
- for directory in directories:
47
- for root, dirs, files in os.walk(directory):
48
- rel_path = os.path.relpath(root, directory)
49
- depth = 0 if rel_path == "." else rel_path.count(os.sep) + 1
50
- if max_depth > 0 and depth > max_depth:
51
- continue
52
- dirs, files = filter_ignored(root, dirs, files)
53
- for filename in files:
54
- if any(filename.lower().endswith(ext) for ext in extensions):
55
- output.append(os.path.join(root, filename))
56
- return output
@@ -1,33 +0,0 @@
1
- import os
2
-
3
-
4
- def expand_path(path: str) -> str:
5
- """
6
- If ~ is present in the path, expands it to the user's home directory.
7
- Otherwise, returns the path unchanged.
8
- """
9
- if "~" in path:
10
- return os.path.expanduser(path)
11
- return path
12
-
13
-
14
- def display_path(path: str) -> str:
15
- """
16
- Returns a user-friendly path for display:
17
- - If the path is relative, return it as-is.
18
- - If the path starts with ~, keep it as ~.
19
- - If the path is under the home directory, replace the home dir with ~.
20
- - Else, show the absolute path.
21
- """
22
- if not (
23
- path.startswith("/")
24
- or path.startswith("~")
25
- or (os.name == "nt" and len(path) > 1 and path[1] == ":")
26
- ):
27
- return path
28
- home = os.path.expanduser("~")
29
- if path.startswith("~"):
30
- return path
31
- if path.startswith(home):
32
- return "~" + path[len(home) :]
33
- return path
@@ -1,163 +0,0 @@
1
- from janito.agent.tool_base import ToolBase
2
- from janito.agent.tool_registry import register_tool
3
- from janito.i18n import tr
4
- import os
5
- import json
6
- import yaml
7
- from janito.agent.tools.utils import display_path
8
-
9
-
10
- @register_tool(name="validate_file_syntax")
11
- class ValidateFileSyntaxTool(ToolBase):
12
- """
13
- Validate a file for syntax issues.
14
-
15
- Supported types:
16
- - Python (.py, .pyw)
17
- - JSON (.json)
18
- - YAML (.yml, .yaml)
19
- - PowerShell (.ps1)
20
- - XML (.xml)
21
- - HTML (.html, .htm) [lxml]
22
-
23
- Args:
24
- file_path (str): Path to the file to validate.
25
- Returns:
26
- str: Validation status message. Example:
27
- - "✅ Syntax OK"
28
- - "⚠️ Warning: Syntax error: <error message>"
29
- - "⚠️ Warning: Unsupported file extension: <ext>"
30
- """
31
-
32
- def run(self, file_path: str) -> str:
33
- disp_path = display_path(file_path)
34
- self.report_info(
35
- tr("🔎 Validating syntax for: {disp_path} ...", disp_path=disp_path)
36
- )
37
- ext = os.path.splitext(file_path)[1].lower()
38
- try:
39
- if ext in [".py", ".pyw"]:
40
- import py_compile
41
-
42
- py_compile.compile(file_path, doraise=True)
43
- elif ext == ".json":
44
- with open(file_path, "r", encoding="utf-8") as f:
45
- json.load(f)
46
- elif ext in [".yml", ".yaml"]:
47
- with open(file_path, "r", encoding="utf-8") as f:
48
- yaml.safe_load(f)
49
- elif ext == ".ps1":
50
- from janito.agent.tools.run_powershell_command import (
51
- RunPowerShellCommandTool,
52
- )
53
-
54
- ps_tool = RunPowerShellCommandTool()
55
- check_cmd = "if (Get-Command Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue) { Write-Output 'PSScriptAnalyzerAvailable' } else { Write-Output 'PSScriptAnalyzerMissing' }"
56
- check_result = ps_tool.run(command=check_cmd, timeout=15)
57
- if "PSScriptAnalyzerMissing" in check_result:
58
- msg = tr(
59
- "⚠️ Warning: PSScriptAnalyzer is not installed. For best PowerShell syntax validation, install it with:\n Install-Module -Name PSScriptAnalyzer -Scope CurrentUser\n"
60
- )
61
- self.report_warning(msg)
62
- return msg
63
- analyze_cmd = f"Invoke-ScriptAnalyzer -Path '{file_path}' -Severity Error | ConvertTo-Json"
64
- analyze_result = ps_tool.run(command=analyze_cmd, timeout=30)
65
- if "[]" in analyze_result or analyze_result.strip() == "":
66
- self.report_success(tr("✅ Syntax OK"))
67
- return tr("✅ Syntax valid")
68
- else:
69
- msg = tr(
70
- "⚠️ Warning: PowerShell syntax issues found:\n{analyze_result}",
71
- analyze_result=analyze_result,
72
- )
73
- self.report_warning(msg)
74
- return msg
75
- elif ext == ".xml":
76
- try:
77
- from lxml import etree
78
- except ImportError:
79
- msg = tr("⚠️ lxml not installed. Cannot validate XML.")
80
- self.report_warning(msg)
81
- return msg
82
- with open(file_path, "rb") as f:
83
- etree.parse(f)
84
- elif ext in (".html", ".htm"):
85
- try:
86
- from lxml import html
87
- except ImportError:
88
- msg = tr("⚠️ lxml not installed. Cannot validate HTML.")
89
- self.report_warning(msg)
90
- return msg
91
- with open(file_path, "rb") as f:
92
- html.parse(f)
93
- from lxml import etree
94
-
95
- parser = etree.HTMLParser(recover=False)
96
- with open(file_path, "rb") as f:
97
- etree.parse(f, parser=parser)
98
- if parser.error_log:
99
- errors = "\n".join(str(e) for e in parser.error_log)
100
- raise ValueError(
101
- tr("HTML syntax errors found:\n{errors}", errors=errors)
102
- )
103
- elif ext == ".md":
104
- import re
105
-
106
- with open(file_path, "r", encoding="utf-8") as f:
107
- content = f.read()
108
- errors = []
109
- # Rule: Headers must start with # followed by a space
110
- for i, line in enumerate(content.splitlines(), 1):
111
- if re.match(r"^#+[^ #]", line):
112
- errors.append(
113
- f"Line {i}: Header missing space after # | {line.strip()}"
114
- )
115
- # Rule: Unclosed code blocks
116
- if content.count("```") % 2 != 0:
117
- errors.append("Unclosed code block (```) detected")
118
- # Rule: Unclosed links/images (flag only if line contains [text]( but not ))
119
- for i, line in enumerate(content.splitlines(), 1):
120
- if re.search(r"\[[^\]]*\]\([^)]+$", line):
121
- errors.append(
122
- f"Line {i}: Unclosed link or image (missing closing parenthesis) | {line.strip()}"
123
- )
124
- # Rule: List items must start with -, *, or + followed by space
125
- for i, line in enumerate(content.splitlines(), 1):
126
- # Skip horizontal rules like --- or ***
127
- if re.match(r"^([-*+])\1{1,}", line):
128
- continue
129
- # Skip table rows (lines starting with |)
130
- if line.lstrip().startswith("|"):
131
- continue
132
- # Only flag as list item if there is text after the bullet (not just emphasis)
133
- if re.match(r"^[-*+][^ \n]", line):
134
- stripped = line.strip()
135
- # If the line is surrounded by * and ends with *, it's likely emphasis, not a list
136
- if not (
137
- stripped.startswith("*")
138
- and stripped.endswith("*")
139
- and len(stripped) > 2
140
- ):
141
- errors.append(
142
- f"Line {i}: List item missing space after bullet | {line.strip()}"
143
- )
144
- # Rule: Inline code must have even number of backticks
145
- if content.count("`") % 2 != 0:
146
- errors.append("Unclosed inline code (`) detected")
147
- if errors:
148
- msg = tr(
149
- "⚠️ Warning: Markdown syntax issues found:\n{errors}",
150
- errors="\n".join(errors),
151
- )
152
- self.report_warning(msg)
153
- return msg
154
- else:
155
- msg = tr("⚠️ Warning: Unsupported file extension: {ext}", ext=ext)
156
- self.report_warning(msg)
157
- return msg
158
- self.report_success(tr("✅ Syntax OK"))
159
- return tr("✅ Syntax valid")
160
- except Exception as e:
161
- msg = tr("⚠️ Warning: Syntax error: {error}", error=e)
162
- self.report_warning(msg)
163
- return msg
@@ -1,163 +0,0 @@
1
- from janito.agent.rich_message_handler import RichMessageHandler
2
- from .chat_state import load_chat_state, save_chat_state
3
- from .chat_ui import setup_prompt_session, print_welcome_message
4
- from .commands import handle_command
5
- from janito.agent.conversation_exceptions import EmptyResponseError, ProviderError
6
-
7
- # Track the active prompt session for cleanup
8
- active_prompt_session = None
9
-
10
-
11
- def start_chat_shell(
12
- profile_manager,
13
- continue_session=False,
14
- max_rounds=50,
15
- termweb_stdout_path=None,
16
- termweb_stderr_path=None,
17
- ):
18
- import janito.i18n as i18n
19
- from janito.agent.runtime_config import runtime_config
20
-
21
- i18n.set_locale(runtime_config.get("lang", "en"))
22
- global active_prompt_session
23
- agent = profile_manager.agent
24
- message_handler = RichMessageHandler()
25
- console = message_handler.console
26
-
27
- # Load state
28
- state = load_chat_state(continue_session)
29
- if termweb_stdout_path:
30
- state["termweb_stdout_path"] = termweb_stdout_path
31
- if termweb_stderr_path:
32
- state["termweb_stderr_path"] = termweb_stderr_path
33
- messages = state["messages"]
34
- mem_history = state["mem_history"]
35
- last_usage_info_ref = {"value": state["last_usage_info"]}
36
- last_elapsed = state["last_elapsed"]
37
-
38
- # Add system prompt if needed (skip in vanilla mode)
39
- from janito.agent.runtime_config import runtime_config
40
-
41
- if (
42
- profile_manager.system_prompt_template
43
- and (
44
- not runtime_config.get("vanilla_mode", False)
45
- or runtime_config.get("system_prompt_template")
46
- )
47
- and not any(m.get("role") == "system" for m in messages)
48
- ):
49
- messages.insert(0, {"role": "system", "content": agent.system_prompt_template})
50
-
51
- print_welcome_message(console, continued=continue_session)
52
-
53
- session = setup_prompt_session(
54
- messages, last_usage_info_ref, last_elapsed, mem_history, profile_manager, agent
55
- )
56
- active_prompt_session = session
57
-
58
- inject_message = state.get("inject_message")
59
- if "inject_message" in state:
60
- del state["inject_message"]
61
-
62
- while True:
63
- try:
64
- if inject_message is not None:
65
- user_input = inject_message
66
- inject_message = None
67
- was_paste_mode = False
68
- elif state.get("paste_mode"):
69
- console.print("")
70
- user_input = session.prompt("Multiline> ", multiline=True)
71
- was_paste_mode = True
72
- state["paste_mode"] = False
73
- else:
74
- from prompt_toolkit.formatted_text import HTML
75
-
76
- user_input = session.prompt(
77
- HTML("<inputline>💬 </inputline>"), multiline=False
78
- )
79
- was_paste_mode = False
80
- except EOFError:
81
- console.print("\n[bold red]Exiting...[/bold red]")
82
- break
83
- except KeyboardInterrupt:
84
- console.print() # Move to next line
85
- try:
86
- confirm = (
87
- session.prompt(
88
- # Use <inputline> for full-line blue background, <prompt> for icon only
89
- HTML(
90
- "<inputline>Do you really want to exit? (y/n): </inputline>"
91
- )
92
- )
93
- .strip()
94
- .lower()
95
- )
96
- except KeyboardInterrupt:
97
- message_handler.handle_message(
98
- {"type": "error", "message": "Exiting..."}
99
- )
100
- break
101
- if confirm == "y":
102
- message_handler.handle_message(
103
- {"type": "error", "message": "Exiting..."}
104
- )
105
- break
106
- else:
107
- continue
108
-
109
- cmd_input = user_input.strip().lower()
110
- if not was_paste_mode and (cmd_input.startswith("/") or cmd_input == "exit"):
111
- # Treat both '/exit' and 'exit' as commands
112
- result = handle_command(
113
- user_input.strip(),
114
- console,
115
- profile_manager=profile_manager,
116
- agent=agent,
117
- messages=messages,
118
- mem_history=mem_history,
119
- state=state,
120
- )
121
- if result == "exit":
122
- break
123
- continue
124
-
125
- if not user_input.strip():
126
- continue
127
-
128
- mem_history.append_string(user_input)
129
- messages.append({"role": "user", "content": user_input})
130
-
131
- import time
132
-
133
- start_time = time.time()
134
-
135
- # No need to propagate verbose; ToolExecutor and others fetch from runtime_config
136
-
137
- try:
138
- response = profile_manager.agent.chat(
139
- messages,
140
- max_rounds=max_rounds,
141
- message_handler=message_handler,
142
- spinner=True,
143
- )
144
- except KeyboardInterrupt:
145
- message_handler.handle_message(
146
- {"type": "info", "message": "Request interrupted. Returning to prompt."}
147
- )
148
- continue
149
- except ProviderError as e:
150
- message_handler.handle_message(
151
- {"type": "error", "message": f"Provider error: {e}"}
152
- )
153
- continue
154
- except EmptyResponseError as e:
155
- message_handler.handle_message({"type": "error", "message": f"Error: {e}"})
156
- continue
157
- last_elapsed = time.time() - start_time
158
-
159
- usage = response.get("usage")
160
- last_usage_info_ref["value"] = usage
161
-
162
- # Save conversation and input history
163
- save_chat_state(messages, mem_history, last_usage_info_ref["value"])
@@ -1,38 +0,0 @@
1
- from .session_manager import (
2
- load_last_conversation,
3
- load_input_history,
4
- save_conversation,
5
- save_input_history,
6
- )
7
- from prompt_toolkit.history import InMemoryHistory
8
-
9
-
10
- def load_chat_state(continue_session: bool):
11
- messages = []
12
- last_usage_info = {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0}
13
- last_elapsed = None
14
- history_list = load_input_history()
15
- mem_history = InMemoryHistory()
16
- for item in history_list:
17
- mem_history.append_string(item)
18
- if continue_session:
19
- msgs, prompts, usage = load_last_conversation()
20
- messages = msgs
21
- last_usage_info = usage
22
- mem_history = InMemoryHistory()
23
- for item in prompts:
24
- mem_history.append_string(item)
25
- state = {
26
- "messages": messages,
27
- "mem_history": mem_history,
28
- "history_list": history_list,
29
- "last_usage_info": last_usage_info,
30
- "last_elapsed": last_elapsed,
31
- }
32
- return state
33
-
34
-
35
- def save_chat_state(messages, mem_history, last_usage_info):
36
- prompts = [h for h in mem_history.get_strings()]
37
- save_conversation(messages, prompts, last_usage_info)
38
- save_input_history(prompts)
@@ -1,37 +0,0 @@
1
- from prompt_toolkit.history import InMemoryHistory
2
- import os
3
-
4
-
5
- def handle_start(console, state, **kwargs):
6
-
7
- save_path = os.path.join(".janito", "last_conversation.json")
8
-
9
- # Clear the terminal screen
10
- os.system("cls" if os.name == "nt" else "clear")
11
-
12
- # Clear in-memory conversation and prompt history
13
- state["messages"].clear()
14
- state["history_list"].clear()
15
- state["mem_history"] = InMemoryHistory()
16
- state["last_usage_info"] = {
17
- "prompt_tokens": 0,
18
- "completion_tokens": 0,
19
- "total_tokens": 0,
20
- }
21
- state["last_elapsed"] = None
22
-
23
- # Delete saved conversation file if exists
24
- if os.path.exists(save_path):
25
- try:
26
- os.remove(save_path)
27
- console.print(
28
- "[bold yellow]Deleted saved conversation history.[/bold yellow]"
29
- )
30
- except Exception as e:
31
- console.print(
32
- f"[bold red]Failed to delete saved conversation:[/bold red] {e}"
33
- )
34
-
35
- console.print(
36
- "[bold green]Conversation history has been started (context reset).[/bold green]"
37
- )