| |
| """ |
| Clarify Tool Module - Interactive Clarifying Questions |
| |
| Allows the agent to present structured multiple-choice questions or open-ended |
| prompts to the user. In CLI mode, choices are navigable with arrow keys. On |
| messaging platforms, choices are rendered as a numbered list. |
| |
| The actual user-interaction logic lives in the platform layer (cli.py for CLI, |
| gateway/run.py for messaging). This module defines the schema, validation, and |
| a thin dispatcher that delegates to a platform-provided callback. |
| """ |
|
|
| import json |
| from typing import Dict, Any, List, Optional, Callable |
|
|
|
|
| |
| |
| MAX_CHOICES = 4 |
|
|
|
|
| def clarify_tool( |
| question: str, |
| choices: Optional[List[str]] = None, |
| callback: Optional[Callable] = None, |
| ) -> str: |
| """ |
| Ask the user a question, optionally with multiple-choice options. |
| |
| Args: |
| question: The question text to present. |
| choices: Up to 4 predefined answer choices. When omitted the |
| question is purely open-ended. |
| callback: Platform-provided function that handles the actual UI |
| interaction. Signature: callback(question, choices) -> str. |
| Injected by the agent runner (cli.py / gateway). |
| |
| Returns: |
| JSON string with the user's response. |
| """ |
| if not question or not question.strip(): |
| return json.dumps({"error": "Question text is required."}, ensure_ascii=False) |
|
|
| question = question.strip() |
|
|
| |
| if choices is not None: |
| if not isinstance(choices, list): |
| return json.dumps({"error": "choices must be a list of strings."}, ensure_ascii=False) |
| choices = [str(c).strip() for c in choices if str(c).strip()] |
| if len(choices) > MAX_CHOICES: |
| choices = choices[:MAX_CHOICES] |
| if not choices: |
| choices = None |
|
|
| if callback is None: |
| return json.dumps( |
| {"error": "Clarify tool is not available in this execution context."}, |
| ensure_ascii=False, |
| ) |
|
|
| try: |
| user_response = callback(question, choices) |
| except Exception as exc: |
| return json.dumps( |
| {"error": f"Failed to get user input: {exc}"}, |
| ensure_ascii=False, |
| ) |
|
|
| return json.dumps({ |
| "question": question, |
| "choices_offered": choices, |
| "user_response": str(user_response).strip(), |
| }, ensure_ascii=False) |
|
|
|
|
| def check_clarify_requirements() -> bool: |
| """Clarify tool has no external requirements -- always available.""" |
| return True |
|
|
|
|
| |
| |
| |
|
|
| CLARIFY_SCHEMA = { |
| "name": "clarify", |
| "description": ( |
| "Ask the user a question when you need clarification, feedback, or a " |
| "decision before proceeding. Supports two modes:\n\n" |
| "1. **Multiple choice** — provide up to 4 choices. The user picks one " |
| "or types their own answer via a 5th 'Other' option.\n" |
| "2. **Open-ended** — omit choices entirely. The user types a free-form " |
| "response.\n\n" |
| "Use this tool when:\n" |
| "- The task is ambiguous and you need the user to choose an approach\n" |
| "- You want post-task feedback ('How did that work out?')\n" |
| "- You want to offer to save a skill or update memory\n" |
| "- A decision has meaningful trade-offs the user should weigh in on\n\n" |
| "Do NOT use this tool for simple yes/no confirmation of dangerous " |
| "commands (the terminal tool handles that). Prefer making a reasonable " |
| "default choice yourself when the decision is low-stakes." |
| ), |
| "parameters": { |
| "type": "object", |
| "properties": { |
| "question": { |
| "type": "string", |
| "description": "The question to present to the user.", |
| }, |
| "choices": { |
| "type": "array", |
| "items": {"type": "string"}, |
| "maxItems": MAX_CHOICES, |
| "description": ( |
| "Up to 4 answer choices. Omit this parameter entirely to " |
| "ask an open-ended question. When provided, the UI " |
| "automatically appends an 'Other (type your answer)' option." |
| ), |
| }, |
| }, |
| "required": ["question"], |
| }, |
| } |
|
|
|
|
| |
| from tools.registry import registry |
|
|
| registry.register( |
| name="clarify", |
| toolset="clarify", |
| schema=CLARIFY_SCHEMA, |
| handler=lambda args, **kw: clarify_tool( |
| question=args.get("question", ""), |
| choices=args.get("choices"), |
| callback=kw.get("callback")), |
| check_fn=check_clarify_requirements, |
| emoji="❓", |
| ) |
|
|