@juspay/neurolink 9.89.0 → 9.91.0

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.
Files changed (85) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/adapters/tts/googleTTSHandler.d.ts +10 -0
  3. package/dist/adapters/tts/googleTTSHandler.js +27 -18
  4. package/dist/agent/directTools.js +1 -1
  5. package/dist/browser/neurolink.min.js +387 -365
  6. package/dist/core/baseProvider.d.ts +37 -3
  7. package/dist/core/baseProvider.js +167 -44
  8. package/dist/core/modules/GenerationHandler.js +7 -1
  9. package/dist/core/modules/ToolsManager.js +5 -0
  10. package/dist/core/toolDedup.js +4 -1
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.js +1 -1
  13. package/dist/lib/adapters/tts/googleTTSHandler.d.ts +10 -0
  14. package/dist/lib/adapters/tts/googleTTSHandler.js +27 -18
  15. package/dist/lib/agent/directTools.js +1 -1
  16. package/dist/lib/core/baseProvider.d.ts +37 -3
  17. package/dist/lib/core/baseProvider.js +167 -44
  18. package/dist/lib/core/modules/GenerationHandler.js +7 -1
  19. package/dist/lib/core/modules/ToolsManager.js +5 -0
  20. package/dist/lib/core/toolDedup.js +4 -1
  21. package/dist/lib/index.d.ts +1 -1
  22. package/dist/lib/index.js +1 -1
  23. package/dist/lib/mcp/toolRegistry.js +7 -2
  24. package/dist/lib/neurolink.d.ts +31 -1
  25. package/dist/lib/neurolink.js +151 -26
  26. package/dist/lib/rag/index.d.ts +1 -0
  27. package/dist/lib/rag/index.js +2 -0
  28. package/dist/lib/rag/stores/chroma.d.ts +90 -0
  29. package/dist/lib/rag/stores/chroma.js +281 -0
  30. package/dist/lib/rag/stores/index.d.ts +21 -0
  31. package/dist/lib/rag/stores/index.js +22 -0
  32. package/dist/lib/rag/stores/pgvector.d.ts +95 -0
  33. package/dist/lib/rag/stores/pgvector.js +400 -0
  34. package/dist/lib/rag/stores/pinecone.d.ts +85 -0
  35. package/dist/lib/rag/stores/pinecone.js +159 -0
  36. package/dist/lib/server/routes/agentRoutes.js +25 -2
  37. package/dist/lib/tools/toolDiscovery.d.ts +48 -0
  38. package/dist/lib/tools/toolDiscovery.js +231 -0
  39. package/dist/lib/tools/toolGate.d.ts +16 -0
  40. package/dist/lib/tools/toolGate.js +51 -0
  41. package/dist/lib/tools/toolPolicy.d.ts +40 -0
  42. package/dist/lib/tools/toolPolicy.js +194 -0
  43. package/dist/lib/types/config.d.ts +43 -3
  44. package/dist/lib/types/index.d.ts +3 -0
  45. package/dist/lib/types/index.js +3 -0
  46. package/dist/lib/types/providers.d.ts +8 -0
  47. package/dist/lib/types/rag.d.ts +30 -0
  48. package/dist/lib/types/toolResolution.d.ts +73 -0
  49. package/dist/lib/types/toolResolution.js +11 -0
  50. package/dist/lib/types/vectorStoreChroma.d.ts +67 -0
  51. package/dist/lib/types/vectorStoreChroma.js +12 -0
  52. package/dist/lib/types/vectorStorePinecone.d.ts +48 -0
  53. package/dist/lib/types/vectorStorePinecone.js +12 -0
  54. package/dist/mcp/toolRegistry.js +7 -2
  55. package/dist/neurolink.d.ts +31 -1
  56. package/dist/neurolink.js +151 -26
  57. package/dist/rag/index.d.ts +1 -0
  58. package/dist/rag/index.js +2 -0
  59. package/dist/rag/stores/chroma.d.ts +90 -0
  60. package/dist/rag/stores/chroma.js +280 -0
  61. package/dist/rag/stores/index.d.ts +21 -0
  62. package/dist/rag/stores/index.js +21 -0
  63. package/dist/rag/stores/pgvector.d.ts +95 -0
  64. package/dist/rag/stores/pgvector.js +399 -0
  65. package/dist/rag/stores/pinecone.d.ts +85 -0
  66. package/dist/rag/stores/pinecone.js +158 -0
  67. package/dist/server/routes/agentRoutes.js +25 -2
  68. package/dist/tools/toolDiscovery.d.ts +48 -0
  69. package/dist/tools/toolDiscovery.js +230 -0
  70. package/dist/tools/toolGate.d.ts +16 -0
  71. package/dist/tools/toolGate.js +50 -0
  72. package/dist/tools/toolPolicy.d.ts +40 -0
  73. package/dist/tools/toolPolicy.js +193 -0
  74. package/dist/types/config.d.ts +43 -3
  75. package/dist/types/index.d.ts +3 -0
  76. package/dist/types/index.js +3 -0
  77. package/dist/types/providers.d.ts +8 -0
  78. package/dist/types/rag.d.ts +30 -0
  79. package/dist/types/toolResolution.d.ts +73 -0
  80. package/dist/types/toolResolution.js +10 -0
  81. package/dist/types/vectorStoreChroma.d.ts +67 -0
  82. package/dist/types/vectorStoreChroma.js +11 -0
  83. package/dist/types/vectorStorePinecone.d.ts +48 -0
  84. package/dist/types/vectorStorePinecone.js +11 -0
  85. package/package.json +12 -7
