676 lines
22 KiB
Markdown
676 lines
22 KiB
Markdown
# CheddahBot Tools Reference
|
|
|
|
## Overview
|
|
|
|
CheddahBot uses an extensible tool system that allows the LLM to invoke Python
|
|
functions during a conversation. Tools are registered via the `@tool` decorator
|
|
and auto-discovered at startup. The LLM receives tool schemas in OpenAI
|
|
function-calling format and can request tool invocations, which the agent
|
|
executes and feeds back into the conversation.
|
|
|
|
---
|
|
|
|
## Registered Tools
|
|
|
|
### Category: files
|
|
|
|
#### `read_file`
|
|
|
|
Read the contents of a file.
|
|
|
|
| Parameter | Type | Required | Description |
|
|
|-----------|--------|----------|------------------|
|
|
| `path` | string | Yes | Path to the file |
|
|
|
|
Returns the file contents as a string. Files larger than 50,000 characters are
|
|
truncated. Returns an error message if the file is not found or is not a regular
|
|
file.
|
|
|
|
---
|
|
|
|
#### `write_file`
|
|
|
|
Write content to a file (creates or overwrites).
|
|
|
|
| Parameter | Type | Required | Description |
|
|
|-----------|--------|----------|--------------------------------|
|
|
| `path` | string | Yes | Path to the file |
|
|
| `content` | string | Yes | Content to write to the file |
|
|
|
|
Creates parent directories automatically if they do not exist.
|
|
|
|
---
|
|
|
|
#### `edit_file`
|
|
|
|
Replace text in a file (first occurrence).
|
|
|
|
| Parameter | Type | Required | Description |
|
|
|------------|--------|----------|--------------------------|
|
|
| `path` | string | Yes | Path to the file |
|
|
| `old_text` | string | Yes | Text to find and replace |
|
|
| `new_text` | string | Yes | Replacement text |
|
|
|
|
Replaces only the first occurrence of `old_text`. Returns an error if the file
|
|
does not exist or the text is not found.
|
|
|
|
---
|
|
|
|
#### `list_directory`
|
|
|
|
List files and folders in a directory.
|
|
|
|
| Parameter | Type | Required | Description |
|
|
|-----------|--------|----------|--------------------------------------|
|
|
| `path` | string | No | Directory path (defaults to `"."`) |
|
|
|
|
Returns up to 200 entries, sorted with directories first. Each entry shows the
|
|
name and file size.
|
|
|
|
---
|
|
|
|
#### `search_files`
|
|
|
|
Search for files matching a glob pattern.
|
|
|
|
| Parameter | Type | Required | Description |
|
|
|-------------|--------|----------|--------------------------------------|
|
|
| `pattern` | string | Yes | Glob pattern (e.g. `"**/*.py"`) |
|
|
| `directory` | string | No | Root directory (defaults to `"."`) |
|
|
|
|
Returns up to 100 matching file paths.
|
|
|
|
---
|
|
|
|
#### `search_in_files`
|
|
|
|
Search for text content across files.
|
|
|
|
| Parameter | Type | Required | Description |
|
|
|-------------|--------|----------|--------------------------------------|
|
|
| `query` | string | Yes | Text to search for (case-insensitive)|
|
|
| `directory` | string | No | Root directory (defaults to `"."`) |
|
|
| `extension` | string | No | File extension filter (e.g. `".py"`) |
|
|
|
|
Returns up to 50 matches in `file:line: content` format. Skips files larger
|
|
than 1 MB.
|
|
|
|
---
|
|
|
|
### Category: shell
|
|
|
|
#### `run_command`
|
|
|
|
Execute a shell command and return output.
|
|
|
|
| Parameter | Type | Required | Description |
|
|
|-----------|---------|----------|------------------------------------------|
|
|
| `command` | string | Yes | Shell command to execute |
|
|
| `timeout` | integer | No | Timeout in seconds (default 30, max 120) |
|
|
|
|
Includes safety checks that block dangerous patterns:
|
|
- `rm -rf /`
|
|
- `format c:`
|
|
- `:(){:|:&};:` (fork bomb)
|
|
- `dd if=/dev/zero`
|
|
- `mkfs.`
|
|
- `> /dev/sda`
|
|
|
|
Output is truncated to 10,000 characters. Returns stdout, stderr, and exit code.
|
|
|
|
---
|
|
|
|
### Category: web
|
|
|
|
#### `web_search`
|
|
|
|
Search the web using DuckDuckGo.
|
|
|
|
| Parameter | Type | Required | Description |
|
|
|---------------|---------|----------|------------------------------------|
|
|
| `query` | string | Yes | Search query |
|
|
| `max_results` | integer | No | Number of results (default 5) |
|
|
|
|
Uses DuckDuckGo HTML search (no API key required). Returns formatted results
|
|
with title, URL, and snippet.
|
|
|
|
---
|
|
|
|
#### `fetch_url`
|
|
|
|
Fetch and extract text content from a URL.
|
|
|
|
| Parameter | Type | Required | Description |
|
|
|-----------|--------|----------|----------------|
|
|
| `url` | string | Yes | URL to fetch |
|
|
|
|
For HTML pages: strips script, style, nav, footer, and header elements, then
|
|
extracts text (truncated to 15,000 characters). For JSON responses: returns raw
|
|
JSON (truncated to 15,000 characters). For other content types: returns raw text
|
|
(truncated to 5,000 characters).
|
|
|
|
---
|
|
|
|
### Category: code
|
|
|
|
#### `run_python`
|
|
|
|
Execute Python code and return the output.
|
|
|
|
| Parameter | Type | Required | Description |
|
|
|-----------|---------|----------|------------------------------------------|
|
|
| `code` | string | Yes | Python code to execute |
|
|
| `timeout` | integer | No | Timeout in seconds (default 30, max 60) |
|
|
|
|
Writes the code to a temporary file and runs it as a subprocess using the same
|
|
Python interpreter that CheddahBot is running on. The temp file is deleted after
|
|
execution. Output is truncated to 10,000 characters.
|
|
|
|
---
|
|
|
|
### Category: memory
|
|
|
|
#### `remember_this`
|
|
|
|
Save an important fact or instruction to long-term memory.
|
|
|
|
| Parameter | Type | Required | Description |
|
|
|-----------|--------|----------|---------------------------------|
|
|
| `text` | string | Yes | The fact or instruction to save |
|
|
|
|
Appends a timestamped entry to `memory/MEMORY.md` and indexes it in the
|
|
embedding database for future semantic search.
|
|
|
|
---
|
|
|
|
#### `search_memory`
|
|
|
|
Search through saved memories.
|
|
|
|
| Parameter | Type | Required | Description |
|
|
|-----------|--------|----------|-------------------|
|
|
| `query` | string | Yes | Search query text |
|
|
|
|
Performs semantic search (or keyword fallback) over all indexed memory entries.
|
|
Returns results with similarity scores.
|
|
|
|
---
|
|
|
|
#### `log_note`
|
|
|
|
Add a timestamped note to today's daily log.
|
|
|
|
| Parameter | Type | Required | Description |
|
|
|-----------|--------|----------|------------------------|
|
|
| `text` | string | Yes | Note text to log |
|
|
|
|
Appends to `memory/YYYY-MM-DD.md` (today's date) and indexes the text for
|
|
semantic search.
|
|
|
|
---
|
|
|
|
### Category: scheduling
|
|
|
|
#### `schedule_task`
|
|
|
|
Schedule a recurring or one-time task.
|
|
|
|
| Parameter | Type | Required | Description |
|
|
|------------|--------|----------|---------------------------------------------------|
|
|
| `name` | string | Yes | Human-readable task name |
|
|
| `prompt` | string | Yes | The prompt to send to the agent when the task runs|
|
|
| `schedule` | string | Yes | Cron expression or `"once:YYYY-MM-DDTHH:MM"` |
|
|
|
|
Examples:
|
|
- `schedule="0 9 * * *"` -- every day at 9:00 AM UTC
|
|
- `schedule="once:2026-03-01T14:00"` -- one-time execution
|
|
|
|
---
|
|
|
|
#### `list_tasks`
|
|
|
|
List all scheduled tasks.
|
|
|
|
| Parameter | Type | Required | Description |
|
|
|-----------|------|----------|-------------|
|
|
| (none) | | | |
|
|
|
|
Returns all tasks with their ID, name, schedule, enabled status, and next run
|
|
time.
|
|
|
|
---
|
|
|
|
### Category: media
|
|
|
|
#### `analyze_image`
|
|
|
|
Describe or analyze an image file.
|
|
|
|
| Parameter | Type | Required | Description |
|
|
|------------|--------|----------|------------------------------------------------|
|
|
| `path` | string | Yes | Path to the image file |
|
|
| `question` | string | No | Question about the image (default: "Describe this image in detail.") |
|
|
|
|
Reads the image, base64-encodes it, and sends it to the current LLM as a
|
|
multimodal message. Supports PNG, JPEG, GIF, WebP, and BMP formats. Requires a
|
|
vision-capable model.
|
|
|
|
---
|
|
|
|
### Category: data
|
|
|
|
#### `read_csv`
|
|
|
|
Read a CSV file and return summary or specific rows.
|
|
|
|
| Parameter | Type | Required | Description |
|
|
|------------|---------|----------|-----------------------------------------|
|
|
| `path` | string | Yes | Path to the CSV file |
|
|
| `max_rows` | integer | No | Maximum rows to display (default 20) |
|
|
|
|
Returns the data formatted as a Markdown table, with a count of total rows if
|
|
the file is larger than `max_rows`.
|
|
|
|
---
|
|
|
|
#### `read_json`
|
|
|
|
Read and pretty-print a JSON file.
|
|
|
|
| Parameter | Type | Required | Description |
|
|
|-----------|--------|----------|-----------------------|
|
|
| `path` | string | Yes | Path to the JSON file |
|
|
|
|
Returns the JSON content pretty-printed with 2-space indentation. Truncated to
|
|
15,000 characters.
|
|
|
|
---
|
|
|
|
#### `query_json`
|
|
|
|
Extract data from a JSON file using a dot-notation path.
|
|
|
|
| Parameter | Type | Required | Description |
|
|
|-------------|--------|----------|--------------------------------------|
|
|
| `path` | string | Yes | Path to the JSON file |
|
|
| `json_path` | string | Yes | Dot-notation path (e.g. `"data.users.0.name"`) |
|
|
|
|
Supports `*` as a wildcard for arrays. For example, `"results.*.id"` returns
|
|
the full array at `results`.
|
|
|
|
---
|
|
|
|
### Category: delivery
|
|
|
|
#### `email_file`
|
|
|
|
Email a file as an attachment via Gmail SMTP.
|
|
|
|
| Parameter | Type | Required | Description |
|
|
|-------------|--------|----------|--------------------------------------------------|
|
|
| `file_path` | string | Yes | Path to the file to send |
|
|
| `to` | string | No | Recipient address (defaults to `EMAIL_DEFAULT_TO`)|
|
|
| `subject` | string | No | Email subject (defaults to filename) |
|
|
|
|
If the file is `.txt`, it is automatically converted to `.docx` before sending.
|
|
Requires `GMAIL_USERNAME` and `GMAIL_APP_PASSWORD` in `.env`.
|
|
|
|
---
|
|
|
|
### Category: content
|
|
|
|
#### `write_press_releases`
|
|
|
|
Full autonomous press-release pipeline.
|
|
|
|
| Parameter | Type | Required | Description |
|
|
|-------------------|--------|----------|------------------------------------------------|
|
|
| `topic` | string | Yes | Press release topic |
|
|
| `company_name` | string | Yes | Company name |
|
|
| `url` | string | No | Reference URL for context |
|
|
| `lsi_terms` | string | No | LSI keywords to integrate |
|
|
| `required_phrase` | string | No | Exact phrase to include once |
|
|
|
|
Generates 7 headlines, AI-picks the best 2, writes 2 full press releases
|
|
(600-750 words each), generates JSON-LD schema for each, and saves all files.
|
|
Output includes `.txt`, `.docx` (Google Docs-ready), and `.json` schema files
|
|
in `data/generated/press_releases/{company}/`.
|
|
|
|
---
|
|
|
|
### Category: meta
|
|
|
|
#### `build_tool`
|
|
|
|
Create a new tool from a description. The agent writes Python code with the
|
|
`@tool` decorator.
|
|
|
|
| Parameter | Type | Required | Description |
|
|
|---------------|--------|----------|----------------------------------------------|
|
|
| `name` | string | Yes | Tool name in snake_case |
|
|
| `description` | string | Yes | What the tool does |
|
|
| `code` | string | Yes | Python code with `@tool` decorator |
|
|
|
|
See the dedicated section below for details.
|
|
|
|
---
|
|
|
|
#### `build_skill`
|
|
|
|
Create a new multi-step skill from a description.
|
|
|
|
| Parameter | Type | Required | Description |
|
|
|---------------|--------|----------|----------------------------------------------|
|
|
| `name` | string | Yes | Skill name in snake_case |
|
|
| `description` | string | Yes | What the skill does |
|
|
| `steps` | string | Yes | Python code with `@skill` decorator |
|
|
|
|
See the dedicated section below for details.
|
|
|
|
---
|
|
|
|
## How to Create Custom Tools Using `@tool`
|
|
|
|
### Step 1: Create a Python file in `cheddahbot/tools/`
|
|
|
|
The file name does not matter (as long as it does not start with `_`), but by
|
|
convention it should describe the category of tools it contains.
|
|
|
|
### Step 2: Import the decorator and define your function
|
|
|
|
```python
|
|
"""My custom tools."""
|
|
from __future__ import annotations
|
|
from . import tool
|
|
|
|
|
|
@tool("greet_user", "Greet a user by name", category="social")
|
|
def greet_user(name: str, enthusiasm: int = 1) -> str:
|
|
exclamation = "!" * enthusiasm
|
|
return f"Hello, {name}{exclamation}"
|
|
```
|
|
|
|
### Decorator Signature
|
|
|
|
```python
|
|
@tool(name: str, description: str, category: str = "general")
|
|
```
|
|
|
|
- `name` -- The tool name the LLM will use to invoke it. Must be unique across
|
|
all registered tools.
|
|
- `description` -- A short description shown to the LLM in the system prompt
|
|
and in the tool schema.
|
|
- `category` -- A grouping label for organizing tools in the system prompt.
|
|
|
|
### Function Requirements
|
|
|
|
- The return type should be `str`. If the function returns a non-string value,
|
|
it is converted via `str()`. If it returns `None`, the result is `"Done."`.
|
|
- Type annotations on parameters are used to generate the JSON Schema:
|
|
- `str` -> `"string"`
|
|
- `int` -> `"integer"`
|
|
- `float` -> `"number"`
|
|
- `bool` -> `"boolean"`
|
|
- `list` -> `"array"` (of strings)
|
|
- No annotation -> `"string"`
|
|
- Parameters with default values are optional in the schema. Parameters without
|
|
defaults are required.
|
|
- To access the agent's runtime context (config, database, memory system, agent
|
|
instance), add a `ctx: dict = None` parameter. The tool registry will
|
|
automatically inject a dictionary with keys `"config"`, `"db"`, `"agent"`,
|
|
and `"memory"`.
|
|
|
|
### Step 3: Restart CheddahBot
|
|
|
|
The tool module is auto-discovered on startup. No additional registration code
|
|
is needed.
|
|
|
|
### Full Example with Context Access
|
|
|
|
```python
|
|
"""Tools that interact with the database."""
|
|
from __future__ import annotations
|
|
from . import tool
|
|
|
|
|
|
@tool("count_conversations", "Count total conversations in the database", category="stats")
|
|
def count_conversations(ctx: dict = None) -> str:
|
|
if not ctx or not ctx.get("db"):
|
|
return "Database not available."
|
|
row = ctx["db"]._conn.execute("SELECT COUNT(*) as cnt FROM conversations").fetchone()
|
|
return f"Total conversations: {row['cnt']}"
|
|
|
|
|
|
@tool("get_setting", "Retrieve a value from the key-value store", category="config")
|
|
def get_setting(key: str, ctx: dict = None) -> str:
|
|
if not ctx or not ctx.get("db"):
|
|
return "Database not available."
|
|
value = ctx["db"].kv_get(key)
|
|
if value is None:
|
|
return f"No value found for key: {key}"
|
|
return f"{key} = {value}"
|
|
```
|
|
|
|
---
|
|
|
|
## How `build_tool` (Meta-Tool) Works
|
|
|
|
The `build_tool` tool allows the LLM to create new tools at runtime without
|
|
restarting the application. This is the mechanism by which you can ask the agent
|
|
"create a tool that does X" and it will write, save, and hot-load the tool.
|
|
|
|
### Internal Process
|
|
|
|
1. **Validation** -- The tool name must be a valid Python identifier.
|
|
2. **Code wrapping** -- The provided `code` parameter is wrapped in a module
|
|
template that adds the necessary `from . import tool` import.
|
|
3. **File creation** -- The module is written to
|
|
`cheddahbot/tools/<name>.py`. If a file with that name already exists, the
|
|
operation is rejected.
|
|
4. **Hot-loading** -- `importlib.import_module()` imports the new module. This
|
|
triggers the `@tool` decorator inside the code, which registers the tool in
|
|
the global `_TOOLS` dictionary.
|
|
5. **Cleanup on failure** -- If the import fails (syntax error, import error,
|
|
etc.), the file is deleted to avoid leaving broken modules.
|
|
|
|
### What the LLM Generates
|
|
|
|
When the LLM calls `build_tool`, it provides:
|
|
|
|
- `name`: e.g. `"word_count"`
|
|
- `description`: e.g. `"Count words in a text string"`
|
|
- `code`: The body of the tool function, including the `@tool` decorator:
|
|
|
|
```python
|
|
@tool("word_count", "Count words in a text string", category="text")
|
|
def word_count(text: str) -> str:
|
|
count = len(text.split())
|
|
return f"Word count: {count}"
|
|
```
|
|
|
|
The `build_tool` function wraps this in the necessary imports and writes it to
|
|
disk.
|
|
|
|
### Persistence
|
|
|
|
Because the tool is saved as a `.py` file in the tools directory, it survives
|
|
application restarts. On the next startup, auto-discovery will find and load it
|
|
like any other built-in tool.
|
|
|
|
---
|
|
|
|
## How `build_skill` Works
|
|
|
|
The `build_skill` tool creates multi-step skills -- higher-level operations that
|
|
can orchestrate multiple actions.
|
|
|
|
### Internal Process
|
|
|
|
1. **Validation** -- The skill name must be a valid Python identifier.
|
|
2. **Code wrapping** -- The provided `steps` parameter is wrapped in a module
|
|
template that adds `from cheddahbot.skills import skill`.
|
|
3. **File creation** -- The module is written to
|
|
`skills/<name>.py` (the project-level skills directory, not inside the
|
|
package).
|
|
4. **Dynamic loading** -- `skills.load_skill()` uses
|
|
`importlib.util.spec_from_file_location` to load the module from the file
|
|
path, triggering the `@skill` decorator.
|
|
|
|
### The `@skill` Decorator
|
|
|
|
```python
|
|
from cheddahbot.skills import skill
|
|
|
|
@skill("my_skill", "Description of what this skill does")
|
|
def my_skill(**kwargs) -> str:
|
|
# Multi-step logic here
|
|
return "Skill completed."
|
|
```
|
|
|
|
Skills are registered in the global `_SKILLS` dictionary and can be listed
|
|
with `skills.list_skills()` and executed with `skills.run_skill(name, **kwargs)`.
|
|
|
|
### Difference Between Tools and Skills
|
|
|
|
| Aspect | Tools | Skills |
|
|
|------------|------------------------------------------|---------------------------------------|
|
|
| Invoked by | The LLM (via function calling) | Code or agent internally |
|
|
| Schema | OpenAI function-calling JSON schema | No schema; free-form kwargs |
|
|
| Location | `cheddahbot/tools/` (inside the package) | `skills/` (project-level directory) |
|
|
| Purpose | Single focused operations | Multi-step workflows |
|
|
|
|
---
|
|
|
|
## Example: Creating a Custom Tool Manually
|
|
|
|
Suppose you want a tool that converts temperatures between Fahrenheit and
|
|
Celsius.
|
|
|
|
### 1. Create the file
|
|
|
|
Create `cheddahbot/tools/temperature.py`:
|
|
|
|
```python
|
|
"""Temperature conversion tools."""
|
|
from __future__ import annotations
|
|
from . import tool
|
|
|
|
|
|
@tool("convert_temperature", "Convert temperature between Fahrenheit and Celsius", category="utility")
|
|
def convert_temperature(value: float, from_unit: str = "F") -> str:
|
|
"""Convert a temperature value.
|
|
|
|
Args:
|
|
value: The temperature value to convert
|
|
from_unit: Source unit - 'F' for Fahrenheit, 'C' for Celsius
|
|
"""
|
|
from_unit = from_unit.upper()
|
|
if from_unit == "F":
|
|
celsius = (value - 32) * 5 / 9
|
|
return f"{value} F = {celsius:.1f} C"
|
|
elif from_unit == "C":
|
|
fahrenheit = value * 9 / 5 + 32
|
|
return f"{value} C = {fahrenheit:.1f} F"
|
|
else:
|
|
return f"Unknown unit: {from_unit}. Use 'F' or 'C'."
|
|
```
|
|
|
|
### 2. Restart CheddahBot
|
|
|
|
The tool is auto-discovered. The LLM will see it in the system prompt:
|
|
|
|
```
|
|
### Utility
|
|
- **convert_temperature**(value, from_unit): Convert temperature between Fahrenheit and Celsius
|
|
```
|
|
|
|
### 3. Use it in conversation
|
|
|
|
> User: "What is 72 degrees Fahrenheit in Celsius?"
|
|
>
|
|
> The LLM calls `convert_temperature(value=72.0, from_unit="F")` and receives
|
|
> `"72.0 F = 22.2 C"`, which it incorporates into its response.
|
|
|
|
---
|
|
|
|
## Example: Asking the Agent to Create a Tool via `build_tool`
|
|
|
|
You can ask the agent to create a tool for you in natural language. The agent
|
|
will use the `build_tool` meta-tool to generate and register it.
|
|
|
|
### Conversation
|
|
|
|
> **User:** Create a tool that counts the number of lines in a file.
|
|
>
|
|
> **Agent:** I'll create that tool for you.
|
|
>
|
|
> *[Uses tool: build_tool]*
|
|
> ```
|
|
> name: "count_lines"
|
|
> description: "Count the number of lines in a file"
|
|
> code:
|
|
> @tool("count_lines", "Count the number of lines in a file", category="files")
|
|
> def count_lines(path: str) -> str:
|
|
> from pathlib import Path
|
|
> p = Path(path).resolve()
|
|
> if not p.exists():
|
|
> return f"File not found: {path}"
|
|
> if not p.is_file():
|
|
> return f"Not a file: {path}"
|
|
> try:
|
|
> content = p.read_text(encoding="utf-8", errors="replace")
|
|
> line_count = len(content.splitlines())
|
|
> return f"{path}: {line_count} lines"
|
|
> except Exception as e:
|
|
> return f"Error: {e}"
|
|
> ```
|
|
> *Tool 'count_lines' created and loaded successfully.*
|
|
>
|
|
> Done. The `count_lines` tool is now available. You can ask me to count lines
|
|
> in any file, or I'll use it automatically when relevant.
|
|
|
|
### What Happened Behind the Scenes
|
|
|
|
1. The agent decided `build_tool` was the right tool for the request.
|
|
2. It generated valid Python code including the `@tool` decorator.
|
|
3. `build_tool` wrote `cheddahbot/tools/count_lines.py` with the wrapped code.
|
|
4. `importlib.import_module` loaded the module, triggering registration.
|
|
5. The tool is now available for the LLM to use in the current session and all
|
|
future sessions.
|
|
|
|
### Verifying the Tool Exists
|
|
|
|
After creation, the tool appears in:
|
|
|
|
- The system prompt's tools section (on the next turn).
|
|
- The output of `ToolRegistry.get_tools_schema()`.
|
|
- The file system at `cheddahbot/tools/count_lines.py`.
|
|
|
|
---
|
|
|
|
## Tool Registry API Summary
|
|
|
|
### `ToolRegistry(config, db, agent)`
|
|
|
|
Constructor. Auto-discovers and imports all tool modules.
|
|
|
|
### `ToolRegistry.get_tools_schema() -> list[dict]`
|
|
|
|
Returns all tools as OpenAI function-calling JSON schema objects.
|
|
|
|
### `ToolRegistry.get_tools_description() -> str`
|
|
|
|
Returns a human-readable Markdown string listing all tools organized by
|
|
category. This is injected into the system prompt.
|
|
|
|
### `ToolRegistry.execute(name, args) -> str`
|
|
|
|
Executes a tool by name with the given arguments. Returns the result as a
|
|
string. Automatically injects the `ctx` context dict if the tool function
|
|
accepts one.
|
|
|
|
### `ToolRegistry.register_external(tool_def)`
|
|
|
|
Manually registers a `ToolDef` object. Used for programmatic tool registration
|
|
outside the `@tool` decorator.
|