janito 3.16.1__py3-none-any.whl → 3.16.2__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.
@@ -1,382 +1,373 @@
1
- import importlib.resources
2
- import re
3
- import os
4
- import sys
5
- import time
6
- import warnings
7
- import threading
8
- from pathlib import Path
9
- from jinja2 import Template
10
- from pathlib import Path
11
- from queue import Queue
12
- from rich import print as rich_print
13
- from janito.tools import get_local_tools_adapter
14
- from janito.llm.agent import LLMAgent
15
-
16
- from janito.platform_discovery import PlatformDiscovery
17
- from janito.tools.tool_base import ToolPermissions
18
- from janito.tools.permissions import get_global_allowed_permissions
19
-
20
-
21
- def _load_template_content(profile, templates_dir):
22
- """
23
- Loads the template content for the given profile from the specified directory or package resources.
24
- If the profile template is not found in the default locations, tries to load from the user profiles directory ~/.janito/profiles.
25
-
26
- Spaces in the profile name are converted to underscores to align with the file-naming convention (e.g. "Developer" ➜ "Developer" (matches: system_prompt_template_Developer.txt.j2)).
27
- """
28
- # Normalize profile name for file matching: convert to lowercase and replace spaces with underscores
29
- normalized_profile = profile.strip().lower().replace(" ", "_")
30
- template_filename = f"system_prompt_template_{normalized_profile}.txt.j2"
31
-
32
- return _find_template_file(template_filename, templates_dir, profile)
33
-
34
-
35
- def _find_template_file(template_filename, templates_dir, profile):
36
- """Find and load template file from various locations."""
37
- template_path = templates_dir / template_filename
38
-
39
- # 1) Check local templates directory
40
- if template_path.exists():
41
- with open(template_path, "r", encoding="utf-8") as file:
42
- return file.read(), template_path
43
-
44
- # 2) Try package resources fallback
45
- try:
46
- with importlib.resources.files("janito.agent.templates.profiles").joinpath(
47
- template_filename
48
- ).open("r", encoding="utf-8") as file:
49
- return file.read(), template_path
50
- except (FileNotFoundError, ModuleNotFoundError, AttributeError):
51
- pass
52
-
53
- # 3) Finally, look in the user profiles directory (~/.janito/profiles)
54
- user_profiles_dir = Path(os.path.expanduser("~/.janito/profiles"))
55
- user_template_path = user_profiles_dir / template_filename
56
- if user_template_path.exists():
57
- with open(user_template_path, "r", encoding="utf-8") as file:
58
- return file.read(), user_template_path
59
-
60
- # If nothing matched, list available profiles and raise an informative error
61
- from janito.cli.cli_commands.list_profiles import (
62
- _gather_default_profiles,
63
- _gather_user_profiles,
64
- )
65
-
66
- default_profiles = _gather_default_profiles()
67
- user_profiles = _gather_user_profiles()
68
-
69
- available_profiles = []
70
- if default_profiles:
71
- available_profiles.extend([(p, "default") for p in default_profiles])
72
- if user_profiles:
73
- available_profiles.extend([(p, "user") for p in user_profiles])
74
-
75
- # Normalize the input profile for better matching suggestions
76
- normalized_input = re.sub(r"\s+", " ", profile.strip().lower())
77
-
78
- if available_profiles:
79
- profile_list = "\n".join(
80
- [f" - {name} ({source})" for name, source in available_profiles]
81
- )
82
-
83
- # Find close matches
84
- close_matches = []
85
- for name, source in available_profiles:
86
- normalized_name = name.lower()
87
- if (
88
- normalized_input in normalized_name
89
- or normalized_name in normalized_input
90
- ):
91
- close_matches.append(name)
92
-
93
- suggestion = ""
94
- if close_matches:
95
- suggestion = f"\nDid you mean: {', '.join(close_matches)}?"
96
-
97
- error_msg = f"[janito] Could not find profile '{profile}'. Available profiles:\n{profile_list}{suggestion}"
98
- else:
99
- error_msg = (
100
- f"[janito] Could not find profile '{profile}'. No profiles available."
101
- )
102
-
103
- raise FileNotFoundError(error_msg)
104
-
105
-
106
- def _prepare_template_context(role, profile, allowed_permissions, args=None):
107
- """
108
- Prepares the context dictionary for Jinja2 template rendering.
109
- """
110
- context = {}
111
- context["role"] = role or "developer"
112
- context["profile"] = profile
113
- if allowed_permissions is None:
114
- allowed_permissions = get_global_allowed_permissions()
115
- # Convert ToolPermissions -> string like "rwx"
116
- if isinstance(allowed_permissions, ToolPermissions):
117
- perm_str = ""
118
- if allowed_permissions.read:
119
- perm_str += "r"
120
- if allowed_permissions.write:
121
- perm_str += "w"
122
- if allowed_permissions.execute:
123
- perm_str += "x"
124
- allowed_permissions = perm_str or None
125
- context["allowed_permissions"] = allowed_permissions
126
-
127
- # Add emoji flag for system prompt
128
- context["emoji_enabled"] = (
129
- getattr(args, "emoji", False) if "args" in locals() else False
130
- )
131
- # Inject platform info if execute permission is present
132
- if allowed_permissions and "x" in allowed_permissions:
133
- pd = PlatformDiscovery()
134
- context["platform"] = pd.get_platform_name()
135
- context["python_version"] = pd.get_python_version()
136
- context["shell_info"] = pd.detect_shell()
137
- # Add Linux distro info if on Linux
138
- if pd.is_linux():
139
- context["linux_distro"] = pd.get_linux_distro()
140
- context["distro_info"] = pd.get_distro_info()
141
-
142
- # Add allowed sites for market analyst profile
143
- if profile == "market-analyst":
144
- from janito.tools.url_whitelist import get_url_whitelist_manager
145
-
146
- whitelist_manager = get_url_whitelist_manager()
147
- allowed_sites = whitelist_manager.get_allowed_sites()
148
- context["allowed_sites"] = allowed_sites
149
-
150
- # Add market data sources documentation
151
- if not allowed_sites:
152
- context["allowed_sites_info"] = (
153
- "No whitelist restrictions - all sites allowed"
154
- )
155
- else:
156
- context["allowed_sites_info"] = f"Restricted to: {', '.join(allowed_sites)}"
157
-
158
- # Add emoji flag for system prompt
159
- context["emoji_enabled"] = (
160
- getattr(args, "emoji", False) if "args" in locals() else False
161
- )
162
-
163
- # Add current date/time with timezone using standard library
164
- from datetime import datetime, timezone
165
- import time
166
-
167
- # Get local time with timezone info
168
- local_time = datetime.now()
169
-
170
- # Get timezone offset
171
- if time.daylight:
172
- offset = time.altzone
173
- else:
174
- offset = time.timezone
175
-
176
- # Format offset as +HHMM or -HHMM
177
- offset_hours = -offset // 3600
178
- offset_minutes = abs(offset) % 3600 // 60
179
- offset_str = f"{offset_hours:+03d}{offset_minutes:02d}"
180
-
181
- # Get timezone name
182
- tz_name = time.tzname[time.daylight and time.daylight or 0]
183
-
184
- context["current_datetime"] = local_time.strftime(
185
- f"%Y-%m-%d %H:%M:%S {tz_name}{offset_str}"
186
- )
187
- context["timezone"] = f"{tz_name} (UTC{offset_str})"
188
-
189
- return context
190
-
191
-
192
- def _create_agent(
193
- provider_instance,
194
- tools_provider,
195
- role,
196
- system_prompt,
197
- input_queue,
198
- output_queue,
199
- verbose_agent,
200
- context,
201
- template_path,
202
- profile,
203
- ):
204
- """
205
- Creates and returns an LLMAgent instance with the provided parameters.
206
- """
207
- agent = LLMAgent(
208
- provider_instance,
209
- tools_provider,
210
- agent_name=role or "developer",
211
- system_prompt=system_prompt,
212
- input_queue=input_queue,
213
- output_queue=output_queue,
214
- verbose_agent=verbose_agent,
215
- )
216
- agent.template_vars["role"] = context["role"]
217
- agent.template_vars["profile"] = profile
218
- agent.system_prompt_template = str(template_path)
219
- agent._template_vars = context.copy()
220
- agent._original_template_vars = context.copy()
221
- return agent
222
-
223
-
224
- def setup_agent(
225
- provider_instance,
226
- llm_driver_config,
227
- role=None,
228
- templates_dir=None,
229
- zero_mode=False,
230
- input_queue=None,
231
- output_queue=None,
232
- verbose_tools=False,
233
- verbose_agent=False,
234
- allowed_permissions=None,
235
- profile=None,
236
- profile_system_prompt=None,
237
- no_tools_mode=False,
238
- ):
239
- """
240
- Creates an agent. A system prompt is rendered from a template only when a profile is specified.
241
- """
242
- if no_tools_mode or zero_mode:
243
- tools_provider = None
244
- else:
245
- tools_provider = get_local_tools_adapter()
246
- tools_provider.set_verbose_tools(verbose_tools)
247
-
248
- # If zero_mode is enabled or no profile is given we skip the system prompt.
249
- if zero_mode or (profile is None and profile_system_prompt is None):
250
- agent = LLMAgent(
251
- provider_instance,
252
- tools_provider,
253
- agent_name=role or "developer",
254
- system_prompt=None,
255
- input_queue=input_queue,
256
- output_queue=output_queue,
257
- verbose_agent=verbose_agent,
258
- )
259
- if role:
260
- agent.template_vars["role"] = role
261
- return agent
262
-
263
- # If profile_system_prompt is set, use it directly
264
- if profile_system_prompt is not None:
265
- agent = LLMAgent(
266
- provider_instance,
267
- tools_provider,
268
- agent_name=role or "developer",
269
- system_prompt=profile_system_prompt,
270
- input_queue=input_queue,
271
- output_queue=output_queue,
272
- verbose_agent=verbose_agent,
273
- )
274
- agent.template_vars["role"] = role or "developer"
275
- agent.template_vars["profile"] = None
276
- agent.template_vars["profile_system_prompt"] = profile_system_prompt
277
- return agent
278
-
279
- # Normal flow (profile-specific system prompt)
280
- if templates_dir is None:
281
- templates_dir = Path(__file__).parent / "templates" / "profiles"
282
- template_content, template_path = _load_template_content(profile, templates_dir)
283
-
284
- template = Template(template_content)
285
- context = _prepare_template_context(
286
- role, profile, allowed_permissions, locals().get("args")
287
- )
288
-
289
- # Debug output if requested
290
- debug_flag = False
291
- try:
292
- debug_flag = hasattr(sys, "argv") and (
293
- "--debug" in sys.argv or "--verbose" in sys.argv or "-v" in sys.argv
294
- )
295
- except Exception:
296
- pass
297
- if debug_flag:
298
- rich_print(
299
- f"[bold magenta][DEBUG][/bold magenta] Rendering system prompt template '[cyan]{template_path.name}[/cyan]' with allowed_permissions: [yellow]{context.get('allowed_permissions')}[/yellow]"
300
- )
301
- rich_print(
302
- f"[bold magenta][DEBUG][/bold magenta] Template context: [green]{context}[/green]"
303
- )
304
- start_render = time.time()
305
- rendered_prompt = template.render(**context)
306
- end_render = time.time()
307
- # Merge multiple empty lines into a single empty line
308
- rendered_prompt = re.sub(r"\n{3,}", "\n\n", rendered_prompt)
309
-
310
- return _create_agent(
311
- provider_instance,
312
- tools_provider,
313
- role,
314
- rendered_prompt,
315
- input_queue,
316
- output_queue,
317
- verbose_agent,
318
- context,
319
- template_path,
320
- profile,
321
- )
322
-
323
-
324
- def create_configured_agent(
325
- *,
326
- provider_instance=None,
327
- llm_driver_config=None,
328
- role=None,
329
- verbose_tools=False,
330
- verbose_agent=False,
331
- templates_dir=None,
332
- zero_mode=False,
333
- allowed_permissions=None,
334
- profile=None,
335
- profile_system_prompt=None,
336
- no_tools_mode=False,
337
- ):
338
- """
339
- Normalizes agent setup for all CLI modes.
340
-
341
- Args:
342
- provider_instance: Provider instance for the agent
343
- llm_driver_config: LLM driver configuration
344
- role: Optional role string
345
- verbose_tools: Optional, default False
346
- verbose_agent: Optional, default False
347
- templates_dir: Optional
348
- zero_mode: Optional, default False
349
-
350
- Returns:
351
- Configured agent instance
352
- """
353
- input_queue = None
354
- output_queue = None
355
- driver = None
356
- if hasattr(provider_instance, "create_driver"):
357
- driver = provider_instance.create_driver()
358
- # Ensure no tools are passed to the driver when --no-tools flag is active
359
- if no_tools_mode:
360
- driver.tools_adapter = None
361
- driver.start() # Ensure the driver background thread is started
362
- input_queue = getattr(driver, "input_queue", None)
363
- output_queue = getattr(driver, "output_queue", None)
364
-
365
- agent = setup_agent(
366
- provider_instance=provider_instance,
367
- llm_driver_config=llm_driver_config,
368
- role=role,
369
- templates_dir=templates_dir,
370
- zero_mode=zero_mode,
371
- input_queue=input_queue,
372
- output_queue=output_queue,
373
- verbose_tools=verbose_tools,
374
- verbose_agent=verbose_agent,
375
- allowed_permissions=allowed_permissions,
376
- profile=profile,
377
- profile_system_prompt=profile_system_prompt,
378
- no_tools_mode=no_tools_mode,
379
- )
380
- if driver is not None:
381
- agent.driver = driver # Attach driver to agent for thread management
382
- return agent
1
+ import importlib.resources
2
+ import re
3
+ import os
4
+ import sys
5
+ import time
6
+ import warnings
7
+ import threading
8
+ from pathlib import Path
9
+ from jinja2 import Template
10
+ from pathlib import Path
11
+ from queue import Queue
12
+ from rich import print as rich_print
13
+ from janito.tools import get_local_tools_adapter
14
+ from janito.llm.agent import LLMAgent
15
+
16
+ from janito.platform_discovery import PlatformDiscovery
17
+ from janito.tools.tool_base import ToolPermissions
18
+ from janito.tools.permissions import get_global_allowed_permissions
19
+
20
+
21
+ def _load_template_content(profile, templates_dir):
22
+ """
23
+ Loads the template content for the given profile from the specified directory or package resources.
24
+ If the profile template is not found in the default locations, tries to load from the user profiles directory ~/.janito/profiles.
25
+
26
+ Spaces in the profile name are converted to underscores to align with the file-naming convention (e.g. "Developer" ➜ "Developer" (matches: system_prompt_template_Developer.txt.j2)).
27
+ """
28
+ # Normalize profile name for file matching: convert to lowercase and replace spaces with underscores
29
+ normalized_profile = profile.strip().lower().replace(" ", "_")
30
+ template_filename = f"system_prompt_template_{normalized_profile}.txt.j2"
31
+
32
+ return _find_template_file(template_filename, templates_dir, profile)
33
+
34
+
35
+ def _find_template_file(template_filename, templates_dir, profile):
36
+ """Find and load template file from various locations."""
37
+ template_path = templates_dir / template_filename
38
+
39
+ # 1) Check local templates directory
40
+ if template_path.exists():
41
+ with open(template_path, "r", encoding="utf-8") as file:
42
+ return file.read(), template_path
43
+
44
+ # 2) Try package resources fallback
45
+ try:
46
+ with importlib.resources.files("janito.agent.templates.profiles").joinpath(
47
+ template_filename
48
+ ).open("r", encoding="utf-8") as file:
49
+ return file.read(), template_path
50
+ except (FileNotFoundError, ModuleNotFoundError, AttributeError):
51
+ pass
52
+
53
+ # 3) Finally, look in the user profiles directory (~/.janito/profiles)
54
+ user_profiles_dir = Path(os.path.expanduser("~/.janito/profiles"))
55
+ user_template_path = user_profiles_dir / template_filename
56
+ if user_template_path.exists():
57
+ with open(user_template_path, "r", encoding="utf-8") as file:
58
+ return file.read(), user_template_path
59
+
60
+ # If nothing matched, list available profiles and raise an informative error
61
+ from janito.cli.cli_commands.list_profiles import (
62
+ _gather_default_profiles,
63
+ _gather_user_profiles,
64
+ )
65
+
66
+ default_profiles = _gather_default_profiles()
67
+ user_profiles = _gather_user_profiles()
68
+
69
+ available_profiles = []
70
+ if default_profiles:
71
+ available_profiles.extend([(p, "default") for p in default_profiles])
72
+ if user_profiles:
73
+ available_profiles.extend([(p, "user") for p in user_profiles])
74
+
75
+ # Normalize the input profile for better matching suggestions
76
+ normalized_input = re.sub(r"\s+", " ", profile.strip().lower())
77
+
78
+ if available_profiles:
79
+ profile_list = "\n".join(
80
+ [f" - {name} ({source})" for name, source in available_profiles]
81
+ )
82
+
83
+ # Find close matches
84
+ close_matches = []
85
+ for name, source in available_profiles:
86
+ normalized_name = name.lower()
87
+ if (
88
+ normalized_input in normalized_name
89
+ or normalized_name in normalized_input
90
+ ):
91
+ close_matches.append(name)
92
+
93
+ suggestion = ""
94
+ if close_matches:
95
+ suggestion = f"\nDid you mean: {', '.join(close_matches)}?"
96
+
97
+ error_msg = f"[janito] Could not find profile '{profile}'. Available profiles:\n{profile_list}{suggestion}"
98
+ else:
99
+ error_msg = (
100
+ f"[janito] Could not find profile '{profile}'. No profiles available."
101
+ )
102
+
103
+ raise FileNotFoundError(error_msg)
104
+
105
+
106
+ def _prepare_template_context(role, profile, allowed_permissions, args=None):
107
+ """
108
+ Prepares the context dictionary for Jinja2 template rendering.
109
+ """
110
+ context = {}
111
+ context["role"] = role or "developer"
112
+ context["profile"] = profile
113
+ if allowed_permissions is None:
114
+ allowed_permissions = get_global_allowed_permissions()
115
+ # Convert ToolPermissions -> string like "rwx"
116
+ if isinstance(allowed_permissions, ToolPermissions):
117
+ perm_str = ""
118
+ if allowed_permissions.read:
119
+ perm_str += "r"
120
+ if allowed_permissions.write:
121
+ perm_str += "w"
122
+ if allowed_permissions.execute:
123
+ perm_str += "x"
124
+ allowed_permissions = perm_str or None
125
+ context["allowed_permissions"] = allowed_permissions
126
+
127
+ # Inject platform info if execute permission is present
128
+ if allowed_permissions and "x" in allowed_permissions:
129
+ pd = PlatformDiscovery()
130
+ context["platform"] = pd.get_platform_name()
131
+ context["python_version"] = pd.get_python_version()
132
+ context["shell_info"] = pd.detect_shell()
133
+ # Add Linux distro info if on Linux
134
+ if pd.is_linux():
135
+ context["linux_distro"] = pd.get_linux_distro()
136
+ context["distro_info"] = pd.get_distro_info()
137
+
138
+ # Add allowed sites for market analyst profile
139
+ if profile == "market-analyst":
140
+ from janito.tools.url_whitelist import get_url_whitelist_manager
141
+
142
+ whitelist_manager = get_url_whitelist_manager()
143
+ allowed_sites = whitelist_manager.get_allowed_sites()
144
+ context["allowed_sites"] = allowed_sites
145
+
146
+ # Add market data sources documentation
147
+ if not allowed_sites:
148
+ context["allowed_sites_info"] = (
149
+ "No whitelist restrictions - all sites allowed"
150
+ )
151
+ else:
152
+ context["allowed_sites_info"] = f"Restricted to: {', '.join(allowed_sites)}"
153
+
154
+ # Add current date/time with timezone using standard library
155
+ from datetime import datetime, timezone
156
+ import time
157
+
158
+ # Get local time with timezone info
159
+ local_time = datetime.now()
160
+
161
+ # Get timezone offset
162
+ if time.daylight:
163
+ offset = time.altzone
164
+ else:
165
+ offset = time.timezone
166
+
167
+ # Format offset as +HHMM or -HHMM
168
+ offset_hours = -offset // 3600
169
+ offset_minutes = abs(offset) % 3600 // 60
170
+ offset_str = f"{offset_hours:+03d}{offset_minutes:02d}"
171
+
172
+ # Get timezone name
173
+ tz_name = time.tzname[time.daylight and time.daylight or 0]
174
+
175
+ context["current_datetime"] = local_time.strftime(
176
+ f"%Y-%m-%d %H:%M:%S {tz_name}{offset_str}"
177
+ )
178
+ context["timezone"] = f"{tz_name} (UTC{offset_str})"
179
+
180
+ return context
181
+
182
+
183
+ def _create_agent(
184
+ provider_instance,
185
+ tools_provider,
186
+ role,
187
+ system_prompt,
188
+ input_queue,
189
+ output_queue,
190
+ verbose_agent,
191
+ context,
192
+ template_path,
193
+ profile,
194
+ ):
195
+ """
196
+ Creates and returns an LLMAgent instance with the provided parameters.
197
+ """
198
+ agent = LLMAgent(
199
+ provider_instance,
200
+ tools_provider,
201
+ agent_name=role or "developer",
202
+ system_prompt=system_prompt,
203
+ input_queue=input_queue,
204
+ output_queue=output_queue,
205
+ verbose_agent=verbose_agent,
206
+ )
207
+ agent.template_vars["role"] = context["role"]
208
+ agent.template_vars["profile"] = profile
209
+ agent.system_prompt_template = str(template_path)
210
+ agent._template_vars = context.copy()
211
+ agent._original_template_vars = context.copy()
212
+ return agent
213
+
214
+
215
+ def setup_agent(
216
+ provider_instance,
217
+ llm_driver_config,
218
+ role=None,
219
+ templates_dir=None,
220
+ zero_mode=False,
221
+ input_queue=None,
222
+ output_queue=None,
223
+ verbose_tools=False,
224
+ verbose_agent=False,
225
+ allowed_permissions=None,
226
+ profile=None,
227
+ profile_system_prompt=None,
228
+ no_tools_mode=False,
229
+ ):
230
+ """
231
+ Creates an agent. A system prompt is rendered from a template only when a profile is specified.
232
+ """
233
+ if no_tools_mode or zero_mode:
234
+ tools_provider = None
235
+ else:
236
+ tools_provider = get_local_tools_adapter()
237
+ tools_provider.set_verbose_tools(verbose_tools)
238
+
239
+ # If zero_mode is enabled or no profile is given we skip the system prompt.
240
+ if zero_mode or (profile is None and profile_system_prompt is None):
241
+ agent = LLMAgent(
242
+ provider_instance,
243
+ tools_provider,
244
+ agent_name=role or "developer",
245
+ system_prompt=None,
246
+ input_queue=input_queue,
247
+ output_queue=output_queue,
248
+ verbose_agent=verbose_agent,
249
+ )
250
+ if role:
251
+ agent.template_vars["role"] = role
252
+ return agent
253
+
254
+ # If profile_system_prompt is set, use it directly
255
+ if profile_system_prompt is not None:
256
+ agent = LLMAgent(
257
+ provider_instance,
258
+ tools_provider,
259
+ agent_name=role or "developer",
260
+ system_prompt=profile_system_prompt,
261
+ input_queue=input_queue,
262
+ output_queue=output_queue,
263
+ verbose_agent=verbose_agent,
264
+ )
265
+ agent.template_vars["role"] = role or "developer"
266
+ agent.template_vars["profile"] = None
267
+ agent.template_vars["profile_system_prompt"] = profile_system_prompt
268
+ return agent
269
+
270
+ # Normal flow (profile-specific system prompt)
271
+ if templates_dir is None:
272
+ templates_dir = Path(__file__).parent / "templates" / "profiles"
273
+ template_content, template_path = _load_template_content(profile, templates_dir)
274
+
275
+ template = Template(template_content)
276
+ context = _prepare_template_context(
277
+ role, profile, allowed_permissions, locals().get("args")
278
+ )
279
+
280
+ # Debug output if requested
281
+ debug_flag = False
282
+ try:
283
+ debug_flag = hasattr(sys, "argv") and (
284
+ "--debug" in sys.argv or "--verbose" in sys.argv or "-v" in sys.argv
285
+ )
286
+ except Exception:
287
+ pass
288
+ if debug_flag:
289
+ rich_print(
290
+ f"[bold magenta][DEBUG][/bold magenta] Rendering system prompt template '[cyan]{template_path.name}[/cyan]' with allowed_permissions: [yellow]{context.get('allowed_permissions')}[/yellow]"
291
+ )
292
+ rich_print(
293
+ f"[bold magenta][DEBUG][/bold magenta] Template context: [green]{context}[/green]"
294
+ )
295
+ start_render = time.time()
296
+ rendered_prompt = template.render(**context)
297
+ end_render = time.time()
298
+ # Merge multiple empty lines into a single empty line
299
+ rendered_prompt = re.sub(r"\n{3,}", "\n\n", rendered_prompt)
300
+
301
+ return _create_agent(
302
+ provider_instance,
303
+ tools_provider,
304
+ role,
305
+ rendered_prompt,
306
+ input_queue,
307
+ output_queue,
308
+ verbose_agent,
309
+ context,
310
+ template_path,
311
+ profile,
312
+ )
313
+
314
+
315
+ def create_configured_agent(
316
+ *,
317
+ provider_instance=None,
318
+ llm_driver_config=None,
319
+ role=None,
320
+ verbose_tools=False,
321
+ verbose_agent=False,
322
+ templates_dir=None,
323
+ zero_mode=False,
324
+ allowed_permissions=None,
325
+ profile=None,
326
+ profile_system_prompt=None,
327
+ no_tools_mode=False,
328
+ ):
329
+ """
330
+ Normalizes agent setup for all CLI modes.
331
+
332
+ Args:
333
+ provider_instance: Provider instance for the agent
334
+ llm_driver_config: LLM driver configuration
335
+ role: Optional role string
336
+ verbose_tools: Optional, default False
337
+ verbose_agent: Optional, default False
338
+ templates_dir: Optional
339
+ zero_mode: Optional, default False
340
+
341
+ Returns:
342
+ Configured agent instance
343
+ """
344
+ input_queue = None
345
+ output_queue = None
346
+ driver = None
347
+ if hasattr(provider_instance, "create_driver"):
348
+ driver = provider_instance.create_driver()
349
+ # Ensure no tools are passed to the driver when --no-tools flag is active
350
+ if no_tools_mode:
351
+ driver.tools_adapter = None
352
+ driver.start() # Ensure the driver background thread is started
353
+ input_queue = getattr(driver, "input_queue", None)
354
+ output_queue = getattr(driver, "output_queue", None)
355
+
356
+ agent = setup_agent(
357
+ provider_instance=provider_instance,
358
+ llm_driver_config=llm_driver_config,
359
+ role=role,
360
+ templates_dir=templates_dir,
361
+ zero_mode=zero_mode,
362
+ input_queue=input_queue,
363
+ output_queue=output_queue,
364
+ verbose_tools=verbose_tools,
365
+ verbose_agent=verbose_agent,
366
+ allowed_permissions=allowed_permissions,
367
+ profile=profile,
368
+ profile_system_prompt=profile_system_prompt,
369
+ no_tools_mode=no_tools_mode,
370
+ )
371
+ if driver is not None:
372
+ agent.driver = driver # Attach driver to agent for thread management
373
+ return agent