@@ -14,6 +14,9 @@ import { getKeyCount, getKeysAsString } from "../utils/transformationUtils.js";
14
14
  import { TTSProcessor } from "../utils/ttsProcessor.js";
15
15
  import { executeVideoAnalysis, hasVideoFrames, } from "../utils/videoAnalysisProcessor.js";
16
16
  import { dedupeTools } from "./toolDedup.js";
17
+ import { resolveToolPolicy, toolNameMatcher } from "../tools/toolPolicy.js";
18
+ import { applyToolGate } from "../tools/toolGate.js";
19
+ import { partitionToolsForDiscovery, isDiscoveryMetaTool, LARGE_CATALOG_WARN_THRESHOLD, } from "../tools/toolDiscovery.js";
17
20
  import { GenerationHandler } from "./modules/GenerationHandler.js";
18
21
  // Import modules for composition
19
22
  import { MessageBuilder } from "./modules/MessageBuilder.js";
@@ -535,69 +538,185 @@ export class BaseProvider {
535
538
  }
536
539
  /**
537
540
  * Apply per-call tool filtering (whitelist/blacklist) to a tools record.
538
- * If toolFilter is set, only tools whose names are in the list are kept.
539
- * If excludeTools is set, matching tools are removed. excludeTools is applied after toolFilter.
541
+ *
542
+ * All filtering surfaces are merged into one ResolvedToolPolicy by
543
+ * `resolveToolPolicy()` — per-call `toolFilter` (whitelist),
544
+ * `enabledToolNames` (merged into the whitelist, as its docs always
545
+ * promised), `excludeTools` (denylist, applied after the whitelist), and
546
+ * the instance-level `tools` config (enabled/include/exclude, `*` globs) —
547
+ * then applied by `applyToolGate()`. This is the single filter semantics
548
+ * for every generate/stream path.
540
549
  */
