janito 0.10.0__py3-none-any.whl → 0.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/__init__.py CHANGED
@@ -2,4 +2,4 @@
2
2
  Janito package.
3
3
  """
4
4
 
5
- __version__ = "0.10.0"
5
+ __version__ = "0.11.0"
janito/__main__.py CHANGED
@@ -1,151 +1,205 @@
1
- """
2
- Main entry point for the janito CLI.
3
- """
4
-
5
- import typer
6
- import os
7
- import sys
8
- from pathlib import Path
9
- from typing import Optional, Dict, Any, List
10
- from rich.console import Console
11
- from rich import print as rprint
12
- from rich.markdown import Markdown
13
- import claudine
14
- from claudine.exceptions import MaxTokensExceededException, MaxRoundsExceededException
15
- import locale
16
-
17
- # Fix console encoding for Windows
18
- if sys.platform == 'win32':
19
- # Try to set UTF-8 mode for Windows 10 version 1903 or newer
20
- os.system('chcp 65001 > NUL')
21
- # Ensure stdout and stderr are using UTF-8
22
- sys.stdout.reconfigure(encoding='utf-8')
23
- sys.stderr.reconfigure(encoding='utf-8')
24
- # Set locale to UTF-8
25
- locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
26
-
27
- from janito.tools.str_replace_editor.editor import str_replace_editor
28
- from janito.tools.find_files import find_files
29
- from janito.tools.delete_file import delete_file
30
- from janito.tools.search_text import search_text
31
- from janito.config import get_config
32
- from janito.callbacks import pre_tool_callback, post_tool_callback
33
- from janito.token_report import generate_token_report
34
-
35
- app = typer.Typer(help="Janito CLI tool")
36
-
37
- @app.command()
38
- def hello(name: str = typer.Argument("World", help="Name to greet")):
39
- """
40
- Say hello to someone.
41
- """
42
- rprint(f"[bold green]Hello {name}[/bold green]")
43
-
44
-
45
-
46
- @app.callback(invoke_without_command=True)
47
- def main(ctx: typer.Context,
48
- query: Optional[str] = typer.Argument(None, help="Query to send to the claudine agent"),
49
- debug: bool = typer.Option(False, "--debug", "-d", help="Enable debug mode"),
50
- verbose: bool = typer.Option(False, "--verbose", "-v", help="Show detailed token usage and pricing information"),
51
- workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Set the workspace directory")):
52
- """
53
- Janito CLI tool. If a query is provided without a command, it will be sent to the claudine agent.
54
- """
55
- console = Console()
56
-
57
- # Set debug mode in config
58
- get_config().debug_mode = debug
59
-
60
- if workspace:
61
- try:
62
- print(f"Setting workspace directory to: {workspace}")
63
- get_config().workspace_dir = workspace
64
- print(f"Workspace directory set to: {get_config().workspace_dir}")
65
- except ValueError as e:
66
- console.print(f"[bold red]Error:[/bold red] {str(e)}")
67
- sys.exit(1)
68
-
69
- if ctx.invoked_subcommand is None:
70
- # If no query provided in command line, read from stdin
71
- if not query:
72
- console.print("[bold blue]No query provided in command line. Reading from stdin...[/bold blue]")
73
- query = sys.stdin.read().strip()
74
-
75
- # Only proceed if we have a query (either from command line or stdin)
76
- if query:
77
- # Get API key from environment variable or ask the user
78
- api_key = os.environ.get("ANTHROPIC_API_KEY")
79
- if not api_key:
80
- console.print("[bold yellow]Warning:[/bold yellow] ANTHROPIC_API_KEY environment variable not set.")
81
- console.print("Please set it or provide your API key now:")
82
- api_key = typer.prompt("Anthropic API Key", hide_input=True)
83
-
84
- # Load instructions from file
85
- import importlib.resources as pkg_resources
86
- try:
87
- # For Python 3.9+
88
- try:
89
- from importlib.resources import files
90
- instructions = files('janito.data').joinpath('instructions.txt').read_text()
91
- # Fallback for older Python versions
92
- except (ImportError, AttributeError):
93
- instructions = pkg_resources.read_text('janito.data', 'instructions.txt')
94
- instructions = instructions.strip()
95
- except Exception as e:
96
- console.print(f"[bold yellow]Warning:[/bold yellow] Could not load instructions file: {str(e)}")
97
- console.print("[dim]Using default instructions instead.[/dim]")
98
- instructions = "You are a helpful AI assistant. Answer the user's questions to the best of your ability."
99
-
100
- # Initialize the agent with the tools
101
- agent = claudine.Agent(
102
- api_key=api_key,
103
- tools=[
104
- delete_file,
105
- find_files,
106
- search_text
107
- ],
108
- text_editor_tool=str_replace_editor,
109
- tool_callbacks=(pre_tool_callback, post_tool_callback),
110
- max_tokens=4096,
111
- temperature=0.7,
112
- instructions=instructions,
113
- debug_mode=debug # Enable debug mode
114
- )
115
-
116
- # Process the query
117
- console.print(f"[bold blue]Query:[/bold blue] {query}")
118
- console.print("[bold blue]Generating response...[/bold blue]")
119
-
120
- try:
121
- response = agent.process_prompt(query)
122
-
123
- console.print("\n[bold green]Janito:[/bold green]")
124
- # Use rich's enhanced Markdown rendering for the response
125
- console.print(Markdown(response, code_theme="monokai"))
126
-
127
- except MaxTokensExceededException as e:
128
- # Display the partial response if available
129
- if e.response_text:
130
- console.print("\n[bold green]Partial Janito:[/bold green]")
131
- console.print(Markdown(e.response_text, code_theme="monokai"))
132
-
133
- console.print("\n[bold red]Error:[/bold red] Response was truncated because it reached the maximum token limit.")
134
- console.print("[dim]Consider increasing the max_tokens parameter or simplifying your query.[/dim]")
135
-
136
- except MaxRoundsExceededException as e:
137
- # Display the final response if available
138
- if e.response_text:
139
- console.print("\n[bold green]Janito:[/bold green]")
140
- console.print(Markdown(e.response_text, code_theme="monokai"))
141
-
142
- console.print(f"\n[bold red]Error:[/bold red] Maximum number of tool execution rounds ({e.rounds}) reached. Some tasks may be incomplete.")
143
- console.print("[dim]Consider increasing the max_rounds parameter or breaking down your task into smaller steps.[/dim]")
144
-
145
- # Show token usage report
146
- generate_token_report(agent, verbose)
147
- else:
148
- console.print("[bold yellow]No query provided. Exiting.[/bold yellow]")
149
-
150
- if __name__ == "__main__":
151
- app()
1
+ """
2
+ Main entry point for Janito.
3
+ """
4
+ import os
5
+ import sys
6
+ from typing import Optional
7
+ import importlib.resources
8
+ import typer
9
+ from rich.console import Console
10
+ import anthropic
11
+ from janito.config import get_config
12
+ from janito.chat_history import store_conversation, get_chat_history_context
13
+ from janito.callbacks import pre_tool_callback, post_tool_callback, text_callback
14
+ from janito.token_report import generate_token_report
15
+ from janito.tools import str_replace_editor
16
+ from janito.tools.bash import bash_tool
17
+ import claudine
18
+
19
+ app = typer.Typer()
20
+
21
+ @app.command()
22
+ def create_tool(name: str = typer.Argument(..., help="Name of the tool to create")):
23
+ """
24
+ Create a new tool with the given name.
25
+ """
26
+ console = Console()
27
+
28
+ # Ensure workspace is set
29
+ workspace_dir = get_config().workspace_dir
30
+
31
+ # Create the tools directory if it doesn't exist
32
+ tools_dir = os.path.join(workspace_dir, "tools")
33
+ os.makedirs(tools_dir, exist_ok=True)
34
+
35
+ # Create the tool file
36
+ tool_file = os.path.join(tools_dir, f"{name}.py")
37
+
38
+ # Check if the file already exists
39
+ if os.path.exists(tool_file):
40
+ console.print(f"[bold red]Error:[/bold red] Tool file already exists: {tool_file}")
41
+ return
42
+
43
+ # Create the tool file with a template
44
+ template = f'''"""
45
+ {name} tool for Janito.
46
+ """
47
+
48
+ def {name}(param1: str) -> str:
49
+ """
50
+ Description of the {name} tool.
51
+
52
+ Args:
53
+ param1: Description of param1
54
+
55
+ Returns:
56
+ str: Description of return value
57
+ """
58
+ # TODO: Implement the tool
59
+ return f"Executed {name} with param1={{param1}}"
60
+ '''
61
+
62
+ with open(tool_file, "w") as f:
63
+ f.write(template)
64
+
65
+ console.print(f"[bold green]Created tool:[/bold green] {tool_file}")
66
+
67
+ @app.callback(invoke_without_command=True)
68
+ def main(ctx: typer.Context,
69
+ query: Optional[str] = typer.Argument(None, help="Query to send to the claudine agent"),
70
+ verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose mode with detailed output"),
71
+ show_tokens: bool = typer.Option(False, "--show-tokens", "-t", help="Show detailed token usage and pricing information"),
72
+ workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Set the workspace directory"),
73
+ config_str: Optional[str] = typer.Option(None, "--set-config", help="Configuration string in format 'key=value', e.g., 'context=5' for number of history messages to include"),
74
+ show_config: bool = typer.Option(False, "--show-config", help="Show current configuration")):
75
+ """
76
+ Janito CLI tool. If a query is provided without a command, it will be sent to the claudine agent.
77
+ """
78
+ console = Console()
79
+
80
+ # Set verbose mode in config
81
+ get_config().verbose = verbose
82
+
83
+ if workspace:
84
+ try:
85
+ print(f"Setting workspace directory to: {workspace}")
86
+ get_config().workspace_dir = workspace
87
+ print(f"Workspace directory set to: {get_config().workspace_dir}")
88
+ except ValueError as e:
89
+ console.print(f"[bold red]Error:[/bold red] {str(e)}")
90
+ sys.exit(1)
91
+
92
+ # Show current configuration if requested
93
+ if show_config:
94
+ config = get_config()
95
+ console.print("[bold blue]Current Configuration:[/bold blue]")
96
+ console.print(f"[bold]Workspace Directory:[/bold] {config.workspace_dir}")
97
+ console.print(f"[bold]Verbose Mode:[/bold] {'Enabled' if config.verbose else 'Disabled'}")
98
+ console.print(f"[bold]Chat History Context Count:[/bold] {config.history_context_count} messages")
99
+ # Exit if this was the only operation requested
100
+ if ctx.invoked_subcommand is None and not query:
101
+ sys.exit(0)
102
+
103
+ # Handle the --set-config parameter
104
+ if config_str is not None:
105
+ try:
106
+ # Parse the config string
107
+ if "context=" in config_str:
108
+ context_value = config_str.split("context=")[1].strip()
109
+ # If there are other configs after context, extract just the number
110
+ if " " in context_value:
111
+ context_value = context_value.split(" ")[0]
112
+
113
+ try:
114
+ context_value = int(context_value)
115
+ if context_value < 0:
116
+ console.print("[bold red]Error:[/bold red] History context count must be a non-negative integer")
117
+ return
118
+
119
+ get_config().history_context_count = context_value
120
+ console.print(f"[bold green]Chat history context count set to {context_value} messages[/bold green]")
121
+ except ValueError:
122
+ console.print(f"[bold red]Error:[/bold red] Invalid context value: {context_value}. Must be an integer.")
123
+ else:
124
+ console.print(f"[bold yellow]Warning:[/bold yellow] Unsupported configuration in: {config_str}")
125
+ except Exception as e:
126
+ console.print(f"[bold red]Error:[/bold red] {str(e)}")
127
+
128
+ if ctx.invoked_subcommand is None:
129
+ # If no query provided in command line, read from stdin
130
+ if not query:
131
+ console.print("[bold blue]No query provided in command line. Reading from stdin...[/bold blue]")
132
+ query = sys.stdin.read().strip()
133
+
134
+ # Only proceed if we have a query (either from command line or stdin)
135
+ if query:
136
+ # Get API key from environment variable or ask the user
137
+ api_key = os.environ.get("ANTHROPIC_API_KEY")
138
+ if not api_key:
139
+ console.print("[bold yellow]Warning:[/bold yellow] ANTHROPIC_API_KEY environment variable not set.")
140
+ console.print("Please set it or provide your API key now:")
141
+ api_key = typer.prompt("Anthropic API Key", hide_input=True)
142
+
143
+ # Load instructions from file
144
+ import importlib.resources as pkg_resources
145
+ try:
146
+ # For Python 3.9+
147
+ try:
148
+ from importlib.resources import files
149
+ instructions = files('janito.data').joinpath('instructions.txt').read_text(encoding='utf-8')
150
+ # Fallback for older Python versions
151
+ except (ImportError, AttributeError):
152
+ instructions = pkg_resources.read_text('janito.data', 'instructions.txt', encoding='utf-8')
153
+ except Exception as e:
154
+ console.print(f"[bold red]Error loading instructions:[/bold red] {str(e)}")
155
+ instructions = "You are Janito, an AI assistant."
156
+
157
+ # Temporarily disable chat history
158
+ # Get chat history context
159
+ # chat_history = get_chat_history_context(get_config().history_context_count)
160
+ # if chat_history:
161
+ # console.print("[dim]Loaded chat history from previous sessions.[/dim]")
162
+ # # Append chat history to instructions
163
+ # instructions = f"{instructions}\n\n{chat_history}"
164
+
165
+ # Get tools
166
+ from janito.tools import get_tools
167
+ tools_list = get_tools()
168
+
169
+ # Initialize the agent with the tools
170
+ agent = claudine.Agent(
171
+ api_key=api_key,
172
+ system_prompt=instructions,
173
+ callbacks={"pre_tool": pre_tool_callback, "post_tool": post_tool_callback, "text": text_callback},
174
+ text_editor_tool=str_replace_editor,
175
+ #bash_tool=bash_tool,
176
+ tools=tools_list,
177
+ verbose=verbose
178
+ )
179
+
180
+ # Send the query to the agent
181
+ try:
182
+ agent.query(query)
183
+
184
+ # Temporarily disable storing conversation in chat history
185
+ # Store the conversation in chat history
186
+ # store_conversation(query, response, agent)
187
+
188
+ # Print token usage report if show_tokens mode is enabled
189
+ if show_tokens:
190
+ generate_token_report(agent, verbose=True)
191
+ else:
192
+ # Show basic token usage
193
+ generate_token_report(agent, verbose=False)
194
+
195
+ except anthropic.APIError as e:
196
+ console.print(f"[bold red]Anthropic API Error:[/bold red] {str(e)}")
197
+
198
+ except Exception as e:
199
+ console.print(f"[bold red]Error:[/bold red] {str(e)}")
200
+ if verbose:
201
+ import traceback
202
+ console.print(traceback.format_exc())
203
+
204
+ if __name__ == "__main__":
205
+ app()
janito/callbacks.py CHANGED
@@ -1,130 +1,132 @@
1
- """
2
- Callback functions for tool execution in janito.
3
- """
4
-
5
- from typing import Dict, Any, Tuple
6
- from rich.console import Console
7
- from rich.markdown import Markdown
8
-
9
- from janito.config import get_config
10
- from janito.tools import find_files
11
- from janito.tools.str_replace_editor.editor import str_replace_editor
12
- from janito.tools.delete_file import delete_file
13
- from janito.tools.search_text import search_text
14
- from janito.tools.decorators import format_tool_label
15
-
16
- def pre_tool_callback(tool_name: str, tool_input: Dict[str, Any], preamble_text: str = "") -> Tuple[Dict[str, Any], bool]:
17
- """
18
- Callback function that runs before a tool is executed.
19
-
20
- Args:
21
- tool_name: Name of the tool being called
22
- tool_input: Input parameters for the tool
23
- preamble_text: Any text generated before the tool call
24
-
25
- Returns:
26
- Tuple of (modified tool input, whether to cancel the tool call)
27
- """
28
- console = Console()
29
-
30
- # Add debug counter only when debug mode is enabled
31
- if get_config().debug_mode:
32
- if not hasattr(pre_tool_callback, "counter"):
33
- pre_tool_callback.counter = 1
34
- console.print(f"[bold yellow]DEBUG: Starting tool call #{pre_tool_callback.counter}[/bold yellow]")
35
- pre_tool_callback.counter += 1
36
-
37
- # Print preamble text with enhanced markdown support if provided
38
- if preamble_text:
39
- # Use a single print statement to avoid extra newlines
40
- console.print("[bold magenta]Janito:[/bold magenta] ", Markdown(preamble_text, code_theme="monokai"), end="")
41
-
42
- # Try to find the tool function
43
- tool_func = None
44
- for tool in [find_files, str_replace_editor, delete_file, search_text]:
45
- if tool.__name__ == tool_name:
46
- tool_func = tool
47
- break
48
-
49
- # Create a copy of tool_input to modify for display
50
- display_input = {}
51
-
52
- # Maximum length for string values
53
- max_length = 50
54
-
55
- # Trim long string values for display
56
- for key, value in tool_input.items():
57
- if isinstance(value, str) and len(value) > max_length:
58
- # For long strings, show first and last part with ellipsis in between
59
- display_input[key] = f"{value[:20]}...{value[-20:]}" if len(value) > 45 else value[:max_length] + "..."
60
- else:
61
- display_input[key] = value
62
-
63
- # If we found the tool and it has a tool_meta label, use that for display
64
- if tool_func:
65
- formatted_label = format_tool_label(tool_func, tool_input)
66
- if formatted_label:
67
- console.print("[bold cyan] Tool:[/bold cyan]", formatted_label, end=" ")
68
- else:
69
- console.print("[bold cyan] Tool:[/bold cyan]", f"{tool_name} {display_input}", end=" → ")
70
-
71
- return tool_input, True # Continue with the tool call
72
-
73
- def post_tool_callback(tool_name: str, tool_input: Dict[str, Any], result: Any) -> Any:
74
- """
75
- Callback function that runs after a tool is executed.
76
-
77
- Args:
78
- tool_name: Name of the tool that was called
79
- tool_input: Input parameters for the tool
80
- result: Result of the tool call
81
-
82
- Returns:
83
- Modified result
84
- """
85
- console = Console()
86
-
87
- # Add debug counter only when debug mode is enabled
88
- if get_config().debug_mode:
89
- if not hasattr(post_tool_callback, "counter"):
90
- post_tool_callback.counter = 1
91
- console.print(f"[bold green]DEBUG: Completed tool call #{post_tool_callback.counter}[/bold green]")
92
- post_tool_callback.counter += 1
93
-
94
- # Extract the last line of the result
95
- if isinstance(result, tuple) and len(result) >= 1:
96
- content, is_error = result
97
- # Define prefix icon based on is_error
98
- icon_prefix = "❌ " if is_error else "✅ "
99
-
100
- if isinstance(content, str):
101
- # For find_files, extract just the count from the last line
102
- if tool_name == "find_files" and content.count("\n") > 0:
103
- lines = content.strip().split('\n')
104
- if lines and lines[-1].isdigit():
105
- console.print(f"{icon_prefix}{lines[-1]}")
106
- else:
107
- # Get the last line
108
- last_line = content.strip().split('\n')[-1]
109
- console.print(f"{icon_prefix}{last_line}")
110
- else:
111
- # For other tools, just get the last line
112
- if '\n' in content:
113
- last_line = content.strip().split('\n')[-1]
114
- console.print(f"{icon_prefix}{last_line}")
115
- else:
116
- console.print(f"{icon_prefix}{content}")
117
- else:
118
- console.print(f"{icon_prefix}{content}")
119
- else:
120
- # If result is not a tuple, convert to string and get the last line
121
- result_str = str(result)
122
- # Default to success icon when no error status is available
123
- icon_prefix = "✅ "
124
- if '\n' in result_str:
125
- last_line = result_str.strip().split('\n')[-1]
126
- console.print(f"{icon_prefix}{last_line}")
127
- else:
128
- console.print(f"{icon_prefix}{result_str}")
129
-
130
- return result
1
+ """
2
+ Callback functions for tool execution in janito.
3
+ """
4
+
5
+ from typing import Dict, Any, Tuple, Optional, List
6
+ from rich.console import Console
7
+ from rich.markdown import Markdown
8
+
9
+ from janito.config import get_config
10
+ from janito.tools import find_files
11
+ from janito.tools.str_replace_editor.editor import str_replace_editor
12
+ from janito.tools.delete_file import delete_file
13
+ from janito.tools.search_text import search_text
14
+ from janito.tools.decorators import format_tool_label
15
+
16
+ # Note: ConsoleCallback has been removed as we're using pre_tool and post_tool callbacks directly
17
+
18
+ def pre_tool_callback(tool_name: str, tool_input: Dict[str, Any]) -> Tuple[Dict[str, Any], bool]:
19
+ """
20
+ Callback function that runs before a tool is executed.
21
+
22
+ Args:
23
+ tool_name: Name of the tool being called
24
+ tool_input: Input parameters for the tool
25
+
26
+ Returns:
27
+ Tuple of (modified tool input, whether to cancel the tool call)
28
+ """
29
+ console = Console()
30
+
31
+ # Add debug counter only when verbose mode is enabled
32
+ if get_config().verbose:
33
+ if not hasattr(pre_tool_callback, "counter"):
34
+ pre_tool_callback.counter = 1
35
+ console.print(f"[bold green]DEBUG: Starting tool call #{pre_tool_callback.counter}[/bold green]")
36
+
37
+ # Print the tool name and input
38
+ console.print(f"[bold green]Tool:[/bold green] {tool_name}")
39
+ console.print(f"[bold green]Input:[/bold green] {tool_input}")
40
+ else:
41
+ # For non-debug mode, just print a simple message
42
+ # Find the tool function
43
+ tool_func = None
44
+ if tool_name == "find_files":
45
+ tool_func = find_files
46
+ elif tool_name == "str_replace_editor":
47
+ tool_func = str_replace_editor
48
+ elif tool_name == "delete_file":
49
+ tool_func = delete_file
50
+ elif tool_name == "search_text":
51
+ tool_func = search_text
52
+
53
+ # Format the input for display
54
+ display_input = ""
55
+ if "path" in tool_input:
56
+ display_input = tool_input["path"]
57
+ elif "file_path" in tool_input:
58
+ display_input = tool_input["file_path"]
59
+
60
+ # Print formatted tool label if available
61
+ formatted_label = format_tool_label(tool_func, tool_input)
62
+ if formatted_label:
63
+ console.print("[bold cyan] Tool:[/bold cyan]", formatted_label, end=" ")
64
+ else:
65
+ console.print("[bold cyan] Tool:[/bold cyan]", f"{tool_name} {display_input}", end=" → ")
66
+
67
+ return tool_input, True # Continue with the tool call
68
+
69
+ def post_tool_callback(tool_name: str, tool_input: Dict[str, Any], result: Any) -> Any:
70
+ """
71
+ Callback function that runs after a tool is executed.
72
+
73
+ Args:
74
+ tool_name: Name of the tool that was called
75
+ tool_input: Input parameters for the tool
76
+ result: Result of the tool call
77
+
78
+ Returns:
79
+ Modified result
80
+ """
81
+ console = Console()
82
+
83
+ # Add debug counter only when verbose mode is enabled
84
+ if get_config().verbose:
85
+ if not hasattr(post_tool_callback, "counter"):
86
+ post_tool_callback.counter = 1
87
+ console.print(f"[bold green]DEBUG: Completed tool call #{post_tool_callback.counter}[/bold green]")
88
+ post_tool_callback.counter += 1
89
+
90
+ # Show the number of lines in the result content
91
+ if isinstance(result, tuple) and len(result) >= 1:
92
+ content, is_error = result
93
+ # Define prefix icon based on is_error
94
+ icon_prefix = "❌ " if is_error else "✅ "
95
+
96
+ if isinstance(content, str):
97
+ # Count the number of lines in the content
98
+ line_count = content.count('\n') + 1 if content else 0
99
+ console.print(f"{icon_prefix}{line_count} items")
100
+ else:
101
+ console.print(f"{icon_prefix}{content}")
102
+ else:
103
+ # If result is not a tuple, convert to string and count lines
104
+ result_str = str(result)
105
+ # Default to success icon when no error status is available
106
+ icon_prefix = "✅ "
107
+ line_count = result_str.count('\n') + 1 if result_str else 0
108
+ console.print(f"{icon_prefix}{line_count} lines")
109
+
110
+ return result
111
+
112
+ def text_callback(text: str) -> None:
113
+ """
114
+ Callback function that handles text output from the agent.
115
+
116
+ Args:
117
+ text: Text output from the agent
118
+
119
+ Returns:
120
+ None
121
+ """
122
+ console = Console()
123
+
124
+ # Add debug counter only when debug mode is enabled
125
+ if get_config().debug_mode:
126
+ if not hasattr(text_callback, "counter"):
127
+ text_callback.counter = 1
128
+ console.print(f"[bold blue]DEBUG: Text callback #{text_callback.counter}[/bold blue]")
129
+ text_callback.counter += 1
130
+
131
+ # Print the text with markdown formatting
132
+ console.print("[bold magenta]Janito:[/bold magenta] ", Markdown(text, code_theme="monokai"), end="")