76 lines
2.5 KiB
TypeScript
76 lines
2.5 KiB
TypeScript
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<string>();
|
|
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<T = unknown>(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<string, unknown>) => 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;
|
|
}
|