janito 1.14.2__py3-none-any.whl → 2.0.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 +6 -1
- janito/__main__.py +1 -1
- janito/agent/setup_agent.py +139 -0
- janito/agent/templates/profiles/{system_prompt_template_base.txt.j2 → system_prompt_template_main.txt.j2} +1 -1
- janito/cli/__init__.py +9 -0
- janito/cli/chat_mode/bindings.py +37 -0
- janito/cli/chat_mode/chat_entry.py +23 -0
- janito/cli/chat_mode/prompt_style.py +19 -0
- janito/cli/chat_mode/session.py +272 -0
- janito/{shell/prompt/completer.py → cli/chat_mode/shell/autocomplete.py} +7 -6
- janito/cli/chat_mode/shell/commands/__init__.py +55 -0
- janito/cli/chat_mode/shell/commands/base.py +9 -0
- janito/cli/chat_mode/shell/commands/clear.py +12 -0
- janito/{shell → cli/chat_mode/shell}/commands/conversation_restart.py +34 -30
- janito/cli/chat_mode/shell/commands/edit.py +25 -0
- janito/cli/chat_mode/shell/commands/help.py +16 -0
- janito/cli/chat_mode/shell/commands/history_view.py +93 -0
- janito/cli/chat_mode/shell/commands/lang.py +25 -0
- janito/cli/chat_mode/shell/commands/last.py +137 -0
- janito/cli/chat_mode/shell/commands/livelogs.py +49 -0
- janito/cli/chat_mode/shell/commands/multi.py +51 -0
- janito/cli/chat_mode/shell/commands/prompt.py +64 -0
- janito/cli/chat_mode/shell/commands/role.py +36 -0
- janito/cli/chat_mode/shell/commands/session.py +40 -0
- janito/{shell → cli/chat_mode/shell}/commands/session_control.py +2 -2
- janito/cli/chat_mode/shell/commands/termweb_log.py +92 -0
- janito/cli/chat_mode/shell/commands/tools.py +32 -0
- janito/{shell → cli/chat_mode/shell}/commands/utility.py +4 -7
- janito/{shell → cli/chat_mode/shell}/commands/verbose.py +5 -5
- janito/cli/chat_mode/shell/session/__init__.py +1 -0
- janito/{shell → cli/chat_mode/shell}/session/manager.py +9 -1
- janito/cli/chat_mode/toolbar.py +90 -0
- janito/cli/cli_commands/list_models.py +35 -0
- janito/cli/cli_commands/list_providers.py +9 -0
- janito/cli/cli_commands/list_tools.py +53 -0
- janito/cli/cli_commands/model_selection.py +50 -0
- janito/cli/cli_commands/model_utils.py +84 -0
- janito/cli/cli_commands/set_api_key.py +19 -0
- janito/cli/cli_commands/show_config.py +51 -0
- janito/cli/cli_commands/show_system_prompt.py +62 -0
- janito/cli/config.py +28 -0
- janito/cli/console.py +3 -0
- janito/cli/core/__init__.py +4 -0
- janito/cli/core/event_logger.py +59 -0
- janito/cli/core/getters.py +31 -0
- janito/cli/core/runner.py +141 -0
- janito/cli/core/setters.py +174 -0
- janito/cli/core/unsetters.py +54 -0
- janito/cli/main.py +8 -196
- janito/cli/main_cli.py +312 -0
- janito/cli/prompt_core.py +230 -0
- janito/cli/prompt_handler.py +6 -0
- janito/cli/rich_terminal_reporter.py +101 -0
- janito/cli/single_shot_mode/__init__.py +6 -0
- janito/cli/single_shot_mode/handler.py +137 -0
- janito/cli/termweb_starter.py +73 -24
- janito/cli/utils.py +25 -0
- janito/cli/verbose_output.py +196 -0
- janito/config.py +5 -0
- janito/config_manager.py +110 -0
- janito/conversation_history.py +30 -0
- janito/{agent/tools_utils/dir_walk_utils.py → dir_walk_utils.py} +3 -2
- janito/driver_events.py +98 -0
- janito/drivers/anthropic/driver.py +113 -0
- janito/drivers/azure_openai/driver.py +36 -0
- janito/drivers/driver_registry.py +33 -0
- janito/drivers/google_genai/driver.py +54 -0
- janito/drivers/google_genai/schema_generator.py +67 -0
- janito/drivers/mistralai/driver.py +41 -0
- janito/drivers/openai/driver.py +334 -0
- janito/event_bus/__init__.py +2 -0
- janito/event_bus/bus.py +68 -0
- janito/event_bus/event.py +15 -0
- janito/event_bus/handler.py +31 -0
- janito/event_bus/queue_bus.py +57 -0
- janito/exceptions.py +23 -0
- janito/formatting_token.py +54 -0
- janito/i18n/pt.py +1 -0
- janito/llm/__init__.py +5 -0
- janito/llm/agent.py +443 -0
- janito/llm/auth.py +62 -0
- janito/llm/driver.py +239 -0
- janito/llm/driver_config.py +34 -0
- janito/llm/driver_config_builder.py +34 -0
- janito/llm/driver_input.py +12 -0
- janito/llm/message_parts.py +60 -0
- janito/llm/model.py +38 -0
- janito/llm/provider.py +187 -0
- janito/perf_singleton.py +3 -0
- janito/performance_collector.py +167 -0
- janito/provider_config.py +98 -0
- janito/provider_registry.py +152 -0
- janito/providers/__init__.py +7 -0
- janito/providers/anthropic/model_info.py +22 -0
- janito/providers/anthropic/provider.py +65 -0
- janito/providers/azure_openai/model_info.py +15 -0
- janito/providers/azure_openai/provider.py +72 -0
- janito/providers/deepseek/__init__.py +1 -0
- janito/providers/deepseek/model_info.py +16 -0
- janito/providers/deepseek/provider.py +91 -0
- janito/providers/google/__init__.py +1 -0
- janito/providers/google/model_info.py +40 -0
- janito/providers/google/provider.py +69 -0
- janito/providers/mistralai/model_info.py +37 -0
- janito/providers/mistralai/provider.py +69 -0
- janito/providers/openai/__init__.py +1 -0
- janito/providers/openai/model_info.py +137 -0
- janito/providers/openai/provider.py +107 -0
- janito/providers/openai/schema_generator.py +63 -0
- janito/providers/provider_static_info.py +21 -0
- janito/providers/registry.py +26 -0
- janito/report_events.py +38 -0
- janito/termweb/app.py +1 -1
- janito/tools/__init__.py +16 -0
- janito/tools/adapters/__init__.py +1 -0
- janito/tools/adapters/local/__init__.py +54 -0
- janito/tools/adapters/local/adapter.py +92 -0
- janito/{agent/tools → tools/adapters/local}/ask_user.py +30 -13
- janito/tools/adapters/local/copy_file.py +84 -0
- janito/{agent/tools → tools/adapters/local}/create_directory.py +11 -10
- janito/tools/adapters/local/create_file.py +82 -0
- janito/tools/adapters/local/delete_text_in_file.py +136 -0
- janito/{agent/tools → tools/adapters/local}/fetch_url.py +18 -19
- janito/tools/adapters/local/find_files.py +140 -0
- janito/tools/adapters/local/get_file_outline/core.py +151 -0
- janito/{agent/tools → tools/adapters/local}/get_file_outline/python_outline.py +125 -0
- janito/tools/adapters/local/get_file_outline/python_outline_v2.py +156 -0
- janito/{agent/tools → tools/adapters/local}/get_file_outline/search_outline.py +12 -7
- janito/{agent/tools → tools/adapters/local}/move_file.py +13 -9
- janito/{agent/tools → tools/adapters/local}/open_url.py +7 -5
- janito/tools/adapters/local/python_code_run.py +165 -0
- janito/tools/adapters/local/python_command_run.py +163 -0
- janito/tools/adapters/local/python_file_run.py +162 -0
- janito/{agent/tools → tools/adapters/local}/remove_directory.py +15 -9
- janito/{agent/tools → tools/adapters/local}/remove_file.py +17 -14
- janito/{agent/tools → tools/adapters/local}/replace_text_in_file.py +27 -22
- janito/tools/adapters/local/run_bash_command.py +176 -0
- janito/tools/adapters/local/run_powershell_command.py +219 -0
- janito/{agent/tools → tools/adapters/local}/search_text/core.py +32 -12
- janito/{agent/tools → tools/adapters/local}/search_text/match_lines.py +13 -4
- janito/{agent/tools → tools/adapters/local}/search_text/pattern_utils.py +12 -4
- janito/{agent/tools → tools/adapters/local}/search_text/traverse_directory.py +15 -2
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/core.py +12 -11
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/css_validator.py +1 -1
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/html_validator.py +1 -1
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/js_validator.py +1 -1
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/json_validator.py +1 -1
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/markdown_validator.py +1 -1
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/ps1_validator.py +1 -1
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/python_validator.py +1 -1
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/xml_validator.py +1 -1
- janito/{agent/tools → tools/adapters/local}/validate_file_syntax/yaml_validator.py +1 -1
- janito/{agent/tools/get_lines.py → tools/adapters/local/view_file.py} +45 -27
- janito/tools/inspect_registry.py +17 -0
- janito/tools/tool_base.py +105 -0
- janito/tools/tool_events.py +58 -0
- janito/tools/tool_run_exception.py +12 -0
- janito/{agent → tools}/tool_use_tracker.py +2 -4
- janito/{agent/tools_utils/utils.py → tools/tool_utils.py} +18 -9
- janito/tools/tools_adapter.py +207 -0
- janito/tools/tools_schema.py +104 -0
- janito/utils.py +11 -0
- janito/version.py +4 -0
- janito-2.0.0.dist-info/METADATA +232 -0
- janito-2.0.0.dist-info/RECORD +180 -0
- janito/agent/__init__.py +0 -0
- janito/agent/api_exceptions.py +0 -4
- janito/agent/config.py +0 -147
- janito/agent/config_defaults.py +0 -12
- janito/agent/config_utils.py +0 -0
- janito/agent/content_handler.py +0 -0
- janito/agent/conversation.py +0 -238
- janito/agent/conversation_api.py +0 -306
- janito/agent/conversation_exceptions.py +0 -18
- janito/agent/conversation_tool_calls.py +0 -39
- janito/agent/conversation_ui.py +0 -17
- janito/agent/event.py +0 -24
- janito/agent/event_dispatcher.py +0 -24
- janito/agent/event_handler_protocol.py +0 -5
- janito/agent/event_system.py +0 -15
- janito/agent/llm_conversation_history.py +0 -82
- janito/agent/message_handler.py +0 -20
- janito/agent/message_handler_protocol.py +0 -5
- janito/agent/openai_client.py +0 -149
- janito/agent/openai_schema_generator.py +0 -187
- janito/agent/profile_manager.py +0 -96
- janito/agent/queued_message_handler.py +0 -50
- janito/agent/rich_live.py +0 -32
- janito/agent/rich_message_handler.py +0 -115
- janito/agent/runtime_config.py +0 -36
- janito/agent/test_handler_protocols.py +0 -47
- janito/agent/test_openai_schema_generator.py +0 -93
- janito/agent/tests/__init__.py +0 -1
- janito/agent/tool_base.py +0 -63
- janito/agent/tool_executor.py +0 -122
- janito/agent/tool_registry.py +0 -49
- janito/agent/tools/__init__.py +0 -47
- janito/agent/tools/create_file.py +0 -59
- janito/agent/tools/delete_text_in_file.py +0 -97
- janito/agent/tools/find_files.py +0 -106
- janito/agent/tools/get_file_outline/core.py +0 -81
- janito/agent/tools/present_choices.py +0 -64
- janito/agent/tools/python_command_runner.py +0 -201
- janito/agent/tools/python_file_runner.py +0 -199
- janito/agent/tools/python_stdin_runner.py +0 -208
- janito/agent/tools/replace_file.py +0 -72
- janito/agent/tools/run_bash_command.py +0 -218
- janito/agent/tools/run_powershell_command.py +0 -251
- janito/agent/tools_utils/__init__.py +0 -1
- janito/agent/tools_utils/action_type.py +0 -7
- janito/agent/tools_utils/test_gitignore_utils.py +0 -46
- janito/cli/_livereload_log_utils.py +0 -13
- janito/cli/_print_config.py +0 -96
- janito/cli/_termweb_log_utils.py +0 -17
- janito/cli/_utils.py +0 -9
- janito/cli/arg_parser.py +0 -272
- janito/cli/cli_main.py +0 -281
- janito/cli/config_commands.py +0 -211
- janito/cli/config_runner.py +0 -35
- janito/cli/formatting_runner.py +0 -12
- janito/cli/livereload_starter.py +0 -60
- janito/cli/logging_setup.py +0 -38
- janito/cli/one_shot.py +0 -80
- janito/livereload/app.py +0 -25
- janito/rich_utils.py +0 -59
- janito/shell/__init__.py +0 -0
- janito/shell/commands/__init__.py +0 -61
- janito/shell/commands/config.py +0 -22
- janito/shell/commands/edit.py +0 -24
- janito/shell/commands/history_view.py +0 -18
- janito/shell/commands/lang.py +0 -19
- janito/shell/commands/livelogs.py +0 -42
- janito/shell/commands/prompt.py +0 -62
- janito/shell/commands/termweb_log.py +0 -94
- janito/shell/commands/tools.py +0 -26
- janito/shell/commands/track.py +0 -36
- janito/shell/main.py +0 -326
- janito/shell/prompt/load_prompt.py +0 -57
- janito/shell/prompt/session_setup.py +0 -57
- janito/shell/session/config.py +0 -109
- janito/shell/session/history.py +0 -0
- janito/shell/ui/interactive.py +0 -226
- janito/termweb/static/editor.css +0 -158
- janito/termweb/static/editor.css.bak +0 -145
- janito/termweb/static/editor.html +0 -46
- janito/termweb/static/editor.html.bak +0 -46
- janito/termweb/static/editor.js +0 -265
- janito/termweb/static/editor.js.bak +0 -259
- janito/termweb/static/explorer.html.bak +0 -59
- janito/termweb/static/favicon.ico +0 -0
- janito/termweb/static/favicon.ico.bak +0 -0
- janito/termweb/static/index.html +0 -53
- janito/termweb/static/index.html.bak +0 -54
- janito/termweb/static/index.html.bak.bak +0 -175
- janito/termweb/static/landing.html.bak +0 -36
- janito/termweb/static/termicon.svg +0 -1
- janito/termweb/static/termweb.css +0 -214
- janito/termweb/static/termweb.css.bak +0 -237
- janito/termweb/static/termweb.js +0 -162
- janito/termweb/static/termweb.js.bak +0 -168
- janito/termweb/static/termweb.js.bak.bak +0 -157
- janito/termweb/static/termweb_quickopen.js +0 -135
- janito/termweb/static/termweb_quickopen.js.bak +0 -125
- janito/tests/test_rich_utils.py +0 -44
- janito/web/__init__.py +0 -0
- janito/web/__main__.py +0 -25
- janito/web/app.py +0 -145
- janito-1.14.2.dist-info/METADATA +0 -306
- janito-1.14.2.dist-info/RECORD +0 -162
- janito-1.14.2.dist-info/licenses/LICENSE +0 -21
- /janito/{shell → cli/chat_mode/shell}/input_history.py +0 -0
- /janito/{shell/commands/session.py → cli/chat_mode/shell/session/history.py} +0 -0
- /janito/{agent/tools_utils/formatting.py → formatting.py} +0 -0
- /janito/{agent/tools_utils/gitignore_utils.py → gitignore_utils.py} +0 -0
- /janito/{agent/platform_discovery.py → platform_discovery.py} +0 -0
- /janito/{agent/tools → tools/adapters/local}/get_file_outline/__init__.py +0 -0
- /janito/{agent/tools → tools/adapters/local}/get_file_outline/markdown_outline.py +0 -0
- /janito/{agent/tools → tools/adapters/local}/search_text/__init__.py +0 -0
- /janito/{agent/tools → tools/adapters/local}/validate_file_syntax/__init__.py +0 -0
- {janito-1.14.2.dist-info → janito-2.0.0.dist-info}/WHEEL +0 -0
- {janito-1.14.2.dist-info → janito-2.0.0.dist-info}/entry_points.txt +0 -0
- {janito-1.14.2.dist-info → janito-2.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,196 @@
|
|
1
|
+
"""
|
2
|
+
Verbose output formatting and error handling for janito CLI (shared for single and chat modes).
|
3
|
+
"""
|
4
|
+
|
5
|
+
from rich import print as rich_print
|
6
|
+
from rich.align import Align
|
7
|
+
from rich.panel import Panel
|
8
|
+
from rich.text import Text
|
9
|
+
from janito.version import __version__ as VERSION
|
10
|
+
from janito.cli.utils import format_tokens
|
11
|
+
|
12
|
+
|
13
|
+
def print_verbose_header(agent, args):
|
14
|
+
if hasattr(args, "verbose") and args.verbose:
|
15
|
+
role = (
|
16
|
+
agent.template_vars.get("role") if hasattr(agent, "template_vars") else None
|
17
|
+
)
|
18
|
+
role_part = f" (Role: {role})" if role else ""
|
19
|
+
parts = [
|
20
|
+
f"Janito {VERSION}",
|
21
|
+
f"Provider: {agent.llm_provider.__class__.__name__}",
|
22
|
+
f"Model: {agent.llm_provider.model_name}{role_part}",
|
23
|
+
f"Driver: {agent.llm_provider.__class__.__module__.split('.')[-2] if len(agent.llm_provider.__class__.__module__.split('.')) > 1 else agent.llm_provider.__class__.__name__}",
|
24
|
+
]
|
25
|
+
if hasattr(args, "think") and args.think:
|
26
|
+
parts.append("Thinking ON")
|
27
|
+
info_line = " | ".join(part.strip() for part in parts)
|
28
|
+
rich_print(
|
29
|
+
Panel(
|
30
|
+
Align(f"[cyan]{info_line}[/cyan]", align="center"),
|
31
|
+
style="on grey11",
|
32
|
+
expand=True,
|
33
|
+
)
|
34
|
+
)
|
35
|
+
|
36
|
+
|
37
|
+
def print_verbose_info(label, content, style="green", align_content=False):
|
38
|
+
icon = "[bold][32m●[/bold]" if style == "green" else "[bold][34m🔷[/bold]"
|
39
|
+
panel_title = f"{icon} [bold {style}]{label}[/bold {style}]"
|
40
|
+
from rich.console import Console
|
41
|
+
from rich.align import Align
|
42
|
+
from rich.text import Text
|
43
|
+
|
44
|
+
console = Console()
|
45
|
+
width = console.size.width
|
46
|
+
obfuscated_content = content
|
47
|
+
# Obfuscate api_key if LLMDriverConfig
|
48
|
+
if (
|
49
|
+
hasattr(content, "__dataclass_fields__")
|
50
|
+
and "api_key" in content.__dataclass_fields__
|
51
|
+
):
|
52
|
+
# Copy and mask the api_key
|
53
|
+
from copy import deepcopy
|
54
|
+
|
55
|
+
obfuscated_content = deepcopy(content)
|
56
|
+
if hasattr(obfuscated_content, "api_key") and obfuscated_content.api_key:
|
57
|
+
val = obfuscated_content.api_key
|
58
|
+
if len(val) > 8:
|
59
|
+
masked = val[:2] + "***" + val[-2:]
|
60
|
+
else:
|
61
|
+
masked = "***"
|
62
|
+
obfuscated_content.api_key = masked
|
63
|
+
else:
|
64
|
+
obfuscated_content.api_key = None
|
65
|
+
if align_content:
|
66
|
+
rendered_content = Align.center(Text(str(obfuscated_content)))
|
67
|
+
else:
|
68
|
+
rendered_content = Text(str(obfuscated_content))
|
69
|
+
panel = Panel(
|
70
|
+
rendered_content,
|
71
|
+
title=panel_title,
|
72
|
+
border_style=style,
|
73
|
+
expand=False,
|
74
|
+
width=min(width - 8, 100),
|
75
|
+
)
|
76
|
+
console.print(Align.center(panel))
|
77
|
+
|
78
|
+
|
79
|
+
def print_performance(start_time, end_time, performance_collector, args):
|
80
|
+
if start_time is None or end_time is None:
|
81
|
+
generation_time_ns = None
|
82
|
+
else:
|
83
|
+
generation_time_ns = (end_time - start_time) * 1e9
|
84
|
+
if hasattr(args, "verbose") and args.verbose:
|
85
|
+
from rich.table import Table
|
86
|
+
from rich.style import Style
|
87
|
+
from rich import box
|
88
|
+
|
89
|
+
total_requests = performance_collector.get_total_requests()
|
90
|
+
avg_duration = performance_collector.get_average_duration()
|
91
|
+
status_counts = performance_collector.get_status_counts()
|
92
|
+
token_usage = performance_collector.get_token_usage()
|
93
|
+
error_count = performance_collector.get_error_count()
|
94
|
+
avg_turns = performance_collector.get_average_turns()
|
95
|
+
content_parts = performance_collector.get_content_part_count()
|
96
|
+
|
97
|
+
left = []
|
98
|
+
right = []
|
99
|
+
right.append(("[bold]Total Requests[/bold]", f"{total_requests}"))
|
100
|
+
left.append(("[bold]Avg Duration[/bold]", f"{avg_duration:.3f}s"))
|
101
|
+
right.append(
|
102
|
+
(
|
103
|
+
"[bold]Status Counts[/bold]",
|
104
|
+
(
|
105
|
+
", ".join(f"{k}: {v}" for k, v in status_counts.items())
|
106
|
+
if status_counts
|
107
|
+
else "-"
|
108
|
+
),
|
109
|
+
)
|
110
|
+
)
|
111
|
+
if token_usage:
|
112
|
+
usage_str = ", ".join(
|
113
|
+
f"{k.removesuffix('_token_count').removesuffix('_tokens')}: {format_tokens(v)}"
|
114
|
+
for k, v in token_usage.items()
|
115
|
+
)
|
116
|
+
else:
|
117
|
+
usage_str = "-"
|
118
|
+
left.append(("[bold]Token Usage[/bold]", usage_str))
|
119
|
+
right.append(
|
120
|
+
("[bold]Avg Turns[/bold]", f"{avg_turns:.2f}" if avg_turns > 0 else "-")
|
121
|
+
)
|
122
|
+
left.append(
|
123
|
+
(
|
124
|
+
"[bold]Content Parts[/bold]",
|
125
|
+
f"{content_parts}" if content_parts > 0 else "-",
|
126
|
+
)
|
127
|
+
)
|
128
|
+
right.append(
|
129
|
+
("[bold]Errors[/bold]", f"{error_count}" if error_count > 0 else "-")
|
130
|
+
)
|
131
|
+
|
132
|
+
total_tool_events = performance_collector.get_total_tool_events()
|
133
|
+
tool_names_counter = performance_collector.get_tool_names_counter()
|
134
|
+
tool_error_count = performance_collector.get_tool_error_count()
|
135
|
+
tool_error_messages = performance_collector.get_tool_error_messages()
|
136
|
+
tool_action_counter = performance_collector.get_tool_action_counter()
|
137
|
+
tool_subtype_counter = performance_collector.get_tool_subtype_counter()
|
138
|
+
|
139
|
+
tool_names_str = (
|
140
|
+
", ".join(f"{k}: {v}" for k, v in tool_names_counter.items())
|
141
|
+
if tool_names_counter
|
142
|
+
else "-"
|
143
|
+
)
|
144
|
+
tool_actions_str = (
|
145
|
+
", ".join(
|
146
|
+
f"{k.split('.')[-1]}: {v}" for k, v in tool_action_counter.items()
|
147
|
+
)
|
148
|
+
if tool_action_counter
|
149
|
+
else "-"
|
150
|
+
)
|
151
|
+
tool_subtypes_str = (
|
152
|
+
", ".join(f"{k}: {v}" for k, v in tool_subtype_counter.items())
|
153
|
+
if tool_subtype_counter
|
154
|
+
else "-"
|
155
|
+
)
|
156
|
+
tool_errors_str = f"{tool_error_count}"
|
157
|
+
tool_error_msgs_str = (
|
158
|
+
"\n".join(tool_error_messages[:2])
|
159
|
+
+ ("\n..." if len(tool_error_messages) > 2 else "")
|
160
|
+
if tool_error_count
|
161
|
+
else "-"
|
162
|
+
)
|
163
|
+
|
164
|
+
left.append(("[bold]Tool Events[/bold]", f"{total_tool_events}"))
|
165
|
+
right.append(("[bold]Tool Usage[/bold]", tool_names_str))
|
166
|
+
left.append(("[bold]Tool Errors[/bold]", tool_errors_str))
|
167
|
+
|
168
|
+
max_len = max(len(left), len(right))
|
169
|
+
while len(left) < max_len:
|
170
|
+
left.append(("", ""))
|
171
|
+
while len(right) < max_len:
|
172
|
+
right.append(("", ""))
|
173
|
+
|
174
|
+
from rich.table import Table
|
175
|
+
|
176
|
+
table = Table(
|
177
|
+
show_header=False,
|
178
|
+
box=box.SIMPLE,
|
179
|
+
pad_edge=False,
|
180
|
+
style="cyan",
|
181
|
+
expand=False,
|
182
|
+
)
|
183
|
+
table.add_column(justify="right")
|
184
|
+
table.add_column(justify="left")
|
185
|
+
table.add_column(justify="right")
|
186
|
+
table.add_column(justify="left")
|
187
|
+
for (l_key, l_val), (r_key, r_val) in zip(left, right):
|
188
|
+
table.add_row(l_key, l_val, r_key, r_val)
|
189
|
+
if total_requests == 0:
|
190
|
+
table.add_row("[bold]Info[/bold]", "No performance data available.", "", "")
|
191
|
+
rich_print(Panel(table, style="on grey11", expand=True))
|
192
|
+
|
193
|
+
|
194
|
+
def handle_exception(e):
|
195
|
+
rich_print(f"[bold red]Error:[/bold red] {e}")
|
196
|
+
return
|
janito/config.py
ADDED
janito/config_manager.py
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
import json
|
2
|
+
from pathlib import Path
|
3
|
+
from threading import Lock
|
4
|
+
|
5
|
+
|
6
|
+
class ConfigManager:
|
7
|
+
"""
|
8
|
+
Unified configuration manager supporting:
|
9
|
+
- Defaults
|
10
|
+
- File-based configuration
|
11
|
+
- Runtime overrides (e.g., CLI args)
|
12
|
+
"""
|
13
|
+
|
14
|
+
_instance = None
|
15
|
+
_lock = Lock()
|
16
|
+
|
17
|
+
def __new__(cls, *args, **kwargs):
|
18
|
+
with cls._lock:
|
19
|
+
if not cls._instance:
|
20
|
+
cls._instance = super(ConfigManager, cls).__new__(cls)
|
21
|
+
return cls._instance
|
22
|
+
|
23
|
+
def __init__(self, config_path=None, defaults=None, runtime_overrides=None):
|
24
|
+
# Lazy single-init
|
25
|
+
if hasattr(self, "_initialized") and self._initialized:
|
26
|
+
return
|
27
|
+
self._initialized = True
|
28
|
+
|
29
|
+
self.config_path = Path(config_path or Path.home() / ".janito" / "config.json")
|
30
|
+
self.defaults = dict(defaults) if defaults else {}
|
31
|
+
self.file_config = {}
|
32
|
+
self.runtime_overrides = dict(runtime_overrides) if runtime_overrides else {}
|
33
|
+
self._load_file_config()
|
34
|
+
|
35
|
+
def _load_file_config(self):
|
36
|
+
if self.config_path.exists():
|
37
|
+
with open(self.config_path, "r", encoding="utf-8") as f:
|
38
|
+
try:
|
39
|
+
self.file_config = json.load(f)
|
40
|
+
except Exception:
|
41
|
+
self.file_config = {}
|
42
|
+
else:
|
43
|
+
self.file_config = {}
|
44
|
+
|
45
|
+
def save(self):
|
46
|
+
self.config_path.parent.mkdir(parents=True, exist_ok=True)
|
47
|
+
with open(self.config_path, "w", encoding="utf-8") as f:
|
48
|
+
json.dump(self.file_config, f, indent=2)
|
49
|
+
|
50
|
+
def get(self, key, default=None):
|
51
|
+
# Precedence: runtime_overrides > file_config > defaults
|
52
|
+
for layer in (self.runtime_overrides, self.file_config, self.defaults):
|
53
|
+
if key in layer and layer[key] is not None:
|
54
|
+
return layer[key]
|
55
|
+
return default
|
56
|
+
|
57
|
+
def runtime_set(self, key, value):
|
58
|
+
self.runtime_overrides[key] = value
|
59
|
+
|
60
|
+
def file_set(self, key, value):
|
61
|
+
# Always reload, update, and persist
|
62
|
+
self._load_file_config()
|
63
|
+
self.file_config[key] = value
|
64
|
+
with open(self.config_path, "w", encoding="utf-8") as f:
|
65
|
+
json.dump(self.file_config, f, indent=2)
|
66
|
+
|
67
|
+
def all(self, layered=False):
|
68
|
+
merged = dict(self.defaults)
|
69
|
+
merged.update(self.file_config)
|
70
|
+
merged.update(self.runtime_overrides)
|
71
|
+
if layered:
|
72
|
+
# Only file+runtime, i.e., what is saved to disk
|
73
|
+
d = dict(self.file_config)
|
74
|
+
d.update(self.runtime_overrides)
|
75
|
+
return d
|
76
|
+
return merged
|
77
|
+
|
78
|
+
# Namespaced provider/model config
|
79
|
+
def get_provider_config(self, provider, default=None):
|
80
|
+
providers = self.file_config.get("providers") or {}
|
81
|
+
return providers.get(provider) or (default or {})
|
82
|
+
|
83
|
+
def set_provider_config(self, provider, key, value):
|
84
|
+
if "providers" not in self.file_config:
|
85
|
+
self.file_config["providers"] = {}
|
86
|
+
if provider not in self.file_config["providers"]:
|
87
|
+
self.file_config["providers"][provider] = {}
|
88
|
+
self.file_config["providers"][provider][key] = value
|
89
|
+
|
90
|
+
def get_provider_model_config(self, provider, model, default=None):
|
91
|
+
return (
|
92
|
+
self.file_config.get("providers")
|
93
|
+
or {}.get(provider, {}).get("models", {}).get(model)
|
94
|
+
or (default or {})
|
95
|
+
)
|
96
|
+
|
97
|
+
def set_provider_model_config(self, provider, model, key, value):
|
98
|
+
if "providers" not in self.file_config:
|
99
|
+
self.file_config["providers"] = {}
|
100
|
+
if provider not in self.file_config["providers"]:
|
101
|
+
self.file_config["providers"][provider] = {}
|
102
|
+
if "models" not in self.file_config["providers"][provider]:
|
103
|
+
self.file_config["providers"][provider]["models"] = {}
|
104
|
+
if model not in self.file_config["providers"][provider]["models"]:
|
105
|
+
self.file_config["providers"][provider]["models"][model] = {}
|
106
|
+
self.file_config["providers"][provider]["models"][model][key] = value
|
107
|
+
|
108
|
+
# Support loading runtime overrides after init (e.g. after parsing CLI args)
|
109
|
+
def apply_runtime_overrides(self, overrides_dict):
|
110
|
+
self.runtime_overrides.update(overrides_dict)
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import json
|
2
|
+
from typing import List, Dict, Optional
|
3
|
+
|
4
|
+
|
5
|
+
class LLMConversationHistory:
|
6
|
+
"""
|
7
|
+
Stores the conversation history between user and LLM (assistant/system).
|
8
|
+
Each message is a dict with keys: 'role', 'content', and optional 'metadata'.
|
9
|
+
"""
|
10
|
+
|
11
|
+
def __init__(self):
|
12
|
+
self._history: List[Dict] = []
|
13
|
+
|
14
|
+
def add_message(self, role: str, content: str, metadata: Optional[Dict] = None):
|
15
|
+
message = {"role": role, "content": content}
|
16
|
+
if metadata:
|
17
|
+
message["metadata"] = metadata
|
18
|
+
self._history.append(message)
|
19
|
+
|
20
|
+
def get_history(self) -> List[Dict]:
|
21
|
+
return list(self._history)
|
22
|
+
|
23
|
+
def clear(self):
|
24
|
+
self._history.clear()
|
25
|
+
|
26
|
+
def export_json(self) -> str:
|
27
|
+
return json.dumps(self._history, indent=2)
|
28
|
+
|
29
|
+
def import_json(self, json_str: str):
|
30
|
+
self._history = json.loads(json_str)
|
@@ -2,7 +2,7 @@ import os
|
|
2
2
|
from .gitignore_utils import GitignoreFilter
|
3
3
|
|
4
4
|
|
5
|
-
def walk_dir_with_gitignore(root_dir, max_depth=None):
|
5
|
+
def walk_dir_with_gitignore(root_dir, max_depth=None, include_gitignored=False):
|
6
6
|
"""
|
7
7
|
Walks the directory tree starting at root_dir, yielding (root, dirs, files) tuples,
|
8
8
|
with .gitignore rules applied.
|
@@ -20,5 +20,6 @@ def walk_dir_with_gitignore(root_dir, max_depth=None):
|
|
20
20
|
# For max_depth=1, only root (depth=0). For max_depth=2, root and one level below (depth=0,1).
|
21
21
|
if depth > 0:
|
22
22
|
continue
|
23
|
-
|
23
|
+
if not include_gitignored:
|
24
|
+
dirs, files = gitignore.filter_ignored(root, dirs, files)
|
24
25
|
yield root, dirs, files
|
janito/driver_events.py
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
import attr
|
2
|
+
from typing import Any, ClassVar
|
3
|
+
from enum import Enum
|
4
|
+
from janito.event_bus.event import Event
|
5
|
+
|
6
|
+
|
7
|
+
class RequestStatus(Enum):
|
8
|
+
SUCCESS = "success"
|
9
|
+
ERROR = "error"
|
10
|
+
CANCELLED = "cancelled"
|
11
|
+
EMPTY_RESPONSE = "empty_response"
|
12
|
+
TIMEOUT = "timeout"
|
13
|
+
|
14
|
+
|
15
|
+
@attr.s(auto_attribs=True, kw_only=True)
|
16
|
+
class DriverEvent(Event):
|
17
|
+
"""
|
18
|
+
Base class for events related to a driver (e.g., LLM, API provider).
|
19
|
+
Includes driver name and request ID for correlation.
|
20
|
+
"""
|
21
|
+
|
22
|
+
category: ClassVar[str] = "driver"
|
23
|
+
driver_name: str = None
|
24
|
+
request_id: str = None
|
25
|
+
|
26
|
+
|
27
|
+
@attr.s(auto_attribs=True, kw_only=True)
|
28
|
+
class GenerationStarted(DriverEvent):
|
29
|
+
conversation_history: Any = None
|
30
|
+
|
31
|
+
|
32
|
+
@attr.s(auto_attribs=True, kw_only=True)
|
33
|
+
class GenerationFinished(DriverEvent):
|
34
|
+
total_turns: int = 0
|
35
|
+
|
36
|
+
|
37
|
+
@attr.s(auto_attribs=True, kw_only=True)
|
38
|
+
class RequestStarted(DriverEvent):
|
39
|
+
payload: Any = None
|
40
|
+
|
41
|
+
|
42
|
+
@attr.s(auto_attribs=True, kw_only=True)
|
43
|
+
class RequestFinished(DriverEvent):
|
44
|
+
"""
|
45
|
+
Used for all request completions: success, error, cancellation, empty response, or timeout.
|
46
|
+
status should be a RequestStatus value.
|
47
|
+
- For errors, fill error/exception/traceback fields.
|
48
|
+
- For cancellations, fill reason field.
|
49
|
+
- For empty response, fill error/details fields as appropriate.
|
50
|
+
- For timeout, fill error/details fields as appropriate.
|
51
|
+
"""
|
52
|
+
|
53
|
+
response: Any = None
|
54
|
+
status: RequestStatus = (
|
55
|
+
None # RequestStatus.SUCCESS, ERROR, CANCELLED, EMPTY_RESPONSE, TIMEOUT
|
56
|
+
)
|
57
|
+
usage: dict = None
|
58
|
+
finish_type: str = None # 'success', 'error', 'cancelled', etc. (legacy)
|
59
|
+
error: str = None
|
60
|
+
exception: Exception = None
|
61
|
+
traceback: str = None
|
62
|
+
reason: str = None # for cancellations or empty/timeout reasons
|
63
|
+
details: dict = None # for additional info (empty response, timeout, etc.)
|
64
|
+
|
65
|
+
|
66
|
+
@attr.s(auto_attribs=True, kw_only=True)
|
67
|
+
class ContentPartFound(DriverEvent):
|
68
|
+
content_part: Any = None
|
69
|
+
|
70
|
+
|
71
|
+
@attr.s(auto_attribs=True, kw_only=True)
|
72
|
+
class ToolCallStarted(DriverEvent):
|
73
|
+
tool_call_id: str = None
|
74
|
+
name: str = None
|
75
|
+
arguments: Any = None
|
76
|
+
|
77
|
+
@property
|
78
|
+
def tool_name(self):
|
79
|
+
return self.name
|
80
|
+
|
81
|
+
|
82
|
+
@attr.s(auto_attribs=True, kw_only=True)
|
83
|
+
class ToolCallFinished(DriverEvent):
|
84
|
+
tool_call_id: str = None
|
85
|
+
name: str = None
|
86
|
+
result: Any = None
|
87
|
+
|
88
|
+
@property
|
89
|
+
def tool_name(self):
|
90
|
+
return self.name
|
91
|
+
|
92
|
+
|
93
|
+
@attr.s(auto_attribs=True, kw_only=True)
|
94
|
+
class ResponseReceived(DriverEvent):
|
95
|
+
parts: list = None
|
96
|
+
tool_results: list = None # each as dict or custom ToolResult dataclass
|
97
|
+
timestamp: float = None # UNIX epoch seconds, normalized
|
98
|
+
metadata: dict = None
|
@@ -0,0 +1,113 @@
|
|
1
|
+
from janito.llm.driver import LLMDriver
|
2
|
+
from janito.llm.driver_config import LLMDriverConfig
|
3
|
+
from janito.driver_events import (
|
4
|
+
GenerationStarted,
|
5
|
+
GenerationFinished,
|
6
|
+
RequestStarted,
|
7
|
+
RequestFinished,
|
8
|
+
ResponseReceived,
|
9
|
+
)
|
10
|
+
from janito.llm.message_parts import TextMessagePart
|
11
|
+
import uuid
|
12
|
+
import traceback
|
13
|
+
import time
|
14
|
+
|
15
|
+
# Safe import of anthropic SDK
|
16
|
+
try:
|
17
|
+
import anthropic
|
18
|
+
|
19
|
+
DRIVER_AVAILABLE = True
|
20
|
+
DRIVER_UNAVAILABLE_REASON = None
|
21
|
+
except ImportError:
|
22
|
+
DRIVER_AVAILABLE = False
|
23
|
+
DRIVER_UNAVAILABLE_REASON = "Missing dependency: anthropic (pip install anthropic)"
|
24
|
+
|
25
|
+
|
26
|
+
class AnthropicModelDriver(LLMDriver):
|
27
|
+
available = False
|
28
|
+
unavailable_reason = "AnthropicModelDriver is not implemented yet."
|
29
|
+
|
30
|
+
@classmethod
|
31
|
+
def is_available(cls):
|
32
|
+
return cls.available
|
33
|
+
|
34
|
+
"""
|
35
|
+
LLMDriver for Anthropic's Claude API (v3), using the anthropic SDK.
|
36
|
+
"""
|
37
|
+
required_config = ["api_key", "model"]
|
38
|
+
|
39
|
+
def __init__(self, tools_adapter=None):
|
40
|
+
raise ImportError(self.unavailable_reason)
|
41
|
+
|
42
|
+
def _create_client(self):
|
43
|
+
try:
|
44
|
+
import anthropic
|
45
|
+
except ImportError:
|
46
|
+
raise Exception(
|
47
|
+
"The 'anthropic' Python SDK is required. Please install via `pip install anthropic`."
|
48
|
+
)
|
49
|
+
return anthropic.Anthropic(api_key=self.api_key)
|
50
|
+
|
51
|
+
def _run_generation(
|
52
|
+
self, messages_or_prompt, system_prompt=None, tools=None, **kwargs
|
53
|
+
):
|
54
|
+
request_id = str(uuid.uuid4())
|
55
|
+
client = self._create_client()
|
56
|
+
try:
|
57
|
+
prompt = ""
|
58
|
+
if isinstance(messages_or_prompt, str):
|
59
|
+
prompt = messages_or_prompt
|
60
|
+
elif isinstance(messages_or_prompt, list):
|
61
|
+
chat = []
|
62
|
+
for msg in messages_or_prompt:
|
63
|
+
if msg.get("role") == "user":
|
64
|
+
chat.append("Human: " + msg.get("content", ""))
|
65
|
+
elif msg.get("role") == "assistant":
|
66
|
+
chat.append("Assistant: " + msg.get("content", ""))
|
67
|
+
prompt = "\n".join(chat)
|
68
|
+
if system_prompt:
|
69
|
+
prompt = f"System: {system_prompt}\n{prompt}"
|
70
|
+
|
71
|
+
self.publish(
|
72
|
+
GenerationStarted,
|
73
|
+
request_id,
|
74
|
+
conversation_history=list(getattr(self, "_history", [])),
|
75
|
+
)
|
76
|
+
self.publish(RequestStarted, request_id, payload={})
|
77
|
+
start_time = time.time()
|
78
|
+
response = client.completions.create(
|
79
|
+
model=self.model_name,
|
80
|
+
max_tokens_to_sample=int(getattr(self.config, "max_response", 1024)),
|
81
|
+
prompt=prompt,
|
82
|
+
temperature=float(getattr(self.config, "default_temp", 0.7)),
|
83
|
+
)
|
84
|
+
duration = time.time() - start_time
|
85
|
+
content = response.completion if hasattr(response, "completion") else None
|
86
|
+
self.publish(
|
87
|
+
RequestFinished,
|
88
|
+
request_id,
|
89
|
+
response=content,
|
90
|
+
status=RequestStatus.SUCCESS,
|
91
|
+
usage={},
|
92
|
+
)
|
93
|
+
parts = []
|
94
|
+
if content:
|
95
|
+
parts.append(TextMessagePart(content=content))
|
96
|
+
self.publish(
|
97
|
+
ResponseReceived,
|
98
|
+
request_id=request_id,
|
99
|
+
parts=parts,
|
100
|
+
tool_results=[],
|
101
|
+
timestamp=time.time(),
|
102
|
+
metadata={"raw_response": response},
|
103
|
+
)
|
104
|
+
self.publish(GenerationFinished, request_id, total_turns=1)
|
105
|
+
except Exception as e:
|
106
|
+
self.publish(
|
107
|
+
RequestFinished,
|
108
|
+
request_id,
|
109
|
+
status=RequestStatus.ERROR,
|
110
|
+
error=str(e),
|
111
|
+
exception=e,
|
112
|
+
traceback=traceback.format_exc(),
|
113
|
+
)
|
@@ -0,0 +1,36 @@
|
|
1
|
+
from janito.drivers.openai.driver import OpenAIModelDriver
|
2
|
+
|
3
|
+
# Safe import of AzureOpenAI SDK
|
4
|
+
try:
|
5
|
+
from openai import AzureOpenAI
|
6
|
+
|
7
|
+
DRIVER_AVAILABLE = True
|
8
|
+
DRIVER_UNAVAILABLE_REASON = None
|
9
|
+
except ImportError:
|
10
|
+
DRIVER_AVAILABLE = False
|
11
|
+
DRIVER_UNAVAILABLE_REASON = "Missing dependency: openai (pip install openai)"
|
12
|
+
|
13
|
+
from janito.llm.driver_config import LLMDriverConfig
|
14
|
+
|
15
|
+
|
16
|
+
class AzureOpenAIModelDriver(OpenAIModelDriver):
|
17
|
+
available = DRIVER_AVAILABLE
|
18
|
+
unavailable_reason = DRIVER_UNAVAILABLE_REASON
|
19
|
+
|
20
|
+
@classmethod
|
21
|
+
def is_available(cls):
|
22
|
+
return cls.available
|
23
|
+
|
24
|
+
required_config = {"base_url"} # Update key as used in your config logic
|
25
|
+
|
26
|
+
def __init__(self, tools_adapter=None):
|
27
|
+
if not self.available:
|
28
|
+
raise ImportError(
|
29
|
+
f"AzureOpenAIModelDriver unavailable: {self.unavailable_reason}"
|
30
|
+
)
|
31
|
+
super().__init__(tools_adapter=tools_adapter)
|
32
|
+
self.azure_endpoint = None
|
33
|
+
self.api_version = None
|
34
|
+
self.api_key = None
|
35
|
+
|
36
|
+
# ... rest of the implementation ...
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# janito/drivers/driver_registry.py
|
2
|
+
"""
|
3
|
+
DriverRegistry: Maps driver string names to class objects for use by providers.
|
4
|
+
"""
|
5
|
+
|
6
|
+
from typing import Dict, Type
|
7
|
+
|
8
|
+
# --- Import driver classes ---
|
9
|
+
from janito.drivers.anthropic.driver import AnthropicModelDriver
|
10
|
+
from janito.drivers.azure_openai.driver import AzureOpenAIModelDriver
|
11
|
+
from janito.drivers.google_genai.driver import GoogleGenaiModelDriver
|
12
|
+
from janito.drivers.mistralai.driver import MistralAIModelDriver
|
13
|
+
from janito.drivers.openai.driver import OpenAIModelDriver
|
14
|
+
|
15
|
+
_DRIVER_REGISTRY: Dict[str, Type] = {
|
16
|
+
"AnthropicModelDriver": AnthropicModelDriver,
|
17
|
+
"AzureOpenAIModelDriver": AzureOpenAIModelDriver,
|
18
|
+
"GoogleGenaiModelDriver": GoogleGenaiModelDriver,
|
19
|
+
"MistralAIModelDriver": MistralAIModelDriver,
|
20
|
+
"OpenAIModelDriver": OpenAIModelDriver,
|
21
|
+
}
|
22
|
+
|
23
|
+
|
24
|
+
def get_driver_class(name: str):
|
25
|
+
"""Get the driver class by string name."""
|
26
|
+
try:
|
27
|
+
return _DRIVER_REGISTRY[name]
|
28
|
+
except KeyError:
|
29
|
+
raise ValueError(f"No driver found for name: {name}")
|
30
|
+
|
31
|
+
|
32
|
+
def register_driver(name: str, cls: type):
|
33
|
+
_DRIVER_REGISTRY[name] = cls
|