Skip to main content
Glama
hints.ts6.46 kB
/** * Centralized, context-aware hints for local file search tools * All hint generation logic lives here - tools only provide context * * Philosophy: Tools send context, hints generate reasoning * @module hints */ /** * Context that tools can provide to generate smarter hints */ export interface HintContext { // Size context fileSize?: number; // KB resultSize?: number; // chars tokenEstimate?: number; // estimated tokens entryCount?: number; // number of entries/files // Search context matchCount?: number; // number of matches fileCount?: number; // number of files isLarge?: boolean; // is result/file large? // Error context errorType?: 'size_limit' | 'not_found' | 'permission' | 'pattern_too_broad'; originalError?: string; // Tool-specific context hasPattern?: boolean; // has matchString/pattern hasPagination?: boolean; // has charLength/pagination path?: string; // path being searched } /** * Smart, reasoning-based hints for each tool */ export const HINTS = { LOCAL_RIPGREP: { hasResults: (ctx: HintContext = {}) => [ 'Next: FETCH_CONTENT for context (prefer matchString).', 'Also search imports/usages/defs with RIPGREP.', ctx.fileCount && ctx.fileCount > 5 ? 'Tip: run queries in parallel.' : undefined, ].filter(Boolean), empty: (_ctx: HintContext = {}) => [ 'No matches. Broaden scope (noIgnore, hidden) or use fixedString.', 'Unsure of paths? VIEW_STRUCTURE or FIND_FILES.', ], error: (ctx: HintContext = {}) => { if (ctx.errorType === 'size_limit') { return [ `Too many results${ctx.matchCount ? ` (${ctx.matchCount} matches)` : ''}. Narrow pattern/scope.`, 'Add type/path filters to focus.', ctx.path?.includes('node_modules') ? 'In node_modules, target specific packages.' : undefined, 'Names only? Use FIND_FILES.', 'Flow: filesOnly=true → refine → read.', ].filter(Boolean); } return ['Tool unavailable; try FIND_FILES or VIEW_STRUCTURE.']; }, }, LOCAL_FETCH_CONTENT: { hasResults: (_ctx: HintContext = {}) => [ 'Next: trace imports/usages with RIPGREP.', 'Open related files (tests/types/impl) together.', 'Prefer matchString over full file.', ], empty: (_ctx: HintContext = {}) => [ 'No match/file. Check path/pattern.', 'Locate via FIND_FILES (name) or RIPGREP (filesOnly for paths).', ], error: (ctx: HintContext = {}) => { if (ctx.errorType === 'size_limit' && ctx.isLarge && !ctx.hasPagination && !ctx.hasPattern) { return [ ctx.fileSize ? `Large file (~${Math.round(ctx.fileSize * 0.25)}K tokens).` : 'Large file.', 'Use matchString for sections, or charLength to paginate.', 'Avoid fullContent without pagination.', ]; } if (ctx.errorType === 'pattern_too_broad') { return [ ctx.tokenEstimate ? `Pattern too broad (~${ctx.tokenEstimate.toLocaleString()} tokens).` : 'Pattern too broad.', 'Tighten pattern or paginate with charLength.', ]; } return ['Unknown path; find via FIND_FILES or RIPGREP.']; }, }, LOCAL_VIEW_STRUCTURE: { hasResults: (ctx: HintContext = {}) => [ 'Next: RIPGREP for patterns; FIND_FILES for filters.', 'Drill deeper with depth=2 when needed.', ctx.entryCount && ctx.entryCount > 10 ? 'Parallelize across directories.' : undefined, ].filter(Boolean), empty: (_ctx: HintContext = {}) => [ 'Empty/missing. Use hidden=true or check parent.', 'Discover dirs with FIND_FILES type="d".', ], error: (ctx: HintContext = {}) => { if (ctx.errorType === 'size_limit' && ctx.entryCount) { return [ `Directory has ${ctx.entryCount} entries${ctx.tokenEstimate ? ` (~${ctx.tokenEstimate.toLocaleString()} tokens)` : ''}. Use entriesPerPage.`, 'Sort by recent; scan page by page.', ]; } return ['Access failed; locate dirs with FIND_FILES type="d".']; }, }, LOCAL_FIND_FILES: { hasResults: (ctx: HintContext = {}) => [ 'Found files. Next: FETCH_CONTENT or RIPGREP.', 'Use modifiedWithin="7d" to track recent changes.', ctx.fileCount && ctx.fileCount > 3 ? 'Batch in parallel.' : undefined, ].filter(Boolean), empty: (_ctx: HintContext = {}) => [ 'No matches. Broaden iname, increase maxDepth, relax filters.', 'Or use VIEW_STRUCTURE/RIPGREP.', 'Syntax: time "7d", size "10M".', ], error: (_ctx: HintContext = {}) => ['Search failed; try VIEW_STRUCTURE or RIPGREP.'], }, } as const; export type ToolName = keyof typeof HINTS; export type HintStatus = 'hasResults' | 'empty' | 'error'; /** * Get smart, context-aware hints * * @param toolName - Tool that was executed * @param status - Result status * @param context - Optional context to generate smarter hints * @returns Array of intelligent, context-aware hints */ export function getToolHints( toolName: ToolName, status: HintStatus, context?: HintContext ): string[] { const hintGenerator = HINTS[toolName]?.[status]; if (!hintGenerator) return []; // Call the hint generator with context const rawHints = typeof hintGenerator === 'function' ? hintGenerator(context || {}) : hintGenerator; // Ensure we return string[] (filter out undefined from conditional hints) const hints = Array.isArray(rawHints) ? rawHints : [rawHints]; return hints.filter((h): h is string => typeof h === 'string'); } /** * Get adaptive workflow guidance for large files/directories * Explains reasoning behind chunking strategies * * @param context - Whether for 'search' (ripgrep) or 'read' (fetch_content) * @returns Intelligent workflow guidance with reasoning */ export function getLargeFileWorkflowHints( context: 'search' | 'read' ): string[] { if (context === 'search') { return [ 'Large codebase: avoid floods.', 'Flow: RIPGREP filesOnly → add type/path filters → FETCH_CONTENT matchString → RIPGREP links.', 'Parallelize where safe.', ]; } return [ 'Large file: don\'t read all.', 'Flow: FETCH_CONTENT matchString → analyze → RIPGREP usages/imports → FETCH_CONTENT related.', 'Use charLength to paginate if needed.', 'Avoid fullContent without charLength.', ]; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/bgauryy/local-explorer-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server