@flutchai/flutch-sdk 0.6.12 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -229,6 +229,35 @@ curl http://localhost:3000/registry
229
229
 
230
230
  ## Advanced Usage
231
231
 
232
+ ### Tool search for agents with many tools
233
+
234
+ Every bound tool's schema is sent on every turn. With dozens of tools that is most of the prompt, and models pick the wrong tool more often past 30–50 of them. Turn on tool search and the model sees a few tools plus `search_tools`, and finds the rest when a task needs them:
235
+
236
+ ```typescript
237
+ import { isToolSearchCall } from "@flutchai/flutch-sdk";
238
+
239
+ const modelConfig = {
240
+ modelId,
241
+ toolsConfig,
242
+ toolSearch: { enabled: true, alwaysLoaded: ["list_sites", "list_products"] },
243
+ };
244
+ const model = await modelInitializer.initializeChatModel(modelConfig);
245
+
246
+ // In the tools node: search runs in the SDK, against the same catalog.
247
+ if (isToolSearchCall(toolCall.name)) {
248
+ const { content } = await modelInitializer.searchTools(
249
+ modelConfig,
250
+ toolCall.args,
251
+ config
252
+ );
253
+ messages.push(
254
+ new ToolMessage({ content, tool_call_id: toolCall.id, name: toolCall.name })
255
+ );
256
+ }
257
+ ```
258
+
259
+ Pass the same config to `searchTools` that built the model — it names the catalog. Found tools are read from the conversation history, so the graph keeps no extra state. Claude through the Anthropic Messages API loads found tools as deferred definitions (`defer_loading` + `tool_reference`) and keeps the prompt cache; other providers bind the core tools plus the found ones each turn. `alwaysLoaded` also takes a comma-separated string; `maxResults` (default 5) and `minTools` (default 15) tune it.
260
+
232
261
  ### Using GraphTypeUtils
233
262
 
