const KNOWN_GETTER_NAMES = new Set([ 'company_domain', 'company_linkedin_url', 'company_name', 'domain', 'email', 'email_status', 'first_name', 'full_name', 'job_change', 'job_change_status', 'job_changed', 'last_name', 'linkedin', 'personal_email', 'phone', 'phone_status', 'status', 'title', ]); export function renderToolCodegenString(value: string): string { return JSON.stringify(value); } export function renderToolRowPathExpression( path: string, rowName = 'row', ): string { const parts = String(path || '') .replace(/\[(\d+)\]/g, '.$1') .split('.') .map((part) => part.trim()) .filter(Boolean); if (parts.length === 0) return rowName; return parts.reduce((expr, part, index) => { if (index === 0) return `${rowName}[${renderToolCodegenString(part)}]`; return `(${expr} as Record | null | undefined)?.[${renderToolCodegenString(part)}]`; }, rowName); } export function renderToolPayloadExpression( value: unknown, rowName = 'row', ): string { if (Array.isArray(value)) { return `[${value.map((entry) => renderToolPayloadExpression(entry, rowName)).join(', ')}]`; } if (value && typeof value === 'object') { const entries = Object.entries(value as Record).sort( ([left], [right]) => left.localeCompare(right), ); return `{ ${entries .map( ([key, entry]) => `${renderToolCodegenString(key)}: ${renderToolPayloadExpression(entry, rowName)}`, ) .join(', ')} }`; } if (typeof value !== 'string') return JSON.stringify(value); const exact = value.match(/^\{\{\s*([^{}]+?)\s*\}\}$/); if (exact) return renderToolRowPathExpression(exact[1] ?? '', rowName); const pieces: string[] = []; let cursor = 0; for (const match of value.matchAll(/\{\{\s*([^{}]+?)\s*\}\}/g)) { const index = match.index ?? 0; if (index > cursor) { pieces.push(renderToolCodegenString(value.slice(cursor, index))); } pieces.push( `String(${renderToolRowPathExpression(match[1] ?? '', rowName)} ?? '')`, ); cursor = index + match[0].length; } if (pieces.length === 0) return renderToolCodegenString(value); if (cursor < value.length) { pieces.push(renderToolCodegenString(value.slice(cursor))); } return pieces.join(' + '); } export function renderExtractedValueGetterExpression( toolResultVar: string, targetKey: string, ): string { return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(targetKey) ? `${toolResultVar}.extractedValues.${targetKey}?.get()` : `${toolResultVar}.extractedValues[${renderToolCodegenString(targetKey)}]?.get()`; } export function getterFromLegacyPath(path: string): string | null { const last = String(path || '') .replace(/\[(\d+)\]/g, '.$1') .split('.') .map((part) => part.trim()) .filter(Boolean) .at(-1); if (!last) return null; const normalized = last.replace(/[^A-Za-z0-9_]/g, '_'); return KNOWN_GETTER_NAMES.has(normalized) ? normalized : null; } export function getterFromLegacyExtractJs( extractJs: string | undefined, fallbackAlias?: string, ): string | null { const source = extractJs?.trim(); if (!source) { const aliasGetter = fallbackAlias?.replace(/[^A-Za-z0-9_]/g, '_'); return aliasGetter && KNOWN_GETTER_NAMES.has(aliasGetter) ? aliasGetter : null; } const directExtract = source.match( /\bextract\(\s*["'][^"']+["']\s*,\s*output_data\s*,\s*["']([^"']+)["']\s*\)/, ); if (directExtract?.[1]) { return getterFromLegacyPath( directExtract[1] === 'linkedin_url' ? 'linkedin' : directExtract[1], ); } const pick = source.match( /\b(?:pick|extract|target)\(\s*(\[[\s\S]*?\]|["'][^"']+["'])\s*\)/, ); if (!pick?.[1]) return null; try { const parsed = JSON.parse(pick[1].replace(/'/g, '"')) as unknown; const paths = Array.isArray(parsed) ? parsed : [parsed]; for (const path of paths) { if (typeof path !== 'string') continue; const getter = getterFromLegacyPath(path); if (getter) return getter; } } catch { return null; } return null; }