formal-ai 0.186.0

Formal symbolic AI implementation with OpenAI-compatible APIs
//! Ordered dispatch table for the universal solver's specialized handlers.
//!
//! Extracted from `solver.rs` to keep that module under the repository line
//! limit. The table is the single source of truth for handler precedence: the
//! first handler that returns `Some` wins, and several tests rely on this
//! resolution order.

use crate::engine::SymbolicAnswer;
use crate::event_log::EventLog;
use crate::proof_engine::ProofRenderConfig;
use crate::solver::ConversationTurn;
use crate::solver_handler_docs::try_docs_method_explanation;
use crate::solver_handler_how::{try_how_it_works, try_how_to_procedure};
use crate::solver_handler_units::try_incompatible_units;
use crate::solver_handlers::{
    try_algorithm, try_arithmetic, try_brainstorming_request, try_calendar_reasoning,
    try_capabilities, try_clarification, try_compound_interest, try_concept_lookup,
    try_conversation_memory, try_conversation_topic_request, try_coreference_request,
    try_definition_merge, try_execution_failure, try_fact_lookup, try_http_fetch, try_ill_formed,
    try_javascript_execution, try_meta_explanation, try_meta_explanation_with_runtime,
    try_network_query, try_number_riddle, try_numeric_list, try_numeric_list_with_history,
    try_opinion_question, try_program_synthesis, try_proof_request, try_proof_request_with_config,
    try_punctuation_only_prompt, try_research_comparison_table, try_roleplay_request,
    try_shell_refusal, try_software_project_followup, try_software_project_request,
    try_source_conflict, try_source_refresh, try_summarization_request, try_text_manipulation,
    try_translation, try_url_navigate, try_web_search, try_who_is_question, try_write_script,
    SelfAwarenessRuntime,
};
use crate::solver_handlers_policy::{try_kupi_slona, try_physical_action_question};

/// Uniform signature every specialized handler conforms to. Handlers that
/// don't need `normalized` go through tiny adapter wrappers below so the
/// dispatch registry stays homogeneous and the loop in
/// `UniversalSolver::handle_specialized_pattern` remains a single line.
pub type SpecializedHandler = fn(&str, &str, &mut EventLog) -> Option<SymbolicAnswer>;

fn handle_arithmetic(
    prompt: &str,
    _normalized: &str,
    log: &mut EventLog,
) -> Option<SymbolicAnswer> {
    try_arithmetic(prompt, log)
}

fn handle_javascript_execution(
    prompt: &str,
    _normalized: &str,
    log: &mut EventLog,
) -> Option<SymbolicAnswer> {
    try_javascript_execution(prompt, log)
}

fn handle_concept_lookup(
    prompt: &str,
    _normalized: &str,
    log: &mut EventLog,
) -> Option<SymbolicAnswer> {
    try_concept_lookup(prompt, log)
}

/// Outcome of routing a handler name through [`try_contextual_override`].
pub enum ContextualOutcome {
    /// `name` is not a contextual handler; fall through to the registry lookup.
    NotHandled,
    /// The contextual handler produced an answer; the loop should return it.
    Answer(SymbolicAnswer),
    /// `name` is contextual but produced nothing; the loop should `continue`
    /// (these handlers never fall back to a plain registry variant).
    Skip,
}

/// A handful of specialized handlers need more than the uniform
/// `(prompt, normalized, log)` signature: they take a runtime/render config or
/// the conversation history. Rather than widen [`SpecializedHandler`] for every
/// handler, the dispatch loop routes those few names through this helper.
///
/// Extracted from `solver.rs` so that module stays under the repository line
/// limit; the three branches were previously inlined in
/// `UniversalSolver::handle_specialized_pattern`.
pub fn try_contextual_override(
    name: &str,
    prompt: &str,
    normalized: &str,
    history: &[ConversationTurn],
    proof_render_config: ProofRenderConfig,
    self_awareness_runtime: SelfAwarenessRuntime,
    log: &mut EventLog,
) -> ContextualOutcome {
    let answer = match name {
        "proof_request" => {
            try_proof_request_with_config(prompt, normalized, log, proof_render_config)
        }
        "meta_explanation" => {
            try_meta_explanation_with_runtime(prompt, normalized, log, self_awareness_runtime)
        }
        "numeric_list" => try_numeric_list_with_history(prompt, normalized, log, history),
        _ => return ContextualOutcome::NotHandled,
    };
    answer.map_or(ContextualOutcome::Skip, |answer| {
        log.append("specialized_handler", name.to_owned());
        ContextualOutcome::Answer(answer)
    })
}