550
+ getToolPolicy(options) {
551
+ return resolveToolPolicy({
552
+ options: {
553
+ toolFilter: options.toolFilter,
554
+ excludeTools: options.excludeTools,
555
+ enabledToolNames: options.enabledToolNames,
556
+ },
557
+ instanceConfig: this.neurolink?.getToolsConfig(),
558
+ builtinToolNames: Object.keys(this.directTools ?? {}),
559
+ });
560
+ }
541
561
  applyToolFiltering(tools, options) {
542
- const hasWhitelist = options.toolFilter && options.toolFilter.length > 0;
543
- const hasDenylist = options.excludeTools && options.excludeTools.length > 0;
562
+ const policy = this.getToolPolicy(options);
544
563
  // Check whether the dedup pass is requested — even when no whitelist/
545
564
  // denylist is set we still need to run the dedup pass if enabled.
546
565
  const dedupConfig = this.neurolink?.getToolDedupConfig();
547
566
  const hasDedupEnabled = dedupConfig !== undefined && dedupConfig.enabled === true;
548
- if (!hasWhitelist && !hasDenylist && !hasDedupEnabled) {
549
- // Fast path: nothing to do.
550
- return tools;
551
- }
552
567
  const beforeCount = Object.keys(tools).length;
553
- let filtered = { ...tools };
554
- if (hasWhitelist) {
555
- const allowSet = new Set(options.toolFilter);
556
- const result = {};
557
- for (const [name, tool] of Object.entries(filtered)) {
558
- if (allowSet.has(name)) {
559
- result[name] = tool;
560
- }
561
- }
562
- filtered = result;
563
- }
564
- if (hasDenylist) {
565
- const denySet = new Set(options.excludeTools);
566
- for (const name of Object.keys(filtered)) {
567
- if (denySet.has(name)) {
568
- delete filtered[name];
569
- }
570
- }
571
- }
568
+ const filtered = applyToolGate(tools, policy);
572
569
  const afterCount = Object.keys(filtered).length;
573
570
  if (beforeCount !== afterCount) {
574
571
  logger.debug(`Tool filtering applied`, {
575
572
  provider: this.providerName,
576
573
  beforeCount,
577
574
  afterCount,
575
+ policySources: policy.sources,
578
576
  toolFilter: options.toolFilter,
579
577
  excludeTools: options.excludeTools,
578
+ enabledToolNames: options.enabledToolNames,
580
579
  });
581
580
  }
582
- // Opt-in signature dedup runs AFTER whitelist/blacklist filtering and
583
- // BEFORE the tool set reaches the provider call. Fails open: any error
584
- // inside dedupeTools returns the original filtered set unchanged.
585
- if (dedupConfig !== undefined && dedupConfig.enabled) {
586
- const { tools: dedupedTools, removed } = dedupeTools(filtered, dedupConfig);
587
- if (removed.length > 0 && logger.shouldLog("debug")) {
588
- logger.debug(`Tool signature dedup removed duplicates`, {
589
- provider: this.providerName,
590
- removedCount: removed.length,
591
- removed: removed.map((r) => ({
592
- name: r.name,
593
- duplicateOf: r.duplicateOf,
594
- similarity: r.similarity,
595
- })),
596
- });
581
+ if (!hasDedupEnabled || dedupConfig === undefined) {
582
+ return this.sortToolRecord(filtered);
583
+ }
584
+ const deduped = this.applyDedupPass(filtered, dedupConfig);
585
+ // A forced toolChoice must survive dedup: keep-first can collapse the
586
+ // forced tool into an earlier near-identical signature, and the provider
587
+ // would then reject the request for naming an unknown tool. Restore it
588
+ // from the pre-dedup record (whitelisted names are already safe — the
589
+ // gate runs before dedup, so a whitelist leaves no duplicate to lose to).
590
+ const forcedName = options.toolChoice?.toolName;
591
+ if (typeof forcedName === "string" &&
592
+ !Object.hasOwn(deduped, forcedName) &&
593
+ Object.hasOwn(filtered, forcedName)) {
594
+ deduped[forcedName] = filtered[forcedName];
595
+ logger.debug(`Restored toolChoice-forced tool removed by signature dedup`, { provider: this.providerName, toolName: forcedName });
596
+ }
597
+ return this.sortToolRecord(deduped);
598
+ }
599
+ /**
600
+ * Deterministic name-sorted key order. External MCP servers connect and
601
+ * discover in parallel, so insertion order varies across process restarts;
602
+ * providers serialize this record in key order (and Anthropic pins its
603
+ * cache_control breakpoint to the LAST tool), so an unstable order silently
604
+ * busts provider prompt caches. Runs AFTER the dedup pass — dedup's
605
+ * keep-first policy must see phase order (built-ins first) so duplicate
606
+ * winners don't flip when an MCP tool name sorts earlier.
607
+ */
608
+ sortToolRecord(tools) {
609
+ // Null prototype: a tool named "__proto__" must become an own entry, not
610
+ // a prototype mutation that silently drops the tool.
611
+ const sorted = Object.create(null);
612
+ for (const name of Object.keys(tools).sort()) {
613
+ sorted[name] = tools[name];
614
+ }
615
+ return sorted;
616
+ }
617
+ /**
618
+ * On-demand discovery (`tools.discovery: true`): defer external MCP tool
619
+ * schemas behind one `search_tools` meta-tool. Built-in tools, per-call
620
+ * tools, explicitly whitelisted tools, and session-pinned (previously
621
+ * discovered) tools always stay hot. No-op when discovery is off — with a
622
+ * one-time WARN when the catalog is large enough that selection accuracy
623
+ * measurably degrades.
624
+ */
625
+ async applyToolDiscovery(toolsInput, options) {
626
+ // Strip a stale meta-tool left by a previous resolution pass (the
627
+ // stream → generate fallback re-enters resolution with options.tools
628
+ // already partitioned). Discovery re-partitions against the fresh merged
629
+ // record below; without this, the collision guard would see our own
630
+ // meta-tool and skip partitioning, shipping the full catalog AND a stale
631
+ // search_tools closure. Real user tools named "search_tools" are not
632
+ // marked and are left untouched.
633
+ let tools = toolsInput;
634
+ if (isDiscoveryMetaTool(tools["search_tools"])) {
635
+ const { search_tools: _stale, ...rest } = tools;
636
+ tools = rest;
637
+ }
638
+ const toolCount = Object.keys(tools).length;
639
+ const policy = this.getToolPolicy(options);
640
+ if (!policy.discovery) {
641
+ if (toolCount > LARGE_CATALOG_WARN_THRESHOLD) {
642
+ this.warnLargeCatalogOnce(toolCount);
597
643
  }
598
- return dedupedTools;
644
+ return tools;
645
+ }
646
+ const externalTools = this.neurolink?.getExternalMCPTools() ?? [];
647
+ if (externalTools.length === 0) {
648
+ return tools;
649
+ }
650
+ // Session pinning requires a caller-provided sessionId. Without one there
651
+ // is no session identity — pinning to a shared fallback key would leak
652
+ // one caller's discoveries into every other caller of a shared instance
653
+ // and monotonically defeat deferral, so pins are simply not persisted.
654
+ const rawSessionId = options.context?.sessionId;
655
+ const sessionKey = typeof rawSessionId === "string" && rawSessionId.length > 0
656
+ ? rawSessionId
657
+ : typeof rawSessionId === "number"
658
+ ? String(rawSessionId)
659
+ : undefined;
660
+ const pinnedNames = (sessionKey ? this.neurolink?.getDiscoveryPins(sessionKey) : undefined) ??
661
+ new Set();
662
+ const perCallNames = new Set(Object.keys(options.tools ?? {}));
663
+ // Explicitly requested tools stay hot. Allowlist entries may be globs
664
+ // (e.g. toolFilter: ["github*"]), so match with the same pattern matcher
665
+ // the gate uses — a Set of pattern STRINGS would defer glob-whitelisted
666
+ // tools the gate deliberately kept.
667
+ const explicitPatterns = [
668
+ ...(options.toolFilter ?? []),
669
+ ...(options.enabledToolNames ?? []),
670
+ ];
671
+ // A toolChoice that forces a named tool must never see that tool
672
+ // deferred — the provider would reject the request (unknown tool name).
673
+ const toolChoice = options.toolChoice;
674
+ if (toolChoice && typeof toolChoice.toolName === "string") {
675
+ explicitPatterns.push(toolChoice.toolName);
676
+ }
677
+ const explicitMatcher = explicitPatterns.length > 0 ? toolNameMatcher(explicitPatterns) : null;
678
+ const deferrableNames = externalTools
679
+ .map((t) => t.name)
680
+ .filter((name) => name in tools &&
681
+ !perCallNames.has(name) &&
682
+ !(explicitMatcher ? explicitMatcher(name) : false));
683
+ return partitionToolsForDiscovery(tools, {
684
+ deferrableNames,
685
+ pinnedNames,
686
+ onHydrate: (names) => {
687
+ if (sessionKey) {
688
+ this.neurolink?.pinDiscoveredTools(sessionKey, names);
689
+ }
690
+ },
691
+ });
692
+ }
693
+ static warnedLargeCatalog = false;
694
+ warnLargeCatalogOnce(toolCount) {
695
+ if (BaseProvider.warnedLargeCatalog) {
696
+ return;
697
+ }
698
+ BaseProvider.warnedLargeCatalog = true;
699
+ logger.warn(`[ToolDiscovery] ${toolCount} tools are being sent in full on every request (~${Math.round((toolCount * 175) / 100) / 10}K tokens). Tool-selection accuracy degrades past 30-50 tools — consider enabling on-demand discovery: new NeuroLink({ tools: { discovery: true } })`, { toolCount, provider: this.providerName });
700
+ }
701
+ /**
702
+ * Opt-in signature dedup — runs AFTER whitelist/blacklist filtering and
703
+ * BEFORE the tool set reaches the provider call. Fails open: any error
704
+ * inside dedupeTools returns the original filtered set unchanged.
705
+ */
706
+ applyDedupPass(filtered, dedupConfig) {
707
+ const { tools: dedupedTools, removed } = dedupeTools(filtered, dedupConfig);
708
+ if (removed.length > 0 && logger.shouldLog("debug")) {
709
+ logger.debug(`Tool signature dedup removed duplicates`, {
710
+ provider: this.providerName,
711
+ removedCount: removed.length,
712
+ removed: removed.map((r) => ({
713
+ name: r.name,
714
+ duplicateOf: r.duplicateOf,
715
+ similarity: r.similarity,
716
+ })),
717
+ });
599
718
  }
600
- return filtered;
719
+ return dedupedTools;
601
720
  }
602
721
  /**
603
722
  * Prepare generation context including tools and model
@@ -613,6 +732,8 @@ export class BaseProvider {
613
732
  : {};
614
733
  // Apply per-call tool filtering (whitelist/blacklist)
615
734
  tools = this.applyToolFiltering(tools, options);
735
+ // On-demand discovery: defer external MCP schemas behind search_tools
736
+ tools = await this.applyToolDiscovery(tools, options);
616
737
  logger.debug(`Final tools prepared for AI`, {
617
738
  provider: this.providerName,
618
739
  directTools: getKeyCount(baseTools),
@@ -644,6 +765,8 @@ export class BaseProvider {
644
765
  let merged = { ...baseTools, ...externalTools };
645
766
  // Apply per-call tool filtering (whitelist/blacklist)
646
767
  merged = this.applyToolFiltering(merged, options);
768
+ // On-demand discovery: defer external MCP schemas behind search_tools
769
+ merged = await this.applyToolDiscovery(merged, options);
647
770
  logger.debug(`Tools prepared for streaming`, {
648
771
  provider: this.providerName,
649
772
  baseToolCount: Object.keys(baseTools).length,
@@ -88,7 +88,13 @@ export class GenerationHandler {
88
88
  // Non-Anthropic providers harmlessly ignore unknown providerOptions.
89
89
  // Note: The AI SDK Tool type doesn't yet include providerOptions, so we
90
90
  // use a type assertion. The Anthropic adapter reads this at runtime.
91
- const toolsWithCache = { ...tools };
91
+ //
92
+ // Deliberately NOT a clone: the record is call-scoped (built fresh in
93
+ // BaseProvider.prepareGenerationContext) and the AI SDK re-reads it on
94
+ // every agent-loop step, so `search_tools` hydration (tools.discovery)
95
+ // can add discovered tools mid-loop and have them callable on the next
96
+ // step. A clone would freeze the tool set for the whole call.
97
+ const toolsWithCache = tools;
92
98
  if (isAnthropicProvider &&
93
99
  shouldUseTools &&
94
100
  Object.keys(toolsWithCache).length > 0) {
@@ -258,6 +258,11 @@ export class ToolsManager {
258
258
  `... and ${toolNames.length - 10} more`,
259
259
  ],
260
260
  });
261
+ // NOTE: insertion order here is phase order (direct → custom →
262
+ // external MCP), which the signature-dedup pass depends on (keep-first
263
+ // must prefer built-in implementations). Deterministic name-sorting
264
+ // for prompt-cache stability happens AFTER filtering+dedup, in
265
+ // BaseProvider.applyToolFiltering.
261
266
  return tools;
262
267
  });
263
268
  }
@@ -171,7 +171,10 @@ export function dedupeTools(tools, options) {
171
171
  if (representativeOf.size === 0) {
172
172
  return noOp;
173
173
  }
174
- const dedupedTools = {};
174
+ // Null prototype: a tool named "__proto__" must become an own entry,
175
+ // not a prototype mutation that silently drops it (mirrors the tool
176
+ // records built in BaseProvider.sortToolRecord / applyToolGate).
177
+ const dedupedTools = Object.create(null);
175
178
  const removed = [];
176
179
  for (const [name, tool] of entries) {
177
180
  const rep = representativeOf.get(name);
@@ -426,7 +426,7 @@ export { AgentExecuteRequestSchema, AlreadyRunningError, AuthenticationError, Au
426
426
  * console.log(response.answer);
427
427
  * ```
428
428
  */
429
- export { assembleContext, batchRerank, CharacterChunker, ChunkerRegistry, CohereRelevanceScorer, CrossEncoderReranker, CSVLoader, chunkText, createChunker, createContextWindow, createHybridSearch, createRAGPipeline, createVectorQueryTool, executeWithCircuitBreaker, extractMetadata, formatContextWithCitations, GraphRAG, getAvailableStrategies, getCircuitBreaker, getDefaultChunkerConfig, getRecommendedStrategy, HTMLChunker, HTMLLoader, InMemoryBM25Index, InMemoryVectorStore, JSONChunker as RAGJSONChunker, JSONLoader, LaTeXChunker, LLMMetadataExtractor, linearCombination, loadDocument, loadDocuments, MarkdownChunker, MarkdownLoader, MDocument, PDFLoader, prepareRAGTool, processDocument, RAGCircuitBreaker, RAGCircuitBreakerManager, RAGPipeline, RAGRetryHandler, RecursiveChunker, ragCircuitBreakerManager, reciprocalRankFusion, rerank, SemanticChunker, SentenceChunker, simpleRerank, summarizeContext, TextLoader, TokenChunker, WebLoader, } from "./rag/index.js";
429
+ export { assembleContext, batchRerank, CharacterChunker, ChunkerRegistry, CohereRelevanceScorer, CrossEncoderReranker, CSVLoader, chunkText, createChunker, createContextWindow, createHybridSearch, createRAGPipeline, createVectorQueryTool, executeWithCircuitBreaker, extractMetadata, formatContextWithCitations, GraphRAG, getAvailableStrategies, getCircuitBreaker, getDefaultChunkerConfig, getRecommendedStrategy, HTMLChunker, HTMLLoader, InMemoryBM25Index, InMemoryVectorStore, JSONChunker as RAGJSONChunker, JSONLoader, LaTeXChunker, LLMMetadataExtractor, linearCombination, loadDocument, loadDocuments, MarkdownChunker, MarkdownLoader, MDocument, PDFLoader, prepareRAGTool, processDocument, RAGCircuitBreaker, RAGCircuitBreakerManager, RAGPipeline, RAGRetryHandler, RecursiveChunker, ragCircuitBreakerManager, reciprocalRankFusion, rerank, SemanticChunker, ChromaVectorStore, PgVectorStore, PineconeVectorStore, SentenceChunker, simpleRerank, summarizeContext, TextLoader, TokenChunker, WebLoader, } from "./rag/index.js";
430
430
  export { ContextBuilder } from "./evaluation/contextBuilder.js";
431
431
  export { AuthProviderFactory, createAuthProvider, AuthProviderRegistry, AuthError as AuthErrorFactory, AuthErrorCodes, BaseAuthProvider, InMemorySessionStorage, AuthProviderError, createAuthMiddleware as createAuthProviderMiddleware, createRBACMiddleware, createProtectedMiddleware, createExpressAuthMiddleware, createRequestContext, extractToken, AuthMiddlewareError, AuthMiddlewareErrorCodes, UserRateLimiter, MemoryRateLimitStorage, RedisRateLimitStorage, createRateLimitByUserMiddleware, createAuthenticatedRateLimitMiddleware, createRateLimitStorage, SessionManager, MemorySessionStorage, RedisSessionStorage, createSessionStorage, AuthContextHolder, globalAuthContext, getAuthContext, getCurrentUser, getCurrentSession, isAuthenticated, hasRole, hasAnyRole, hasPermission, hasAllPermissions, requireAuth, requireRole, requirePermission, requireUser, runWithAuthContext, createAuthenticatedContext, RequestContext, NEUROLINK_RESOURCE_ID_KEY, NEUROLINK_THREAD_ID_KEY, createAuthValidatorFromProvider, } from "./auth/index.js";
432
432
  export { detectAndRedactPII } from "./utils/piiDetector.js";
package/dist/lib/index.js CHANGED
@@ -683,7 +683,7 @@ extractMetadata, formatContextWithCitations,
683
683
  // Graph RAG
684
684
  GraphRAG, getAvailableStrategies, getCircuitBreaker, getDefaultChunkerConfig, getRecommendedStrategy, HTMLChunker, HTMLLoader, InMemoryBM25Index, InMemoryVectorStore, JSONChunker as RAGJSONChunker, JSONLoader, LaTeXChunker, LLMMetadataExtractor, linearCombination, loadDocument, loadDocuments, MarkdownChunker, MarkdownLoader, MDocument, PDFLoader,
685
685
  // RAG Integration for generate/stream
686
- prepareRAGTool, processDocument, RAGCircuitBreaker, RAGCircuitBreakerManager, RAGPipeline, RAGRetryHandler, RecursiveChunker, ragCircuitBreakerManager, reciprocalRankFusion, rerank, SemanticChunker, SentenceChunker, simpleRerank, summarizeContext, TextLoader, TokenChunker, WebLoader, } from "./rag/index.js";
686
+ prepareRAGTool, processDocument, RAGCircuitBreaker, RAGCircuitBreakerManager, RAGPipeline, RAGRetryHandler, RecursiveChunker, ragCircuitBreakerManager, reciprocalRankFusion, rerank, SemanticChunker, ChromaVectorStore, PgVectorStore, PineconeVectorStore, SentenceChunker, simpleRerank, summarizeContext, TextLoader, TokenChunker, WebLoader, } from "./rag/index.js";
687
687
  // Legacy RAGAS evaluation classes are now exported from the unified
