import { dirname, join } from 'node:path'; /** * n8n bundles `@langchain/openai` and `@n8n/ai-utilities` inside its own installation, and those * modules are only reachable by a plain `require` when the package was installed as a community * node (npm installs it below `~/.n8n/nodes`). When the package is mounted as a custom extension * the resolution starts from our own folder, so we additionally look next to n8n itself * (the directory of `require.main.filename` is n8n's CLI entry point). */ function candidateModuleDirs(): string[] { const dirs = new Set(); const addChain = (start: string | undefined): void => { if (!start) return; let current = start; for (;;) { dirs.add(join(current, 'node_modules')); const parent = dirname(current); if (parent === current) break; current = parent; } }; addChain(__dirname); addChain(process.cwd()); if (require.main?.filename) addChain(dirname(require.main.filename)); return [...dirs]; } export function loadN8nModule(moduleName: string): T | undefined { try { return require(moduleName) as T; } catch { // Not reachable from our own folder - fall through to the n8n installation. } for (const dir of candidateModuleDirs()) { try { return require(join(dir, moduleName)) as T; } catch { // Keep looking. } } return undefined; } export type ChatOpenAiConstructor = new (config: Record) => unknown; export function loadChatOpenAi(): ChatOpenAiConstructor { const loaded = loadN8nModule<{ ChatOpenAI?: ChatOpenAiConstructor }>('@langchain/openai'); if (typeof loaded?.ChatOpenAI !== 'function') { throw new Error( 'Could not load "@langchain/openai" from the n8n installation. The Chat Model node needs the LangChain packages that ship with n8n (@n8n/n8n-nodes-langchain must be installed).', ); } return loaded.ChatOpenAI; } /** * Token/cost accounting in the execution log is provided by n8n's own tracer. It is an internal * helper, so we use it when it is reachable and stay silent when it is not - the model itself * works either way. */ export function loadN8nLlmTracing(context: unknown): unknown | undefined { for (const moduleName of ['@n8n/ai-utilities', 'n8n-core']) { const loaded = loadN8nModule<{ N8nLlmTracing?: new (context: unknown) => unknown }>(moduleName); if (typeof loaded?.N8nLlmTracing === 'function') { try { return new loaded.N8nLlmTracing(context); } catch { return undefined; } } } return undefined; }