234
263
  ```typescript
package/dist/index.cjs CHANGED
@@ -21,6 +21,8 @@ var dispatch = require('@langchain/core/callbacks/dispatch');
21
21
  var tools = require('@langchain/core/tools');
22
22
  var axios2 = require('axios');
23
23
  var manager = require('@langchain/core/callbacks/manager');
24
+ var runnables = require('@langchain/core/runnables');
25
+ var zod = require('zod');
24
26
  var openai = require('@langchain/openai');
25
27
  var aws = require('@langchain/aws');
26
28
  var core$1 = require('cohere-ai/core');
@@ -7086,6 +7088,314 @@ exports.McpRuntimeHttpClient = _ts_decorate25([
7086
7088
  ])
7087
7089
  ], exports.McpRuntimeHttpClient);
7088
7090
 
7091
+ // src/tools/tool-search/tool-search.catalog.ts
7092
+ var K1 = 1.2;
7093
+ var B = 0.75;
7094
+ var NAME_WEIGHT = 3;
7095
+ var STEM_LENGTH = 5;
7096
+ var HIT_DESCRIPTION_CHARS = 240;
7097
+ function toolSearchTerms(text) {
7098
+ return text.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((word) => word.length > 1).map((word) => word.slice(0, STEM_LENGTH));
7099
+ }
7100
+ __name(toolSearchTerms, "toolSearchTerms");
7101
+ function schemaParams(schema) {
7102
+ const json = schema;
7103
+ const properties = json?.properties ?? json?.shape ?? {};
7104
+ return Object.entries(properties).flatMap(([name, property]) => property?.description ? [
7105
+ name,
7106
+ property.description
7107
+ ] : [
7108
+ name
7109
+ ]);
7110
+ }
7111
+ __name(schemaParams, "schemaParams");
7112
+ function shortDescription(description) {
7113
+ const flat = description.replace(/\s+/g, " ").trim();
7114
+ if (flat.length <= HIT_DESCRIPTION_CHARS) return flat;
7115
+ const cut = flat.slice(0, HIT_DESCRIPTION_CHARS);
7116
+ const sentenceEnd = cut.lastIndexOf(". ");
7117
+ return sentenceEnd > HIT_DESCRIPTION_CHARS / 2 ? cut.slice(0, sentenceEnd + 1) : `${cut.trimEnd()}\u2026`;
7118
+ }
7119
+ __name(shortDescription, "shortDescription");
7120
+ var ToolSearchCatalog = class {
7121
+ static {
7122
+ __name(this, "ToolSearchCatalog");
7123
+ }
7124
+ entries;
7125
+ documents;
7126
+ lengths;
7127
+ averageLength;
7128
+ documentFrequency = /* @__PURE__ */ new Map();
7129
+ constructor(tools) {
7130
+ this.entries = tools.map((tool) => ({
7131
+ name: tool.name,
7132
+ description: tool.description ?? "",
7133
+ params: schemaParams(tool.schema)
7134
+ }));
7135
+ this.documents = this.entries.map((entry) => {
7136
+ const nameTerms = toolSearchTerms(entry.name);
7137
+ const terms = [
7138
+ ...Array.from({
7139
+ length: NAME_WEIGHT
7140
+ }, () => nameTerms).flat(),
7141
+ ...toolSearchTerms(entry.description),
7142
+ ...entry.params.flatMap(toolSearchTerms)
7143
+ ];
7144
+ const counts = /* @__PURE__ */ new Map();
7145
+ for (const term of terms) counts.set(term, (counts.get(term) ?? 0) + 1);
7146
+ return counts;
7147
+ });
7148
+ this.lengths = this.documents.map((counts) => Array.from(counts.values()).reduce((sum, n) => sum + n, 0));
7149
+ this.averageLength = this.lengths.reduce((sum, n) => sum + n, 0) / Math.max(this.lengths.length, 1);
7150
+ for (const counts of this.documents) {
7151
+ for (const term of counts.keys()) {
7152
+ this.documentFrequency.set(term, (this.documentFrequency.get(term) ?? 0) + 1);
7153
+ }
7154
+ }
7155
+ }
7156
+ get size() {
7157
+ return this.entries.length;
7158
+ }
7159
+ has(name) {
7160
+ return this.entries.some((entry) => entry.name === name);
7161
+ }
7162
+ /**
7163
+ * The best `limit` tools for a query, best first. A query that names a tool
7164
+ * exactly returns that tool first whatever its score. Tools that share no
7165
+ * word with the query are never returned.
7166
+ */
7167
+ search(query, limit) {
7168
+ const terms = Array.from(new Set(toolSearchTerms(query)));
7169
+ const exact = query.split(/[\s,]+/).map((word) => word.trim()).filter((word) => this.has(word));
7170
+ const total = this.entries.length;
7171
+ const scored = this.entries.map((entry, index) => {
7172
+ const counts = this.documents[index];
7173
+ const length = this.lengths[index];
7174
+ let score = 0;
7175
+ for (const term of terms) {
7176
+ const frequency = counts.get(term);
7177
+ if (!frequency) continue;
7178
+ const df = this.documentFrequency.get(term) ?? 0;
7179
+ const idf = Math.log(1 + (total - df + 0.5) / (df + 0.5));
7180
+ score += idf * frequency * (K1 + 1) / (frequency + K1 * (1 - B + B * length / this.averageLength));
7181
+ }
7182
+ if (exact.includes(entry.name)) score += 1e3;
7183
+ return {
7184
+ entry,
7185
+ score
7186
+ };
7187
+ }).filter(({ score }) => score > 0).sort((a, b) => b.score - a.score);
7188
+ return scored.slice(0, Math.max(1, limit)).map(({ entry }) => ({
7189
+ name: entry.name,
7190
+ description: shortDescription(entry.description)
7191
+ }));
7192
+ }
7193
+ /**
7194
+ * What the catalog covers, as name prefixes with counts:
7195
+ * "funnel_* (4), recipe_* (5), rl_* (41)". Goes into the search tool's
7196
+ * description so the model knows a search is worth making. Sorted, so the
7197
+ * description — and the prompt cache — stays the same between requests.
7198
+ */
7199
+ summary(maxGroups = 20) {
7200
+ const groups = /* @__PURE__ */ new Map();
7201
+ for (const entry of this.entries) {
7202
+ const prefix = entry.name.includes("_") ? `${entry.name.split("_")[0]}_*` : entry.name;
7203
+ groups.set(prefix, (groups.get(prefix) ?? 0) + 1);
7204
+ }
7205
+ const sorted = Array.from(groups.entries()).sort(([a], [b]) => a.localeCompare(b));
7206
+ const shown = sorted.slice(0, maxGroups).map(([prefix, count]) => count > 1 ? `${prefix} (${count})` : prefix);
7207
+ return sorted.length > maxGroups ? `${shown.join(", ")}, \u2026` : shown.join(", ");
7208
+ }
7209
+ };
7210
+
7211
+ // src/tools/tool-search/tool-search.result.ts
7212
+ var TOOL_SEARCH_NAME = "search_tools";
7213
+ function isToolSearchCall(name) {
7214
+ return name === TOOL_SEARCH_NAME;
7215
+ }
7216
+ __name(isToolSearchCall, "isToolSearchCall");
7217
+ function formatToolSearchResult(hits) {
7218
+ if (hits.length === 0) {
7219
+ return JSON.stringify({
7220
+ tools: [],
7221
+ note: "No tools matched. Search again with other words: what the tool does, the object it works on, or a name prefix."
7222
+ });
7223
+ }
7224
+ return JSON.stringify({
7225
+ tools: hits,
7226
+ note: "These tools are available now. Call them directly."
7227
+ });
7228
+ }
7229
+ __name(formatToolSearchResult, "formatToolSearchResult");
7230
+ function parseToolSearchResult(content) {
7231
+ const text = contentText(content);
7232
+ if (!text) return [];
7233
+ try {
7234
+ const parsed = JSON.parse(text);
7235
+ return (parsed.tools ?? []).map((tool) => tool?.name).filter((name) => typeof name === "string");
7236
+ } catch {
7237
+ return [];
7238
+ }
7239
+ }
7240
+ __name(parseToolSearchResult, "parseToolSearchResult");
7241
+ function contentText(content) {
7242
+ if (typeof content === "string") return content;
7243
+ if (Array.isArray(content)) {
7244
+ return content.map((part) => typeof part === "string" ? part : part && typeof part === "object" && "text" in part ? String(part.text) : "").join("");
7245
+ }
7246
+ return "";
7247
+ }
7248
+ __name(contentText, "contentText");
7249
+ function loadedToolNames(messages) {
7250
+ const searchCallIds = /* @__PURE__ */ new Set();
7251
+ for (const message of messages) {
7252
+ const calls = message.tool_calls;
7253
+ for (const call of calls ?? []) {
7254
+ if (isToolSearchCall(call.name) && call.id) searchCallIds.add(call.id);
7255
+ }
7256
+ }
7257
+ const loaded = [];
7258
+ for (const message of messages) {
7259
+ if (message._getType() !== "tool") continue;
7260
+ const tool = message;
7261
+ const isSearch = isToolSearchCall(tool.name) || tool.tool_call_id !== void 0 && searchCallIds.has(tool.tool_call_id);
7262
+ if (!isSearch) continue;
7263
+ for (const name of parseToolSearchResult(tool.content)) {
7264
+ if (!loaded.includes(name)) loaded.push(name);
7265
+ }
7266
+ }
7267
+ return loaded;
7268
+ }
7269
+ __name(loadedToolNames, "loadedToolNames");
7270
+ var DEFAULT_TOOL_SEARCH_RESULTS = 5;
7271
+ var DEFAULT_TOOL_SEARCH_MIN_TOOLS = 15;
7272
+ function toolSearchApplies(options, toolCount) {
7273
+ return !!options?.enabled && toolCount >= (options.minTools ?? DEFAULT_TOOL_SEARCH_MIN_TOOLS);
7274
+ }
7275
+ __name(toolSearchApplies, "toolSearchApplies");
7276
+ function splitTools(tools, options, extraCore = []) {
7277
+ const listed = typeof options.alwaysLoaded === "string" ? options.alwaysLoaded.split(",") : options.alwaysLoaded ?? [];
7278
+ const always = /* @__PURE__ */ new Set([
7279
+ ...listed.map((name) => name.trim()).filter(Boolean),
7280
+ ...extraCore.map((tool) => tool.name)
7281
+ ]);
7282
+ return {
7283
+ core: tools.filter((tool) => always.has(tool.name)),
7284
+ deferred: tools.filter((tool) => !always.has(tool.name))
7285
+ };
7286
+ }
7287
+ __name(splitTools, "splitTools");
7288
+ function createToolSearchTool(catalog, maxResults = DEFAULT_TOOL_SEARCH_RESULTS) {
7289
+ return new tools.DynamicStructuredTool({
7290
+ name: TOOL_SEARCH_NAME,
7291
+ description: `Find more tools. You see only some of your tools; ${catalog.size} more are found by search: ${catalog.summary()}. Describe the task in a few words (what to do and with what, e.g. "customer review quotes by theme"), or give exact tool names. Found tools become callable right away and stay available for the rest of the conversation. Search before saying you cannot do something.`,
7292
+ schema: zod.z.object({
7293
+ query: zod.z.string().describe("What the tool should do, in a few words, or tool names separated by commas"),
7294
+ limit: zod.z.number().int().min(1).max(20).optional().describe(`How many tools to return (default ${maxResults})`)
7295
+ }),
7296
+ func: /* @__PURE__ */ __name(async ({ query, limit }) => formatToolSearchResult(catalog.search(query, limit ?? maxResults)), "func")
7297
+ });
7298
+ }
7299
+ __name(createToolSearchTool, "createToolSearchTool");
7300
+ function deferredCopy(tool) {
7301
+ const copy = Object.create(tool);
7302
+ copy.extras = {
7303
+ ...tool.extras ?? {},
7304
+ defer_loading: true
7305
+ };
7306
+ return copy;
7307
+ }
7308
+ __name(deferredCopy, "deferredCopy");
7309
+ function bindToolsWithSearch(params) {
7310
+ const { model, core, deferred, searchTool, mode, bindOptions } = params;
7311
+ const bind = /* @__PURE__ */ __name((tools) => bindOptions ? model.bindTools(tools, bindOptions) : model.bindTools(tools), "bind");
7312
+ if (mode === "deferred") {
7313
+ return bind([
7314
+ searchTool,
7315
+ ...core,
7316
+ ...deferred.map(deferredCopy)
7317
+ ]);
7318
+ }
7319
+ const byName = new Map(deferred.map((tool) => [
7320
+ tool.name,
7321
+ tool
7322
+ ]));
7323
+ const bound = /* @__PURE__ */ new Map();
7324
+ const forHistory = /* @__PURE__ */ __name((messages) => {
7325
+ const found = loadedToolNames(messages).filter((name) => byName.has(name));
7326
+ const key = found.join(",");
7327
+ let runnable = bound.get(key);
7328
+ if (!runnable) {
7329
+ runnable = bind([
7330
+ ...core,
7331
+ searchTool,
7332
+ ...found.map((name) => byName.get(name))
7333
+ ]);
7334
+ bound.set(key, runnable);
7335
+ }
7336
+ return runnable;
7337
+ }, "forHistory");
7338
+ return runnables.RunnableLambda.from(async (input, config) => {
7339
+ const messages = Array.isArray(input) ? input : [];
7340
+ return forHistory(messages).invoke(input, config);
7341
+ }).withConfig({
7342
+ runName: "ToolSearchModel"
7343
+ });
7344
+ }
7345
+ __name(bindToolsWithSearch, "bindToolsWithSearch");
7346
+
7347
+ // src/tools/tool-search/tool-reference.fetch.ts
7348
+ function rewriteToolReferences(body) {
7349
+ if (!body.includes(TOOL_SEARCH_NAME)) return body;
7350
+ let parsed;
7351
+ try {
7352
+ parsed = JSON.parse(body);
7353
+ } catch {
7354
+ return body;
7355
+ }
7356
+ const known = new Set((parsed.tools ?? []).map((tool) => tool.name));
7357
+ const searchIds = /* @__PURE__ */ new Set();
7358
+ for (const message of parsed.messages ?? []) {
7359
+ if (message.role !== "assistant" || !Array.isArray(message.content)) continue;
7360
+ for (const block of message.content) {
7361
+ if (block.type === "tool_use" && block.name === TOOL_SEARCH_NAME && block.id) {
7362
+ searchIds.add(block.id);
7363
+ }
7364
+ }
7365
+ }
7366
+ if (searchIds.size === 0) return body;
7367
+ let changed = false;
7368
+ for (const message of parsed.messages ?? []) {
7369
+ if (message.role !== "user" || !Array.isArray(message.content)) continue;
7370
+ for (const block of message.content) {
7371
+ if (block.type !== "tool_result" || !block.tool_use_id) continue;
7372
+ if (!searchIds.has(block.tool_use_id)) continue;
7373
+ const names = parseToolSearchResult(block.content).filter((name) => known.has(name));
7374
+ if (names.length === 0) continue;
7375
+ block.content = names.map((name) => ({
7376
+ type: "tool_reference",
7377
+ tool_name: name
7378
+ }));
7379
+ changed = true;
7380
+ }
7381
+ }
7382
+ return changed ? JSON.stringify(parsed) : body;
7383
+ }
7384
+ __name(rewriteToolReferences, "rewriteToolReferences");
7385
+ function withToolReferences(inner) {
7386
+ return (input, init) => {
7387
+ if (init && typeof init.body === "string") {
7388
+ const body = rewriteToolReferences(init.body);
7389
+ if (body !== init.body) return inner(input, {
7390
+ ...init,
7391
+ body
7392
+ });
7393
+ }
7394
+ return inner(input, init);
7395
+ };
7396
+ }
7397
+ __name(withToolReferences, "withToolReferences");
7398
+
7089
7399
  // src/models/enums.ts
7090
7400
  var ModelProvider = /* @__PURE__ */ (function(ModelProvider2) {
7091
7401
  ModelProvider2["MISTRAL"] = "mistral";
@@ -7267,7 +7577,8 @@ function buildOpenAIModelConfig(modelName, temperature, maxTokens, apiToken) {
7267
7577
  temperature: 1,
7268
7578
  maxCompletionTokens: maxTokens,
7269
7579
  streaming: true,
7270
- openAIApiKey: apiToken
7580
+ useResponsesApi: true,
7581
+ apiKey: apiToken
7271
7582
  };
7272
7583
  }
7273
7584
  return {
@@ -7279,7 +7590,7 @@ function buildOpenAIModelConfig(modelName, temperature, maxTokens, apiToken) {
7279
7590
  },
7280
7591
  maxTokens,
7281
7592
  streaming: true,
7282
- openAIApiKey: apiToken
7593
+ apiKey: apiToken
7283
7594
  };
7284
7595
  }
7285
7596
  __name(buildOpenAIModelConfig, "buildOpenAIModelConfig");
@@ -7452,6 +7763,8 @@ var ModelInitializer = class _ModelInitializer {
7452
7763
  modelConfigCache = /* @__PURE__ */ new Map();
7453
7764
  // Cache for model instances to avoid recreating identical models
7454
7765
  modelInstanceCache = /* @__PURE__ */ new Map();
7766
+ // Tool-search catalogs, keyed like the model instance that uses them
7767
+ toolSearchCatalogs = /* @__PURE__ */ new Map();
7455
7768
  static DEFAULT_ENV_MAP = {
7456
7769
  [ModelProvider.OPENAI]: "OPENAI_API_KEY",
7457
7770
  [ModelProvider.ANTHROPIC]: "ANTHROPIC_API_KEY",
@@ -7527,7 +7840,8 @@ var ModelInitializer = class _ModelInitializer {
7527
7840
  const withMcp = config.mcpServers && config.mcpServers.length > 0 ? `${base}:mcp:${JSON.stringify(config.mcpServers)}` : base;
7528
7841
  const withSampling = config.supportsSamplingParams === void 0 ? withMcp : `${withMcp}:sp:${config.supportsSamplingParams}`;
7529
7842
  const withCache = config.promptCache === void 0 && config.promptCacheTtl === void 0 ? withSampling : `${withSampling}:pc:${config.promptCache ?? "auto"}:${config.promptCacheTtl ?? "default"}`;
7530
- return config.effort === void 0 ? withCache : `${withCache}:eff:${config.effort}`;
7843
+ const withEffort = config.effort === void 0 ? withCache : `${withCache}:eff:${config.effort}`;
7844
+ return config.toolSearch?.enabled ? `${withEffort}:ts:${JSON.stringify(config.toolSearch)}` : withEffort;
7531
7845
  }
7532
7846
  // Chat model creators
7533
7847
  chatModelCreators = {
@@ -7561,10 +7875,13 @@ var ModelInitializer = class _ModelInitializer {
7561
7875
  },
7562
7876
  anthropicApiKey: apiToken || this.resolveApiKey(ModelProvider.ANTHROPIC),
7563
7877
  ...routerURL && {
7564
- anthropicApiUrl: routerURL,
7565
- clientOptions: {
7566
- fetch: flutchFetch
7567
- }
7878
+ anthropicApiUrl: routerURL
7879
+ },
7880
+ // Tool search results become `tool_reference` blocks on the wire
7881
+ // (LangChain cannot express them). A request without a search passes
7882
+ // through untouched.
7883
+ clientOptions: {
7884
+ fetch: withToolReferences(routerURL ? flutchFetch : fetch)
7568
7885
  }
7569
7886
  });
7570
7887
  },
@@ -7808,7 +8125,11 @@ var ModelInitializer = class _ModelInitializer {
7808
8125
  // Converse takes cache control as a per-call option; the Anthropic
7809
8126
  // client already got it as a constructor field, and handing it the
7810
8127
  // same value twice would put an unknown key in its call options.
7811
- finalConfig.useBedrock ? promptCacheControl : void 0
8128
+ finalConfig.useBedrock ? promptCacheControl : void 0,
8129
+ config.toolSearch?.enabled ? {
8130
+ options: config.toolSearch,
8131
+ cacheKey
8132
+ } : void 0
7812
8133
  );
7813
8134
  this.modelInstanceCache.set(cacheKey, boundModel);
7814
8135
  return boundModel;
@@ -7834,8 +8155,9 @@ var ModelInitializer = class _ModelInitializer {
7834
8155
  * - Runnable when tools are bound (model.bindTools returns Runnable)
7835
8156
  * - BaseChatModel when no tools
7836
8157
  */
7837
- async bindToolsToModel(model, toolsConfig, customTools, mcpServers, mcpContext, promptCacheControl) {
8158
+ async bindToolsToModel(model, toolsConfig, customTools, mcpServers, mcpContext, promptCacheControl, toolSearch) {
7838
8159
  const allTools = [];
8160
+ const mcpToolsFound = [];
7839
8161
  const enabledToolsConfig = (toolsConfig || []).filter((tc) => tc.enabled !== false);
7840
8162
  const hasInlineServers = !!(mcpServers && mcpServers.length > 0);
7841
8163
  if (enabledToolsConfig.length > 0 || hasInlineServers) {
@@ -7845,6 +8167,7 @@ var ModelInitializer = class _ModelInitializer {
7845
8167
  const mcpTools = await mcpToolFilter.getFilteredTools(enabledToolsConfig, mcpServers, mcpContext);
7846
8168
  this.logger.debug(`Successfully fetched ${mcpTools.length} tools with dynamic schemas from MCP Runtime`);
7847
8169
  allTools.push(...mcpTools);
8170
+ mcpToolsFound.push(...mcpTools);
7848
8171
  } catch (error) {
7849
8172
  this.logger.error(`Failed to fetch tools from MCP Runtime: ${error instanceof Error ? error.message : String(error)}`);
7850
8173
  }
@@ -7858,11 +8181,86 @@ var ModelInitializer = class _ModelInitializer {
7858
8181
  const boundOptions = {
7859
8182
  cache_control: promptCacheControl
7860
8183
  };
8184
+ if (toolSearch && toolSearchApplies(toolSearch.options, allTools.length)) {
8185
+ const { core, deferred } = splitTools(mcpToolsFound, toolSearch.options, customTools ?? []);
8186
+ if (deferred.length > 0) {
8187
+ const maxResults = toolSearch.options.maxResults ?? DEFAULT_TOOL_SEARCH_RESULTS;
8188
+ const catalog = new ToolSearchCatalog(deferred);
8189
+ this.toolSearchCatalogs.set(toolSearch.cacheKey, {
8190
+ catalog,
8191
+ maxResults
8192
+ });
8193
+ const mode = model instanceof anthropic.ChatAnthropic ? "deferred" : "rebind";
8194
+ this.logger.debug(`Tool search (${mode}): ${core.length + (customTools?.length ?? 0)} tools in sight, ${deferred.length} found on demand`);
8195
+ return bindToolsWithSearch({
8196
+ model,
8197
+ core: [
8198
+ ...core,
8199
+ ...customTools ?? []
8200
+ ],
8201
+ deferred,
8202
+ searchTool: createToolSearchTool(catalog, maxResults),
8203
+ mode,
8204
+ bindOptions: promptCacheControl ? boundOptions : void 0
8205
+ });
8206
+ }
8207
+ }
7861
8208
  const modelWithTools = promptCacheControl ? model.bindTools(allTools, boundOptions) : model.bindTools(allTools);
7862
8209
  return modelWithTools;
7863
8210
  }
7864
8211
  return model;
7865
8212
  }
8213
+ /**
8214
+ * Runs a `search_tools` call. `config` must be the one the model was
8215
+ * initialized with (same tools, same `toolSearch`): it names the catalog.
8216
+ * Emits tool start/end events like `McpRuntimeHttpClient.executeToolWithEvents`,
8217
+ * so the call shows as a finished step in the chat.
8218
+ *
8219
+ * Never throws: a failure comes back as the tool's answer.
8220
+ */
8221
+ async searchTools(config, args, runConfig) {
8222
+ const parsedConfig = manager.parseCallbackConfigArg(runConfig);
8223
+ const callbackManager = manager.CallbackManager.configure(parsedConfig.callbacks);
8224
+ const runManager = await callbackManager?.handleToolStart({
8225
+ name: TOOL_SEARCH_NAME,
8226
+ lc: 1,
8227
+ type: "not_implemented",
8228
+ id: [
8229
+ "langchain",
8230
+ "tools",
8231
+ TOOL_SEARCH_NAME
8232
+ ]
8233
+ }, JSON.stringify(args ?? {}), parsedConfig.runId, void 0, parsedConfig.tags, parsedConfig.metadata, TOOL_SEARCH_NAME);
8234
+ try {
8235
+ const key = this.generateModelCacheKey(config);
8236
+ if (!this.toolSearchCatalogs.has(key)) {
8237
+ await this.initializeChatModel(config);
8238
+ }
8239
+ const entry = this.toolSearchCatalogs.get(key);
8240
+ const query = typeof args?.query === "string" ? args.query : "";
8241
+ const limit = Number(args?.limit);
8242
+ const content = !entry ? JSON.stringify({
8243
+ error: "Tool search is not available for this agent: every tool is already loaded."
8244
+ }) : !query.trim() ? JSON.stringify({
8245
+ error: "query is required"
8246
+ }) : formatToolSearchResult(entry.catalog.search(query, Number.isFinite(limit) && limit > 0 ? limit : entry.maxResults));
8247
+ await runManager?.handleToolEnd(content);
8248
+ return {
8249
+ content,
8250
+ success: !!entry && !!query.trim()
8251
+ };
8252
+ } catch (error) {
8253
+ await runManager?.handleToolError(error);
8254
+ const message = error instanceof Error ? error.message : String(error);
8255
+ this.logger.error(`Tool search failed: ${message}`);
8256
+ return {
8257
+ content: JSON.stringify({
8258
+ error: `Tool search failed: ${message}`
8259
+ }),
8260
+ success: false
8261
+ };
8262
+ }
8263
+ }
7866
8264
  async initializeRerankModel(config) {
7867
8265
  const cacheKey = this.generateModelCacheKey(config);
7868
8266
  const cachedModel = this.modelInstanceCache.get(cacheKey);
@@ -8661,6 +9059,8 @@ exports.CallbackStore = CallbackStore;
8661
9059
  exports.ChatFeature = ChatFeature;
8662
9060
  exports.DEFAULT_ATTACHMENT_THRESHOLD = DEFAULT_ATTACHMENT_THRESHOLD;
8663
9061
  exports.DEFAULT_ROUTER_URL = DEFAULT_ROUTER_URL;
9062
+ exports.DEFAULT_TOOL_SEARCH_MIN_TOOLS = DEFAULT_TOOL_SEARCH_MIN_TOOLS;
9063
+ exports.DEFAULT_TOOL_SEARCH_RESULTS = DEFAULT_TOOL_SEARCH_RESULTS;
8664
9064
  exports.DEFAULT_TRACER_OPTIONS = DEFAULT_TRACER_OPTIONS;
8665
9065
  exports.Endpoint = Endpoint;
8666
9066
  exports.FileTokenStore = FileTokenStore;
@@ -8681,6 +9081,8 @@ exports.PROMPT_CACHE_MIN_VERSIONS = PROMPT_CACHE_MIN_VERSIONS;
8681
9081
  exports.RetrieverSearchType = RetrieverSearchType;
8682
9082
  exports.SAMPLING_PARAM_FREE_THRESHOLDS = SAMPLING_PARAM_FREE_THRESHOLDS;
8683
9083
  exports.StreamChannel = StreamChannel;
9084
+ exports.TOOL_SEARCH_NAME = TOOL_SEARCH_NAME;
9085
+ exports.ToolSearchCatalog = ToolSearchCatalog;
8684
9086
  exports.UIEndpoint = UIEndpoint;
8685
9087
  exports.UniversalCallbackService = UniversalCallbackService;
8686
9088
  exports.VoyageAIRerank = VoyageAIRerank;
@@ -8688,6 +9090,7 @@ exports.WithCallbacks = WithCallbacks;
8688
9090
  exports.WithEndpoints = WithEndpoints;
8689
9091
  exports.WithUIEndpoints = WithUIEndpoints;
8690
9092
  exports._internals = _internals;
9093
+ exports.bindToolsWithSearch = bindToolsWithSearch;
8691
9094
  exports.bootstrap = bootstrap;
8692
9095
  exports.buildEffortRequestFields = buildEffortRequestFields;
8693
9096
  exports.buildOAuthAuthorizationUrl = buildOAuthAuthorizationUrl;
@@ -8697,6 +9100,7 @@ exports.createEndpointDescriptors = createEndpointDescriptors;
8697
9100
  exports.createGraphAttachment = createGraphAttachment;
8698
9101
  exports.createMongoClientAdapter = createMongoClientAdapter;
8699
9102
  exports.createStaticMessage = createStaticMessage;
9103
+ exports.createToolSearchTool = createToolSearchTool;
8700
9104
  exports.decryptTokens = decryptTokens;
8701
9105
  exports.dispatchAttachments = dispatchAttachments;
8702
9106
  exports.encryptTokens = encryptTokens;
@@ -8706,6 +9110,7 @@ exports.findEndpointMethod = findEndpointMethod;
8706
9110
  exports.flutchFetch = flutchFetch;
8707
9111
  exports.flutchHeaders = flutchHeaders;
8708
9112
  exports.flutchMistralHook = flutchMistralHook;
9113
+ exports.formatToolSearchResult = formatToolSearchResult;
8709
9114
  exports.generateAttachmentSummary = generateAttachmentSummary;
8710
9115
  exports.generateModelCacheKey = generateModelCacheKey;
8711
9116
  exports.getAttachmentData = getAttachmentData;
@@ -8723,11 +9128,14 @@ exports.isInternalMode = isInternalMode;
8723
9128
  exports.isReasoningBlock = isReasoningBlock;
8724
9129
  exports.isReasoningModel = isReasoningModel;
8725
9130
  exports.isSendableReasoningBlock = isSendableReasoningBlock;
9131
+ exports.isToolSearchCall = isToolSearchCall;
8726
9132
  exports.joinTextSteps = joinTextSteps;
8727
9133
  exports.loadOAuthProviders = loadOAuthProviders;
9134
+ exports.loadedToolNames = loadedToolNames;
8728
9135
  exports.modelAcceptsSamplingParams = modelAcceptsSamplingParams;
8729
9136
  exports.modelSupportsPromptCache = modelSupportsPromptCache;
8730
9137
  exports.normalizeToolConfigs = normalizeToolConfigs;
9138
+ exports.parseToolSearchResult = parseToolSearchResult;
8731
9139
  exports.redactConnectionString = redactConnectionString;
8732
9140
  exports.registerFinanceExampleCallback = registerFinanceExampleCallback;
8733
9141
  exports.registerUIEndpointsFromClass = registerUIEndpointsFromClass;
@@ -8735,13 +9143,18 @@ exports.resolveOAuthProviderConfig = resolveOAuthProviderConfig;
8735
9143
  exports.resolvePromptCacheControl = resolvePromptCacheControl;
8736
9144
  exports.resolveRouterURL = resolveRouterURL;
8737
9145
  exports.resolveSamplingParams = resolveSamplingParams;
9146
+ exports.rewriteToolReferences = rewriteToolReferences;
8738
9147
  exports.sanitizeMessageReasoning = sanitizeMessageReasoning;
8739
9148
  exports.sanitizeReasoningForBedrock = sanitizeReasoningForBedrock;
8740
9149
  exports.sanitizeTraceData = sanitizeTraceData;
9150
+ exports.splitTools = splitTools;
8741
9151
  exports.storeAttachmentData = storeAttachmentData;
8742
9152
  exports.toOptionalNumber = toOptionalNumber;
9153
+ exports.toolSearchApplies = toolSearchApplies;
9154
+ exports.toolSearchTerms = toolSearchTerms;
8743
9155
  exports.traceApiCall = traceApiCall;
8744
9156
  exports.withFlutchContext = withFlutchContext;
9157
+ exports.withToolReferences = withToolReferences;
8745
9158
  exports.wrapCohereFetcher = wrapCohereFetcher;
8746
9159
  //# sourceMappingURL=index.cjs.map
8747
9160
  //# sourceMappingURL=index.cjs.map