688
688
  // evaluation block above (via ./evaluation/index.js barrel).
689
689
  // ContextBuilder is the only class not covered by the barrel export.
@@ -7,6 +7,7 @@ import { registryLogger } from "../utils/logger.js";
7
7
  import { randomUUID } from "crypto";
8
8
  import { shouldDisableBuiltinTools } from "../utils/toolUtils.js";
9
9
  import { directAgentTools } from "../agent/directTools.js";
10
+ import { convertZodToJsonSchema } from "../utils/schemaConversion.js";
10
11
  import { detectCategory, createMCPServerInfo } from "../utils/mcpDefaults.js";
11
12
  import { FlexibleToolValidator } from "./flexibleToolValidator.js";
12
13
  import { ErrorFactory } from "../utils/errorHandling.js";
@@ -57,10 +58,14 @@ export class MCPToolRegistry extends MCPRegistry {
57
58
  continue;
58
59
  }
59
60
  const toolId = `direct.${toolName}`;
61
+ // Register the tool's real parameter schema (converted from Zod) instead
62
+ // of a `{}` placeholder — token-budget accounting and tool listings read
63
+ // this ToolInfo, and an empty schema makes both under-count reality.
64
+ const inputSchema = convertZodToJsonSchema(toolDef.inputSchema);
60
65
  const toolInfo = {
61
66
  name: toolName,
62
67
  description: toolDef.description || `Direct tool: ${toolName}`,
63
- inputSchema: {},
68
+ inputSchema,
64
69
  serverId: "direct",
65
70
  category: detectCategory({ isBuiltIn: true, serverId: "direct" }),
66
71
  };
