// Force strict mode and setup for ESM
"use strict";
import {
  DiscoveredMCPTool
} from "./chunk-O7HNDMMU.js";
import "./chunk-3PJXIDKI.js";
import "./chunk-UWCTAVOD.js";
import "./chunk-OFEVLU4C.js";
import "./chunk-OMX7CUOE.js";
import "./chunk-BNXANYLH.js";
import "./chunk-T2VDEFUU.js";
import "./chunk-PO5LVPK3.js";
import {
  BaseDeclarativeTool,
  BaseToolInvocation,
  ToolDisplayNames,
  ToolNames
} from "./chunk-PLY4F7QW.js";
import "./chunk-3UALQ56H.js";
import "./chunk-CGEGHF2C.js";
import "./chunk-EJYJYJZ2.js";
import "./chunk-55ZMG67I.js";
import "./chunk-H6BD2ELD.js";
import "./chunk-5IFG2VC4.js";
import {
  createDebugLogger
} from "./chunk-C4K3FEQ2.js";
import "./chunk-ZERZSAZL.js";
import "./chunk-QN5NZ3UQ.js";
import "./chunk-BR4QREVK.js";
import "./chunk-Z2Z3GUXZ.js";
import {
  init_esbuild_shims
} from "./chunk-A4BMJM77.js";
import {
  __name
} from "./chunk-J2S4EL5Y.js";

