// Force strict mode and setup for ESM
"use strict";
import {
  DiscoveredMCPTool
} from "./chunk-G5EEAVVW.js";
import "./chunk-J5VCSWPA.js";
import "./chunk-UWCTAVOD.js";
import "./chunk-W57YDFU5.js";
import "./chunk-OFEVLU4C.js";
import "./chunk-NQ3E7YLD.js";
import "./chunk-ODPVJ6JJ.js";
import "./chunk-UQRYJQBE.js";
import {
  ToolDisplayNames,
  ToolNames
} from "./chunk-6RQTH7UQ.js";
import {
  BaseDeclarativeTool,
  BaseToolInvocation
} from "./chunk-R2B65CAN.js";
import "./chunk-6NUSWV4M.js";
import "./chunk-HCSJIOLR.js";
import "./chunk-SZOEIL6S.js";
import "./chunk-5IFG2VC4.js";
import {
  createDebugLogger
} from "./chunk-ACBGEKB7.js";
import "./chunk-ZERZSAZL.js";
import "./chunk-QN5NZ3UQ.js";
import "./chunk-BR4QREVK.js";
import "./chunk-QWSRH265.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" section of the system prompt. 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) {
        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
 */
