janito 2.29.0__py3-none-any.whl → 2.30.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/README.md CHANGED
@@ -116,11 +116,11 @@ Janito includes powerful built-in tools for:
116
116
  - System commands
117
117
  - And more...
118
118
 
119
- ### Profiles and Roles
119
+ ### Profiles
120
120
  Use predefined system prompts:
121
121
  ```bash
122
- janito --profile developer "Create a REST API"
123
- janito --role python-expert "Optimize this algorithm"
122
+ janito --developer "Create a REST API" # Same as --profile developer
123
+ janito --market "Analyze market trends" # Same as --profile market-analyst
124
124
  ```
125
125
 
126
126
  ### Environment Variables
@@ -25,10 +25,14 @@ def _load_template_content(profile, templates_dir):
25
25
 
26
26
  Spaces in the profile name are converted to underscores to align with the file-naming convention (e.g. "Developer with Python Tools" ➜ "Developer_with_Python_Tools" (matches: system_prompt_template_Developer_with_Python_Tools.txt.j2)).
27
27
  """
28
- # Sanitize profile for filename resolution (convert whitespace to underscores)
29
- sanitized_profile = re.sub(r"\s+", "_", profile.strip()) if profile else profile
30
-
28
+ sanitized_profile = re.sub(r"\s+", "_", profile.strip())
31
29
  template_filename = f"system_prompt_template_{sanitized_profile}.txt.j2"
30
+
31
+ return _find_template_file(template_filename, templates_dir)
32
+
33
+
34
+ def _find_template_file(template_filename, templates_dir):
35
+ """Find and load template file from various locations."""
32
36
  template_path = templates_dir / template_filename
33
37
 
34
38
  # 1) Check local templates directory
@@ -96,39 +100,9 @@ def _load_template_content(profile, templates_dir):
96
100
  )
97
101
 
98
102
  raise FileNotFoundError(error_msg)
99
- # Replace spaces in profile name with underscores for filename resolution
100
- sanitized_profile = re.sub(r"\\s+", "_", profile.strip()) if profile else profile
101
- """
102
- Loads the template content for the given profile from the specified directory or package resources.
103
- If the profile template is not found in the default locations, tries to load from the user profiles directory ~/.janito/profiles.
104
- """
105
-
106
- # Sanitize profile name by replacing spaces with underscores to match filename conventions
107
- sanitized_profile = re.sub(r"\\s+", "_", profile.strip())
108
- template_filename = f"system_prompt_template_{sanitized_profile}.txt.j2"
109
- template_path = templates_dir / template_filename
110
- if template_path.exists():
111
- with open(template_path, "r", encoding="utf-8") as file:
112
- return file.read(), template_path
113
- # Try package import fallback
114
- try:
115
- with importlib.resources.files("janito.agent.templates.profiles").joinpath(
116
- template_filename
117
- ).open("r", encoding="utf-8") as file:
118
- return file.read(), template_path
119
- except (FileNotFoundError, ModuleNotFoundError, AttributeError):
120
- # Try user profiles directory
121
- user_profiles_dir = Path(os.path.expanduser("~/.janito/profiles"))
122
- user_template_path = user_profiles_dir / profile
123
- if user_template_path.exists():
124
- with open(user_template_path, "r", encoding="utf-8") as file:
125
- return file.read(), user_template_path
126
- raise FileNotFoundError(
127
- f"[janito] Could not find profile-specific template '{template_filename}' in {template_path} nor in janito.agent.templates.profiles package nor in user profiles directory {user_template_path}."
128
- )
129
103
 
130
104
 
131
- def _prepare_template_context(role, profile, allowed_permissions):
105
+ def _prepare_template_context(role, profile, allowed_permissions, args=None):
132
106
  """
133
107
  Prepares the context dictionary for Jinja2 template rendering.
