@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.
- package/CHANGELOG.md +16 -0
- package/dist/adapters/tts/googleTTSHandler.d.ts +10 -0
- package/dist/adapters/tts/googleTTSHandler.js +27 -18
- package/dist/agent/directTools.js +1 -1
- package/dist/browser/neurolink.min.js +387 -365
- package/dist/core/baseProvider.d.ts +37 -3
- package/dist/core/baseProvider.js +167 -44
- package/dist/core/modules/GenerationHandler.js +7 -1
- package/dist/core/modules/ToolsManager.js +5 -0
- package/dist/core/toolDedup.js +4 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/adapters/tts/googleTTSHandler.d.ts +10 -0
- package/dist/lib/adapters/tts/googleTTSHandler.js +27 -18
- package/dist/lib/agent/directTools.js +1 -1
- package/dist/lib/core/baseProvider.d.ts +37 -3
- package/dist/lib/core/baseProvider.js +167 -44
- package/dist/lib/core/modules/GenerationHandler.js +7 -1
- package/dist/lib/core/modules/ToolsManager.js +5 -0
- package/dist/lib/core/toolDedup.js +4 -1
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/index.js +1 -1
- package/dist/lib/mcp/toolRegistry.js +7 -2
- package/dist/lib/neurolink.d.ts +31 -1
- package/dist/lib/neurolink.js +151 -26
- package/dist/lib/rag/index.d.ts +1 -0
- package/dist/lib/rag/index.js +2 -0
- package/dist/lib/rag/stores/chroma.d.ts +90 -0
- package/dist/lib/rag/stores/chroma.js +281 -0
- package/dist/lib/rag/stores/index.d.ts +21 -0
- package/dist/lib/rag/stores/index.js +22 -0
- package/dist/lib/rag/stores/pgvector.d.ts +95 -0
- package/dist/lib/rag/stores/pgvector.js +400 -0
- package/dist/lib/rag/stores/pinecone.d.ts +85 -0
- package/dist/lib/rag/stores/pinecone.js +159 -0
- package/dist/lib/server/routes/agentRoutes.js +25 -2
- package/dist/lib/tools/toolDiscovery.d.ts +48 -0
- package/dist/lib/tools/toolDiscovery.js +231 -0
- package/dist/lib/tools/toolGate.d.ts +16 -0
- package/dist/lib/tools/toolGate.js +51 -0
- package/dist/lib/tools/toolPolicy.d.ts +40 -0
- package/dist/lib/tools/toolPolicy.js +194 -0
- package/dist/lib/types/config.d.ts +43 -3
- package/dist/lib/types/index.d.ts +3 -0
- package/dist/lib/types/index.js +3 -0
- package/dist/lib/types/providers.d.ts +8 -0
- package/dist/lib/types/rag.d.ts +30 -0
- package/dist/lib/types/toolResolution.d.ts +73 -0
- package/dist/lib/types/toolResolution.js +11 -0
- package/dist/lib/types/vectorStoreChroma.d.ts +67 -0
- package/dist/lib/types/vectorStoreChroma.js +12 -0
- package/dist/lib/types/vectorStorePinecone.d.ts +48 -0
- package/dist/lib/types/vectorStorePinecone.js +12 -0
- package/dist/mcp/toolRegistry.js +7 -2
- package/dist/neurolink.d.ts +31 -1
- package/dist/neurolink.js +151 -26
- package/dist/rag/index.d.ts +1 -0
- package/dist/rag/index.js +2 -0
- package/dist/rag/stores/chroma.d.ts +90 -0
- package/dist/rag/stores/chroma.js +280 -0
- package/dist/rag/stores/index.d.ts +21 -0
- package/dist/rag/stores/index.js +21 -0
- package/dist/rag/stores/pgvector.d.ts +95 -0
- package/dist/rag/stores/pgvector.js +399 -0
- package/dist/rag/stores/pinecone.d.ts +85 -0
- package/dist/rag/stores/pinecone.js +158 -0
- package/dist/server/routes/agentRoutes.js +25 -2
- package/dist/tools/toolDiscovery.d.ts +48 -0
- package/dist/tools/toolDiscovery.js +230 -0
- package/dist/tools/toolGate.d.ts +16 -0
- package/dist/tools/toolGate.js +50 -0
- package/dist/tools/toolPolicy.d.ts +40 -0
- package/dist/tools/toolPolicy.js +193 -0
- package/dist/types/config.d.ts +43 -3
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.js +3 -0
- package/dist/types/providers.d.ts +8 -0
- package/dist/types/rag.d.ts +30 -0
- package/dist/types/toolResolution.d.ts +73 -0
- package/dist/types/toolResolution.js +10 -0
- package/dist/types/vectorStoreChroma.d.ts +67 -0
- package/dist/types/vectorStoreChroma.js +11 -0
- package/dist/types/vectorStorePinecone.d.ts +48 -0
- package/dist/types/vectorStorePinecone.js +11 -0
- package/package.json +12 -7
|
@@ -77,10 +77,44 @@ export declare abstract class BaseProvider implements AIProvider {
|
|
|
77
77
|
private executeFakeStreaming;
|
|
78
78
|
/**
|
|
79
79
|
* Apply per-call tool filtering (whitelist/blacklist) to a tools record.
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
|
|
80
|
+
*
|
|
81
|
+
* All filtering surfaces are merged into one ResolvedToolPolicy by
|
|
82
|
+
* `resolveToolPolicy()` — per-call `toolFilter` (whitelist),
|
|
83
|
+
* `enabledToolNames` (merged into the whitelist, as its docs always
|
|
84
|
+
* promised), `excludeTools` (denylist, applied after the whitelist), and
|
|
85
|
+
* the instance-level `tools` config (enabled/include/exclude, `*` globs) —
|
|
86
|
+
* then applied by `applyToolGate()`. This is the single filter semantics
|
|
87
|
+
* for every generate/stream path.
|
|
88
|
+
*/
|
|
89
|
+
private getToolPolicy;
|
|
83
90
|
private applyToolFiltering;
|
|
91
|
+
/**
|
|
92
|
+
* Deterministic name-sorted key order. External MCP servers connect and
|
|
93
|
+
* discover in parallel, so insertion order varies across process restarts;
|
|
94
|
+
* providers serialize this record in key order (and Anthropic pins its
|
|
95
|
+
* cache_control breakpoint to the LAST tool), so an unstable order silently
|
|
96
|
+
* busts provider prompt caches. Runs AFTER the dedup pass — dedup's
|
|
97
|
+
* keep-first policy must see phase order (built-ins first) so duplicate
|
|
98
|
+
* winners don't flip when an MCP tool name sorts earlier.
|
|
99
|
+
*/
|
|
100
|
+
private sortToolRecord;
|
|
101
|
+
/**
|
|
102
|
+
* On-demand discovery (`tools.discovery: true`): defer external MCP tool
|
|
103
|
+
* schemas behind one `search_tools` meta-tool. Built-in tools, per-call
|
|
104
|
+
* tools, explicitly whitelisted tools, and session-pinned (previously
|
|
105
|
+
* discovered) tools always stay hot. No-op when discovery is off — with a
|
|
106
|
+
* one-time WARN when the catalog is large enough that selection accuracy
|
|
107
|
+
* measurably degrades.
|
|
108
|
+
*/
|
|
109
|
+
private applyToolDiscovery;
|
|
110
|
+
private static warnedLargeCatalog;
|
|
111
|
+
private warnLargeCatalogOnce;
|
|
112
|
+
/**
|
|
113
|
+
* Opt-in signature dedup — runs AFTER whitelist/blacklist filtering and
|
|
114
|
+
* BEFORE the tool set reaches the provider call. Fails open: any error
|
|
115
|
+
* inside dedupeTools returns the original filtered set unchanged.
|
|
116
|
+
*/
|
|
117
|
+
private applyDedupPass;
|
|
84
118
|
/**
|
|
85
119
|
* Prepare generation context including tools and model
|
|
86
120
|
*/
|
|
@@ -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
|
-
*
|
|
539
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
}
|
package/dist/core/toolDedup.js
CHANGED
|
@@ -171,7 +171,10 @@ export function dedupeTools(tools, options) {
|
|
|
171
171
|
if (representativeOf.size === 0) {
|
|
172
172
|
return noOp;
|
|
173
173
|
}
|
|
174
|
-
|
|
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);
|
package/dist/index.d.ts
CHANGED
|
@@ -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/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.
|
|
@@ -24,6 +24,7 @@ export declare class GoogleTTSHandler implements TTSHandler {
|
|
|
24
24
|
* before invoking provider handlers, not inside this class.
|
|
25
25
|
*/
|
|
26
26
|
readonly maxTextLength: number;
|
|
27
|
+
private readonly credentialsPath;
|
|
27
28
|
constructor(credentialsPath?: string);
|
|
28
29
|
/**
|
|
29
30
|
* Validate that the provider is properly configured
|
|
@@ -31,6 +32,15 @@ export declare class GoogleTTSHandler implements TTSHandler {
|
|
|
31
32
|
* @returns True if provider can generate TTS
|
|
32
33
|
*/
|
|
33
34
|
isConfigured(): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Lazily construct (and cache) the Google Cloud TTS client.
|
|
37
|
+
*
|
|
38
|
+
* `@google-cloud/text-to-speech` is an optional dependency: importing it
|
|
39
|
+
* only happens here, on first actual use, so a handler instance can be
|
|
40
|
+
* constructed (e.g. during auto-registration at module load) without the
|
|
41
|
+
* package being installed.
|
|
42
|
+
*/
|
|
43
|
+
private getClient;
|
|
34
44
|
/**
|
|
35
45
|
* Get available voices for the provider
|
|
36
46
|
*
|
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Google Cloud Text-to-Speech Handler
|
|
3
|
-
*
|
|
4
|
-
* Handler for Google Cloud Text-to-Speech API integration.
|
|
5
|
-
*
|
|
6
|
-
* @module adapters/tts/googleTTSHandler
|
|
7
|
-
* @see https://cloud.google.com/text-to-speech/docs
|
|
8
|
-
*/
|
|
9
|
-
import { TextToSpeechClient } from "@google-cloud/text-to-speech";
|
|
10
1
|
import { TTSError, TTS_ERROR_CODES } from "../../utils/ttsProcessor.js";
|
|
11
2
|
import { ErrorCategory, ErrorSeverity } from "../../constants/enums.js";
|
|
12
3
|
import { logger } from "../../utils/logger.js";
|
|
@@ -36,11 +27,10 @@ export class GoogleTTSHandler {
|
|
|
36
27
|
* before invoking provider handlers, not inside this class.
|
|
37
28
|
*/
|
|
38
29
|
maxTextLength = GoogleTTSHandler.DEFAULT_MAX_TEXT_LENGTH;
|
|
30
|
+
credentialsPath;
|
|
39
31
|
constructor(credentialsPath) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
this.client = new TextToSpeechClient({ keyFilename: path });
|
|
43
|
-
}
|
|
32
|
+
this.credentialsPath =
|
|
33
|
+
credentialsPath ?? process.env.GOOGLE_APPLICATION_CREDENTIALS;
|
|
44
34
|
}
|
|
45
35
|
/**
|
|
46
36
|
* Validate that the provider is properly configured
|
|
@@ -48,7 +38,24 @@ export class GoogleTTSHandler {
|
|
|
48
38
|
* @returns True if provider can generate TTS
|
|
49
39
|
*/
|
|
50
40
|
isConfigured() {
|
|
51
|
-
return this.
|
|
41
|
+
return this.credentialsPath !== undefined;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Lazily construct (and cache) the Google Cloud TTS client.
|
|
45
|
+
*
|
|
46
|
+
* `@google-cloud/text-to-speech` is an optional dependency: importing it
|
|
47
|
+
* only happens here, on first actual use, so a handler instance can be
|
|
48
|
+
* constructed (e.g. during auto-registration at module load) without the
|
|
49
|
+
* package being installed.
|
|
50
|
+
*/
|
|
51
|
+
async getClient() {
|
|
52
|
+
if (!this.client) {
|
|
53
|
+
const { TextToSpeechClient } = await import("@google-cloud/text-to-speech");
|
|
54
|
+
this.client = new TextToSpeechClient({
|
|
55
|
+
keyFilename: this.credentialsPath,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return this.client;
|
|
52
59
|
}
|
|
53
60
|
/**
|
|
54
61
|
* Get available voices for the provider
|
|
@@ -60,7 +67,7 @@ export class GoogleTTSHandler {
|
|
|
60
67
|
* @returns List of available voices
|
|
61
68
|
*/
|
|
62
69
|
async getVoices(languageCode) {
|
|
63
|
-
if (!this.
|
|
70
|
+
if (!this.isConfigured()) {
|
|
64
71
|
throw new TTSError({
|
|
65
72
|
code: TTS_ERROR_CODES.PROVIDER_NOT_CONFIGURED,
|
|
66
73
|
message: "Google Cloud TTS client not initialized. Set GOOGLE_APPLICATION_CREDENTIALS or pass credentials path.",
|
|
@@ -69,6 +76,7 @@ export class GoogleTTSHandler {
|
|
|
69
76
|
retriable: false,
|
|
70
77
|
});
|
|
71
78
|
}
|
|
79
|
+
const client = await this.getClient();
|
|
72
80
|
const span = SpanSerializer.createSpan(SpanType.TTS, "tts.google.listVoices", {
|
|
73
81
|
"tts.operation": "listVoices",
|
|
74
82
|
"tts.provider": "google",
|
|
@@ -84,7 +92,7 @@ export class GoogleTTSHandler {
|
|
|
84
92
|
return this.voicesCache.voices;
|
|
85
93
|
}
|
|
86
94
|
// Call Google Cloud listVoices API
|
|
87
|
-
const [response] = await
|
|
95
|
+
const [response] = await client.listVoices(languageCode ? { languageCode } : {});
|
|
88
96
|
if (!response.voices || response.voices.length === 0) {
|
|
89
97
|
logger.warn("Google Cloud TTS returned no voices");
|
|
90
98
|
const endedSpan = SpanSerializer.endSpan(span, SpanStatus.OK);
|
|
@@ -149,7 +157,7 @@ export class GoogleTTSHandler {
|
|
|
149
157
|
* @returns Audio buffer with metadata
|
|
150
158
|
*/
|
|
151
159
|
async synthesize(text, options) {
|
|
152
|
-
if (!this.
|
|
160
|
+
if (!this.isConfigured()) {
|
|
153
161
|
throw new TTSError({
|
|
154
162
|
code: TTS_ERROR_CODES.PROVIDER_NOT_CONFIGURED,
|
|
155
163
|
message: "Google Cloud TTS client not initialized. Set GOOGLE_APPLICATION_CREDENTIALS or pass credentials path.",
|
|
@@ -158,6 +166,7 @@ export class GoogleTTSHandler {
|
|
|
158
166
|
retriable: false,
|
|
159
167
|
});
|
|
160
168
|
}
|
|
169
|
+
const client = await this.getClient();
|
|
161
170
|
const voiceId = options.voice ?? "en-US-Neural2-C";
|
|
162
171
|
const span = SpanSerializer.createSpan(SpanType.TTS, "tts.google.synthesize", {
|
|
163
172
|
"tts.operation": "synthesize",
|
|
@@ -196,7 +205,7 @@ export class GoogleTTSHandler {
|
|
|
196
205
|
volumeGainDb: options.volumeGainDb ?? 0.0,
|
|
197
206
|
},
|
|
198
207
|
};
|
|
199
|
-
const [response] = await
|
|
208
|
+
const [response] = await client.synthesizeSpeech(request, {
|
|
200
209
|
timeout: GoogleTTSHandler.DEFAULT_API_TIMEOUT_MS,
|
|
201
210
|
});
|
|
202
211
|
const audioContent = response.audioContent;
|
|
@@ -3,7 +3,6 @@ import * as fs from "fs";
|
|
|
3
3
|
import * as path from "path";
|
|
4
4
|
import { execFile } from "child_process";
|
|
5
5
|
import { logger } from "../utils/logger.js";
|
|
6
|
-
import { VertexAI } from "@google-cloud/vertexai";
|
|
7
6
|
import { CSVProcessor } from "../utils/csvProcessor.js";
|
|
8
7
|
import { shouldEnableBashTool } from "../utils/toolUtils.js";
|
|
9
8
|
import { tool } from "../utils/tool.js";
|
|
@@ -604,6 +603,7 @@ export const directAgentTools = {
|
|
|
604
603
|
};
|
|
605
604
|
}
|
|
606
605
|
const limitedResults = Math.min(Math.max(maxResults, 1), 5);
|
|
606
|
+
const { VertexAI } = await import("@google-cloud/vertexai");
|
|
607
607
|
const vertex_ai = new VertexAI({
|
|
608
608
|
project: hasProjectId,
|
|
609
609
|
location: projectLocation,
|
|
@@ -77,10 +77,44 @@ export declare abstract class BaseProvider implements AIProvider {
|
|
|
77
77
|
private executeFakeStreaming;
|
|
78
78
|
/**
|
|
79
79
|
* Apply per-call tool filtering (whitelist/blacklist) to a tools record.
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
|
|
80
|
+
*
|
|
81
|
+
* All filtering surfaces are merged into one ResolvedToolPolicy by
|
|
82
|
+
* `resolveToolPolicy()` — per-call `toolFilter` (whitelist),
|
|
83
|
+
* `enabledToolNames` (merged into the whitelist, as its docs always
|
|
84
|
+
* promised), `excludeTools` (denylist, applied after the whitelist), and
|
|
85
|
+
* the instance-level `tools` config (enabled/include/exclude, `*` globs) —
|
|
86
|
+
* then applied by `applyToolGate()`. This is the single filter semantics
|
|
87
|
+
* for every generate/stream path.
|
|
88
|
+
*/
|
|
89
|
+
private getToolPolicy;
|
|
83
90
|
private applyToolFiltering;
|
|
91
|
+
/**
|
|
92
|
+
* Deterministic name-sorted key order. External MCP servers connect and
|
|
93
|
+
* discover in parallel, so insertion order varies across process restarts;
|
|
94
|
+
* providers serialize this record in key order (and Anthropic pins its
|
|
95
|
+
* cache_control breakpoint to the LAST tool), so an unstable order silently
|
|
96
|
+
* busts provider prompt caches. Runs AFTER the dedup pass — dedup's
|
|
97
|
+
* keep-first policy must see phase order (built-ins first) so duplicate
|
|
98
|
+
* winners don't flip when an MCP tool name sorts earlier.
|
|
99
|
+
*/
|
|
100
|
+
private sortToolRecord;
|
|
101
|
+
/**
|
|
102
|
+
* On-demand discovery (`tools.discovery: true`): defer external MCP tool
|
|
103
|
+
* schemas behind one `search_tools` meta-tool. Built-in tools, per-call
|
|
104
|
+
* tools, explicitly whitelisted tools, and session-pinned (previously
|
|
105
|
+
* discovered) tools always stay hot. No-op when discovery is off — with a
|
|
106
|
+
* one-time WARN when the catalog is large enough that selection accuracy
|
|
107
|
+
* measurably degrades.
|
|
108
|
+
*/
|
|
109
|
+
private applyToolDiscovery;
|
|
110
|
+
private static warnedLargeCatalog;
|
|
111
|
+
private warnLargeCatalogOnce;
|
|
112
|
+
/**
|
|
113
|
+
* Opt-in signature dedup — runs AFTER whitelist/blacklist filtering and
|
|
114
|
+
* BEFORE the tool set reaches the provider call. Fails open: any error
|
|
115
|
+
* inside dedupeTools returns the original filtered set unchanged.
|
|
116
|
+
*/
|
|
117
|
+
private applyDedupPass;
|
|
84
118
|
/**
|
|
85
119
|
* Prepare generation context including tools and model
|
|
86
120
|
*/
|