// packages/core/src/tools/tool-search.ts
init_esbuild_shims();
var debugLogger = createDebugLogger("TOOL_SEARCH");
var DEFAULT_MAX_RESULTS = 5;
var HARD_MAX_RESULTS = 20;
var SCORE_NAME_EXACT_BUILTIN = 10;
var SCORE_NAME_SUBSTR_BUILTIN = 5;
var SCORE_HINT_BUILTIN = 4;
var SCORE_DESC_BUILTIN = 2;
var SCORE_NAME_EXACT_MCP = 12;
var SCORE_NAME_SUBSTR_MCP = 6;
var toolSearchDescription = `Fetches function declarations for deferred tools and registers them with the active session so subsequent turns can call them.

Deferred tools appear by name in the deferred-tools startup reminder. Until fetched, only the name is known \u2014 there is no parameter schema, so the tool cannot be invoked. This tool takes a query, matches it against the deferred tool list, and returns the matched tools' function declarations (name + description + parameter schema) inside a <functions> block.

The returned <functions> block is informational \u2014 it shows what the schema looks like. Calling the tool itself happens via the model's normal function-call mechanism on the NEXT turn, after the active session's declaration list has been updated. Tools fetched here remain available for the rest of the session.

Query forms:
- "select:ToolA,ToolB" \u2014 fetch these exact tools by name
- "keyword phrase" \u2014 keyword search, up to max_results best matches
- "+must-word other" \u2014 require "must-word" in the name, rank remaining terms
`;
var ToolSearchInvocation = class extends BaseToolInvocation {
  constructor(config, params) {
    super(params);
    this.config = config;
  }
  static {
    __name(this, "ToolSearchInvocation");
  }
  getDescription() {
    return this.params.query;
  }
  async execute(_signal) {
    const query = (this.params.query ?? "").trim();
    if (!query) {
      return {
        llmContent: "Error: query is empty. Use `select:ToolName` or free-text keywords.",
        returnDisplay: "Empty query",
        error: { message: "Empty query" }
      };
    }
    const maxResults = clamp(
      this.params.max_results ?? DEFAULT_MAX_RESULTS,
      1,
      HARD_MAX_RESULTS
    );
    if (query.toLowerCase().startsWith("select:")) {
      const seen = /* @__PURE__ */ new Set();
      const names = [];
      const truncated = [];
      for (const raw of query.slice("select:".length).split(",")) {
        const stripped = stripMatchingQuotes(raw.trim());
        if (!stripped) continue;
        const key = stripped.toLowerCase();
        if (seen.has(key)) continue;
        seen.add(key);
        if (names.length >= maxResults) {
          truncated.push(stripped);
          continue;
        }
        names.push(stripped);
      }
      return this.loadAndReturnSchemas(names, truncated);
    }
    const terms = tokenize(query);
    const requiredTerms = terms.filter((t) => t.startsWith("+")).map((t) => t.slice(1)).filter((t) => t.length > 0);
    const searchTerms = terms.map((t) => t.startsWith("+") ? t.slice(1) : t).filter((t) => t.length > 0);
    if (searchTerms.length === 0) {
      return {
        llmContent: "Error: no search terms extracted from query. Use `select:ToolName` or include keywords.",
        returnDisplay: "No search terms",
        error: { message: "No search terms" }
      };
    }
    const candidates = this.collectCandidates();
    const scored = [];
    for (const tool of candidates) {
      if (!candidateMatchesRequired(tool, requiredTerms)) continue;
      const score = scoreTool(tool, searchTerms);
      if (score > 0) scored.push({ tool, score });
    }
    scored.sort((a, b) => {
      if (b.score !== a.score) return b.score - a.score;
      return a.tool.name.localeCompare(b.tool.name);
    });
    const matches = scored.slice(0, maxResults).map((s) => s.tool.name);
    if (matches.length === 0) {
      return {
        llmContent: `No tools found matching '${query}'. Try broader keywords or use \`select:ToolName\`.`,
        returnDisplay: `No matches for '${query}'`
      };
    }
    return this.loadAndReturnSchemas(matches);
  }
  /**
   * Candidates for keyword search: only deferred tools that have NOT yet
   * been revealed this session. Already-loaded (core) tools are in the
   * model's tool-declaration list already, so surfacing them here would
   * be noise. Already-revealed deferred tools were loaded via a prior
   * `select:` or keyword search and ARE in the declaration list too —
   * re-surfacing them in subsequent searches wastes tokens and risks
   * the model retrying a tool it already has.
   *
   * `select:<name>` mode is unrestricted — the model may legitimately
   * want to re-inspect the schema of a loaded tool — and handles its
   * own lookup via {@link loadAndReturnSchemas}.
   */
  collectCandidates() {
    const registry = this.config.getToolRegistry();
    return registry.getAllTools().filter(
      (t) => t.shouldDefer && !t.alwaysLoad && !registry.isDeferredToolRevealed(t.name)
    );
  }
  async loadAndReturnSchemas(names, truncated = []) {
    if (names.length === 0) {
      return {
        llmContent: "Error: no tool names provided.",
        returnDisplay: "No tool names",
        error: { message: "No tool names" }
      };
    }
    const registry = this.config.getToolRegistry();
    const loaded = [];
    const missing = [];
    const lowerIndex = /* @__PURE__ */ new Map();
    for (const realName of registry.getAllToolNames()) {
      lowerIndex.set(realName.toLowerCase(), realName);
    }
    const newlyRevealed = [];
    for (const requested of names) {
      const canonical = lowerIndex.get(requested.toLowerCase());
      if (!canonical) {
        missing.push(requested);
        continue;
      }
      let tool;
      try {
        tool = await registry.ensureTool(canonical);
      } catch (err) {
        debugLogger.warn(`ensureTool failed for ${canonical}:`, err);
        process.stderr.write(
          `[ToolSearch] ensureTool failed for "${canonical}": ${err instanceof Error ? err.message : String(err)}
`
        );
      }
      if (!tool) {
        missing.push(requested);
        continue;
      }
      const isLoadable = tool.shouldDefer && !tool.alwaysLoad;
      if (isLoadable) {
        const wasRevealed = registry.isDeferredToolRevealed(canonical);
        registry.revealDeferredTool(canonical);
        if (!wasRevealed) {
          newlyRevealed.push(canonical);
        }
      }
      loaded.push(tool);
    }
    let setToolsError;
    if (newlyRevealed.length > 0) {
      const geminiClient = this.config.getGeminiClient();
      if (!geminiClient) {
        setToolsError = "GeminiClient not initialised";
      } else {
        try {
          await geminiClient.setTools();
        } catch (err) {
          setToolsError = err instanceof Error ? err.message : String(err);
          debugLogger.warn(
            "setTools() failed while revealing deferred tools:",
            err
          );
          process.stderr.write(
            `[ToolSearch] setTools() failed while revealing deferred tools: ${setToolsError}
`
          );
        }
        if (!setToolsError) {
          try {
            await geminiClient.refreshStartupContextReminder();
          } catch (err) {
            const refreshError = err instanceof Error ? err.message : String(err);
            debugLogger.warn(
              "refreshStartupContextReminder() failed after revealing deferred tools:",
              err
            );
            process.stderr.write(
              `[ToolSearch] refreshStartupContextReminder() failed after revealing deferred tools: ${refreshError}
`
            );
          }
        }
      }
      if (setToolsError) {
        for (const name of newlyRevealed) {
          registry.unrevealDeferredTool(name);
        }
      }
    }
    if (setToolsError) {
      return {
        llmContent: `Error: tools were located but could not be exposed to the API (setTools failed: ${setToolsError}). Retry the search next turn or call ToolSearch again with select:Name1,Name2 \u2014 re-running tool registration usually clears transient init races.`,
        returnDisplay: `setTools failed: ${setToolsError}`,
        error: {
          message: `setTools failed while revealing deferred tools: ${setToolsError}`
        }
      };
    }
    const schemaBlocks = loaded.map(
      (tool) => `<function>${JSON.stringify(tool.schema).replace(/</g, "\\u003c")}</function>`
    );
    let llmContent = "";
    if (schemaBlocks.length > 0) {
      llmContent += `<functions>
${schemaBlocks.join("\n")}
</functions>`;
    }
    if (missing.length > 0) {
      const header = llmContent ? "\n\n" : "";
      llmContent += `${header}Not found: ${missing.join(", ")}`;
    }
    if (truncated.length > 0) {
      const header = llmContent ? "\n\n" : "";
      llmContent += `${header}Truncated by max_results \u2014 request these in a follow-up call: ${truncated.join(", ")}`;
    }
    const displayParts = [];
    if (loaded.length > 0) displayParts.push(`Loaded ${loaded.length} tool(s)`);
    if (missing.length > 0) displayParts.push(`${missing.length} missing`);
    if (truncated.length > 0)
      displayParts.push(`${truncated.length} truncated`);
    const returnDisplay = displayParts.join(", ") || "No tools loaded";
    return { llmContent, returnDisplay };
  }
};
var ToolSearchTool = class _ToolSearchTool extends BaseDeclarativeTool {
  constructor(config) {
    super(
      _ToolSearchTool.Name,
      ToolDisplayNames.TOOL_SEARCH,
      toolSearchDescription,
      "other" /* Other */,
      {
        type: "object",
        properties: {
          query: {
            type: "string",
            description: 'Query to find deferred tools. Use "select:<tool_name>" for direct selection, or keywords to search.',
            // Reject empty queries at validation time so the model
            // doesn't waste a tool call to discover the runtime error
            // (`Error: query is empty`). The runtime guard stays as a
            // safety net for whitespace-only inputs that pass minLength.
            minLength: 1
          },
          max_results: {
            type: "integer",
            description: "Maximum number of results to return (default: 5)",
            minimum: 1,
            maximum: HARD_MAX_RESULTS,
            default: DEFAULT_MAX_RESULTS
          }
        },
        required: ["query"],
        additionalProperties: false
      },
      true,
      // isOutputMarkdown
      false,
      // canUpdateOutput
      false,
      // shouldDefer — this tool itself must always be visible
      true,
      // alwaysLoad — core discovery tool, never hidden
      "tool search discover find schema"
    );
    this.config = config;
  }
  static {
    __name(this, "ToolSearchTool");
  }
  static Name = ToolNames.TOOL_SEARCH;
  createInvocation(params) {
    return new ToolSearchInvocation(this.config, params);
  }
};
function tokenize(query) {
  return query.toLowerCase().split(/\s+/g).map((t) => t.trim()).filter((t) => t.length > 0);
}
__name(tokenize, "tokenize");
function clamp(n, lo, hi) {
  if (!Number.isFinite(n)) return lo;
  return Math.min(hi, Math.max(lo, Math.floor(n)));
}
__name(clamp, "clamp");
function stripMatchingQuotes(s) {
  if (s.length < 2) return s;
  const first = s[0];
  const last = s[s.length - 1];
  if (first === '"' && last === '"' || first === "'" && last === "'") {
    return s.slice(1, -1);
  }
  return s;
}
__name(stripMatchingQuotes, "stripMatchingQuotes");
function candidateMatchesRequired(tool, requiredTerms) {
  if (requiredTerms.length === 0) return true;
  const nameLower = tool.name.toLowerCase();
  return requiredTerms.every((t) => nameLower.includes(t));
}
__name(candidateMatchesRequired, "candidateMatchesRequired");
function scoreTool(tool, terms) {
  const isMcp = tool instanceof DiscoveredMCPTool;
  const nameLower = tool.name.toLowerCase();
  const descLower = (tool.description ?? "").toLowerCase();
  const hintLower = (tool.searchHint ?? "").toLowerCase();
  const hintParts = hintLower ? hintLower.split(/\s+/g).filter(Boolean) : [];
  let total = 0;
  for (const term of terms) {
    if (term.length === 0) continue;
    if (nameLower === term || nameLower.endsWith("_" + term) || nameLower.endsWith("." + term)) {
      total += isMcp ? SCORE_NAME_EXACT_MCP : SCORE_NAME_EXACT_BUILTIN;
    } else if (nameLower.includes(term)) {
      total += isMcp ? SCORE_NAME_SUBSTR_MCP : SCORE_NAME_SUBSTR_BUILTIN;
    }
    if (hintParts.some((p) => p === term)) {
      total += SCORE_HINT_BUILTIN;
    }
    if (descLower.includes(term)) {
      total += SCORE_DESC_BUILTIN;
    }
  }
  return total;
}
__name(scoreTool, "scoreTool");
export {
  ToolSearchTool,
  scoreTool,
  tokenize
};
/**
 * @license
 * Copyright 2025 Qwen Team
 * SPDX-License-Identifier: Apache-2.0
 */