@@ -98,7 +103,7 @@ export class MCPToolRegistry extends MCPRegistry {
98
103
  }
99
104
  },
100
105
  description: toolDef.description,
101
- inputSchema: {},
106
+ inputSchema,
102
107
  });
103
108
  registryLogger.debug(`Registered direct tool: ${toolName} as ${toolId}`);
104
109
  }
@@ -6,7 +6,7 @@
6
6
  * Uses real MCP infrastructure for tool discovery and execution.
7
7
  */
8
8
  import type { AgentDefinition, AgentNetworkConfig, NetworkExecutionInput, NetworkExecutionOptions, NetworkExecutionResult, NetworkStreamChunk } from "./types/index.js";
9
- import type { CompactionConfig, CompactionResult, SpanData, ObservabilityConfig, MetricsSummary, MCPToolAnnotations, TraceView, AuthenticatedContext, AuthProvider, JsonObject, NeuroLinkEvents, TypedEventEmitter, MCPEnhancementsConfig, NeuroLinkAuthConfig, NeurolinkConstructorConfig, ChatMessage, ExternalMCPOperationResult, ExternalMCPServerInstance, ExternalMCPToolInfo, GenerateOptions, GenerateResult, ProviderStatus, TextGenerationOptions, TextGenerationResult, MCPExecutableTool, MCPServerInfo, MCPStatus, StreamOptions, StreamResult, ToolExecutionContext, ToolExecutionSummary, ToolInfo, ToolRegistrationOptions, BatchOperationResult, StreamGenerationEndContext, ToolRoutingServerDescriptor, ToolDedupConfig } from "./types/index.js";
9
+ import type { CompactionConfig, CompactionResult, SpanData, ObservabilityConfig, MetricsSummary, MCPToolAnnotations, TraceView, AuthenticatedContext, AuthProvider, JsonObject, NeuroLinkEvents, TypedEventEmitter, MCPEnhancementsConfig, NeuroLinkAuthConfig, NeurolinkConstructorConfig, ChatMessage, ExternalMCPOperationResult, ExternalMCPServerInstance, ExternalMCPToolInfo, GenerateOptions, GenerateResult, ProviderStatus, TextGenerationOptions, TextGenerationResult, MCPExecutableTool, MCPServerInfo, MCPStatus, StreamOptions, StreamResult, ToolExecutionContext, ToolExecutionSummary, ToolInfo, ToolRegistrationOptions, BatchOperationResult, StreamGenerationEndContext, ToolRoutingServerDescriptor, ToolDedupConfig, ToolConfig } from "./types/index.js";
10
10
  import { ConversationMemoryManager } from "./core/conversationMemoryManager.js";
11
11
  import type { RedisConversationMemoryManager } from "./core/redisConversationMemoryManager.js";
12
12
  import { ExternalServerManager } from "./mcp/externalServerManager.js";
@@ -107,6 +107,9 @@ export declare class NeuroLink {
107
107
  private toolRoutingCacheInstance?;
108
108
  private toolRoutingVectorCache?;
109
109
  private toolDedupConfig?;
110
+ private toolsConfig?;
111
+ /** Session-scoped pins of tools discovered via search_tools (tools.discovery mode). */
112
+ private discoveryPins;
110
113
  private enableOrchestration;
111
114
  private authProvider?;
112
115
  private pendingAuthConfig?;
@@ -812,6 +815,11 @@ export declare class NeuroLink {
812
815
  /**
813
816
  * Apply per-call tool filtering (whitelist/blacklist) to a ToolInfo array.
814
817
  * Used to filter the tool list before building the system prompt.
818
+ *
819
+ * Resolves the SAME policy as the native gate (`BaseProvider`
820
+ * `applyToolFiltering`) — per-call options plus the instance-level `tools`
821
+ * config — so what the model is told about tools never diverges from the
822
+ * tools it can actually call.
815
823
  */
816
824
  private applyToolInfoFiltering;
817
825
  private createToolAwareSystemPrompt;
@@ -1194,6 +1202,28 @@ export declare class NeuroLink {
1194
1202
  * parameter through the full call stack.
1195
1203
  */
1196
1204
  getToolDedupConfig(): ToolDedupConfig | undefined;
1205
+ /**
1206
+ * Returns the instance-level `tools` config (master switch, include/exclude
1207
+ * lists, discovery mode), or `undefined` when not provided at construction.
1208
+ *
1209
+ * Called by `BaseProvider.applyToolFiltering` so the tool gate composes the
1210
+ * instance policy with per-call options on every generate/stream call.
1211
+ */
1212
+ getToolsConfig(): ToolConfig | undefined;
1213
+ /**
1214
+ * Tools discovered via `search_tools` for a session (`tools.discovery`
1215
+ * mode). Pinned tools are sent in full on every subsequent call of that
1216
+ * session instead of being deferred — discovery cost is paid once.
1217
+ * Reading refreshes the session's recency (LRU), so active conversations
1218
+ * are never the ones evicted at the session cap.
1219
+ */
1220
+ getDiscoveryPins(sessionKey: string): ReadonlySet<string>;
1221
+ /**
1222
+ * Pin discovered tools to a session (called by the `search_tools`
1223
+ * meta-tool on hydration). Append-only within a session; the map is
1224
+ * bounded by evicting the least-recently-used session past 1000 sessions.
1225
+ */
1226
+ pinDiscoveredTools(sessionKey: string, toolNames: string[]): void;
1197
1227
  /**
1198
1228
  * Curator P1-1: synchronous credential health check for a single provider.
1199
1229
  *