()
| 525 | } |
| 526 | |
| 527 | async function buildToolDescriptionMap(): Promise<ToolMaps> { |
| 528 | const toolsDir = path.join(rootDir, 'apps/sim/tools') |
| 529 | const desc = new Map<string, string>() |
| 530 | const name = new Map<string, string>() |
| 531 | try { |
| 532 | const toolFiles = await glob(`${toolsDir}/**/*.ts`) |
| 533 | for (const file of toolFiles) { |
| 534 | const basename = path.basename(file) |
| 535 | if (basename === 'index.ts' || basename === 'types.ts') continue |
| 536 | const content = fs.readFileSync(file, 'utf-8') |
| 537 | |
| 538 | // Find every `id: 'tool_id'` occurrence in the file. For each, search |
| 539 | // the next ~600 characters for `name:` and `description:` fields, cutting |
| 540 | // off at the first `params:` block within that window. This handles both |
| 541 | // the simple inline pattern (id → description → params in one object) and |
| 542 | // the two-step pattern (base object holds params, ToolConfig export holds |
| 543 | // id + description after the base object). |
| 544 | const idRegex = /\bid\s*:\s*['"]([^'"]+)['"]/g |
| 545 | let idMatch: RegExpExecArray | null |
| 546 | while ((idMatch = idRegex.exec(content)) !== null) { |
| 547 | const toolId = idMatch[1] |
| 548 | if (desc.has(toolId)) continue |
| 549 | const windowStart = idMatch.index |
| 550 | const windowEnd = Math.min(windowStart + 600, content.length) |
| 551 | const window = content.substring(windowStart, windowEnd) |
| 552 | // Stop before any params block so we don't pick up param-level values |
| 553 | const paramsOffset = window.search(/\bparams\s*:\s*\{/) |
| 554 | const searchWindow = paramsOffset > 0 ? window.substring(0, paramsOffset) : window |
| 555 | // Match against the actual opening quote so apostrophes inside a |
| 556 | // double-quoted description (e.g. "Find someone's email") are preserved |
| 557 | // rather than being treated as the closing quote and truncating the value. |
| 558 | const descMatch = searchWindow.match( |
| 559 | /\bdescription\s*:\s*(?:'([^']{5,})'|"([^"]{5,})"|`([^`]{5,})`)/ |
| 560 | ) |
| 561 | const nameMatch = searchWindow.match(/\bname\s*:\s*(?:'([^']+)'|"([^"]+)"|`([^`]+)`)/) |
| 562 | if (descMatch) desc.set(toolId, descMatch[1] ?? descMatch[2] ?? descMatch[3] ?? '') |
| 563 | if (nameMatch) name.set(toolId, nameMatch[1] ?? nameMatch[2] ?? nameMatch[3] ?? '') |
| 564 | } |
| 565 | } |
| 566 | } catch { |
| 567 | // Non-fatal: descriptions will be empty strings |
| 568 | } |
| 569 | return { desc, name } |
| 570 | } |
| 571 | |
| 572 | /** |
| 573 | * Detect the authentication type from block content. |
no test coverage detected