mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-04 14:57:42 +00:00
fix MCP tool name limits
This commit is contained in:
parent
d8427ffc5e
commit
1349a2cd2d
13 changed files with 682 additions and 61 deletions
|
|
@ -27,6 +27,8 @@ const {
|
|||
buildMCPAuthRunStepEvent,
|
||||
buildMCPAuthRunStepDeltaEvent,
|
||||
buildMCPAuthRunStepCompletedEvent,
|
||||
resolveToolNameMaxLength,
|
||||
resolveToolNameForExecution,
|
||||
isFileAuthoringToolDefinition,
|
||||
ASK_USER_QUESTION_TOOL_NAME,
|
||||
} = require('@librechat/api');
|
||||
|
|
@ -561,6 +563,10 @@ async function loadToolDefinitionsWrapper({
|
|||
}
|
||||
|
||||
const appConfig = req.config;
|
||||
const toolNameMaxLength = resolveToolNameMaxLength({
|
||||
appConfig,
|
||||
provider: agent.provider,
|
||||
});
|
||||
const enabledCapabilities = await resolveAgentCapabilities(req, appConfig, agent.id);
|
||||
|
||||
const checkCapability = (capability) => enabledCapabilities.has(capability);
|
||||
|
|
@ -885,6 +891,7 @@ async function loadToolDefinitionsWrapper({
|
|||
programmaticToolsEnabled,
|
||||
codeExecutionEnabled,
|
||||
provider: agent.provider,
|
||||
toolNameMaxLength,
|
||||
},
|
||||
{
|
||||
isBuiltInTool,
|
||||
|
|
@ -967,6 +974,7 @@ async function loadToolDefinitionsWrapper({
|
|||
programmaticToolsEnabled,
|
||||
codeExecutionEnabled,
|
||||
provider: agent.provider,
|
||||
toolNameMaxLength,
|
||||
},
|
||||
{
|
||||
isBuiltInTool,
|
||||
|
|
@ -1127,6 +1135,10 @@ async function loadAgentTools({
|
|||
}
|
||||
|
||||
const appConfig = req.config;
|
||||
const toolNameMaxLength = resolveToolNameMaxLength({
|
||||
appConfig,
|
||||
provider: agent.provider,
|
||||
});
|
||||
const enabledCapabilities = await resolveAgentCapabilities(req, appConfig, agent.id);
|
||||
const checkCapability = (capability) => {
|
||||
const enabled = enabledCapabilities.has(capability);
|
||||
|
|
@ -1233,6 +1245,7 @@ async function loadAgentTools({
|
|||
programmaticToolsEnabled,
|
||||
codeExecutionEnabled,
|
||||
authHeaders: () => getCodeApiAuthHeaders(req),
|
||||
toolNameMaxLength,
|
||||
});
|
||||
|
||||
const agentTools = [];
|
||||
|
|
@ -1487,6 +1500,8 @@ async function loadToolsForExecution({
|
|||
}) {
|
||||
const appConfig = req.config;
|
||||
const allLoadedTools = [];
|
||||
const runtimeToolMap = new Map();
|
||||
const runtimeNamesByLoadName = new Map();
|
||||
const mcpRequestScopedConnections = requestScopedConnections ?? getMCPRequestContext(req, res);
|
||||
const configurable = { userMCPAuthMap, requestScopedConnections: mcpRequestScopedConnections };
|
||||
/** Per-agent set of tools that received the injected `run_in_background`
|
||||
|
|
@ -1496,6 +1511,31 @@ async function loadToolsForExecution({
|
|||
configurable.backgroundToolNames = backgroundToolNames;
|
||||
}
|
||||
|
||||
const addRuntimeNameForLoadName = (loadName, runtimeName) => {
|
||||
if (!loadName || !runtimeName) {
|
||||
return;
|
||||
}
|
||||
const runtimeNames = runtimeNamesByLoadName.get(loadName) ?? new Set();
|
||||
runtimeNames.add(runtimeName);
|
||||
runtimeNamesByLoadName.set(loadName, runtimeNames);
|
||||
};
|
||||
|
||||
const addLoadedTool = (tool) => {
|
||||
allLoadedTools.push(tool);
|
||||
if (!tool?.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
runtimeToolMap.set(tool.name, tool);
|
||||
const runtimeNames = runtimeNamesByLoadName.get(tool.name);
|
||||
if (!runtimeNames) {
|
||||
return;
|
||||
}
|
||||
for (const runtimeName of runtimeNames) {
|
||||
runtimeToolMap.set(runtimeName, tool);
|
||||
}
|
||||
};
|
||||
|
||||
const isToolSearch = toolNames.includes(AgentConstants.TOOL_SEARCH);
|
||||
const ptcToolNames = [
|
||||
AgentConstants.BASH_PROGRAMMATIC_TOOL_CALLING,
|
||||
|
|
@ -1543,7 +1583,7 @@ async function loadToolsForExecution({
|
|||
mode: 'local',
|
||||
toolRegistry,
|
||||
});
|
||||
allLoadedTools.push(toolSearchTool);
|
||||
addLoadedTool(toolSearchTool);
|
||||
configurable.toolRegistry = toolRegistry;
|
||||
}
|
||||
|
||||
|
|
@ -1559,7 +1599,7 @@ async function loadToolsForExecution({
|
|||
authHeaders: () => getCodeApiAuthHeaders(req),
|
||||
});
|
||||
ptcTool.name = name;
|
||||
allLoadedTools.push(ptcTool);
|
||||
addLoadedTool(ptcTool);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('[loadToolsForExecution] Error creating PTC tool:', error);
|
||||
|
|
@ -1582,7 +1622,7 @@ async function loadToolsForExecution({
|
|||
authHeaders: () => getCodeApiAuthHeaders(req),
|
||||
statefulSessions: statefulCodeSessions,
|
||||
});
|
||||
allLoadedTools.push(bashTool);
|
||||
addLoadedTool(bashTool);
|
||||
} catch (error) {
|
||||
logger.error('[loadToolsForExecution] Failed to create bash_tool', error);
|
||||
}
|
||||
|
|
@ -1630,9 +1670,21 @@ async function loadToolsForExecution({
|
|||
? [...new Set([...allowedNonSpecialToolNames, ...ptcOrchestratedToolNames])]
|
||||
: allowedNonSpecialToolNames;
|
||||
|
||||
const resolvedToolNamesToLoad = [];
|
||||
for (const name of allToolNamesToLoad) {
|
||||
const resolvedName = resolveToolNameForExecution(name, toolRegistry);
|
||||
addRuntimeNameForLoadName(resolvedName, name);
|
||||
addRuntimeNameForLoadName(resolvedName, resolvedName);
|
||||
resolvedToolNamesToLoad.push(resolvedName);
|
||||
if (resolvedName !== name) {
|
||||
logger.debug(`[loadToolsForExecution] Resolved tool name "${name}" -> "${resolvedName}"`);
|
||||
}
|
||||
}
|
||||
const uniqueToolNamesToLoad = [...new Set(resolvedToolNamesToLoad)];
|
||||
|
||||
const actionToolNames = [];
|
||||
const regularToolNames = [];
|
||||
for (const name of allToolNamesToLoad) {
|
||||
for (const name of uniqueToolNamesToLoad) {
|
||||
(isActionTool(name) ? actionToolNames : regularToolNames).push(name);
|
||||
}
|
||||
|
||||
|
|
@ -1667,7 +1719,9 @@ async function loadToolsForExecution({
|
|||
});
|
||||
|
||||
if (loadedTools) {
|
||||
allLoadedTools.push(...loadedTools);
|
||||
for (const tool of loadedTools) {
|
||||
addLoadedTool(tool);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1681,7 +1735,9 @@ async function loadToolsForExecution({
|
|||
jobCreatedAt,
|
||||
actionToolNames,
|
||||
});
|
||||
allLoadedTools.push(...actionTools);
|
||||
for (const tool of actionTools) {
|
||||
addLoadedTool(tool);
|
||||
}
|
||||
} else if (actionToolNames.length > 0 && agent && !actionsEnabled) {
|
||||
logger.warn(
|
||||
`[loadToolsForExecution] Capability "${AgentCapabilities.actions}" disabled. ` +
|
||||
|
|
@ -1691,13 +1747,12 @@ async function loadToolsForExecution({
|
|||
|
||||
if (isPTC && allLoadedTools.length > 0) {
|
||||
const ptcToolMap = new Map();
|
||||
for (const tool of allLoadedTools) {
|
||||
for (const [name, tool] of runtimeToolMap.entries()) {
|
||||
if (
|
||||
tool.name &&
|
||||
tool.name !== AgentConstants.PROGRAMMATIC_TOOL_CALLING &&
|
||||
tool.name !== AgentConstants.BASH_PROGRAMMATIC_TOOL_CALLING
|
||||
name !== AgentConstants.PROGRAMMATIC_TOOL_CALLING &&
|
||||
name !== AgentConstants.BASH_PROGRAMMATIC_TOOL_CALLING
|
||||
) {
|
||||
ptcToolMap.set(tool.name, tool);
|
||||
ptcToolMap.set(name, tool);
|
||||
}
|
||||
}
|
||||
configurable.ptcToolMap = ptcToolMap;
|
||||
|
|
@ -1706,6 +1761,7 @@ async function loadToolsForExecution({
|
|||
return {
|
||||
configurable,
|
||||
loadedTools: allLoadedTools,
|
||||
toolMap: runtimeToolMap,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ export interface ToolExecuteOptions {
|
|||
agentId?: string,
|
||||
) => Promise<{
|
||||
loadedTools: StructuredToolInterface[];
|
||||
toolMap?: Map<string, StructuredToolInterface>;
|
||||
/** Additional configurable properties to merge (e.g., userMCPAuthMap) */
|
||||
configurable?: Record<string, unknown>;
|
||||
}>;
|
||||
|
|
@ -3631,11 +3632,12 @@ export function createToolExecuteHandler(options: ToolExecuteOptions): EventHand
|
|||
await runOutsideTracing(async () => {
|
||||
try {
|
||||
const toolNames = [...new Set(toolCalls.map((tc: ToolCallRequest) => tc.name))];
|
||||
const { loadedTools, configurable: toolConfigurable } = await loadTools(
|
||||
toolNames,
|
||||
agentId,
|
||||
);
|
||||
const toolMap = new Map(loadedTools.map((t) => [t.name, t]));
|
||||
const {
|
||||
loadedTools,
|
||||
toolMap: loadedToolMap,
|
||||
configurable: toolConfigurable,
|
||||
} = await loadTools(toolNames, agentId);
|
||||
const toolMap = loadedToolMap ?? new Map(loadedTools.map((t) => [t.name, t]));
|
||||
const sourceConfigurable = configurable as Record<string, unknown> | undefined;
|
||||
const loadedConfigurable = toolConfigurable as Record<string, unknown> | undefined;
|
||||
const mergedConfigurable = mergeToolConfigurables(
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ export interface SkillConfigurableContext {
|
|||
|
||||
export interface SkillConfigurableLoadResult {
|
||||
loadedTools: unknown[];
|
||||
toolMap?: Map<string, unknown>;
|
||||
configurable?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
|
|
@ -63,6 +64,7 @@ export interface EnrichWithSkillConfigurableParams {
|
|||
|
||||
export function enrichWithSkillConfigurable(params: EnrichWithSkillConfigurableParams): {
|
||||
loadedTools: unknown[];
|
||||
toolMap?: Map<string, unknown>;
|
||||
configurable: Record<string, unknown>;
|
||||
};
|
||||
export function enrichWithSkillConfigurable(
|
||||
|
|
@ -74,7 +76,11 @@ export function enrichWithSkillConfigurable(
|
|||
activeSkillNames?: Set<string>,
|
||||
skillAuthoringAvailable?: boolean,
|
||||
fileAuthoringToolNames?: Set<string>,
|
||||
): { loadedTools: unknown[]; configurable: Record<string, unknown> };
|
||||
): {
|
||||
loadedTools: unknown[];
|
||||
toolMap?: Map<string, unknown>;
|
||||
configurable: Record<string, unknown>;
|
||||
};
|
||||
export function enrichWithSkillConfigurable(
|
||||
first: EnrichWithSkillConfigurableParams | SkillConfigurableLoadResult,
|
||||
req?: { user?: { id?: string } },
|
||||
|
|
@ -84,7 +90,11 @@ export function enrichWithSkillConfigurable(
|
|||
activeSkillNames?: Set<string>,
|
||||
skillAuthoringAvailable?: boolean,
|
||||
fileAuthoringToolNames?: Set<string>,
|
||||
): { loadedTools: unknown[]; configurable: Record<string, unknown> } {
|
||||
): {
|
||||
loadedTools: unknown[];
|
||||
toolMap?: Map<string, unknown>;
|
||||
configurable: Record<string, unknown>;
|
||||
} {
|
||||
const { result, context } =
|
||||
'result' in first && 'context' in first
|
||||
? first
|
||||
|
|
|
|||
|
|
@ -9,6 +9,13 @@ import {
|
|||
getServerNameFromTool,
|
||||
agentHasDeferredTools,
|
||||
} from './classification';
|
||||
import { resolveToolNameForExecution } from './names';
|
||||
|
||||
type MCPNameMetadata = {
|
||||
canonicalName?: string;
|
||||
providerToolName?: string;
|
||||
mcpRawName?: string;
|
||||
};
|
||||
|
||||
describe('classification.ts', () => {
|
||||
describe('getServerNameFromTool', () => {
|
||||
|
|
@ -79,6 +86,60 @@ describe('classification.ts', () => {
|
|||
|
||||
expect(registry.get('tool1')?.allowed_callers).toEqual(['direct']);
|
||||
});
|
||||
|
||||
it('should alias long MCP names and preserve canonical tool options', () => {
|
||||
const rawName = 'search_records_with_a_very_long_raw_name';
|
||||
const canonicalName = `${rawName}_mcp_server_with_a_very_long_name_that_exceeds_limits`;
|
||||
const tools = [{ name: canonicalName, description: 'Long MCP tool' }];
|
||||
|
||||
const agentToolOptions: AgentToolOptions = {
|
||||
[canonicalName]: {
|
||||
defer_loading: true,
|
||||
allowed_callers: ['code_execution'],
|
||||
},
|
||||
};
|
||||
|
||||
const registry = buildToolRegistryFromAgentOptions(tools, agentToolOptions, 64);
|
||||
const [providerToolName, toolDef] = Array.from(registry.entries())[0];
|
||||
const metadata = toolDef as typeof toolDef & MCPNameMetadata;
|
||||
|
||||
expect(providerToolName).not.toBe(canonicalName);
|
||||
expect(providerToolName.length).toBeLessThanOrEqual(64);
|
||||
expect(toolDef.name).toBe(providerToolName);
|
||||
expect(toolDef.defer_loading).toBe(true);
|
||||
expect(toolDef.allowed_callers).toEqual(['code_execution']);
|
||||
expect(metadata.canonicalName).toBe(canonicalName);
|
||||
expect(metadata.providerToolName).toBe(providerToolName);
|
||||
expect(metadata.mcpRawName).toBe(rawName);
|
||||
expect(resolveToolNameForExecution(providerToolName, registry)).toBe(canonicalName);
|
||||
expect(resolveToolNameForExecution(rawName, registry)).toBe(canonicalName);
|
||||
});
|
||||
|
||||
it('should not resolve ambiguous raw MCP names', () => {
|
||||
const rawName = 'search_records';
|
||||
const firstCanonicalName = `${rawName}_mcp_server_one`;
|
||||
const secondCanonicalName = `${rawName}_mcp_server_two`;
|
||||
const registry = buildToolRegistryFromAgentOptions(
|
||||
[{ name: firstCanonicalName }, { name: secondCanonicalName }],
|
||||
{},
|
||||
);
|
||||
|
||||
expect(resolveToolNameForExecution(rawName, registry)).toBe(rawName);
|
||||
});
|
||||
|
||||
it('should expose duplicate canonical MCP definitions once', () => {
|
||||
const canonicalName =
|
||||
'search_records_with_a_very_long_raw_name_mcp_server_with_a_very_long_name';
|
||||
const registry = buildToolRegistryFromAgentOptions(
|
||||
[{ name: canonicalName }, { name: canonicalName }],
|
||||
{},
|
||||
64,
|
||||
);
|
||||
|
||||
expect(registry.size).toBe(1);
|
||||
const [providerToolName] = Array.from(registry.keys());
|
||||
expect(resolveToolNameForExecution(providerToolName, registry)).toBe(canonicalName);
|
||||
});
|
||||
});
|
||||
|
||||
describe('agentHasDeferredTools', () => {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
|
||||
import { logger } from '@librechat/data-schemas';
|
||||
import { Constants } from 'librechat-data-provider';
|
||||
import {
|
||||
Providers,
|
||||
createToolSearch,
|
||||
|
|
@ -22,6 +21,8 @@ import type {
|
|||
LCTool,
|
||||
} from '@librechat/agents';
|
||||
import type { AgentToolOptions } from 'librechat-data-provider';
|
||||
import type { LCToolWithMCPNameMetadata } from './names';
|
||||
import { parseMCPToolName, createProviderToolName, DEFAULT_TOOL_NAME_MAX_LENGTH } from './names';
|
||||
import { sanitizeGeminiSchema } from '~/mcp/zod';
|
||||
|
||||
export type { LCTool, LCToolRegistry, AllowedCaller, JsonSchemaType };
|
||||
|
|
@ -32,6 +33,10 @@ export interface ToolDefinition {
|
|||
parameters?: JsonSchemaType;
|
||||
/** MCP server name extracted from tool name */
|
||||
serverName?: string;
|
||||
/** Original LibreChat MCP key: toolName_mcp_serverName */
|
||||
canonicalName?: string;
|
||||
/** Raw MCP tool name before LibreChat appends the server suffix */
|
||||
mcpRawName?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -41,11 +46,60 @@ export interface ToolDefinition {
|
|||
* @returns The server name or undefined if not an MCP tool
|
||||
*/
|
||||
export function getServerNameFromTool(toolName: string): string | undefined {
|
||||
const parts = toolName.split(Constants.mcp_delimiter);
|
||||
if (parts.length >= 2) {
|
||||
return parts[parts.length - 1];
|
||||
return parseMCPToolName(toolName)?.serverName;
|
||||
}
|
||||
|
||||
function createRegistryTool({
|
||||
tool,
|
||||
agentToolOptions,
|
||||
usedToolNames,
|
||||
toolNameMaxLength,
|
||||
}: {
|
||||
tool: ToolDefinition;
|
||||
agentToolOptions?: AgentToolOptions;
|
||||
usedToolNames: Set<string>;
|
||||
toolNameMaxLength: number;
|
||||
}): LCToolWithMCPNameMetadata {
|
||||
const { description, parameters } = tool;
|
||||
const canonicalName = tool.canonicalName ?? tool.name;
|
||||
const parsed = parseMCPToolName(canonicalName);
|
||||
const providerToolName = createProviderToolName({
|
||||
canonicalName,
|
||||
usedToolNames,
|
||||
maxLength: toolNameMaxLength,
|
||||
});
|
||||
usedToolNames.add(providerToolName);
|
||||
|
||||
const agentOptions = agentToolOptions?.[canonicalName] ?? agentToolOptions?.[providerToolName];
|
||||
|
||||
const allowed_callers: AllowedCaller[] =
|
||||
agentOptions?.allowed_callers && agentOptions.allowed_callers.length > 0
|
||||
? agentOptions.allowed_callers
|
||||
: ['direct'];
|
||||
|
||||
const defer_loading = agentOptions?.defer_loading === true;
|
||||
|
||||
const toolDef: LCToolWithMCPNameMetadata = {
|
||||
name: providerToolName,
|
||||
allowed_callers,
|
||||
defer_loading,
|
||||
toolType: 'mcp',
|
||||
canonicalName,
|
||||
providerToolName,
|
||||
mcpRawName: tool.mcpRawName ?? parsed?.rawName,
|
||||
};
|
||||
|
||||
if (description) {
|
||||
toolDef.description = description;
|
||||
}
|
||||
return undefined;
|
||||
if (parameters) {
|
||||
toolDef.parameters = parameters;
|
||||
}
|
||||
if (tool.serverName || parsed?.serverName) {
|
||||
toolDef.serverName = tool.serverName ?? parsed?.serverName;
|
||||
}
|
||||
|
||||
return toolDef;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -58,38 +112,26 @@ export function getServerNameFromTool(toolName: string): string | undefined {
|
|||
export function buildToolRegistryFromAgentOptions(
|
||||
tools: ToolDefinition[],
|
||||
agentToolOptions: AgentToolOptions,
|
||||
toolNameMaxLength = DEFAULT_TOOL_NAME_MAX_LENGTH,
|
||||
): LCToolRegistry {
|
||||
const registry: LCToolRegistry = new Map();
|
||||
const usedToolNames = new Set<string>();
|
||||
const usedCanonicalNames = new Set<string>();
|
||||
|
||||
for (const tool of tools) {
|
||||
const { name, description, parameters } = tool;
|
||||
const agentOptions = agentToolOptions[name];
|
||||
|
||||
const allowed_callers: AllowedCaller[] =
|
||||
agentOptions?.allowed_callers && agentOptions.allowed_callers.length > 0
|
||||
? agentOptions.allowed_callers
|
||||
: ['direct'];
|
||||
|
||||
const defer_loading = agentOptions?.defer_loading === true;
|
||||
|
||||
const toolDef: LCTool = {
|
||||
name,
|
||||
allowed_callers,
|
||||
defer_loading,
|
||||
toolType: 'mcp',
|
||||
};
|
||||
|
||||
if (description) {
|
||||
toolDef.description = description;
|
||||
}
|
||||
if (parameters) {
|
||||
toolDef.parameters = parameters;
|
||||
}
|
||||
if (tool.serverName) {
|
||||
toolDef.serverName = tool.serverName;
|
||||
const canonicalName = tool.canonicalName ?? tool.name;
|
||||
if (usedCanonicalNames.has(canonicalName)) {
|
||||
continue;
|
||||
}
|
||||
usedCanonicalNames.add(canonicalName);
|
||||
|
||||
registry.set(name, toolDef);
|
||||
const toolDef = createRegistryTool({
|
||||
tool,
|
||||
agentToolOptions,
|
||||
usedToolNames,
|
||||
toolNameMaxLength,
|
||||
});
|
||||
registry.set(toolDef.name, toolDef);
|
||||
}
|
||||
|
||||
return registry;
|
||||
|
|
@ -111,7 +153,12 @@ interface MCPToolInstance {
|
|||
* @returns Tool definition
|
||||
*/
|
||||
export function extractMCPToolDefinition(tool: MCPToolInstance): ToolDefinition {
|
||||
const def: ToolDefinition = { name: tool.name };
|
||||
const parsed = parseMCPToolName(tool.name);
|
||||
const def: ToolDefinition = {
|
||||
name: tool.name,
|
||||
canonicalName: tool.name,
|
||||
mcpRawName: parsed?.rawName,
|
||||
};
|
||||
|
||||
if (tool.description) {
|
||||
def.description = tool.description;
|
||||
|
|
@ -121,9 +168,8 @@ export function extractMCPToolDefinition(tool: MCPToolInstance): ToolDefinition
|
|||
def.parameters = tool.mcpJsonSchema;
|
||||
}
|
||||
|
||||
const serverName = getServerNameFromTool(tool.name);
|
||||
if (serverName) {
|
||||
def.serverName = serverName;
|
||||
if (parsed?.serverName) {
|
||||
def.serverName = parsed.serverName;
|
||||
}
|
||||
|
||||
return def;
|
||||
|
|
@ -156,21 +202,29 @@ export function cleanupMCPToolSchemas(tools: MCPToolInstance[]): void {
|
|||
function buildToolRegistry(
|
||||
mcpToolDefs: ToolDefinition[],
|
||||
agentToolOptions?: AgentToolOptions,
|
||||
toolNameMaxLength = DEFAULT_TOOL_NAME_MAX_LENGTH,
|
||||
): LCToolRegistry {
|
||||
if (agentToolOptions && Object.keys(agentToolOptions).length > 0) {
|
||||
return buildToolRegistryFromAgentOptions(mcpToolDefs, agentToolOptions);
|
||||
return buildToolRegistryFromAgentOptions(mcpToolDefs, agentToolOptions, toolNameMaxLength);
|
||||
}
|
||||
|
||||
/** No agent options - build basic definitions for event-driven mode */
|
||||
const registry: LCToolRegistry = new Map<string, LCTool>();
|
||||
const usedToolNames = new Set<string>();
|
||||
const usedCanonicalNames = new Set<string>();
|
||||
for (const toolDef of mcpToolDefs) {
|
||||
registry.set(toolDef.name, {
|
||||
name: toolDef.name,
|
||||
description: toolDef.description,
|
||||
parameters: toolDef.parameters,
|
||||
serverName: toolDef.serverName,
|
||||
toolType: 'mcp',
|
||||
const canonicalName = toolDef.canonicalName ?? toolDef.name;
|
||||
if (usedCanonicalNames.has(canonicalName)) {
|
||||
continue;
|
||||
}
|
||||
usedCanonicalNames.add(canonicalName);
|
||||
|
||||
const registryTool = createRegistryTool({
|
||||
tool: toolDef,
|
||||
usedToolNames,
|
||||
toolNameMaxLength,
|
||||
});
|
||||
registry.set(registryTool.name, registryTool);
|
||||
}
|
||||
return registry;
|
||||
}
|
||||
|
|
@ -197,6 +251,8 @@ export interface BuildToolClassificationParams {
|
|||
provider?: Providers | string;
|
||||
/** Optional host-supplied Code API auth headers for remote programmatic execution. */
|
||||
authHeaders?: () => Promise<Record<string, string>> | Record<string, string>;
|
||||
/** Provider-facing maximum tool/function name length. */
|
||||
toolNameMaxLength?: number;
|
||||
}
|
||||
|
||||
/** Result from building tool classification */
|
||||
|
|
@ -265,6 +321,7 @@ export async function buildToolClassification(
|
|||
programmaticToolsEnabled = false,
|
||||
codeExecutionEnabled = false,
|
||||
authHeaders,
|
||||
toolNameMaxLength = DEFAULT_TOOL_NAME_MAX_LENGTH,
|
||||
} = params;
|
||||
const isGoogle = provider === Providers.GOOGLE || provider === Providers.VERTEXAI;
|
||||
const additionalTools: GenericTool[] = [];
|
||||
|
|
@ -280,7 +337,11 @@ export async function buildToolClassification(
|
|||
}
|
||||
|
||||
const mcpToolDefs = mcpTools.map(extractMCPToolDefinition);
|
||||
const toolRegistry: LCToolRegistry = buildToolRegistry(mcpToolDefs, agentToolOptions);
|
||||
const toolRegistry: LCToolRegistry = buildToolRegistry(
|
||||
mcpToolDefs,
|
||||
agentToolOptions,
|
||||
toolNameMaxLength,
|
||||
);
|
||||
|
||||
/** Clean up temporary mcpJsonSchema property from tools now that registry is populated */
|
||||
cleanupMCPToolSchemas(mcpTools);
|
||||
|
|
|
|||
|
|
@ -5,11 +5,18 @@ import type {
|
|||
ActionToolDefinition,
|
||||
} from './definitions';
|
||||
import { toolkitExpansion, toolkitParent } from './toolkits/mapping';
|
||||
import { resolveToolNameForExecution } from './names';
|
||||
import { getToolDefinition } from './registry/definitions';
|
||||
import { loadToolDefinitions } from './definitions';
|
||||
|
||||
const MAX_PROVIDER_TOOL_DESCRIPTION_LENGTH = 1024;
|
||||
|
||||
type MCPNameMetadata = {
|
||||
canonicalName?: string;
|
||||
providerToolName?: string;
|
||||
mcpRawName?: string;
|
||||
};
|
||||
|
||||
describe('definitions.ts', () => {
|
||||
const mockGetOrFetchMCPServerTools = jest.fn().mockResolvedValue(null);
|
||||
const mockIsBuiltInTool = jest.fn().mockReturnValue(false);
|
||||
|
|
@ -598,6 +605,53 @@ describe('definitions.ts', () => {
|
|||
expect(result.toolDefinitions[0].name).toBe('list_items_mcp_server-one');
|
||||
});
|
||||
|
||||
it('should expose aliases for MCP tools that exceed provider name limits', async () => {
|
||||
const rawName = 'search_records_with_a_very_long_raw_name';
|
||||
const serverName = 'server_with_a_very_long_name_that_exceeds_provider_limits';
|
||||
const canonicalName = `${rawName}_mcp_${serverName}`;
|
||||
const mockServerTools = {
|
||||
[canonicalName]: {
|
||||
function: {
|
||||
name: canonicalName,
|
||||
description: 'Search records',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
query: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
mockGetOrFetchMCPServerTools.mockResolvedValue(mockServerTools);
|
||||
|
||||
const result = await loadToolDefinitions(
|
||||
{
|
||||
userId: 'user-123',
|
||||
agentId: 'agent-123',
|
||||
tools: [canonicalName],
|
||||
toolNameMaxLength: 64,
|
||||
},
|
||||
{
|
||||
getOrFetchMCPServerTools: mockGetOrFetchMCPServerTools,
|
||||
isBuiltInTool: mockIsBuiltInTool,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.toolDefinitions).toHaveLength(1);
|
||||
const toolDef = result.toolDefinitions[0];
|
||||
const metadata = toolDef as typeof toolDef & MCPNameMetadata;
|
||||
|
||||
expect(toolDef.name).not.toBe(canonicalName);
|
||||
expect(toolDef.name.length).toBeLessThanOrEqual(64);
|
||||
expect(metadata.canonicalName).toBe(canonicalName);
|
||||
expect(metadata.providerToolName).toBe(toolDef.name);
|
||||
expect(metadata.mcpRawName).toBe(rawName);
|
||||
expect(resolveToolNameForExecution(toolDef.name, result.toolRegistry)).toBe(canonicalName);
|
||||
expect(resolveToolNameForExecution(rawName, result.toolRegistry)).toBe(canonicalName);
|
||||
});
|
||||
|
||||
it('should include hyphenated server name tools in registry with correct serverName', async () => {
|
||||
const mockServerTools = {
|
||||
'list_items_mcp_my-server': {
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ export interface LoadToolDefinitionsParams {
|
|||
codeExecutionEnabled?: boolean;
|
||||
/** Agent provider — Gemini/Vertex tool schemas get union-flattened for compatibility */
|
||||
provider?: Providers;
|
||||
/** Provider-facing maximum tool/function name length */
|
||||
toolNameMaxLength?: number;
|
||||
}
|
||||
|
||||
export interface ActionToolDefinition {
|
||||
|
|
@ -87,6 +89,7 @@ export async function loadToolDefinitions(
|
|||
programmaticToolsEnabled = false,
|
||||
codeExecutionEnabled = false,
|
||||
provider,
|
||||
toolNameMaxLength,
|
||||
} = params;
|
||||
const { getOrFetchMCPServerTools, isBuiltInTool, getActionToolDefinitions } = deps;
|
||||
|
||||
|
|
@ -217,6 +220,7 @@ export async function loadToolDefinitions(
|
|||
deferredToolsEnabled,
|
||||
programmaticToolsEnabled,
|
||||
codeExecutionEnabled,
|
||||
toolNameMaxLength,
|
||||
definitionsOnly: true,
|
||||
agentToolOptions: toolOptions,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,3 +3,4 @@ export * from './registry';
|
|||
export * from './toolkits';
|
||||
export * from './definitions';
|
||||
export * from './classification';
|
||||
export * from './names';
|
||||
|
|
|
|||
74
packages/api/src/tools/names.spec.ts
Normal file
74
packages/api/src/tools/names.spec.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import { EModelEndpoint } from 'librechat-data-provider';
|
||||
import {
|
||||
createProviderToolName,
|
||||
resolveToolNameMaxLength,
|
||||
isProviderToolNameCompatible,
|
||||
} from './names';
|
||||
|
||||
describe('names.ts', () => {
|
||||
describe('resolveToolNameMaxLength', () => {
|
||||
it('uses the provider default when config is missing', () => {
|
||||
expect(resolveToolNameMaxLength({ provider: EModelEndpoint.openAI })).toBe(64);
|
||||
expect(resolveToolNameMaxLength({ provider: EModelEndpoint.anthropic })).toBe(64);
|
||||
expect(resolveToolNameMaxLength({ provider: EModelEndpoint.google })).toBe(64);
|
||||
expect(resolveToolNameMaxLength({ provider: EModelEndpoint.bedrock })).toBe(64);
|
||||
});
|
||||
|
||||
it('uses endpoints.all as the global override', () => {
|
||||
const appConfig = {
|
||||
endpoints: {
|
||||
all: { toolNameMaxLength: 80 },
|
||||
},
|
||||
};
|
||||
|
||||
expect(resolveToolNameMaxLength({ appConfig, provider: EModelEndpoint.openAI })).toBe(80);
|
||||
});
|
||||
|
||||
it('uses provider config before endpoints.all', () => {
|
||||
const appConfig = {
|
||||
endpoints: {
|
||||
all: { toolNameMaxLength: 80 },
|
||||
[EModelEndpoint.openAI]: { toolNameMaxLength: 48 },
|
||||
},
|
||||
};
|
||||
|
||||
expect(resolveToolNameMaxLength({ appConfig, provider: EModelEndpoint.openAI })).toBe(48);
|
||||
});
|
||||
|
||||
it('uses matching custom endpoint config before endpoints.all', () => {
|
||||
const appConfig = {
|
||||
endpoints: {
|
||||
all: { toolNameMaxLength: 80 },
|
||||
[EModelEndpoint.custom]: [{ name: 'OpenRouter', toolNameMaxLength: 52 }],
|
||||
},
|
||||
};
|
||||
|
||||
expect(resolveToolNameMaxLength({ appConfig, provider: 'openrouter' })).toBe(52);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createProviderToolName', () => {
|
||||
it('keeps compatible canonical names unchanged', () => {
|
||||
const canonicalName = 'list_items_mcp_server';
|
||||
expect(createProviderToolName({ canonicalName, maxLength: 64 })).toBe(canonicalName);
|
||||
});
|
||||
|
||||
it('aliases long canonical names within the configured limit', () => {
|
||||
const canonicalName =
|
||||
'search_records_with_a_very_long_raw_name_mcp_server_with_a_very_long_name';
|
||||
const alias = createProviderToolName({ canonicalName, maxLength: 64 });
|
||||
|
||||
expect(alias).not.toBe(canonicalName);
|
||||
expect(alias.length).toBeLessThanOrEqual(64);
|
||||
expect(isProviderToolNameCompatible(alias, 64)).toBe(true);
|
||||
});
|
||||
|
||||
it('aliases provider-incompatible canonical names', () => {
|
||||
const canonicalName = 'search.records_mcp_server';
|
||||
const alias = createProviderToolName({ canonicalName, maxLength: 64 });
|
||||
|
||||
expect(alias).not.toBe(canonicalName);
|
||||
expect(isProviderToolNameCompatible(alias, 64)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
265
packages/api/src/tools/names.ts
Normal file
265
packages/api/src/tools/names.ts
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
import { createHash } from 'crypto';
|
||||
import {
|
||||
Constants,
|
||||
EModelEndpoint,
|
||||
Providers,
|
||||
normalizeEndpointName,
|
||||
} from 'librechat-data-provider';
|
||||
import type { LCTool, LCToolRegistry } from '@librechat/agents';
|
||||
|
||||
export const DEFAULT_TOOL_NAME_MAX_LENGTH = 64;
|
||||
export const MIN_TOOL_NAME_MAX_LENGTH = 16;
|
||||
|
||||
export const PROVIDER_TOOL_NAME_MAX_LENGTH_DEFAULTS: Readonly<Record<string, number>> =
|
||||
Object.freeze({
|
||||
[EModelEndpoint.openAI]: 64,
|
||||
[EModelEndpoint.azureOpenAI]: 64,
|
||||
[EModelEndpoint.anthropic]: 64,
|
||||
[EModelEndpoint.google]: 64,
|
||||
[EModelEndpoint.bedrock]: 64,
|
||||
[EModelEndpoint.custom]: 64,
|
||||
[EModelEndpoint.agents]: 64,
|
||||
[Providers.VERTEXAI]: 64,
|
||||
[Providers.MISTRALAI]: 64,
|
||||
[Providers.MISTRAL]: 64,
|
||||
[Providers.DEEPSEEK]: 64,
|
||||
[Providers.MOONSHOT]: 64,
|
||||
[Providers.OPENROUTER]: 64,
|
||||
[Providers.XAI]: 64,
|
||||
});
|
||||
|
||||
export interface MCPToolNameParts {
|
||||
rawName: string;
|
||||
serverName: string;
|
||||
}
|
||||
|
||||
export interface MCPToolNameMetadata {
|
||||
canonicalName?: string;
|
||||
providerToolName?: string;
|
||||
mcpRawName?: string;
|
||||
}
|
||||
|
||||
export type LCToolWithMCPNameMetadata = LCTool & MCPToolNameMetadata;
|
||||
|
||||
type EndpointConfigRecord = Record<string, unknown>;
|
||||
|
||||
function isRecord(value: unknown): value is EndpointConfigRecord {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function getEndpoints(appConfig?: unknown): EndpointConfigRecord | undefined {
|
||||
if (!isRecord(appConfig)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const endpoints = appConfig.endpoints;
|
||||
return isRecord(endpoints) ? endpoints : undefined;
|
||||
}
|
||||
|
||||
function readToolNameMaxLength(config: unknown): number | undefined {
|
||||
if (!isRecord(config)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const maxLength = config.toolNameMaxLength;
|
||||
if (typeof maxLength !== 'number' || !Number.isFinite(maxLength)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return normalizeToolNameMaxLength(maxLength);
|
||||
}
|
||||
|
||||
function getCustomEndpointConfig(
|
||||
endpoints: EndpointConfigRecord | undefined,
|
||||
provider?: string,
|
||||
): EndpointConfigRecord | undefined {
|
||||
if (!endpoints || !provider) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const customEndpoints = endpoints[EModelEndpoint.custom];
|
||||
if (!Array.isArray(customEndpoints)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const normalizedProvider = normalizeEndpointName(provider);
|
||||
const customEndpointRecords = customEndpoints.filter(
|
||||
(endpoint): endpoint is EndpointConfigRecord =>
|
||||
isRecord(endpoint) && typeof endpoint.name === 'string',
|
||||
);
|
||||
const match = customEndpointRecords.find(
|
||||
(endpoint) => normalizeEndpointName(String(endpoint.name)) === normalizedProvider,
|
||||
);
|
||||
if (match) {
|
||||
return match;
|
||||
}
|
||||
|
||||
const lowercaseMatches = customEndpointRecords.filter(
|
||||
(endpoint) => String(endpoint.name).toLowerCase() === provider.toLowerCase(),
|
||||
);
|
||||
return lowercaseMatches.length === 1 ? lowercaseMatches[0] : undefined;
|
||||
}
|
||||
|
||||
function getProviderEndpointConfig(
|
||||
endpoints: EndpointConfigRecord | undefined,
|
||||
provider?: string,
|
||||
): unknown {
|
||||
if (!endpoints || !provider) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const directConfig = endpoints[provider];
|
||||
if (isRecord(directConfig)) {
|
||||
return directConfig;
|
||||
}
|
||||
|
||||
return getCustomEndpointConfig(endpoints, provider);
|
||||
}
|
||||
|
||||
export function normalizeToolNameMaxLength(maxLength?: number): number {
|
||||
if (typeof maxLength !== 'number' || !Number.isFinite(maxLength)) {
|
||||
return DEFAULT_TOOL_NAME_MAX_LENGTH;
|
||||
}
|
||||
|
||||
return Math.max(MIN_TOOL_NAME_MAX_LENGTH, Math.floor(maxLength));
|
||||
}
|
||||
|
||||
export function getProviderToolNameMaxLengthDefault(provider?: string): number {
|
||||
if (!provider) {
|
||||
return DEFAULT_TOOL_NAME_MAX_LENGTH;
|
||||
}
|
||||
|
||||
return (
|
||||
PROVIDER_TOOL_NAME_MAX_LENGTH_DEFAULTS[provider] ??
|
||||
PROVIDER_TOOL_NAME_MAX_LENGTH_DEFAULTS[provider.toLowerCase()] ??
|
||||
DEFAULT_TOOL_NAME_MAX_LENGTH
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveToolNameMaxLength({
|
||||
appConfig,
|
||||
provider,
|
||||
}: {
|
||||
appConfig?: unknown;
|
||||
provider?: string;
|
||||
}): number {
|
||||
const endpoints = getEndpoints(appConfig);
|
||||
const providerConfig = getProviderEndpointConfig(endpoints, provider);
|
||||
const providerMaxLength = readToolNameMaxLength(providerConfig);
|
||||
const allMaxLength = readToolNameMaxLength(endpoints?.all);
|
||||
|
||||
return normalizeToolNameMaxLength(
|
||||
providerMaxLength ?? allMaxLength ?? getProviderToolNameMaxLengthDefault(provider),
|
||||
);
|
||||
}
|
||||
|
||||
export function parseMCPToolName(toolName: string): MCPToolNameParts | undefined {
|
||||
const delimiterIndex = toolName.lastIndexOf(Constants.mcp_delimiter);
|
||||
if (delimiterIndex === -1) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
rawName: toolName.slice(0, delimiterIndex),
|
||||
serverName: toolName.slice(delimiterIndex + Constants.mcp_delimiter.length),
|
||||
};
|
||||
}
|
||||
|
||||
export function isProviderToolNameCompatible(
|
||||
toolName: string,
|
||||
maxLength = DEFAULT_TOOL_NAME_MAX_LENGTH,
|
||||
): boolean {
|
||||
return (
|
||||
toolName.length > 0 &&
|
||||
toolName.length <= normalizeToolNameMaxLength(maxLength) &&
|
||||
/^[A-Za-z0-9_-]+$/.test(toolName)
|
||||
);
|
||||
}
|
||||
|
||||
function sanitizeToolNamePart(name: string): string {
|
||||
const sanitized = name
|
||||
.replace(/[^A-Za-z0-9_-]+/g, '_')
|
||||
.replace(/_+/g, '_')
|
||||
.replace(/^_+|_+$/g, '');
|
||||
|
||||
return sanitized || 'tool';
|
||||
}
|
||||
|
||||
function getToolNameHash(input: string, attempt: number, length: number): string {
|
||||
const hashInput = attempt === 0 ? input : `${input}:${attempt}`;
|
||||
return createHash('sha256').update(hashInput).digest('hex').slice(0, length);
|
||||
}
|
||||
|
||||
export function createProviderToolName({
|
||||
canonicalName,
|
||||
maxLength = DEFAULT_TOOL_NAME_MAX_LENGTH,
|
||||
usedToolNames = new Set<string>(),
|
||||
}: {
|
||||
canonicalName: string;
|
||||
maxLength?: number;
|
||||
usedToolNames?: Set<string>;
|
||||
}): string {
|
||||
const normalizedMaxLength = normalizeToolNameMaxLength(maxLength);
|
||||
if (
|
||||
isProviderToolNameCompatible(canonicalName, normalizedMaxLength) &&
|
||||
!usedToolNames.has(canonicalName)
|
||||
) {
|
||||
return canonicalName;
|
||||
}
|
||||
|
||||
const parsed = parseMCPToolName(canonicalName);
|
||||
const sourceName = parsed?.rawName ?? canonicalName;
|
||||
const prefix = 'mcp_';
|
||||
const hashLength = normalizedMaxLength >= 24 ? 12 : 8;
|
||||
const suffixLength = hashLength + 1;
|
||||
const baseLength = Math.max(1, normalizedMaxLength - prefix.length - suffixLength);
|
||||
const base = sanitizeToolNamePart(sourceName).slice(0, baseLength);
|
||||
|
||||
for (let attempt = 0; attempt < 1000; attempt++) {
|
||||
const hash = getToolNameHash(canonicalName, attempt, hashLength);
|
||||
const candidate = `${prefix}${base}_${hash}`;
|
||||
if (!usedToolNames.has(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Unable to create unique provider tool name for "${canonicalName}"`);
|
||||
}
|
||||
|
||||
function getCanonicalName(registryName: string, tool: LCTool): string {
|
||||
return (tool as LCToolWithMCPNameMetadata).canonicalName ?? registryName;
|
||||
}
|
||||
|
||||
export function resolveToolNameForExecution(
|
||||
toolName: string,
|
||||
toolRegistry?: LCToolRegistry,
|
||||
): string {
|
||||
if (!toolRegistry) {
|
||||
return toolName;
|
||||
}
|
||||
|
||||
const directTool = toolRegistry.get(toolName);
|
||||
if (directTool) {
|
||||
return getCanonicalName(toolName, directTool);
|
||||
}
|
||||
|
||||
const rawMatches: string[] = [];
|
||||
for (const [registryName, tool] of toolRegistry.entries()) {
|
||||
const metadata = tool as LCToolWithMCPNameMetadata;
|
||||
const canonicalName = getCanonicalName(registryName, tool);
|
||||
|
||||
if (
|
||||
metadata.providerToolName === toolName ||
|
||||
metadata.canonicalName === toolName ||
|
||||
tool.name === toolName
|
||||
) {
|
||||
return canonicalName;
|
||||
}
|
||||
|
||||
if (metadata.mcpRawName === toolName) {
|
||||
rawMatches.push(canonicalName);
|
||||
}
|
||||
}
|
||||
|
||||
return rawMatches.length === 1 ? rawMatches[0] : toolName;
|
||||
}
|
||||
|
|
@ -58,6 +58,36 @@ describe('bedrockEndpointSchema', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('baseEndpointSchema toolNameMaxLength', () => {
|
||||
it('accepts endpoint-wide and provider-specific tool name limits', () => {
|
||||
const result = configSchema.safeParse({
|
||||
version: '1.0',
|
||||
endpoints: {
|
||||
all: { toolNameMaxLength: 80 },
|
||||
[EModelEndpoint.openAI]: { toolNameMaxLength: 64 },
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
if (!result.success) {
|
||||
return;
|
||||
}
|
||||
expect(result.data.endpoints?.all?.toolNameMaxLength).toBe(80);
|
||||
expect(result.data.endpoints?.[EModelEndpoint.openAI]?.toolNameMaxLength).toBe(64);
|
||||
});
|
||||
|
||||
it('rejects tool name limits below the alias minimum', () => {
|
||||
const result = configSchema.safeParse({
|
||||
version: '1.0',
|
||||
endpoints: {
|
||||
all: { toolNameMaxLength: 8 },
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveEndpointType', () => {
|
||||
describe('non-agents endpoints', () => {
|
||||
it('returns the config type for a custom endpoint', () => {
|
||||
|
|
|
|||
|
|
@ -615,6 +615,8 @@ export const baseEndpointSchema = z.object({
|
|||
titleTiming: z.union([z.literal('immediate'), z.literal('final')]).optional(),
|
||||
/** Maximum characters allowed in a single tool result before truncation. */
|
||||
maxToolResultChars: z.number().positive().optional(),
|
||||
/** Maximum provider-facing tool/function name length. */
|
||||
toolNameMaxLength: z.number().int().min(16).optional(),
|
||||
});
|
||||
|
||||
export type TBaseEndpoint = z.infer<typeof baseEndpointSchema>;
|
||||
|
|
|
|||
|
|
@ -480,6 +480,7 @@ export type TConfig = {
|
|||
disableBuilder?: boolean;
|
||||
retrievalModels?: string[];
|
||||
capabilities?: string[];
|
||||
toolNameMaxLength?: number;
|
||||
customParams?: {
|
||||
defaultParamsEndpoint?: string;
|
||||
reasoningFormat?: ReasoningParameterFormat;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue