/** * Simple Levenshtein distance for fuzzy matching of glossary terms. */ function levenshtein(a, b) { const m = a.length; const n = b.length; const dp = Array.from({ length: m + 1 }, (_, i) => [i]); for (let j = 1; j <= n; j++) dp[0][j] = j; for (let i = 1; i <= m; i++) { for (let j = 1; j <= n; j++) { dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]); } } return dp[m][n]; } export const glossaryToolDefinition = { name: 'get_glossary_term', description: 'Look up a WMS-specific term, abbreviation, or acronym in the EasyWMS glossary (e.g. "LPN", "PDL", "ASN").', inputSchema: { type: 'object', properties: { term: { type: 'string', description: 'Term or abbreviation to look up (case-insensitive)', }, }, required: ['term'], }, }; export function handleGetGlossaryTerm(args, { glossary }) { const { term } = args; if (!term || term.trim().length === 0) { return { content: [{ type: 'text', text: 'Error: term parameter is required.' }] }; } const query = term.trim().toLowerCase(); // 1. Exact match if (glossary.has(query)) { const entry = glossary.get(query); return { content: [ { type: 'text', text: `**${entry.term}**\n\n${entry.definition}\n\n*Source: [Glossary](glossary.md)*`, }, ], }; } // 2. Partial match — prefer entries whose term STARTS WITH the query // (handles "pdl" → "pdl (picking dedicated location)") const startsWithMatches = []; const containsMatches = []; for (const [key, entry] of glossary) { if (key.startsWith(query + ' ') || key.startsWith(query + '(') || key === query) { startsWithMatches.push(entry); } else if (key.includes(query)) { containsMatches.push(entry); } } // Single strong match if (startsWithMatches.length === 1) { const entry = startsWithMatches[0]; return { content: [ { type: 'text', text: `**${entry.term}**\n\n${entry.definition}\n\n*Source: [Glossary](glossary.md)*`, }, ], }; } const partialMatches = startsWithMatches.length > 0 ? startsWithMatches : containsMatches; if (partialMatches.length === 1) { const entry = partialMatches[0]; return { content: [ { type: 'text', text: `**${entry.term}** *(partial match)*\n\n${entry.definition}\n\n*Source: [Glossary](glossary.md)*`, }, ], }; } if (partialMatches.length > 1) { const list = partialMatches .slice(0, 5) .map(e => `- **${e.term}**: ${e.definition.slice(0, 100)}...`) .join('\n'); return { content: [ { type: 'text', text: `Multiple matches for "${term}":\n\n${list}\n\n*Refine your query for an exact match.*`, }, ], }; } // 3. Fuzzy match (Levenshtein) const candidates = []; for (const [key, entry] of glossary) { const dist = levenshtein(query, key); if (dist <= 3) candidates.push({ entry, dist }); } candidates.sort((a, b) => a.dist - b.dist); if (candidates.length > 0) { const suggestions = candidates .slice(0, 3) .map(c => `- **${c.entry.term}**`) .join('\n'); return { content: [ { type: 'text', text: `Term "${term}" not found in glossary. Did you mean:\n\n${suggestions}\n\n*See [Glossary](glossary.md) for the full list.*`, }, ], }; } return { content: [ { type: 'text', text: `Term "${term}" not found in the EasyWMS glossary. See [Glossary](glossary.md) for the full list.`, }, ], }; }