/// Ordered dispatch table for the universal solver's specialized handlers.
///
/// Order matters: the first handler that returns `Some` wins, and several
/// downstream tests rely on the resolution order (for example, conversation
/// memory must trigger before the concept lookup when both could match).
/// New handlers should be slotted into the position that preserves intent
/// precedence rather than appended unconditionally.
pub const SPECIALIZED_HANDLERS: &[(&str, SpecializedHandler)] = &[
    ("http_fetch", try_http_fetch),
    ("url_navigate", try_url_navigate),
    ("web_search", try_web_search),
    ("research_comparison_table", try_research_comparison_table),
    ("docs_method_explanation", try_docs_method_explanation),
    ("procedural_how_to", try_how_to_procedure),
    ("conversation_memory", try_conversation_memory),
    // Issue #341: a decomposed agent step like "test it by scraping
    // wikipedia.org and show me the top 10 most frequent words" must stay
    // bound to the active software-project dialogue instead of resolving the
    // `wikipedia` concept or hitting the unknown opener. The handler only
    // fires when the previous assistant turn formalized a
    // `software_project_request`, so it sits above the general lookups.
    ("software_project_followup", try_software_project_followup),
    ("summarization", try_summarization_request),
    ("text_manipulation", try_text_manipulation),
    ("brainstorming", try_brainstorming_request),
    ("conversation_topic", try_conversation_topic_request),
    ("fact_lookup", try_fact_lookup),
    ("coreference", try_coreference_request),
    ("roleplay", try_roleplay_request),
    ("translation", try_translation),
    ("capabilities", try_capabilities),
    ("calendar_reasoning", try_calendar_reasoning),
    ("compound_interest", try_compound_interest),
    // Issue #395: a concrete "<operation> these numbers in <language>, give me
    // the code and the result" request must produce generated code plus the
    // deterministically-computed result. The universal numeric-list engine
    // covers sort/reverse_sort/reverse and the sum/product/minimum/maximum
    // reductions. It runs before `arithmetic` (which would otherwise claim the
    // numeric prompt) and before the generic, result-less `algorithm` handler.
    ("numeric_list", try_numeric_list),
    ("number_constraint_reasoning", try_number_riddle),
    ("arithmetic", handle_arithmetic),
    ("javascript_execution", handle_javascript_execution),
    ("definition_merge", try_definition_merge),
    ("concept_lookup", handle_concept_lookup),
    ("who_is", try_who_is_question),
    ("how_it_works", try_how_it_works),
    ("meta_explanation", try_meta_explanation),
    ("network_query", try_network_query),
    // `execution_failure` must run before `write_script`/`algorithm` so that
    // explicit failure prompts (e.g. "calls undefined_function()") surface a
    // failure trace instead of being silently transformed into a passing
    // hello-world snippet.
    ("execution_failure", try_execution_failure),
    ("write_script", try_write_script),
    ("program_synthesis", try_program_synthesis),
    ("software_project", try_software_project_request),
    ("algorithm", try_algorithm),
    ("source_refresh", try_source_refresh),
    ("source_conflict", try_source_conflict),
    ("clarification", try_clarification),
    ("punctuation_only_prompt", try_punctuation_only_prompt),
    ("ill_formed", try_ill_formed),
    ("physical_action_question", try_physical_action_question),
    ("kupi_slona", try_kupi_slona),
    ("shell_refusal", try_shell_refusal),
    // Proof requests must beat `opinion_question` so prompts like
    // "Do you think you can prove …" land on the formalization pipeline
    // explanation instead of the no-opinion policy.
    ("proof_request", try_proof_request),
    ("opinion_question", try_opinion_question),
    ("incompatible_units", try_incompatible_units),
];