navi-core 0.3.6

Local agentic engine and terminal-first coding agent.
Documentation
use anyhow::Result;
use async_trait::async_trait;
use serde_json::json;

use super::helpers;
use crate::tool::{Tool, ToolDefinition, ToolInvocation, ToolKind, ToolResult};

pub(crate) struct QuestionTool;

#[async_trait]
impl Tool for QuestionTool {
    fn definition(&self) -> ToolDefinition {
        helpers::definition(
            "question",
            "Ask the user a question when input is needed to proceed.\n\
             - Multiple choice: pass `options` (and optional multiple/custom).\n\
             - Free-form: omit `options` and set `freeform` true (or pass title/description for long prompts).\n\
             Prefer this over request_user_input (hidden alias).",
            ToolKind::Read,
            json!({
                "type": "object",
                "properties": {
                    "question": {
                        "type": "string",
                        "description": "The question shown to the user. Also accepts free-form prompts when options are omitted."
                    },
                    "title": {
                        "type": "string",
                        "description": "Optional short title (free-form / request_user_input compatibility)."
                    },
                    "description": {
                        "type": "string",
                        "description": "Optional longer description (free-form compatibility). Used when question is empty."
                    },
                    "options": {
                        "type": "array",
                        "minItems": 1,
                        "items": {
                            "oneOf": [
                                { "type": "string" },
                                {
                                    "type": "object",
                                    "properties": {
                                        "label": { "type": "string" },
                                        "description": { "type": "string" }
                                    },
                                    "required": ["label"],
                                    "additionalProperties": false
                                }
                            ]
                        },
                        "description": "Options for multiple choice. Omit for free-form text input."
                    },
                    "multiple": {
                        "type": "boolean",
                        "description": "When true, the user may select more than one option. Defaults to false."
                    },
                    "custom": {
                        "type": "boolean",
                        "description": "When true, allow the user to type a custom answer alongside options. Defaults to false."
                    },
                    "freeform": {
                        "type": "boolean",
                        "description": "When true (or when options are omitted), request free-form text input instead of a fixed choice list."
                    },
                    "required": {
                        "type": "boolean",
                        "description": "Whether the user must answer before continuing (free-form). Defaults to true."
                    }
                },
                "required": [],
                "additionalProperties": false,
            }),
        )
    }

    async fn invoke(&self, invocation: ToolInvocation) -> Result<ToolResult> {
        Ok(ToolResult {
            invocation_id: invocation.id,
            ok: false,
            output: helpers::tool_error(
                "interactive_question_unavailable",
                "question requires an interactive client",
                true,
                Some(
                    "Run this turn from the TUI or another client that supports question resolution.",
                ),
                None,
            ),
        })
    }
}