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