134
108
  """
@@ -148,6 +122,11 @@ def _prepare_template_context(role, profile, allowed_permissions):
148
122
  perm_str += "x"
149
123
  allowed_permissions = perm_str or None
150
124
  context["allowed_permissions"] = allowed_permissions
125
+
126
+ # Add emoji flag for system prompt
127
+ context["emoji_enabled"] = (
128
+ getattr(args, "emoji", False) if "args" in locals() else False
129
+ )
151
130
  # Inject platform info if execute permission is present
152
131
  if allowed_permissions and "x" in allowed_permissions:
153
132
  pd = PlatformDiscovery()
@@ -171,6 +150,11 @@ def _prepare_template_context(role, profile, allowed_permissions):
171
150
  else:
172
151
  context["allowed_sites_info"] = f"Restricted to: {', '.join(allowed_sites)}"
173
152
 
153
+ # Add emoji flag for system prompt
154
+ context["emoji_enabled"] = (
155
+ getattr(args, "emoji", False) if "args" in locals() else False
156
+ )
157
+
174
158
  return context
175
159
 
176
160
 
@@ -267,7 +251,9 @@ def setup_agent(
267
251
  template_content, template_path = _load_template_content(profile, templates_dir)
268
252
 
269
253
  template = Template(template_content)
270
- context = _prepare_template_context(role, profile, allowed_permissions)
254
+ context = _prepare_template_context(
255
+ role, profile, allowed_permissions, locals().get("args")
256
+ )
271
257
 
272
258
  # Debug output if requested
273
259
  debug_flag = False
@@ -88,4 +88,10 @@ You are: {{ role | default('developer') }}
88
88
  {# Trying to prevent surrogates generation, found this frequently in gpt4.1/windows #}
89
89
  - While writing code, if you need an emoji or special Unicode character in a string, then insert the actual character (e.g., 📖) directly instead of using surrogate pairs or escape sequences.
90
90
  {% endif %}
91
+ {% if emoji_enabled %}
92
+ ## Emoji Usage
93
+ - Feel free to use emojis in your responses to make them more engaging and expressive 🎉
94
+ - Use appropriate emojis to enhance communication: ✅ for success, ⚠️ for warnings, 🔄 for progress, etc.
95
+ - Emojis should complement the message, not replace clear explanations
96
+ {% endif %}
91
97
 
@@ -78,4 +78,10 @@ You are: {{ role | default('software developer') }}
78
78
  {# Trying to prevent surrogates generation, found this frequently in gpt4.1/windows #}
79
79
  - While writing code, if you need an emoji or special Unicode character in a string, then insert the actual character (e.g., 📖) directly instead of using surrogate pairs or escape sequences.
80
80
  {% endif %}
81
+ {% if emoji_enabled %}
82
+ ## Emoji Usage
83
+ - Feel free to use emojis in your responses to make them more engaging and expressive 🎉
84
+ - Use appropriate emojis to enhance communication: ✅ for success, ⚠️ for warnings, 🔄 for progress, etc.
85
+ - Emojis should complement the message, not replace clear explanations
86
+ {% endif %}
81
87
 
@@ -107,4 +107,10 @@ When asked about specific stocks or market movements, provide detailed analysis
107
107
  {% endfor %}
108
108
  {% endif %}
109
109
 
110
- ## Guidelines
110
+ ## Guidelines
111
+ {% if emoji_enabled %}
112
+ ## Emoji Usage
113
+ - Feel free to use emojis in your responses to make them more engaging and expressive 📊
114
+ - Use appropriate emojis to enhance communication: 📈 for uptrends, 📉 for downtrends, 💰 for profits, ⚠️ for warnings, etc.
115
+ - Emojis should complement the message, not replace clear financial analysis
116
+ {% endif %}
@@ -50,4 +50,10 @@ You are: {{ role | default('helpful assistant') }}
50
50
  ## Guidelines
51
51
  - Provide helpful, accurate, and concise responses
52
52
  - Ask clarifying questions when needed
53
- - Maintain a friendly and professional tone
53
+ - Maintain a friendly and professional tone
54
+ {% if emoji_enabled %}
55
+ ## Emoji Usage
56
+ - Feel free to use emojis in your responses to make them more engaging and expressive 😊
57
+ - Use appropriate emojis to enhance communication: ✅ for confirmations, ❓ for questions, 💡 for ideas, etc.
58
+ - Emojis should complement the message, not replace clear communication
59
+ {% endif %}
@@ -119,9 +119,7 @@ class ChatSession:
119
119
  profile_system_prompt = None
120
120
  no_tools_mode = False
121
121
 
122
- profile = self._determine_profile(
123
- profile, role_arg, python_profile, market_profile
124
- )
122
+ profile = self._determine_profile(profile, python_profile, market_profile)
125
123
 
126
124
  if (
127
125
  profile is None
@@ -136,31 +134,8 @@ class ChatSession:
136
134
  if skip_profile_selection:
137
135
  profile = "Developer with Python Tools" # Default for non-interactive commands
138
136
  else:
139
- try:
140
- from janito.cli.chat_mode.session_profile_select import (
141
- select_profile,
142
- )
143
-
144
- result = select_profile()
145
- if isinstance(result, dict):
146
- profile = result.get("profile")
147
- profile_system_prompt = result.get("profile_system_prompt")
148
- no_tools_mode = result.get("no_tools_mode", False)
149
- elif isinstance(result, str) and result.startswith("role:"):
150
- role = result[len("role:") :].strip()
151
- profile = "Developer with Python Tools"
152
- else:
153
- profile = (
154
- "Developer with Python Tools"
155
- if result == "Developer"
156
- else result
157
- )
158
- except ImportError:
159
- profile = "Raw Model Session (no tools, no context)"
160
- if role_arg is not None:
161
- role = role_arg
162
- if profile is None:
163
137
  profile = "Developer with Python Tools"
138
+
164
139
  return profile, role, profile_system_prompt, no_tools_mode
165
140
 
166
141
  def _create_conversation_history(self):
@@ -308,25 +283,6 @@ class ChatSession:
308
283
  )
309
284
  )
310
285
 
311
- for candidate in candidates:
312
- try:
313
- if not candidate:
314
- continue
315
- parsed = urlparse(str(candidate))
316
- host = parsed.netloc or parsed.path
317
- if host:
318
- backend_hostname = host
319
- break
320
- except Exception:
321
- backend_hostname = str(candidate)
322
- break
323
-
324
- self.console.print(
325
- Rule(
326
- f"[bold blue]Model: {model_name} ({provider_name}) | Backend: {backend_hostname}[/bold blue]"
327
- )
328
- )
329
-
330
286
  self._prompt_handler.run_prompt(cmd_input)
331
287
  end_time = time.time()
332
288
  elapsed = end_time - start_time
@@ -356,18 +312,18 @@ class ChatSession:
356
312
  def _extract_args(self, args):
357
313
  """Extract profile and role arguments from args."""
358
314
  profile = getattr(args, "profile", None) if args is not None else None
359
- role_arg = getattr(args, "role", None) if args is not None else None
315
+ role_arg = None
360
316
  python_profile = (
361
317
  getattr(args, "developer", False) if args is not None else False
362
318
  )
363
319
  market_profile = getattr(args, "market", False) if args is not None else False
364
320
  return profile, role_arg, python_profile, market_profile
365
321
 
366
- def _determine_profile(self, profile, role_arg, python_profile, market_profile):
322
+ def _determine_profile(self, profile, python_profile, market_profile):
367
323
  """Determine the profile based on flags and arguments."""
368
- if python_profile and profile is None and role_arg is None:
324
+ if python_profile and profile is None:
369
325
  return "Developer with Python Tools"
370
- if market_profile and profile is None and role_arg is None:
326
+ if market_profile and profile is None:
371
327
  return "Market Analyst"
372
328
  return profile
373
329
 
@@ -517,9 +473,19 @@ class ChatSession:
517
473
  else:
518
474
  duration_str = f"{session_duration/3600:.1f}h"
519
475
 
476
+ # Format tokens in k/m/t as appropriate
477
+ if total_tokens >= 1_000_000_000:
478
+ token_str = f"{total_tokens/1_000_000_000:.1f}t"
479
+ elif total_tokens >= 1_000_000:
480
+ token_str = f"{total_tokens/1_000_000:.1f}m"
481
+ elif total_tokens >= 1_000:
482
+ token_str = f"{total_tokens/1_000:.1f}k"
483
+ else:
484
+ token_str = f"{total_tokens}"
485
+
520
486
  self.console.print(f"[bold yellow]Session completed![/bold yellow]")
521
487
  self.console.print(
522
- f"[dim]Session time: {duration_str} | Total tokens: {total_tokens:,}[/dim]"
488
+ f"[dim]Session time: {duration_str} | Total tokens: {token_str}[/dim]"
523
489
  )
524
490
  self.console.print("[bold yellow]Goodbye![/bold yellow]")
525
491
 
@@ -14,80 +14,104 @@ def handle_list_plugins(args: argparse.Namespace) -> None:
14
14
  """List plugins command handler."""
15
15
 
16
16
  if getattr(args, "list_plugins_available", False):
17
- # List available plugins
18
- available = list_available_plugins()
19
- builtin_plugins = BuiltinPluginRegistry.list_builtin_plugins()
20
-
21
- if available or builtin_plugins:
22
- print("Available plugins:")
23
-
24
- # Show builtin plugins first
25
- if builtin_plugins:
26
- print(" Builtin plugins:")
27
- for plugin in builtin_plugins:
28
- print(f" - {plugin} [BUILTIN]")
29
-
30
- # Show other available plugins
31
- other_plugins = [p for p in available if p not in builtin_plugins]
32
- if other_plugins:
33
- print(" External plugins:")
34
- for plugin in other_plugins:
35
- print(f" - {plugin}")
36
- else:
37
- print("No plugins found in search paths")
38
- print("Search paths:")
39
- print(f" - {os.getcwd()}/plugins")
40
- print(f" - {os.path.expanduser('~')}/.janito/plugins")
17
+ _list_available_plugins()
41
18
  elif getattr(args, "list_resources", False):
42
- # List all resources from loaded plugins
43
- manager = PluginManager()
44
- all_resources = manager.list_all_resources()
45
-
46
- if all_resources:
47
- print("Plugin Resources:")
48
- for plugin_name, resources in all_resources.items():
49
- metadata = manager.get_plugin_metadata(plugin_name)
50
- print(
51
- f"\n{plugin_name} v{metadata.version if metadata else 'unknown'}:"
52
- )
53
-
54
- # Group resources by type
55
- tools = [r for r in resources if r["type"] == "tool"]
56
- commands = [r for r in resources if r["type"] == "command"]
57
- configs = [r for r in resources if r["type"] == "config"]
58
-
59
- if tools:
60
- print(" Tools:")
61
- for tool in tools:
62
- print(f" - {tool['name']}: {tool['description']}")
63
-
64
- if commands:
65
- print(" Commands:")
66
- for cmd in commands:
67
- print(f" - {cmd['name']}: {cmd['description']}")
68
-
69
- if configs:
70
- print(" Configuration:")
71
- for config in configs:
72
- print(f" - {config['name']}: {config['description']}")
73
- else:
74
- print("No plugins loaded")
19
+ _list_plugin_resources()
75
20
  else:
76
- # List loaded plugins
77
- manager = PluginManager()
78
- loaded = manager.list_plugins()
79
-
80
- if loaded:
81
- print("Loaded plugins:")
82
- for plugin_name in loaded:
83
- metadata = manager.get_plugin_metadata(plugin_name)
84
- is_builtin = BuiltinPluginRegistry.is_builtin(plugin_name)
85
- if metadata:
86
- builtin_tag = " [BUILTIN]" if is_builtin else ""
87
- print(f" - {metadata.name} v{metadata.version}{builtin_tag}")
88
- print(f" {metadata.description}")
89
- if metadata.author:
90
- print(f" Author: {metadata.author}")
91
- print()
92
- else:
93
- print("No plugins loaded")
21
+ _list_loaded_plugins()
22
+
23
+
24
+ def _list_available_plugins():
25
+ """List available plugins."""
26
+ available = list_available_plugins()
27
+ builtin_plugins = BuiltinPluginRegistry.list_builtin_plugins()
28
+
29
+ if available or builtin_plugins:
30
+ print("Available plugins:")
31
+ _print_builtin_plugins(builtin_plugins)
32
+ _print_external_plugins(available, builtin_plugins)
33
+ else:
34
+ print("No plugins found in search paths")
35
+ print("Search paths:")
36
+ print(f" - {os.getcwd()}/plugins")
37
+ print(f" - {os.path.expanduser('~')}/.janito/plugins")
38
+
39
+
40
+ def _print_builtin_plugins(builtin_plugins):
41
+ """Print builtin plugins."""
42
+ if builtin_plugins:
43
+ print(" Builtin plugins:")
44
+ for plugin in builtin_plugins:
45
+ print(f" - {plugin} [BUILTIN]")
46
+
47
+
48
+ def _print_external_plugins(available, builtin_plugins):
49
+ """Print external plugins."""
50
+ other_plugins = [p for p in available if p not in builtin_plugins]
51
+ if other_plugins:
52
+ print(" External plugins:")
53
+ for plugin in other_plugins:
54
+ print(f" - {plugin}")
55
+
56
+
57
+ def _list_plugin_resources():
58
+ """List all resources from loaded plugins."""
59
+ manager = PluginManager()
60
+ all_resources = manager.list_all_resources()
61
+
62
+ if all_resources:
63
+ print("Plugin Resources:")
64
+ for plugin_name, resources in all_resources.items():
65
+ metadata = manager.get_plugin_metadata(plugin_name)
66
+ print(f"\n{plugin_name} v{metadata.version if metadata else 'unknown'}:")
67
+ _print_resources_by_type(resources)
68
+ else:
69
+ print("No plugins loaded")
70
+
71
+
72
+ def _print_resources_by_type(resources):
73
+ """Print resources grouped by type."""
74
+ tools = [r for r in resources if r["type"] == "tool"]
75
+ commands = [r for r in resources if r["type"] == "command"]
76
+ configs = [r for r in resources if r["type"] == "config"]
77
+
78
+ if tools:
79
+ print(" Tools:")
80
+ for tool in tools:
81
+ print(f" - {tool['name']}: {tool['description']}")
82
+
83
+ if commands:
84
+ print(" Commands:")
85
+ for cmd in commands:
86
+ print(f" - {cmd['name']}: {cmd['description']}")
87
+
88
+ if configs:
89
+ print(" Configuration:")
90
+ for config in configs:
91
+ print(f" - {config['name']}: {config['description']}")
92
+
93
+
94
+ def _list_loaded_plugins():
95
+ """List loaded plugins."""
96
+ manager = PluginManager()
97
+ loaded = manager.list_plugins()
98
+
99
+ if loaded:
100
+ print("Loaded plugins:")
101
+ for plugin_name in loaded:
102
+ _print_plugin_details(manager, plugin_name)
103
+ else:
104
+ print("No plugins loaded")
105
+
106
+
107
+ def _print_plugin_details(manager, plugin_name):
108
+ """Print details for a loaded plugin."""
109
+ metadata = manager.get_plugin_metadata(plugin_name)
110
+ is_builtin = BuiltinPluginRegistry.is_builtin(plugin_name)
111
+ if metadata:
112
+ builtin_tag = " [BUILTIN]" if is_builtin else ""
113
+ print(f" - {metadata.name} v{metadata.version}{builtin_tag}")
114
+ print(f" {metadata.description}")
115
+ if metadata.author:
116
+ print(f" Author: {metadata.author}")
117
+ print()
@@ -35,6 +35,7 @@ def _prepare_context(args, agent_role, allowed_permissions):
35
35
  context["role"] = agent_role or "developer"
36
36
  context["profile"] = getattr(args, "profile", None)
37
37
  context["allowed_permissions"] = allowed_permissions
38
+ context["emoji_enabled"] = getattr(args, "emoji", False)
38
39
  if allowed_permissions and "x" in allowed_permissions:
39
40
  pd = PlatformDiscovery()
40
41
  context["platform"] = pd.get_platform_name()
@@ -122,6 +123,10 @@ def handle_show_system_prompt(args):
122
123
  if profile is None and getattr(args, "market", False):
123
124
  profile = "Market Analyst"
124
125
 
126
+ # Handle --developer flag mapping to Developer With Python Tools profile
127
+ if profile is None and getattr(args, "developer", False):
128
+ profile = "Developer With Python Tools"
129
+
125
130
  if not profile:
126
131
  print(
127
132
  "[janito] No profile specified. The main agent runs without a system prompt template.\n"
@@ -152,9 +157,9 @@ def handle_show_system_prompt(args):
152
157
  system_prompt = template.render(**context)
153
158
  system_prompt = re.sub(r"\n{3,}", "\n\n", system_prompt)
154
159
 
155
- print(
156
- f"\n--- System Prompt (resolved, profile: {getattr(args, 'profile', 'main')}) ---\n"
157
- )
160
+ # Use the actual profile name for display, not the resolved value
161
+ display_profile = profile or "main"
162
+ print(f"\n--- System Prompt (resolved, profile: {display_profile}) ---\n")
158
163
  print(system_prompt)
159
164
  print("\n-------------------------------\n")
160
165
  if agent_role:
janito/cli/core/runner.py CHANGED
@@ -109,8 +109,8 @@ def prepare_llm_driver_config(args, modifiers):
109
109
  llm_driver_config = LLMDriverConfig(**driver_config_data)
110
110
  if getattr(llm_driver_config, "verbose_api", None):
111
111
  pass
112
- # If both --role and --profile are provided, --role takes precedence for agent_role
113
- agent_role = modifiers.get("role") or modifiers.get("profile") or "developer"
112
+
113
+ agent_role = modifiers.get("profile") or "developer"
114
114
  return provider, llm_driver_config, agent_role
115
115
 
116
116
 
janito/cli/main_cli.py CHANGED
@@ -51,14 +51,6 @@ definition = [
51
51
  "help": "Start with the Market Analyst profile (equivalent to --profile 'Market Analyst')",
52
52
  },
53
53
  ),
54
- (
55
- ["--role"],
56
- {
57
- "metavar": "ROLE",
58
- "help": "Select the developer role name (overrides profile, e.g. 'python-expert').",
59
- "default": None,
60
- },
61
- ),
62
54
  (
63
55
  ["-W", "--workdir"],
64
56
  {
@@ -205,6 +197,13 @@ definition = [
205
197
  "help": "Set the reasoning effort for models that support it (low, medium, high, none)",
206
198
  },
207
199
  ),
200
+ (
201
+ ["--emoji"],
202
+ {
203
+ "action": "store_true",
204
+ "help": "Enable emoji usage in responses to make output more engaging and expressive",
205
+ },
206
+ ),
208
207
  (["user_prompt"], {"nargs": argparse.REMAINDER, "help": "Prompt to submit"}),
209
208
  (
210
209
  ["-e", "--event-log"],
@@ -244,7 +243,6 @@ definition = [
244
243
  MODIFIER_KEYS = [
245
244
  "provider",
246
245
  "model",
247
- "role",
248
246
  "profile",
249
247
  "developer",
250
248
  "market",
@@ -257,6 +255,7 @@ MODIFIER_KEYS = [
257
255
  "exec",
258
256
  "read",
259
257
  "write",
258
+ "emoji",
260
259
  ]
261
260
  SETTER_KEYS = ["set", "set_provider", "set_api_key", "unset"]
262
261
  GETTER_KEYS = [
@@ -372,9 +371,7 @@ class JanitoCLI:
372
371
  for k in MODIFIER_KEYS
373
372
  if getattr(self.args, k, None) is not None
374
373
  }
375
- # If --role is provided, override role in modifiers
376
- if getattr(self.args, "role", None):
377
- modifiers["role"] = getattr(self.args, "role")
374
+
378
375
  return modifiers
379
376
 
380
377
  def classify(self):
@@ -420,9 +417,6 @@ class JanitoCLI:
420
417
  self.args.exec = True
421
418
  # Remove the /rwx prefix from the prompt
422
419
  self.args.user_prompt = self.args.user_prompt[1:]
423
- elif self.args.user_prompt and self.args.user_prompt[0].startswith("/"):
424
- # Skip LLM processing for other commands that start with /
425
- return
426
420
 
427
421
  # If running in single shot mode and --profile is not provided, default to 'developer' profile
428
422
  # Skip profile selection for list commands that don't need it
janito/llm/agent.py CHANGED
@@ -244,7 +244,12 @@ class LLMAgent:
244
244
  f"[agent] [DEBUG] Tool call detected: {getattr(part, 'name', repr(part))} with arguments: {getattr(part, 'arguments', None)}"
245
245
  )
246
246
  tool_calls.append(part)
247
- result = self.tools_adapter.execute_function_call_message_part(part)
247
+ try:
248
+ result = self.tools_adapter.execute_function_call_message_part(part)
249
+ except Exception as e:
250
+ # Catch any exception during tool execution and return as string
251
+ # instead of letting it propagate to the user
252
+ result = str(e)
248
253
  tool_results.append(result)
249
254
  if tool_calls:
250
255
  # Prepare tool_calls message for assistant
@@ -5,6 +5,7 @@ from janito.tools.loop_protection_decorator import protect_against_loops
5
5
  from rich import print as rich_print
6
6
  from janito.i18n import tr
7
7
  from rich.panel import Panel
8
+ from rich.markdown import Markdown
8
9
  from prompt_toolkit import PromptSession
9
10
  from prompt_toolkit.key_binding import KeyBindings
10
11
  from prompt_toolkit.enums import EditingMode
@@ -36,7 +37,7 @@ class AskUserTool(ToolBase):
36
37
  def run(self, question: str) -> str:
37
38
 
38
39
  print() # Print an empty line before the question panel
39
- rich_print(Panel.fit(question, title=tr("Question"), style="cyan"))
40
+ rich_print(Panel.fit(Markdown(question), title=tr("Question"), style="cyan"))
40
41
 
41
42
  bindings = KeyBindings()
42
43
  mode = {"multiline": False}
@@ -107,4 +108,5 @@ class AskUserTool(ToolBase):
107
108
  rich_print(
108
109
  "[yellow]Warning: Some characters in your input were not valid UTF-8 and have been replaced.[/yellow]"
109
110
  )
111
+ print("\a", end="", flush=True) # Print bell character
110
112
  return sanitized