janito 3.9.0__py3-none-any.whl → 3.11.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- janito/agent_events.py +75 -0
- janito/cli/chat_mode/session.py +1 -0
- janito/cli/chat_mode/shell/commands/__init__.py +2 -0
- janito/cli/chat_mode/shell/commands/interactive.py +33 -0
- janito/cli/chat_mode/toolbar.py +16 -1
- janito/cli/core/runner.py +33 -0
- janito/cli/main_cli.py +9 -0
- janito/cli/prompt_core.py +67 -33
- janito/cli/rich_terminal_reporter.py +170 -179
- janito/cli/single_shot_mode/handler.py +19 -0
- janito/llm/agent.py +59 -0
- janito/plugins/tools/local/__init__.py +7 -0
- janito/plugins/tools/local/create_directory.py +44 -1
- janito/tests/test_tool_adapter_case_insensitive.py +112 -0
- janito/tools/tools_adapter.py +514 -510
- {janito-3.9.0.dist-info → janito-3.11.0.dist-info}/METADATA +1 -1
- {janito-3.9.0.dist-info → janito-3.11.0.dist-info}/RECORD +21 -18
- {janito-3.9.0.dist-info → janito-3.11.0.dist-info}/WHEEL +0 -0
- {janito-3.9.0.dist-info → janito-3.11.0.dist-info}/entry_points.txt +0 -0
- {janito-3.9.0.dist-info → janito-3.11.0.dist-info}/licenses/LICENSE +0 -0
- {janito-3.9.0.dist-info → janito-3.11.0.dist-info}/top_level.txt +0 -0
janito/agent_events.py
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
import attr
|
2
|
+
from typing import Any, ClassVar, Optional
|
3
|
+
from janito.event_bus.event import Event
|
4
|
+
|
5
|
+
|
6
|
+
@attr.s(auto_attribs=True, kw_only=True)
|
7
|
+
class AgentEvent(Event):
|
8
|
+
"""
|
9
|
+
Base class for events related to an agent.
|
10
|
+
Includes agent name for identification.
|
11
|
+
"""
|
12
|
+
|
13
|
+
category: ClassVar[str] = "agent"
|
14
|
+
agent_name: Optional[str] = None
|
15
|
+
|
16
|
+
|
17
|
+
@attr.s(auto_attribs=True, kw_only=True)
|
18
|
+
class AgentInitialized(AgentEvent):
|
19
|
+
"""Emitted when an agent is initialized."""
|
20
|
+
pass
|
21
|
+
|
22
|
+
|
23
|
+
@attr.s(auto_attribs=True, kw_only=True)
|
24
|
+
class AgentChatStarted(AgentEvent):
|
25
|
+
"""Emitted when an agent starts a chat session."""
|
26
|
+
prompt: Optional[str] = None
|
27
|
+
messages: Optional[list] = None
|
28
|
+
role: Optional[str] = None
|
29
|
+
|
30
|
+
|
31
|
+
@attr.s(auto_attribs=True, kw_only=True)
|
32
|
+
class AgentChatFinished(AgentEvent):
|
33
|
+
"""Emitted when an agent completes a chat session."""
|
34
|
+
result: Any = None
|
35
|
+
loop_count: int = 0
|
36
|
+
|
37
|
+
|
38
|
+
@attr.s(auto_attribs=True, kw_only=True)
|
39
|
+
class AgentProcessingResponse(AgentEvent):
|
40
|
+
"""Emitted when an agent is processing a response."""
|
41
|
+
response: Any = None
|
42
|
+
|
43
|
+
|
44
|
+
@attr.s(auto_attribs=True, kw_only=True)
|
45
|
+
class AgentToolCallStarted(AgentEvent):
|
46
|
+
"""Emitted when an agent starts processing a tool call."""
|
47
|
+
tool_call_id: Optional[str] = None
|
48
|
+
name: Optional[str] = None
|
49
|
+
arguments: Any = None
|
50
|
+
|
51
|
+
|
52
|
+
@attr.s(auto_attribs=True, kw_only=True)
|
53
|
+
class AgentToolCallFinished(AgentEvent):
|
54
|
+
"""Emitted when an agent completes processing a tool call."""
|
55
|
+
tool_call_id: Optional[str] = None
|
56
|
+
name: Optional[str] = None
|
57
|
+
result: Any = None
|
58
|
+
|
59
|
+
|
60
|
+
@attr.s(auto_attribs=True, kw_only=True)
|
61
|
+
class AgentWaitingForResponse(AgentEvent):
|
62
|
+
"""Emitted when an agent is waiting for a response from the LLM API."""
|
63
|
+
pass
|
64
|
+
|
65
|
+
|
66
|
+
@attr.s(auto_attribs=True, kw_only=True)
|
67
|
+
class AgentReceivedResponse(AgentEvent):
|
68
|
+
"""Emitted when an agent receives a response from the LLM API."""
|
69
|
+
response: Any = None
|
70
|
+
|
71
|
+
|
72
|
+
@attr.s(auto_attribs=True, kw_only=True)
|
73
|
+
class AgentShutdown(AgentEvent):
|
74
|
+
"""Emitted when an agent is shutting down."""
|
75
|
+
pass
|
janito/cli/chat_mode/session.py
CHANGED
@@ -11,6 +11,7 @@ from .session import HistoryShellHandler
|
|
11
11
|
from .tools import ToolsShellHandler
|
12
12
|
from .help import HelpShellHandler
|
13
13
|
from .security_command import SecurityCommand
|
14
|
+
from .interactive import InteractiveShellHandler
|
14
15
|
from janito.cli.console import shared_console
|
15
16
|
|
16
17
|
COMMAND_HANDLERS = {
|
@@ -45,6 +46,7 @@ COMMAND_HANDLERS = {
|
|
45
46
|
"/help": HelpShellHandler,
|
46
47
|
"/security": SecurityCommand,
|
47
48
|
"/provider": ProviderCmdHandler,
|
49
|
+
"/interactive": InteractiveShellHandler,
|
48
50
|
}
|
49
51
|
|
50
52
|
|
@@ -0,0 +1,33 @@
|
|
1
|
+
from janito.cli.chat_mode.shell.commands.base import ShellCmdHandler
|
2
|
+
from janito.cli.console import shared_console
|
3
|
+
|
4
|
+
|
5
|
+
class InteractiveShellHandler(ShellCmdHandler):
|
6
|
+
help_text = "Toggle interactive mode on/off"
|
7
|
+
|
8
|
+
def run(self):
|
9
|
+
args = self.after_cmd_line.strip().lower()
|
10
|
+
|
11
|
+
if args not in ["on", "off"]:
|
12
|
+
shared_console.print(
|
13
|
+
"[bold red]Usage: /interactive on|off[/bold red]"
|
14
|
+
)
|
15
|
+
return
|
16
|
+
|
17
|
+
# Get current interactive state from shell_state if available
|
18
|
+
current_state = getattr(self.shell_state, 'interactive_mode', True)
|
19
|
+
|
20
|
+
if args == "on":
|
21
|
+
if current_state:
|
22
|
+
shared_console.print("[yellow]Interactive mode is already enabled.[/yellow]")
|
23
|
+
else:
|
24
|
+
if hasattr(self.shell_state, 'interactive_mode'):
|
25
|
+
self.shell_state.interactive_mode = True
|
26
|
+
shared_console.print("[green]Interactive mode enabled.[/green]")
|
27
|
+
elif args == "off":
|
28
|
+
if not current_state:
|
29
|
+
shared_console.print("[yellow]Interactive mode is already disabled.[/yellow]")
|
30
|
+
else:
|
31
|
+
if hasattr(self.shell_state, 'interactive_mode'):
|
32
|
+
self.shell_state.interactive_mode = False
|
33
|
+
shared_console.print("[green]Interactive mode disabled.[/green]")
|
janito/cli/chat_mode/toolbar.py
CHANGED
@@ -51,6 +51,20 @@ def assemble_bindings_line(width, permissions=None):
|
|
51
51
|
)
|
52
52
|
|
53
53
|
|
54
|
+
def assemble_platform_line():
|
55
|
+
import platform
|
56
|
+
import sys
|
57
|
+
from janito.platform_discovery import PlatformDiscovery
|
58
|
+
|
59
|
+
discovery = PlatformDiscovery()
|
60
|
+
system_info = f"{platform.system()} {platform.release()}"
|
61
|
+
arch_info = platform.machine()
|
62
|
+
shell_info = discovery.detect_shell()
|
63
|
+
python_version = f"Python {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
64
|
+
|
65
|
+
return f" Platform: {system_info} ({arch_info}) | Shell: {shell_info} | {python_version}"
|
66
|
+
|
67
|
+
|
54
68
|
def _get_status(shell_state):
|
55
69
|
_support = getattr(shell_state, "_support", False)
|
56
70
|
_status = getattr(shell_state, "_status", None)
|
@@ -96,7 +110,8 @@ def get_toolbar_func(perf: PerformanceCollector, msg_count: int, shell_state):
|
|
96
110
|
first_line = assemble_first_line(provider_name, model_name, role, agent=agent)
|
97
111
|
permissions = _get_permissions()
|
98
112
|
bindings_line = assemble_bindings_line(width, permissions)
|
99
|
-
|
113
|
+
platform_line = assemble_platform_line()
|
114
|
+
toolbar_text = first_line + "\n" + platform_line + "\n" + bindings_line
|
100
115
|
|
101
116
|
return HTML(toolbar_text)
|
102
117
|
|
janito/cli/core/runner.py
CHANGED
@@ -38,6 +38,16 @@ def _choose_provider(args):
|
|
38
38
|
return provider
|
39
39
|
|
40
40
|
|
41
|
+
def _select_coder_model_for_provider(provider: str) -> str:
|
42
|
+
"""Auto-select the best coder model for the given provider when --developer mode is used."""
|
43
|
+
coder_models = {
|
44
|
+
"alibaba": "qwen3-coder-plus",
|
45
|
+
"cerebras": "qwen-3-coder-480b",
|
46
|
+
}
|
47
|
+
|
48
|
+
return coder_models.get(provider)
|
49
|
+
|
50
|
+
|
41
51
|
def _populate_driver_config_data(args, modifiers, provider, model):
|
42
52
|
from janito.provider_config import get_effective_setting
|
43
53
|
|
@@ -80,6 +90,17 @@ def prepare_llm_driver_config(args, modifiers):
|
|
80
90
|
if not model:
|
81
91
|
model = get_effective_model(provider)
|
82
92
|
|
93
|
+
# Auto-select coder model when --developer mode is used and no model is specified
|
94
|
+
if not model and getattr(args, "developer", False):
|
95
|
+
model = _select_coder_model_for_provider(provider)
|
96
|
+
if model and getattr(args, "verbose", False):
|
97
|
+
print_verbose_info(
|
98
|
+
"Auto-selected coder model",
|
99
|
+
f"{model} for provider {provider} (developer mode)",
|
100
|
+
style="magenta",
|
101
|
+
align_content=True,
|
102
|
+
)
|
103
|
+
|
83
104
|
# Validate that the chosen model is supported by the selected provider
|
84
105
|
if model:
|
85
106
|
from janito.provider_registry import ProviderRegistry
|
@@ -149,6 +170,15 @@ def handle_runner(
|
|
149
170
|
|
150
171
|
load_disabled_tools_from_config()
|
151
172
|
|
173
|
+
# Disable bash tools when running in PowerShell
|
174
|
+
from janito.platform_discovery import PlatformDiscovery
|
175
|
+
|
176
|
+
pd = PlatformDiscovery()
|
177
|
+
if pd.detect_shell().startswith("PowerShell"):
|
178
|
+
from janito.tools.disabled_tools import DisabledToolsState
|
179
|
+
|
180
|
+
DisabledToolsState.disable_tool("run_bash_command")
|
181
|
+
|
152
182
|
unrestricted = getattr(args, "unrestricted", False)
|
153
183
|
adapter = janito.tools.get_local_tools_adapter(
|
154
184
|
workdir=getattr(args, "workdir", None)
|
@@ -230,4 +260,7 @@ def handle_runner(
|
|
230
260
|
|
231
261
|
|
232
262
|
def get_prompt_mode(args):
|
263
|
+
# If interactive flag is set, force chat mode regardless of user_prompt
|
264
|
+
if getattr(args, "interactive", False):
|
265
|
+
return "chat_mode"
|
233
266
|
return "single_shot" if getattr(args, "user_prompt", None) else "chat_mode"
|
janito/cli/main_cli.py
CHANGED
@@ -204,6 +204,13 @@ definition = [
|
|
204
204
|
"help": "Enable emoji usage in responses to make output more engaging and expressive",
|
205
205
|
},
|
206
206
|
),
|
207
|
+
(
|
208
|
+
["-i", "--interactive"],
|
209
|
+
{
|
210
|
+
"action": "store_true",
|
211
|
+
"help": "Signal that this is an interactive chat session",
|
212
|
+
},
|
213
|
+
),
|
207
214
|
(["user_prompt"], {"nargs": argparse.REMAINDER, "help": "Prompt to submit"}),
|
208
215
|
(
|
209
216
|
["-e", "--event-log"],
|
@@ -256,6 +263,7 @@ MODIFIER_KEYS = [
|
|
256
263
|
"read",
|
257
264
|
"write",
|
258
265
|
"emoji",
|
266
|
+
"interactive",
|
259
267
|
]
|
260
268
|
SETTER_KEYS = ["set", "set_provider", "set_api_key", "unset"]
|
261
269
|
GETTER_KEYS = [
|
@@ -420,6 +428,7 @@ class JanitoCLI:
|
|
420
428
|
|
421
429
|
# If running in single shot mode and --profile is not provided, default to 'developer' profile
|
422
430
|
# Skip profile selection for list commands that don't need it
|
431
|
+
# Also skip if interactive mode is enabled (forces chat mode)
|
423
432
|
if get_prompt_mode(self.args) == "single_shot" and not getattr(
|
424
433
|
self.args, "profile", None
|
425
434
|
):
|
janito/cli/prompt_core.py
CHANGED
@@ -3,6 +3,7 @@ Core PromptHandler: Handles prompt submission and response formatting for janito
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
import time
|
6
|
+
import sys
|
6
7
|
from janito import __version__ as VERSION
|
7
8
|
from janito.performance_collector import PerformanceCollector
|
8
9
|
from rich.status import Status
|
@@ -78,12 +79,27 @@ class PromptHandler:
|
|
78
79
|
)
|
79
80
|
return None
|
80
81
|
|
82
|
+
def _clear_current_line(self):
|
83
|
+
"""
|
84
|
+
Clears the current line in the terminal and returns the cursor to column 1.
|
85
|
+
"""
|
86
|
+
# Use raw ANSI escape sequences but write directly to the underlying file
|
87
|
+
# to bypass Rich's escaping/interpretation
|
88
|
+
if hasattr(self.console, "file") and hasattr(self.console.file, "write"):
|
89
|
+
self.console.file.write("\r\033[2K")
|
90
|
+
self.console.file.flush()
|
91
|
+
else:
|
92
|
+
# Fallback to sys.stdout if console.file is not available
|
93
|
+
sys.stdout.write("\r\033[2K")
|
94
|
+
sys.stdout.flush()
|
95
|
+
|
81
96
|
def _handle_tool_call_started(self, inner_event, status):
|
82
|
-
"""Handle ToolCallStarted event -
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
97
|
+
"""Handle ToolCallStarted event - clear the status before any tool execution."""
|
98
|
+
# Always clear the status when any tool starts to avoid cluttering the UI
|
99
|
+
if status:
|
100
|
+
status.update("")
|
101
|
+
# Also clear the current line to ensure clean terminal output
|
102
|
+
self._clear_current_line()
|
87
103
|
return None
|
88
104
|
|
89
105
|
def _handle_tool_call_finished(self, inner_event):
|
@@ -217,42 +233,60 @@ class PromptHandler:
|
|
217
233
|
"""
|
218
234
|
try:
|
219
235
|
self._print_verbose_debug("Calling agent.chat()...")
|
220
|
-
|
236
|
+
|
221
237
|
# Show waiting status with elapsed time
|
222
238
|
start_time = time.time()
|
223
|
-
|
239
|
+
|
224
240
|
# Get provider and model info for status display
|
225
|
-
provider_name =
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
241
|
+
provider_name = (
|
242
|
+
self.agent.get_provider_name()
|
243
|
+
if hasattr(self.agent, "get_provider_name")
|
244
|
+
else "LLM"
|
245
|
+
)
|
246
|
+
model_name = (
|
247
|
+
self.agent.get_model_name()
|
248
|
+
if hasattr(self.agent, "get_model_name")
|
249
|
+
else "unknown"
|
250
|
+
)
|
251
|
+
|
252
|
+
status = Status(
|
253
|
+
f"[bold blue]Waiting for {provider_name} (model: {model_name})...[/bold blue]"
|
254
|
+
)
|
230
255
|
# Thread coordination event
|
231
256
|
stop_updater = threading.Event()
|
232
|
-
|
257
|
+
|
233
258
|
def update_status():
|
234
259
|
elapsed = time.time() - start_time
|
235
|
-
status.update(
|
236
|
-
|
260
|
+
status.update(
|
261
|
+
f"[bold blue]Waiting for {provider_name} (model: {model_name})... ({elapsed:.1f}s)[/bold blue]"
|
262
|
+
)
|
263
|
+
|
237
264
|
# Start status display and update timer
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
265
|
+
status.start()
|
266
|
+
|
267
|
+
# Update status every second in a separate thread
|
268
|
+
def status_updater():
|
269
|
+
while not stop_updater.is_set():
|
270
|
+
update_status()
|
271
|
+
stop_updater.wait(1.0) # Wait for 1 second or until stopped
|
272
|
+
|
273
|
+
updater_thread = threading.Thread(target=status_updater, daemon=True)
|
274
|
+
updater_thread.start()
|
275
|
+
|
276
|
+
try:
|
277
|
+
# Stop status before calling agent.chat() to prevent interference with tools
|
278
|
+
status.stop()
|
279
|
+
# Clear the current line after status is stopped
|
280
|
+
self._clear_current_line()
|
281
|
+
|
282
|
+
final_event = self.agent.chat(prompt=user_prompt)
|
283
|
+
finally:
|
284
|
+
# Signal the updater thread to stop
|
285
|
+
stop_updater.set()
|
286
|
+
# Wait a bit for the thread to clean up
|
287
|
+
updater_thread.join(timeout=0.1)
|
288
|
+
# Clear the current line after status is suspended/closed
|
289
|
+
self._clear_current_line()
|
256
290
|
if hasattr(self.agent, "set_latest_event"):
|
257
291
|
self.agent.set_latest_event(final_event)
|
258
292
|
self.agent.last_event = final_event
|