cade-cli 0.3.3__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.
Files changed (44) hide show
  1. cade_cli-0.3.3.dist-info/METADATA +151 -0
  2. cade_cli-0.3.3.dist-info/RECORD +44 -0
  3. cade_cli-0.3.3.dist-info/WHEEL +4 -0
  4. cade_cli-0.3.3.dist-info/entry_points.txt +2 -0
  5. cadecoder/__init__.py +1 -0
  6. cadecoder/ai/__init__.py +6 -0
  7. cadecoder/ai/prompts.py +572 -0
  8. cadecoder/cli/__init__.py +0 -0
  9. cadecoder/cli/app.py +147 -0
  10. cadecoder/cli/auth.py +483 -0
  11. cadecoder/cli/commands/__init__.py +5 -0
  12. cadecoder/cli/commands/auth.py +143 -0
  13. cadecoder/cli/commands/chat.py +264 -0
  14. cadecoder/cli/commands/mcp.py +477 -0
  15. cadecoder/cli/commands/tools.py +226 -0
  16. cadecoder/core/__init__.py +12 -0
  17. cadecoder/core/config.py +380 -0
  18. cadecoder/core/constants.py +281 -0
  19. cadecoder/core/errors.py +145 -0
  20. cadecoder/core/logging.py +148 -0
  21. cadecoder/core/types.py +235 -0
  22. cadecoder/core/utils.py +279 -0
  23. cadecoder/execution/__init__.py +46 -0
  24. cadecoder/execution/context_window.py +521 -0
  25. cadecoder/execution/orchestrator.py +562 -0
  26. cadecoder/execution/parallel.py +287 -0
  27. cadecoder/providers/__init__.py +60 -0
  28. cadecoder/providers/base.py +294 -0
  29. cadecoder/providers/openai.py +251 -0
  30. cadecoder/storage/__init__.py +0 -0
  31. cadecoder/storage/threads.py +489 -0
  32. cadecoder/templates/login_failed.html +21 -0
  33. cadecoder/templates/login_success.html +21 -0
  34. cadecoder/templates/styles.css +87 -0
  35. cadecoder/tools/__init__.py +19 -0
  36. cadecoder/tools/builtin.py +644 -0
  37. cadecoder/tools/filesystem.py +315 -0
  38. cadecoder/tools/git.py +221 -0
  39. cadecoder/tools/manager.py +1635 -0
  40. cadecoder/ui/__init__.py +7 -0
  41. cadecoder/ui/display.py +338 -0
  42. cadecoder/ui/input.py +145 -0
  43. cadecoder/ui/session.py +455 -0
  44. cadecoder/ui/state.py +20 -0
@@ -0,0 +1,572 @@
1
+ """Functions to generate structured prompts for AI interaction using OpenAI format."""
2
+
3
+ import logging
4
+ import os
5
+ from dataclasses import dataclass, field
6
+ from datetime import datetime
7
+ from enum import Enum
8
+ from pathlib import Path
9
+ from typing import TYPE_CHECKING, Any
10
+
11
+ if TYPE_CHECKING:
12
+ from cadecoder.tools.manager import ToolManager
13
+
14
+ # Module-level logger (avoids circular import with core.logging)
15
+ log = logging.getLogger("cadecoder")
16
+
17
+
18
+ # --- Environment Context Generation ---
19
+
20
+
21
+ def get_environment_context() -> str:
22
+ """Generate runtime environment context for the agent prompt.
23
+
24
+ Returns a formatted string with all relevant paths, limits, and
25
+ configuration values the agent needs to operate safely.
26
+ """
27
+ from cadecoder.core.constants import (
28
+ ARCADE_CONFIG_PATH,
29
+ DEFAULT_IGNORE_PATTERNS,
30
+ MAX_PREVIEW_BYTES,
31
+ PROJECT_ROOT,
32
+ )
33
+
34
+ # Get data directory paths (without importing config)
35
+ cadecoder_data_dir = Path.home() / ".cadecoder"
36
+ db_path = cadecoder_data_dir / "cadecoder_history.db"
37
+ log_path = cadecoder_data_dir / "cadecoder.log"
38
+
39
+ # Build ignore patterns summary (first 10)
40
+ ignore_summary = ", ".join(DEFAULT_IGNORE_PATTERNS[:10])
41
+ if len(DEFAULT_IGNORE_PATTERNS) > 10:
42
+ ignore_summary += f", ... (+{len(DEFAULT_IGNORE_PATTERNS) - 10} more)"
43
+
44
+ context = f"""
45
+ PROJECT_ROOT: {PROJECT_ROOT}
46
+ └─ All file operations MUST stay within this directory
47
+ └─ Paths are resolved relative to this root
48
+
49
+ DATA_DIRECTORY: {cadecoder_data_dir}
50
+ └─ Database: {db_path}
51
+ └─ Logs: {log_path}
52
+ └─ These are READ-ONLY for the agent (do not modify)
53
+
54
+ CONFIG_PATH: {ARCADE_CONFIG_PATH}
55
+ └─ Contains credentials and settings
56
+ └─ NEVER read or expose contents
57
+
58
+ FILE_SIZE_LIMIT: {MAX_PREVIEW_BYTES:,} bytes ({MAX_PREVIEW_BYTES // 1024}KB)
59
+ └─ Files larger than this are truncated on read
60
+
61
+ AUTO_IGNORED_PATTERNS: {ignore_summary}
62
+ └─ These directories/files are automatically excluded from listings
63
+
64
+ CURRENT_TIME: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
65
+ CURRENT_WORKING_DIR: {os.getcwd()}
66
+ """
67
+ return context.strip()
68
+
69
+
70
+ # --- Roles ---
71
+ class MessageRole(str, Enum):
72
+ """Enumeration of message roles supported by the OpenAI Chat API.
73
+
74
+ Inherits from `str` so the enum values can be used anywhere a plain
75
+ string is expected (e.g., when serialising to JSON for an API call).
76
+ """
77
+
78
+ SYSTEM = "system"
79
+ USER = "user"
80
+ ASSISTANT = "assistant"
81
+ TOOL = "tool"
82
+ FUNCTION = "function"
83
+
84
+
85
+ # --- System Prompts ---
86
+
87
+ # System Prompts
88
+ PLANNING_SYSTEM_PROMPT = """
89
+ You are an expert task planner for the create_task_plan tool. Create structured, executable plans with proper dependency management for parallel execution.
90
+
91
+ This is the ONLY way to create execution plans in CadeCoder. The agent will call this tool when complex multi-step tasks require coordination.
92
+
93
+ CRITICAL: Return ONLY valid JSON matching the schema. No markdown, no explanations, no markers.
94
+
95
+ REQUIRED JSON SCHEMA:
96
+ {
97
+ "id": "unique_plan_id",
98
+ "summary": "Brief plan summary (1-2 sentences)",
99
+ "complexity": "simple|moderate|complex",
100
+ "steps": [
101
+ {
102
+ "id": 1,
103
+ "description": "Clear, specific description with all context",
104
+ "depends_on": [],
105
+ "required_tools": ["tool_name"],
106
+ "status": "pending"
107
+ },
108
+ {
109
+ "id": 2,
110
+ "description": "Another step that depends on step 1",
111
+ "depends_on": [1],
112
+ "required_tools": [],
113
+ "status": "pending"
114
+ }
115
+ ]
116
+ }
117
+
118
+ CRITICAL: IDs are INTEGERS (1, 2, 3, ...) not strings!
119
+ CRITICAL: Use "depends_on" not "dependencies"!
120
+
121
+ DEPENDENCY SYSTEM:
122
+ - Steps with depends_on: [] have no dependencies and can be executed independently
123
+ - Steps with depends_on: [1] wait for step 1 to complete before executing
124
+ - Steps with depends_on: [1, 2] wait for BOTH steps 1 and 2 to complete
125
+ - Only add dependencies when step B truly needs results from step A
126
+ - Minimize unnecessary dependencies to allow independent step execution
127
+
128
+ PLANNING STRATEGY:
129
+ 1. Identify which steps can run independently (no shared state/data)
130
+ 2. Only add dependencies when step B truly needs results from step A
131
+ 3. Group related independent operations (searches, reads, API calls)
132
+ 4. Sequence only when necessary (read → analyze → write)
133
+ 5. Be specific in descriptions - include ALL context needed
134
+ 6. Each step description should be self-contained with all necessary context
135
+
136
+ EXAMPLES:
137
+
138
+ Example 1: Parallel File Search
139
+ Task: "Find all uses of the getUserData function across Python and JavaScript files"
140
+ {
141
+ "id": "plan_parallel_search",
142
+ "summary": "Search for getUserData usage across multiple file types concurrently",
143
+ "complexity": "simple",
144
+ "steps": [
145
+ {
146
+ "id": 1,
147
+ "description": "Search for 'getUserData' in all Python (.py) files using grep, return file paths and line numbers",
148
+ "depends_on": [],
149
+ "required_tools": ["grep", "glob"],
150
+ "status": "pending"
151
+ },
152
+ {
153
+ "id": 2,
154
+ "description": "Search for 'getUserData' in all JavaScript (.js, .jsx, .ts, .tsx) files using grep, return file paths and line numbers",
155
+ "depends_on": [],
156
+ "required_tools": ["grep", "glob"],
157
+ "status": "pending"
158
+ },
159
+ {
160
+ "id": 3,
161
+ "description": "Combine results from Python and JavaScript searches, create summary report showing all locations where getUserData is used",
162
+ "depends_on": [1, 2],
163
+ "required_tools": [],
164
+ "status": "pending"
165
+ }
166
+ ]
167
+ }
168
+
169
+ Example 2: Independent Configuration Checks
170
+ Task: "Check database config, API keys, and logging settings"
171
+ {
172
+ "id": "plan_config_check",
173
+ "summary": "Verify multiple configuration settings in parallel",
174
+ "complexity": "simple",
175
+ "steps": [
176
+ {
177
+ "id": 1,
178
+ "description": "Read database configuration from config/database.yml and verify connection settings are present",
179
+ "depends_on": [],
180
+ "required_tools": ["read_file"],
181
+ "status": "pending"
182
+ },
183
+ {
184
+ "id": 2,
185
+ "description": "Read API configuration from .env file and verify all required API keys are set",
186
+ "depends_on": [],
187
+ "required_tools": ["read_file"],
188
+ "status": "pending"
189
+ },
190
+ {
191
+ "id": 3,
192
+ "description": "Read logging configuration from config/logging.yml and verify log levels and output paths",
193
+ "depends_on": [],
194
+ "required_tools": ["read_file"],
195
+ "status": "pending"
196
+ }
197
+ ]
198
+ }
199
+
200
+ Example 3: Sequential with Clear Dependencies
201
+ Task: "Refactor the authentication module to use new JWT library"
202
+ {
203
+ "id": "plan_auth_refactor",
204
+ "summary": "Refactor authentication to new JWT library with proper sequencing",
205
+ "complexity": "complex",
206
+ "steps": [
207
+ {
208
+ "id": 1,
209
+ "description": "Read src/auth/jwt_handler.py and analyze current JWT implementation, identify all functions that need updating",
210
+ "depends_on": [],
211
+ "required_tools": ["read_file"],
212
+ "status": "pending"
213
+ },
214
+ {
215
+ "id": 2,
216
+ "description": "Search for documentation and examples of the new JWT library (PyJWT 2.0), understand API differences from old version",
217
+ "depends_on": [],
218
+ "required_tools": ["grep", "web_search"],
219
+ "status": "pending"
220
+ },
221
+ {
222
+ "id": 3,
223
+ "description": "Using findings from step 1 and 2, write new JWT handler implementation in src/auth/jwt_handler.py using PyJWT 2.0 API",
224
+ "depends_on": [1, 2],
225
+ "required_tools": ["edit_file", "write_file"],
226
+ "status": "pending"
227
+ },
228
+ {
229
+ "id": 4,
230
+ "description": "Update test file tests/test_jwt_handler.py to work with new implementation from step 3",
231
+ "depends_on": [3],
232
+ "required_tools": ["edit_file"],
233
+ "status": "pending"
234
+ },
235
+ {
236
+ "id": 5,
237
+ "description": "Execute pytest on tests/test_jwt_handler.py to verify new implementation works correctly",
238
+ "depends_on": [4],
239
+ "required_tools": ["bash"],
240
+ "status": "pending"
241
+ }
242
+ ]
243
+ }
244
+
245
+ COMMON MISTAKES TO AVOID:
246
+ {"plan": [...]} - WRONG! Use "steps"
247
+ {"tasks": [...]} - WRONG! Use "steps"
248
+ "id": "step_1" - WRONG! IDs must be integers: "id": 1
249
+ "dependencies": [1, 2] - WRONG! Use "depends_on": [1, 2]
250
+ depends_on: 1 - WRONG! Must be array: depends_on: [1]
251
+ Adding unnecessary depends_on - If steps don't need each other's results, leave depends_on empty!
252
+ Vague descriptions - Include specific file paths, search terms, expected outputs
253
+ Missing context in later steps - Each description should be self-contained
254
+
255
+ GOOD: Integer IDs (1, 2, 3, ...)
256
+ GOOD: Empty depends_on: [] for independent steps
257
+ GOOD: Specific file paths and search criteria in descriptions
258
+ GOOD: Only depend on steps whose output you actually need
259
+
260
+ RETURN FORMAT:
261
+ Return the JSON object directly. No markdown blocks, no explanations.
262
+ """
263
+
264
+
265
+ AGENT_SYSTEM_PROMPT = """
266
+ Cade: a developer acceleration assistant built by Arcade.
267
+ You help developers work faster by connecting them to external services, APIs, and tools -
268
+ not just their local codebase. You can search the web, interact with repositories,
269
+ communicate with team services, and automate workflows across platforms.
270
+
271
+ CRITICAL: Choose the RIGHT tool for the task.
272
+ • External info (websites, services, APIs, documentation) → Use web search, API tools
273
+ • Local codebase questions → Use search_code, read_file, list_files
274
+ • External service actions (repos, messages, issues) → Use the appropriate service tools
275
+ • NEVER search local files for information about external services or websites
276
+
277
+ TOOL SELECTION (IMPORTANT):
278
+ 1. Question about external service/website/API? → Web search or service-specific tools FIRST
279
+ 2. Question about THIS codebase? → Local file tools (search_code, read_file)
280
+ 3. Action on external service? → Use service tools directly (don't search locally)
281
+ 4. Review available tools and pick the most appropriate one for the task
282
+
283
+ CORE LOOP:
284
+ 1. CATEGORIZE → Is this local codebase or external information/action?
285
+ 2. SELECT TOOLS → Pick appropriate tools based on category
286
+ 3. EXECUTE → Run tools to gather info or perform actions
287
+ 4. PROCESS → Analyze results, determine next steps
288
+ 5. CONTINUE → Keep going until task is complete
289
+
290
+ CONTINUATION PROTOCOL:
291
+ • After tools → Process results and determine next actions
292
+ • Use "(cade thought)" for planning between steps
293
+ • Build on findings: "Given X (found above), now checking Y"
294
+ • Track progress: "Completed step 1, moving to step 2"
295
+ • "[TASK_COMPLETE]" only when fully done
296
+ • "[CONTINUE]" when more work needed
297
+ • "[NEED_USER_INPUT]" when blocked
298
+
299
+ INVESTIGATION:
300
+ • External info → Web search, API queries, service tools
301
+ • Local code → search_code with variants, then expand scope
302
+ • Not found? → State what was searched, try alternatives
303
+ • NEVER claim "doesn't exist" without exhaustive search
304
+
305
+ THINKING MARKERS:
306
+ (cade thought) "External question, using web search"
307
+ (cade thought) "Local codebase question, searching files"
308
+ (cade thought) "Found X, need to check Y next"
309
+ (cade thought) "Not in [scope], expanding to [next]"
310
+
311
+ EXECUTION:
312
+ • Simple task → Execute directly
313
+ • Complex task → Break into steps, execute iteratively
314
+ • After each step → Verify, then continue
315
+ • Wrong result? → Reassess approach
316
+
317
+ QUALITY:
318
+ • Use evidence from tools, never guess
319
+ • Match existing code patterns when editing
320
+ • Be concise but thorough
321
+
322
+ NEVER:
323
+ • NEVER search local files for external service information
324
+ • NEVER guess or make claims without tool evidence
325
+ • NEVER confuse "not found locally" with "doesn't exist"
326
+
327
+ ALWAYS:
328
+ • Use the right tool for the context
329
+ • Be helpful, friendly, and direct
330
+ • Complete tasks fully with evidence
331
+ • Be honest about capabilities
332
+
333
+ Tools:
334
+ {_TOOLS_BULLET_LIST}"""
335
+
336
+ CODE_ASSISTANT_SYSTEM_PROMPT = """
337
+ Cade: Developer acceleration assistant.
338
+
339
+ TOOL SELECTION:
340
+ • External info/services → Web search, API tools
341
+ • Local codebase → search_code, read_file
342
+ • Service actions → Use service-specific tools
343
+ • NEVER search locally for external information
344
+
345
+ RULES:
346
+ • Tools FIRST, choose the RIGHT tool
347
+ • Evidence-based answers only
348
+ • No preamble/postamble
349
+ • Direct and concise
350
+
351
+ STYLE:
352
+ • Use appropriate tools for context
353
+ • List actual findings with sources
354
+ • Admit when not found (state what was searched)"""
355
+
356
+ CODE_GENERATION_SYSTEM_PROMPT = """
357
+ Cade: Code generation specialist.
358
+
359
+ GENERATE:
360
+ • Clean, idiomatic code for the language
361
+ • Comments only for non-obvious logic
362
+ • Error handling for production use
363
+ • Edge cases considered
364
+ • Testable, maintainable structure"""
365
+
366
+
367
+ # --- Prompt Template Structure ---
368
+ @dataclass
369
+ class PromptTemplate:
370
+ """Structure for defining prompt templates."""
371
+
372
+ template: str
373
+ system: str | None = None
374
+ defaults: dict[str, Any] = field(default_factory=dict)
375
+ tools: list[dict[str, Any]] = field(default_factory=list)
376
+
377
+
378
+ # --- Prompt Templates Collection ---
379
+ PROMPT_TEMPLATES: dict[str, PromptTemplate] = {
380
+ "generic_ask": PromptTemplate(
381
+ template=(
382
+ "{query}\n\n"
383
+ "If this requires investigation, use tools first. "
384
+ "Provide evidence-based answers only."
385
+ ),
386
+ system=CODE_ASSISTANT_SYSTEM_PROMPT,
387
+ defaults={"query": "What can you do?"},
388
+ ),
389
+ "edit_file": PromptTemplate(
390
+ template=(
391
+ "Task: {task_description}\n\n"
392
+ "Edit the following code{file_context}. If 'target_symbol' is provided, focus changes on that symbol.\n"
393
+ "Return ONLY the full modified file content in a single code block. No explanations.\n\n"
394
+ "Code:\n"
395
+ "```\n{code}\n```"
396
+ ),
397
+ system=CODE_GENERATION_SYSTEM_PROMPT,
398
+ defaults={
399
+ "code": "// No code provided",
400
+ "task_description": "Edit this file as described.",
401
+ "file_path": None,
402
+ "target_symbol": None,
403
+ },
404
+ ),
405
+ "agent": PromptTemplate(
406
+ template=(
407
+ "Task: {task}\n\n"
408
+ "Execute this task following the investigation protocol. "
409
+ "Use (cade thought) markers for thinking between steps."
410
+ ),
411
+ system=AGENT_SYSTEM_PROMPT,
412
+ defaults={"task": "Help me with a coding task."},
413
+ ),
414
+ "investigate": PromptTemplate(
415
+ template=(
416
+ "Investigate: {topic}\n\n"
417
+ "Context: {context}\n\n"
418
+ "SEARCH PROTOCOL:\n"
419
+ "1. Start with default scope, state boundaries if not found\n"
420
+ "2. Expand systematically: broader patterns, wider ranges, older data\n"
421
+ "3. Try alternative approaches: different tools, direct lookups\n"
422
+ "4. Document EACH search attempt with its scope\n"
423
+ "5. Only declare absence after listing ALL searches tried"
424
+ ),
425
+ system=AGENT_SYSTEM_PROMPT,
426
+ defaults={"topic": "Unknown topic", "context": "No context provided"},
427
+ ),
428
+ "search_progressive": PromptTemplate(
429
+ template=(
430
+ "Search for: {target}\n"
431
+ "Initial scope: {initial_scope}\n\n"
432
+ "If not found in initial scope:\n"
433
+ "- State: 'Not found in {initial_scope}, expanding to {next_scope}'\n"
434
+ "- Try: {expansion_strategy}\n"
435
+ "- Document each attempt\n"
436
+ "Never say 'doesn't exist' - say 'not found in [scopes tried]'"
437
+ ),
438
+ system=AGENT_SYSTEM_PROMPT,
439
+ defaults={
440
+ "target": "Unknown target",
441
+ "initial_scope": "default parameters",
442
+ "expansion_strategy": "wider search, variants, alternative tools",
443
+ },
444
+ ),
445
+ "execute_command": PromptTemplate(
446
+ template=(
447
+ "Execute the following shell command: {command}\n\n"
448
+ "Provide the output of the command as a markdown code block.\n\n"
449
+ ),
450
+ ),
451
+ }
452
+
453
+
454
+ # --- Formatting and Message Creation ---
455
+
456
+
457
+ def _format_prompt(template_str: str, values: dict[str, Any], defaults: dict[str, Any]) -> str:
458
+ """Format a prompt template string using provided values and defaults.
459
+
460
+ Performs placeholder replacement while inserting extra contextual
461
+ fragments (e.g., `file_context`, `error_context`) when the relevant
462
+ keys are present.
463
+ """
464
+ merged_values = {**defaults, **values}
465
+
466
+ # Handle special context fields based on optional values
467
+ if merged_values.get("file_path"):
468
+ merged_values["file_context"] = f" from the file '{merged_values['file_path']}'"
469
+ else:
470
+ merged_values["file_context"] = ""
471
+
472
+ if "error_message" in merged_values and merged_values["error_message"]:
473
+ merged_values["error_context"] = (
474
+ f"The code produces the following error:\n```\n{merged_values['error_message']}\n```\n\n"
475
+ )
476
+ elif "error_message" in merged_values:
477
+ # Key exists but value is None/empty
478
+ merged_values["error_context"] = (
479
+ "No specific error message provided. Analyze the code for potential issues.\n\n"
480
+ )
481
+
482
+ # Filter out keys that are not actual placeholders in the template string
483
+ relevant_values = {k: v for k, v in merged_values.items() if f"{{{k}}}" in template_str}
484
+
485
+ try:
486
+ return template_str.format(**relevant_values)
487
+ except KeyError as e:
488
+ # A placeholder exists in the template but no value was supplied.
489
+ print(f"Warning: Missing value for placeholder {e} in template.")
490
+ return template_str # Return unformatted template as a fallback
491
+
492
+
493
+ async def generate_prompt_messages(
494
+ template_name: str,
495
+ values: dict[str, Any],
496
+ tool_manager: "ToolManager | None" = None,
497
+ ) -> list[dict[str, str]]:
498
+ """Generate the list of messages required by the OpenAI Chat API.
499
+
500
+ Dynamically formats the AGENT_SYSTEM_PROMPT with:
501
+ - Environment context (paths, limits, config)
502
+ - Available tools list
503
+
504
+ Args:
505
+ template_name: Name of the prompt template (must exist in PROMPT_TEMPLATES)
506
+ values: Runtime values to substitute into the template placeholders
507
+ tool_manager: Optional ToolManager instance to retrieve available tools
508
+
509
+ Returns:
510
+ List of message objects in OpenAI Chat API format
511
+ """
512
+ template = PROMPT_TEMPLATES.get(template_name)
513
+ if not template:
514
+ raise ValueError(f"Prompt template '{template_name}' not found.")
515
+
516
+ # Format user prompt
517
+ user_prompt: str = _format_prompt(template.template, values, template.defaults)
518
+
519
+ messages: list[dict[str, str]] = []
520
+
521
+ if template.system:
522
+ system_prompt_content = template.system
523
+
524
+ # Build replacement values for system prompt placeholders
525
+ replacements: dict[str, str] = {}
526
+
527
+ # Generate environment context if placeholder exists
528
+ if "{_ENVIRONMENT_CONTEXT}" in template.system:
529
+ try:
530
+ replacements["_ENVIRONMENT_CONTEXT"] = get_environment_context()
531
+ except Exception as e:
532
+ log.warning(f"Failed to generate environment context: {e}")
533
+ replacements["_ENVIRONMENT_CONTEXT"] = "(Environment context unavailable)"
534
+
535
+ # Generate tools list if placeholder exists and tool_manager provided
536
+ if "{_TOOLS_BULLET_LIST}" in template.system:
537
+ tools_bullet_list = "(No tools available)"
538
+ if tool_manager:
539
+ try:
540
+ agent_tool_schemas = await tool_manager.get_tools()
541
+ if agent_tool_schemas:
542
+ tools_info = []
543
+ for i, schema in enumerate(agent_tool_schemas, start=1):
544
+ func_info = schema.get("function", {})
545
+ name = func_info.get("name", "Unknown Tool")
546
+ desc = func_info.get("description", "No description.")
547
+ # Truncate long descriptions
548
+ if len(desc) > 80:
549
+ desc = desc[:77] + "..."
550
+ tools_info.append(f" {i}. {name}: {desc}")
551
+ if tools_info:
552
+ tools_bullet_list = "\n".join(tools_info)
553
+ except Exception as e:
554
+ log.warning(f"Failed to list tools for agent prompt: {e}", exc_info=True)
555
+ tools_bullet_list = "(Error retrieving tools)"
556
+ replacements["_TOOLS_BULLET_LIST"] = tools_bullet_list
557
+
558
+ # Apply all replacements
559
+ if replacements:
560
+ try:
561
+ system_prompt_content = template.system.format(**replacements)
562
+ except KeyError as e:
563
+ log.warning(f"Missing placeholder value: {e}")
564
+ # Partial format - replace what we can
565
+ for key, value in replacements.items():
566
+ system_prompt_content = system_prompt_content.replace(f"{{{key}}}", value)
567
+
568
+ messages.append({"role": MessageRole.SYSTEM.value, "content": system_prompt_content})
569
+
570
+ messages.append({"role": MessageRole.USER.value, "content": user_prompt})
571
+
572
+ return messages
File without changes