mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-14 00:24:07 -08:00
Merge branch 'master' of https://github.com/n8n-io/n8n into node-1861-ai-transform-node-prompt
This commit is contained in:
commit
382b4e5cde
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
|
@ -7,6 +7,7 @@
|
|||
"EditorConfig.EditorConfig",
|
||||
"esbenp.prettier-vscode",
|
||||
"mjmlio.vscode-mjml",
|
||||
"Vue.volar"
|
||||
"Vue.volar",
|
||||
"vitest.explorer"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ If you already have VS Code and Docker installed, you can click [here](https://v
|
|||
|
||||
#### Node.js
|
||||
|
||||
[Node.js](https://nodejs.org/en/) version 18.10 or newer is required for development purposes.
|
||||
[Node.js](https://nodejs.org/en/) version 20.15 or newer is required for development purposes.
|
||||
|
||||
#### pnpm
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import { WEBHOOK_NODE_NAME } from '../constants';
|
||||
import { NDV, WorkflowPage } from '../pages';
|
||||
|
||||
const workflowPage = new WorkflowPage();
|
||||
const ndv = new NDV();
|
||||
|
||||
describe('ADO-2270 Save button resets on webhook node open', () => {
|
||||
it('should not reset the save button if webhook node is opened and closed', () => {
|
||||
workflowPage.actions.visit();
|
||||
workflowPage.actions.addInitialNodeToCanvas(WEBHOOK_NODE_NAME);
|
||||
workflowPage.getters.saveButton().click();
|
||||
workflowPage.actions.openNode(WEBHOOK_NODE_NAME);
|
||||
|
||||
ndv.actions.close();
|
||||
|
||||
cy.ifCanvasVersion(
|
||||
() => cy.getByTestId('workflow-save-button').should('not.contain', 'Saved'),
|
||||
() => cy.getByTestId('workflow-save-button').should('contain', 'Saved'),
|
||||
);
|
||||
});
|
||||
});
|
|
@ -90,6 +90,14 @@ function createRunDataWithError(inputMessage: string) {
|
|||
routine: 'InitPostgres',
|
||||
} as unknown as Error,
|
||||
} as ExecutionError,
|
||||
metadata: {
|
||||
subRun: [
|
||||
{
|
||||
node: 'Postgres Chat Memory',
|
||||
runIndex: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
createMockNodeExecutionData(AGENT_NODE_NAME, {
|
||||
executionStatus: 'error',
|
||||
|
@ -124,14 +132,6 @@ function createRunDataWithError(inputMessage: string) {
|
|||
description: 'Internal error',
|
||||
message: 'Internal error',
|
||||
} as unknown as ExecutionError,
|
||||
metadata: {
|
||||
subRun: [
|
||||
{
|
||||
node: 'Postgres Chat Memory',
|
||||
runIndex: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
|
|
@ -278,6 +278,9 @@ describe('Langchain Integration', () => {
|
|||
},
|
||||
},
|
||||
},
|
||||
metadata: {
|
||||
subRun: [{ node: AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, runIndex: 0 }],
|
||||
},
|
||||
inputOverride: {
|
||||
ai_languageModel: [
|
||||
[
|
||||
|
@ -316,9 +319,6 @@ describe('Langchain Integration', () => {
|
|||
jsonData: {
|
||||
main: { output: 'Hi there! How can I assist you today?' },
|
||||
},
|
||||
metadata: {
|
||||
subRun: [{ node: AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, runIndex: 0 }],
|
||||
},
|
||||
}),
|
||||
],
|
||||
lastNodeExecuted: AGENT_NODE_NAME,
|
||||
|
|
|
@ -10,8 +10,10 @@
|
|||
"N8N_RUNNERS_GRANT_TOKEN",
|
||||
"N8N_RUNNERS_N8N_URI",
|
||||
"N8N_RUNNERS_MAX_PAYLOAD",
|
||||
"N8N_RUNNERS_MAX_CONCURRENCY",
|
||||
"NODE_FUNCTION_ALLOW_BUILTIN",
|
||||
"NODE_FUNCTION_ALLOW_EXTERNAL"
|
||||
"NODE_FUNCTION_ALLOW_EXTERNAL",
|
||||
"NODE_OPTIONS"
|
||||
],
|
||||
"uid": 2000,
|
||||
"gid": 2000
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
"dist/**/*"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@n8n/config": "workspace:*",
|
||||
"n8n-workflow": "workspace:*"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import type { FrontendBetaFeatures } from '@n8n/config';
|
||||
import type { ExpressionEvaluatorType, LogLevel, WorkflowSettings } from 'n8n-workflow';
|
||||
|
||||
export interface IVersionNotificationSettings {
|
||||
|
@ -169,4 +170,5 @@ export interface FrontendSettings {
|
|||
security: {
|
||||
blockFileAccessToN8nFiles: boolean;
|
||||
};
|
||||
betaFeatures: FrontendBetaFeatures[];
|
||||
}
|
||||
|
|
11
packages/@n8n/config/src/configs/frontend.config.ts
Normal file
11
packages/@n8n/config/src/configs/frontend.config.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { Config, Env } from '../decorators';
|
||||
import { StringArray } from '../utils';
|
||||
|
||||
export type FrontendBetaFeatures = 'canvas_v2';
|
||||
|
||||
@Config
|
||||
export class FrontendConfig {
|
||||
/** Which UI experiments to enable. Separate multiple values with a comma `,` */
|
||||
@Env('N8N_UI_BETA_FEATURES')
|
||||
betaFeatures: StringArray<FrontendBetaFeatures> = [];
|
||||
}
|
28
packages/@n8n/config/src/configs/license.config.ts
Normal file
28
packages/@n8n/config/src/configs/license.config.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { Config, Env } from '../decorators';
|
||||
|
||||
@Config
|
||||
export class LicenseConfig {
|
||||
/** License server URL to retrieve license. */
|
||||
@Env('N8N_LICENSE_SERVER_URL')
|
||||
serverUrl: string = 'https://license.n8n.io/v1';
|
||||
|
||||
/** Whether autorenewal for licenses is enabled. */
|
||||
@Env('N8N_LICENSE_AUTO_RENEW_ENABLED')
|
||||
autoRenewalEnabled: boolean = true;
|
||||
|
||||
/** How long (in seconds) before expiry a license should be autorenewed. */
|
||||
@Env('N8N_LICENSE_AUTO_RENEW_OFFSET')
|
||||
autoRenewOffset: number = 60 * 60 * 72; // 72 hours
|
||||
|
||||
/** Activation key to initialize license. */
|
||||
@Env('N8N_LICENSE_ACTIVATION_KEY')
|
||||
activationKey: string = '';
|
||||
|
||||
/** Tenant ID used by the license manager SDK, e.g. for self-hosted, sandbox, embed, cloud. */
|
||||
@Env('N8N_LICENSE_TENANT_ID')
|
||||
tenantId: number = 1;
|
||||
|
||||
/** Ephemeral license certificate. See: https://github.com/n8n-io/license-management?tab=readme-ov-file#concept-ephemeral-entitlements */
|
||||
@Env('N8N_LICENSE_CERT')
|
||||
cert: string = '';
|
||||
}
|
|
@ -11,6 +11,7 @@ export const LOG_SCOPES = [
|
|||
'redis',
|
||||
'scaling',
|
||||
'waiting-executions',
|
||||
'task-runner',
|
||||
] as const;
|
||||
|
||||
export type LogScope = (typeof LOG_SCOPES)[number];
|
||||
|
|
|
@ -42,4 +42,12 @@ export class TaskRunnersConfig {
|
|||
/** Which task runner to launch from the config */
|
||||
@Env('N8N_RUNNERS_LAUNCHER_RUNNER')
|
||||
launcherRunner: string = 'javascript';
|
||||
|
||||
/** The --max-old-space-size option to use for the runner (in MB). Default means node.js will determine it based on the available memory. */
|
||||
@Env('N8N_RUNNERS_MAX_OLD_SPACE_SIZE')
|
||||
maxOldSpaceSize: string = '';
|
||||
|
||||
/** How many concurrent tasks can a runner execute at a time */
|
||||
@Env('N8N_RUNNERS_MAX_CONCURRENCY')
|
||||
maxConcurrency: number = 5;
|
||||
}
|
||||
|
|
27
packages/@n8n/config/src/configs/security.config.ts
Normal file
27
packages/@n8n/config/src/configs/security.config.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { Config, Env } from '../decorators';
|
||||
|
||||
@Config
|
||||
export class SecurityConfig {
|
||||
/**
|
||||
* Which directories to limit n8n's access to. Separate multiple dirs with semicolon `;`.
|
||||
*
|
||||
* @example N8N_RESTRICT_FILE_ACCESS_TO=/home/user/.n8n;/home/user/n8n-data
|
||||
*/
|
||||
@Env('N8N_RESTRICT_FILE_ACCESS_TO')
|
||||
restrictFileAccessTo: string = '';
|
||||
|
||||
/**
|
||||
* Whether to block access to all files at:
|
||||
* - the ".n8n" directory,
|
||||
* - the static cache dir at ~/.cache/n8n/public, and
|
||||
* - user-defined config files.
|
||||
*/
|
||||
@Env('N8N_BLOCK_FILE_ACCESS_TO_N8N_FILES')
|
||||
blockFileAccessToN8nFiles: boolean = true;
|
||||
|
||||
/**
|
||||
* In a [security audit](https://docs.n8n.io/hosting/securing/security-audit/), how many days for a workflow to be considered abandoned if not executed.
|
||||
*/
|
||||
@Env('N8N_SECURITY_AUDIT_DAYS_ABANDONED_WORKFLOW')
|
||||
daysAbandonedWorkflow: number = 90;
|
||||
}
|
|
@ -6,21 +6,25 @@ import { EventBusConfig } from './configs/event-bus.config';
|
|||
import { ExternalSecretsConfig } from './configs/external-secrets.config';
|
||||
import { ExternalStorageConfig } from './configs/external-storage.config';
|
||||
import { GenericConfig } from './configs/generic.config';
|
||||
import { LicenseConfig } from './configs/license.config';
|
||||
import { LoggingConfig } from './configs/logging.config';
|
||||
import { MultiMainSetupConfig } from './configs/multi-main-setup.config';
|
||||
import { NodesConfig } from './configs/nodes.config';
|
||||
import { PublicApiConfig } from './configs/public-api.config';
|
||||
import { TaskRunnersConfig } from './configs/runners.config';
|
||||
export { TaskRunnersConfig } from './configs/runners.config';
|
||||
import { ScalingModeConfig } from './configs/scaling-mode.config';
|
||||
import { SecurityConfig } from './configs/security.config';
|
||||
import { SentryConfig } from './configs/sentry.config';
|
||||
import { TemplatesConfig } from './configs/templates.config';
|
||||
import { UserManagementConfig } from './configs/user-management.config';
|
||||
import { VersionNotificationsConfig } from './configs/version-notifications.config';
|
||||
import { WorkflowsConfig } from './configs/workflows.config';
|
||||
import { Config, Env, Nested } from './decorators';
|
||||
export { Config, Env, Nested } from './decorators';
|
||||
|
||||
export { Config, Env, Nested } from './decorators';
|
||||
export { TaskRunnersConfig } from './configs/runners.config';
|
||||
export { SecurityConfig } from './configs/security.config';
|
||||
export { FrontendBetaFeatures, FrontendConfig } from './configs/frontend.config';
|
||||
export { LOG_SCOPES } from './configs/logging.config';
|
||||
export type { LogScope } from './configs/logging.config';
|
||||
|
||||
|
@ -102,4 +106,10 @@ export class GlobalConfig {
|
|||
|
||||
@Nested
|
||||
generic: GenericConfig;
|
||||
|
||||
@Nested
|
||||
license: LicenseConfig;
|
||||
|
||||
@Nested
|
||||
security: SecurityConfig;
|
||||
}
|
||||
|
|
|
@ -231,6 +231,8 @@ describe('GlobalConfig', () => {
|
|||
port: 5679,
|
||||
launcherPath: '',
|
||||
launcherRunner: 'javascript',
|
||||
maxOldSpaceSize: '',
|
||||
maxConcurrency: 5,
|
||||
},
|
||||
sentry: {
|
||||
backendDsn: '',
|
||||
|
@ -256,6 +258,19 @@ describe('GlobalConfig', () => {
|
|||
releaseChannel: 'dev',
|
||||
gracefulShutdownTimeout: 30,
|
||||
},
|
||||
license: {
|
||||
serverUrl: 'https://license.n8n.io/v1',
|
||||
autoRenewalEnabled: true,
|
||||
autoRenewOffset: 60 * 60 * 72,
|
||||
activationKey: '',
|
||||
tenantId: 1,
|
||||
cert: '',
|
||||
},
|
||||
security: {
|
||||
restrictFileAccessTo: '',
|
||||
blockFileAccessToN8nFiles: true,
|
||||
daysAbandonedWorkflow: 90,
|
||||
},
|
||||
};
|
||||
|
||||
it('should use all default values when no env variables are defined', () => {
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeOperationError,
|
||||
type IExecuteFunctions,
|
||||
type INodeExecutionData,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type INodeOutputConfiguration,
|
||||
type SupplyData,
|
||||
NodeConnectionType,
|
||||
import { NodeOperationError, NodeConnectionType } from 'n8n-workflow';
|
||||
import type {
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
INodeOutputConfiguration,
|
||||
SupplyData,
|
||||
ISupplyDataFunctions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
// TODO: Add support for execute function. Got already started but got commented out
|
||||
|
@ -72,7 +72,7 @@ export const vmResolver = makeResolverFromLegacyOptions({
|
|||
});
|
||||
|
||||
function getSandbox(
|
||||
this: IExecuteFunctions,
|
||||
this: IExecuteFunctions | ISupplyDataFunctions,
|
||||
code: string,
|
||||
options?: { addItems?: boolean; itemIndex?: number },
|
||||
) {
|
||||
|
@ -354,7 +354,7 @@ export class Code implements INodeType {
|
|||
}
|
||||
}
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const code = this.getNodeParameter('code', itemIndex) as { supplyData?: { code: string } };
|
||||
|
||||
if (!code.supplyData?.code) {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -177,7 +177,7 @@ export class DocumentBinaryInputLoader implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions): Promise<SupplyData> {
|
||||
this.logger.debug('Supply Data for Binary Input Loader');
|
||||
const textSplitter = (await this.getInputConnectionData(
|
||||
NodeConnectionType.AiTextSplitter,
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -283,7 +283,7 @@ export class DocumentDefaultDataLoader implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const dataType = this.getNodeParameter('dataType', itemIndex, 'json') as 'json' | 'binary';
|
||||
const textSplitter = (await this.getInputConnectionData(
|
||||
NodeConnectionType.AiTextSplitter,
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
import { GithubRepoLoader } from '@langchain/community/document_loaders/web/github';
|
||||
|
@ -93,7 +93,7 @@ export class DocumentGithubLoader implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
console.log('Supplying data for Github Document Loader');
|
||||
|
||||
const repository = this.getNodeParameter('repository', itemIndex) as string;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -79,7 +79,7 @@ export class DocumentJsonInputLoader implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions): Promise<SupplyData> {
|
||||
this.logger.debug('Supply Data for JSON Input Loader');
|
||||
const textSplitter = (await this.getInputConnectionData(
|
||||
NodeConnectionType.AiTextSplitter,
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
import { BedrockEmbeddings } from '@langchain/aws';
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -104,7 +104,7 @@ export class EmbeddingsAwsBedrock implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const credentials = await this.getCredentials('aws');
|
||||
const modelName = this.getNodeParameter('model', itemIndex) as string;
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -92,7 +92,7 @@ export class EmbeddingsAzureOpenAi implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
this.logger.debug('Supply data for embeddings');
|
||||
const credentials = await this.getCredentials<{
|
||||
apiKey: string;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
import { CohereEmbeddings } from '@langchain/cohere';
|
||||
|
@ -99,7 +99,7 @@ export class EmbeddingsCohere implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
this.logger.debug('Supply data for embeddings Cohere');
|
||||
const modelName = this.getNodeParameter('modelName', itemIndex, 'embed-english-v2.0') as string;
|
||||
const credentials = await this.getCredentials<{ apiKey: string }>('cohereApi');
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
import { GoogleGenerativeAIEmbeddings } from '@langchain/google-genai';
|
||||
|
@ -116,7 +116,7 @@ export class EmbeddingsGoogleGemini implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
this.logger.debug('Supply data for embeddings Google Gemini');
|
||||
const modelName = this.getNodeParameter(
|
||||
'modelName',
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
import { HuggingFaceInferenceEmbeddings } from '@langchain/community/embeddings/hf';
|
||||
|
@ -81,7 +81,7 @@ export class EmbeddingsHuggingFaceInference implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
this.logger.debug('Supply data for embeddings HF Inference');
|
||||
const model = this.getNodeParameter(
|
||||
'modelName',
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
import type { MistralAIEmbeddingsParams } from '@langchain/mistralai';
|
||||
|
@ -134,7 +134,7 @@ export class EmbeddingsMistralCloud implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const credentials = await this.getCredentials('mistralCloudApi');
|
||||
const modelName = this.getNodeParameter('model', itemIndex) as string;
|
||||
const options = this.getNodeParameter(
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
import { OllamaEmbeddings } from '@langchain/ollama';
|
||||
|
@ -44,7 +44,7 @@ export class EmbeddingsOllama implements INodeType {
|
|||
properties: [getConnectionHintNoticeField([NodeConnectionType.AiVectorStore]), ollamaModel],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
this.logger.debug('Supply data for embeddings Ollama');
|
||||
const modelName = this.getNodeParameter('model', itemIndex) as string;
|
||||
const credentials = await this.getCredentials('ollamaApi');
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type SupplyData,
|
||||
type ISupplyDataFunctions,
|
||||
type INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -170,7 +170,7 @@ export class EmbeddingsOpenAi implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
this.logger.debug('Supply data for embeddings');
|
||||
const credentials = await this.getCredentials('openAiApi');
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import {
|
|||
NodeConnectionType,
|
||||
type INodePropertyOptions,
|
||||
type INodeProperties,
|
||||
type IExecuteFunctions,
|
||||
type ISupplyDataFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type SupplyData,
|
||||
|
@ -20,6 +20,10 @@ const modelField: INodeProperties = {
|
|||
type: 'options',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
|
||||
options: [
|
||||
{
|
||||
name: 'Claude 3.5 Sonnet(20241022)',
|
||||
value: 'claude-3-5-sonnet-20241022',
|
||||
},
|
||||
{
|
||||
name: 'Claude 3 Opus(20240229)',
|
||||
value: 'claude-3-opus-20240229',
|
||||
|
@ -175,7 +179,7 @@ export class LmChatAnthropic implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const credentials = await this.getCredentials('anthropicApi');
|
||||
|
||||
const modelName = this.getNodeParameter('model', itemIndex) as string;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -52,7 +52,7 @@ export class LmChatOllama implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const credentials = await this.getCredentials('ollamaApi');
|
||||
|
||||
const modelName = this.getNodeParameter('model', itemIndex) as string;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
type JsonObject,
|
||||
NodeApiError,
|
||||
|
@ -242,7 +242,7 @@ export class LmChatOpenAi implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const credentials = await this.getCredentials('openAiApi');
|
||||
|
||||
const modelName = this.getNodeParameter('model', itemIndex) as string;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -90,7 +90,7 @@ export class LmCohere implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const credentials = await this.getCredentials('cohereApi');
|
||||
|
||||
const options = this.getNodeParameter('options', itemIndex, {}) as object;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -51,7 +51,7 @@ export class LmOllama implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const credentials = await this.getCredentials('ollamaApi');
|
||||
|
||||
const modelName = this.getNodeParameter('model', itemIndex) as string;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import { NodeConnectionType } from 'n8n-workflow';
|
||||
import type {
|
||||
IExecuteFunctions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
ISupplyDataFunctions,
|
||||
SupplyData,
|
||||
ILoadOptionsFunctions,
|
||||
} from 'n8n-workflow';
|
||||
|
@ -229,7 +229,7 @@ export class LmOpenAi implements INodeType {
|
|||
},
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const credentials = await this.getCredentials('openAiApi');
|
||||
|
||||
const modelName = this.getNodeParameter('model', itemIndex, '', {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -132,7 +132,7 @@ export class LmOpenHuggingFaceInference implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const credentials = await this.getCredentials('huggingFaceApi');
|
||||
|
||||
const modelName = this.getNodeParameter('model', itemIndex) as string;
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
import { ChatBedrockConverse } from '@langchain/aws';
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -132,7 +132,7 @@ export class LmChatAwsBedrock implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const credentials = await this.getCredentials('aws');
|
||||
const modelName = this.getNodeParameter('model', itemIndex) as string;
|
||||
const options = this.getNodeParameter('options', itemIndex, {}) as {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -162,7 +162,7 @@ export class LmChatAzureOpenAi implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const credentials = await this.getCredentials<{
|
||||
apiKey: string;
|
||||
resourceName: string;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
|
||||
|
@ -113,7 +113,7 @@ export class LmChatGoogleGemini implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const credentials = await this.getCredentials('googlePalmApi');
|
||||
|
||||
const modelName = this.getNodeParameter('modelName', itemIndex) as string;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
type ILoadOptionsFunctions,
|
||||
type JsonObject,
|
||||
|
@ -124,7 +124,7 @@ export class LmChatGoogleVertex implements INodeType {
|
|||
},
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const credentials = await this.getCredentials('googleApi');
|
||||
const privateKey = formatPrivateKey(credentials.privateKey as string);
|
||||
const email = (credentials.email as string).trim();
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -129,7 +129,7 @@ export class LmChatGroq implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const credentials = await this.getCredentials('groqApi');
|
||||
|
||||
const modelName = this.getNodeParameter('model', itemIndex) as string;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -172,7 +172,7 @@ export class LmChatMistralCloud implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const credentials = await this.getCredentials('mistralCloudApi');
|
||||
|
||||
const modelName = this.getNodeParameter('model', itemIndex) as string;
|
||||
|
|
|
@ -7,7 +7,7 @@ import type {
|
|||
SerializedSecret,
|
||||
} from '@langchain/core/load/serializable';
|
||||
import type { LLMResult } from '@langchain/core/outputs';
|
||||
import type { IDataObject, IExecuteFunctions } from 'n8n-workflow';
|
||||
import type { IDataObject, ISupplyDataFunctions } from 'n8n-workflow';
|
||||
import { NodeConnectionType } from 'n8n-workflow';
|
||||
import { pick } from 'lodash';
|
||||
import type { BaseMessage } from '@langchain/core/messages';
|
||||
|
@ -30,8 +30,6 @@ const TIKTOKEN_ESTIMATE_MODEL = 'gpt-4o';
|
|||
export class N8nLlmTracing extends BaseCallbackHandler {
|
||||
name = 'N8nLlmTracing';
|
||||
|
||||
executionFunctions: IExecuteFunctions;
|
||||
|
||||
connectionType = NodeConnectionType.AiLanguageModel;
|
||||
|
||||
promptTokensEstimate = 0;
|
||||
|
@ -61,11 +59,10 @@ export class N8nLlmTracing extends BaseCallbackHandler {
|
|||
};
|
||||
|
||||
constructor(
|
||||
executionFunctions: IExecuteFunctions,
|
||||
private executionFunctions: ISupplyDataFunctions,
|
||||
options?: { tokensUsageParser: TokensUsageParser },
|
||||
) {
|
||||
super();
|
||||
this.executionFunctions = executionFunctions;
|
||||
this.options = { ...this.options, ...options };
|
||||
}
|
||||
|
||||
|
@ -138,7 +135,7 @@ export class N8nLlmTracing extends BaseCallbackHandler {
|
|||
this.executionFunctions.addOutputData(this.connectionType, runDetails.index, [
|
||||
[{ json: { ...response } }],
|
||||
]);
|
||||
void logAiEvent(this.executionFunctions, 'ai-llm-generated-output', {
|
||||
logAiEvent(this.executionFunctions, 'ai-llm-generated-output', {
|
||||
messages: parsedMessages,
|
||||
options: runDetails.options,
|
||||
response,
|
||||
|
@ -186,7 +183,7 @@ export class N8nLlmTracing extends BaseCallbackHandler {
|
|||
});
|
||||
}
|
||||
|
||||
void logAiEvent(this.executionFunctions, 'ai-llm-errored', {
|
||||
logAiEvent(this.executionFunctions, 'ai-llm-errored', {
|
||||
error: Object.keys(error).length === 0 ? error.toString() : error,
|
||||
runId,
|
||||
parentRunId,
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
import type { BufferWindowMemoryInput } from 'langchain/memory';
|
||||
|
@ -134,7 +134,7 @@ export class MemoryBufferWindow implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const contextWindowLength = this.getNodeParameter('contextWindowLength', itemIndex) as number;
|
||||
const workflowId = this.getWorkflow().id;
|
||||
const memoryInstance = MemoryChatBufferSingleton.getInstance();
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -86,7 +86,7 @@ export class MemoryMotorhead implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const credentials = await this.getCredentials('motorheadApi');
|
||||
const nodeVersion = this.getNode().typeVersion;
|
||||
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import type { IExecuteFunctions, INodeType, INodeTypeDescription, SupplyData } from 'n8n-workflow';
|
||||
import type {
|
||||
ISupplyDataFunctions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeConnectionType } from 'n8n-workflow';
|
||||
import { BufferMemory, BufferWindowMemory } from 'langchain/memory';
|
||||
import { PostgresChatMessageHistory } from '@langchain/community/stores/message/postgres';
|
||||
|
@ -73,7 +78,7 @@ export class MemoryPostgresChat implements INodeType {
|
|||
},
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const credentials = await this.getCredentials<PostgresNodeCredentials>('postgres');
|
||||
const tableName = this.getNodeParameter('tableName', itemIndex, 'n8n_chat_histories') as string;
|
||||
const sessionId = getSessionId(this, itemIndex);
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeOperationError,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
NodeConnectionType,
|
||||
} from 'n8n-workflow';
|
||||
|
@ -102,7 +102,7 @@ export class MemoryRedisChat implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const credentials = await this.getCredentials('redis');
|
||||
const nodeVersion = this.getNode().typeVersion;
|
||||
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import { NodeConnectionType, NodeOperationError } from 'n8n-workflow';
|
||||
import type { IExecuteFunctions, INodeType, INodeTypeDescription, SupplyData } from 'n8n-workflow';
|
||||
import type {
|
||||
ISupplyDataFunctions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
import { XataChatMessageHistory } from '@langchain/community/stores/message/xata';
|
||||
import { BufferMemory, BufferWindowMemory } from 'langchain/memory';
|
||||
import { BaseClient } from '@xata.io/client';
|
||||
|
@ -88,7 +93,7 @@ export class MemoryXata implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const credentials = await this.getCredentials('xataApi');
|
||||
const nodeVersion = this.getNode().typeVersion;
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type ISupplyDataFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type SupplyData,
|
||||
|
@ -103,7 +103,7 @@ export class MemoryZep implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const credentials = await this.getCredentials<{
|
||||
apiKey?: string;
|
||||
apiUrl?: string;
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import type { BaseLanguageModel } from '@langchain/core/language_models/base';
|
||||
import { NodeConnectionType } from 'n8n-workflow';
|
||||
import type { IExecuteFunctions, INodeType, INodeTypeDescription, SupplyData } from 'n8n-workflow';
|
||||
import type {
|
||||
ISupplyDataFunctions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
N8nOutputFixingParser,
|
||||
|
@ -63,7 +68,7 @@ export class OutputParserAutofixing implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const model = (await this.getInputConnectionData(
|
||||
NodeConnectionType.AiLanguageModel,
|
||||
itemIndex,
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -80,7 +80,7 @@ export class OutputParserItemList implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const options = this.getNodeParameter('options', itemIndex, {}) as {
|
||||
numberOfItems?: number;
|
||||
separator?: string;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import type { JSONSchema7 } from 'json-schema';
|
||||
import {
|
||||
jsonParse,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
NodeOperationError,
|
||||
NodeConnectionType,
|
||||
|
@ -122,7 +122,7 @@ export class OutputParserStructured implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const schemaType = this.getNodeParameter('schemaType', itemIndex, '') as 'fromJson' | 'manual';
|
||||
// We initialize these even though one of them will always be empty
|
||||
// it makes it easer to navigate the ternary operator
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -63,7 +63,7 @@ export class RetrieverContextualCompression implements INodeType {
|
|||
properties: [],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
this.logger.debug('Supplying data for Contextual Compression Retriever');
|
||||
|
||||
const model = (await this.getInputConnectionData(
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -82,7 +82,7 @@ export class RetrieverMultiQuery implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
this.logger.debug('Supplying data for MultiQuery Retriever');
|
||||
|
||||
const options = this.getNodeParameter('options', itemIndex, {}) as { queryCount?: number };
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
import type { VectorStore } from '@langchain/core/vectorstores';
|
||||
|
@ -56,7 +56,7 @@ export class RetrieverVectorStore implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
this.logger.debug('Supplying data for Vector Store Retriever');
|
||||
|
||||
const topK = this.getNodeParameter('topK', itemIndex, 4) as number;
|
||||
|
|
|
@ -5,7 +5,7 @@ import type {
|
|||
IExecuteWorkflowInfo,
|
||||
INodeExecutionData,
|
||||
IWorkflowBase,
|
||||
IExecuteFunctions,
|
||||
ISupplyDataFunctions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
SupplyData,
|
||||
|
@ -292,15 +292,15 @@ export class RetrieverWorkflow implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
class WorkflowRetriever extends BaseRetriever {
|
||||
lc_namespace = ['n8n-nodes-langchain', 'retrievers', 'workflow'];
|
||||
|
||||
executeFunctions: IExecuteFunctions;
|
||||
|
||||
constructor(executeFunctions: IExecuteFunctions, fields: BaseRetrieverInput) {
|
||||
constructor(
|
||||
private executeFunctions: ISupplyDataFunctions,
|
||||
fields: BaseRetrieverInput,
|
||||
) {
|
||||
super(fields);
|
||||
this.executeFunctions = executeFunctions;
|
||||
}
|
||||
|
||||
async _getRelevantDocuments(
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
import type { CharacterTextSplitterParams } from '@langchain/textsplitters';
|
||||
|
@ -63,7 +63,7 @@ export class TextSplitterCharacterTextSplitter implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
this.logger.debug('Supply Data for Text Splitter');
|
||||
|
||||
const separator = this.getNodeParameter('separator', itemIndex) as string;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
import type {
|
||||
|
@ -94,7 +94,7 @@ export class TextSplitterRecursiveCharacterTextSplitter implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
this.logger.debug('Supply Data for Text Splitter');
|
||||
|
||||
const chunkSize = this.getNodeParameter('chunkSize', itemIndex) as number;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
import { TokenTextSplitter } from '@langchain/textsplitters';
|
||||
|
@ -56,7 +56,7 @@ export class TextSplitterTokenSplitter implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
this.logger.debug('Supply Data for Text Splitter');
|
||||
|
||||
const chunkSize = this.getNodeParameter('chunkSize', itemIndex) as number;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
import { Calculator } from '@langchain/community/tools/calculator';
|
||||
|
@ -43,7 +43,7 @@ export class ToolCalculator implements INodeType {
|
|||
properties: [getConnectionHintNoticeField([NodeConnectionType.AiAgent])],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions): Promise<SupplyData> {
|
||||
return {
|
||||
response: logWrapper(new Calculator(), this),
|
||||
};
|
||||
|
|
|
@ -6,9 +6,9 @@ import { PythonSandbox } from 'n8n-nodes-base/dist/nodes/Code/PythonSandbox';
|
|||
import type { Sandbox } from 'n8n-nodes-base/dist/nodes/Code/Sandbox';
|
||||
import { getSandboxContext } from 'n8n-nodes-base/dist/nodes/Code/Sandbox';
|
||||
import type {
|
||||
IExecuteFunctions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
ISupplyDataFunctions,
|
||||
SupplyData,
|
||||
ExecutionError,
|
||||
IDataObject,
|
||||
|
@ -175,7 +175,7 @@ export class ToolCode implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const node = this.getNode();
|
||||
const workflowMode = this.getMode();
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import type {
|
||||
IExecuteFunctions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
ISupplyDataFunctions,
|
||||
SupplyData,
|
||||
IHttpRequestMethods,
|
||||
IHttpRequestOptions,
|
||||
|
@ -250,7 +250,7 @@ export class ToolHttpRequest implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const name = this.getNode().name.replace(/ /g, '_');
|
||||
try {
|
||||
tryToParseAlphanumericString(name);
|
||||
|
|
|
@ -10,19 +10,19 @@ describe('ToolHttpRequest', () => {
|
|||
const helpers = mock<IExecuteFunctions['helpers']>();
|
||||
const executeFunctions = mock<IExecuteFunctions>({ helpers });
|
||||
|
||||
describe('Binary response', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
executeFunctions.getNode.mockReturnValue(
|
||||
mock<INode>({
|
||||
type: 'n8n-nodes-base.httpRequest',
|
||||
name: 'HTTP Request',
|
||||
typeVersion: 1.1,
|
||||
}),
|
||||
);
|
||||
executeFunctions.addInputData.mockReturnValue({ index: 0 });
|
||||
});
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
executeFunctions.getNode.mockReturnValue(
|
||||
mock<INode>({
|
||||
type: 'n8n-nodes-base.httpRequest',
|
||||
name: 'HTTP Request',
|
||||
typeVersion: 1.1,
|
||||
}),
|
||||
);
|
||||
executeFunctions.addInputData.mockReturnValue({ index: 0 });
|
||||
});
|
||||
|
||||
describe('Binary response', () => {
|
||||
it('should return the error when receiving a binary response', async () => {
|
||||
helpers.httpRequest.mockResolvedValue({
|
||||
body: Buffer.from(''),
|
||||
|
@ -237,4 +237,62 @@ describe('ToolHttpRequest', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Optimize response', () => {
|
||||
it('should extract body from the response HTML', async () => {
|
||||
helpers.httpRequest.mockResolvedValue({
|
||||
body: `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Test</h1>
|
||||
|
||||
<div>
|
||||
<p>
|
||||
Test content
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>`,
|
||||
headers: {
|
||||
'content-type': 'text/html',
|
||||
},
|
||||
});
|
||||
|
||||
executeFunctions.getNodeParameter.mockImplementation(
|
||||
(paramName: string, _: any, fallback: any) => {
|
||||
switch (paramName) {
|
||||
case 'method':
|
||||
return 'GET';
|
||||
case 'url':
|
||||
return '{url}';
|
||||
case 'options':
|
||||
return {};
|
||||
case 'placeholderDefinitions.values':
|
||||
return [];
|
||||
case 'optimizeResponse':
|
||||
return true;
|
||||
case 'responseType':
|
||||
return 'html';
|
||||
case 'cssSelector':
|
||||
return 'body';
|
||||
default:
|
||||
return fallback;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const { response } = await httpTool.supplyData.call(executeFunctions, 0);
|
||||
|
||||
const res = await (response as N8nTool).invoke({
|
||||
url: 'https://httpbin.org/html',
|
||||
});
|
||||
|
||||
expect(helpers.httpRequest).toHaveBeenCalled();
|
||||
expect(res).toEqual(
|
||||
JSON.stringify(['<h1>Test</h1> <div> <p> Test content </p> </div>'], null, 2),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Readability } from '@mozilla/readability';
|
||||
import cheerio from 'cheerio';
|
||||
import * as cheerio from 'cheerio';
|
||||
import { convert } from 'html-to-text';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import get from 'lodash/get';
|
||||
|
@ -8,12 +8,12 @@ import unset from 'lodash/unset';
|
|||
import * as mime from 'mime-types';
|
||||
import { getOAuth2AdditionalParameters } from 'n8n-nodes-base/dist/nodes/HttpRequest/GenericFunctions';
|
||||
import type {
|
||||
IExecuteFunctions,
|
||||
IDataObject,
|
||||
IHttpRequestOptions,
|
||||
IRequestOptionsSimplified,
|
||||
ExecutionError,
|
||||
NodeApiError,
|
||||
ISupplyDataFunctions,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeConnectionType, NodeOperationError, jsonParse } from 'n8n-workflow';
|
||||
import { z } from 'zod';
|
||||
|
@ -28,7 +28,7 @@ import type {
|
|||
} from './interfaces';
|
||||
import type { DynamicZodObject } from '../../../types/zod.types';
|
||||
|
||||
const genericCredentialRequest = async (ctx: IExecuteFunctions, itemIndex: number) => {
|
||||
const genericCredentialRequest = async (ctx: ISupplyDataFunctions, itemIndex: number) => {
|
||||
const genericType = ctx.getNodeParameter('genericAuthType', itemIndex) as string;
|
||||
|
||||
if (genericType === 'httpBasicAuth' || genericType === 'httpDigestAuth') {
|
||||
|
@ -104,7 +104,7 @@ const genericCredentialRequest = async (ctx: IExecuteFunctions, itemIndex: numbe
|
|||
});
|
||||
};
|
||||
|
||||
const predefinedCredentialRequest = async (ctx: IExecuteFunctions, itemIndex: number) => {
|
||||
const predefinedCredentialRequest = async (ctx: ISupplyDataFunctions, itemIndex: number) => {
|
||||
const predefinedType = ctx.getNodeParameter('nodeCredentialType', itemIndex) as string;
|
||||
const additionalOptions = getOAuth2AdditionalParameters(predefinedType);
|
||||
|
||||
|
@ -119,7 +119,7 @@ const predefinedCredentialRequest = async (ctx: IExecuteFunctions, itemIndex: nu
|
|||
};
|
||||
|
||||
export const configureHttpRequestFunction = async (
|
||||
ctx: IExecuteFunctions,
|
||||
ctx: ISupplyDataFunctions,
|
||||
credentialsType: 'predefinedCredentialType' | 'genericCredentialType' | 'none',
|
||||
itemIndex: number,
|
||||
) => {
|
||||
|
@ -146,7 +146,7 @@ const defaultOptimizer = <T>(response: T) => {
|
|||
return String(response);
|
||||
};
|
||||
|
||||
const htmlOptimizer = (ctx: IExecuteFunctions, itemIndex: number, maxLength: number) => {
|
||||
const htmlOptimizer = (ctx: ISupplyDataFunctions, itemIndex: number, maxLength: number) => {
|
||||
const cssSelector = ctx.getNodeParameter('cssSelector', itemIndex, '') as string;
|
||||
const onlyContent = ctx.getNodeParameter('onlyContent', itemIndex, false) as boolean;
|
||||
let elementsToOmit: string[] = [];
|
||||
|
@ -214,7 +214,7 @@ const htmlOptimizer = (ctx: IExecuteFunctions, itemIndex: number, maxLength: num
|
|||
};
|
||||
};
|
||||
|
||||
const textOptimizer = (ctx: IExecuteFunctions, itemIndex: number, maxLength: number) => {
|
||||
const textOptimizer = (ctx: ISupplyDataFunctions, itemIndex: number, maxLength: number) => {
|
||||
return (response: string | IDataObject) => {
|
||||
if (typeof response === 'object') {
|
||||
try {
|
||||
|
@ -245,7 +245,7 @@ const textOptimizer = (ctx: IExecuteFunctions, itemIndex: number, maxLength: num
|
|||
};
|
||||
};
|
||||
|
||||
const jsonOptimizer = (ctx: IExecuteFunctions, itemIndex: number) => {
|
||||
const jsonOptimizer = (ctx: ISupplyDataFunctions, itemIndex: number) => {
|
||||
return (response: string): string => {
|
||||
let responseData: IDataObject | IDataObject[] | string = response;
|
||||
|
||||
|
@ -324,7 +324,7 @@ const jsonOptimizer = (ctx: IExecuteFunctions, itemIndex: number) => {
|
|||
};
|
||||
};
|
||||
|
||||
export const configureResponseOptimizer = (ctx: IExecuteFunctions, itemIndex: number) => {
|
||||
export const configureResponseOptimizer = (ctx: ISupplyDataFunctions, itemIndex: number) => {
|
||||
const optimizeResponse = ctx.getNodeParameter('optimizeResponse', itemIndex, false) as boolean;
|
||||
|
||||
if (optimizeResponse) {
|
||||
|
@ -469,7 +469,7 @@ const MODEL_INPUT_DESCRIPTION = {
|
|||
};
|
||||
|
||||
export const updateParametersAndOptions = (options: {
|
||||
ctx: IExecuteFunctions;
|
||||
ctx: ISupplyDataFunctions;
|
||||
itemIndex: number;
|
||||
toolParameters: ToolParameter[];
|
||||
placeholdersDefinitions: PlaceholderDefinition[];
|
||||
|
@ -558,7 +558,7 @@ export const prepareToolDescription = (
|
|||
};
|
||||
|
||||
export const configureToolFunction = (
|
||||
ctx: IExecuteFunctions,
|
||||
ctx: ISupplyDataFunctions,
|
||||
itemIndex: number,
|
||||
toolParameters: ToolParameter[],
|
||||
requestOptions: IHttpRequestOptions,
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
import { SerpAPI } from '@langchain/community/tools/serpapi';
|
||||
|
@ -113,7 +113,7 @@ export class ToolSerpApi implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const credentials = await this.getCredentials('serpApi');
|
||||
|
||||
const options = this.getNodeParameter('options', itemIndex) as object;
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
import type { IExecuteFunctions, INodeType, INodeTypeDescription, SupplyData } from 'n8n-workflow';
|
||||
import type {
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
ISupplyDataFunctions,
|
||||
SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeConnectionType } from 'n8n-workflow';
|
||||
|
||||
import { VectorStoreQATool } from 'langchain/tools';
|
||||
|
@ -58,7 +63,7 @@ export class ToolVectorStore implements INodeType {
|
|||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'e.g. state_of_union_address',
|
||||
placeholder: 'e.g. company_knowledge_base',
|
||||
validateType: 'string-alphanumeric',
|
||||
description: 'Name of the vector store',
|
||||
},
|
||||
|
@ -67,7 +72,7 @@ export class ToolVectorStore implements INodeType {
|
|||
name: 'description',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'The most recent state of the Union address',
|
||||
placeholder: 'Retrieves data about [insert information about your data here]...',
|
||||
typeOptions: {
|
||||
rows: 3,
|
||||
},
|
||||
|
@ -82,7 +87,7 @@ export class ToolVectorStore implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const name = this.getNodeParameter('name', itemIndex) as string;
|
||||
const toolDescription = this.getNodeParameter('description', itemIndex) as string;
|
||||
const topK = this.getNodeParameter('topK', itemIndex, 4) as number;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
import { WikipediaQueryRun } from '@langchain/community/tools/wikipedia_query_run';
|
||||
|
@ -43,7 +43,7 @@ export class ToolWikipedia implements INodeType {
|
|||
properties: [getConnectionHintNoticeField([NodeConnectionType.AiAgent])],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions): Promise<SupplyData> {
|
||||
const WikiTool = new WikipediaQueryRun();
|
||||
|
||||
WikiTool.description =
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
import { WolframAlphaTool } from '@langchain/community/tools/wolframalpha';
|
||||
|
@ -49,7 +49,7 @@ export class ToolWolframAlpha implements INodeType {
|
|||
properties: [getConnectionHintNoticeField([NodeConnectionType.AiAgent])],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions): Promise<SupplyData> {
|
||||
const credentials = await this.getCredentials('wolframAlphaApi');
|
||||
|
||||
return {
|
||||
|
|
|
@ -6,12 +6,12 @@ import isObject from 'lodash/isObject';
|
|||
import type { SetField, SetNodeOptions } from 'n8n-nodes-base/dist/nodes/Set/v2/helpers/interfaces';
|
||||
import * as manual from 'n8n-nodes-base/dist/nodes/Set/v2/manual.mode';
|
||||
import type {
|
||||
IExecuteFunctions,
|
||||
IExecuteWorkflowInfo,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
IWorkflowBase,
|
||||
ISupplyDataFunctions,
|
||||
SupplyData,
|
||||
ExecutionError,
|
||||
IDataObject,
|
||||
|
@ -357,7 +357,7 @@ export class ToolWorkflow implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const name = this.getNodeParameter('name', itemIndex) as string;
|
||||
const description = this.getNodeParameter('description', itemIndex) as string;
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
|
||||
import {
|
||||
NodeConnectionType,
|
||||
type SupplyData,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
import type { Embeddings } from '@langchain/core/embeddings';
|
||||
import { MemoryVectorStoreManager } from '../shared/MemoryVectorStoreManager';
|
||||
|
@ -59,7 +59,7 @@ export class VectorStoreInMemoryLoad implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const embeddings = (await this.getInputConnectionData(
|
||||
NodeConnectionType.AiEmbedding,
|
||||
itemIndex,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
import type { PineconeStoreParams } from '@langchain/pinecone';
|
||||
|
@ -84,7 +84,7 @@ export class VectorStorePineconeLoad implements INodeType {
|
|||
},
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
this.logger.debug('Supplying data for Pinecone Load Vector Store');
|
||||
|
||||
const namespace = this.getNodeParameter('pineconeNamespace', itemIndex) as string;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
NodeConnectionType,
|
||||
} from 'n8n-workflow';
|
||||
|
@ -81,7 +81,7 @@ export class VectorStoreSupabaseLoad implements INodeType {
|
|||
|
||||
methods = { listSearch: { supabaseTableNameSearch } };
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
this.logger.debug('Supply Supabase Load Vector Store');
|
||||
|
||||
const tableName = this.getNodeParameter('tableName', itemIndex, '', {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import {
|
||||
NodeConnectionType,
|
||||
type IExecuteFunctions,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type ISupplyDataFunctions,
|
||||
type SupplyData,
|
||||
} from 'n8n-workflow';
|
||||
import type { IZepConfig } from '@langchain/community/vectorstores/zep';
|
||||
|
@ -83,7 +83,7 @@ export class VectorStoreZepLoad implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
this.logger.debug('Supplying data for Zep Load Vector Store');
|
||||
|
||||
const collectionName = this.getNodeParameter('collectionName', itemIndex) as string;
|
||||
|
|
|
@ -7,11 +7,8 @@ export class MemoryVectorStoreManager {
|
|||
|
||||
private vectorStoreBuffer: Map<string, MemoryVectorStore>;
|
||||
|
||||
private embeddings: Embeddings;
|
||||
|
||||
private constructor(embeddings: Embeddings) {
|
||||
private constructor(private embeddings: Embeddings) {
|
||||
this.vectorStoreBuffer = new Map();
|
||||
this.embeddings = embeddings;
|
||||
}
|
||||
|
||||
public static getInstance(embeddings: Embeddings): MemoryVectorStoreManager {
|
||||
|
|
|
@ -5,12 +5,13 @@ import type { Embeddings } from '@langchain/core/embeddings';
|
|||
import type { VectorStore } from '@langchain/core/vectorstores';
|
||||
import { NodeConnectionType, NodeOperationError } from 'n8n-workflow';
|
||||
import type {
|
||||
IExecuteFunctions,
|
||||
INodeCredentialDescription,
|
||||
INodeProperties,
|
||||
INodeExecutionData,
|
||||
IExecuteFunctions,
|
||||
INodeTypeDescription,
|
||||
SupplyData,
|
||||
ISupplyDataFunctions,
|
||||
INodeType,
|
||||
ILoadOptionsFunctions,
|
||||
INodeListSearchResult,
|
||||
|
@ -57,13 +58,13 @@ interface VectorStoreNodeConstructorArgs {
|
|||
retrieveFields?: INodeProperties[];
|
||||
updateFields?: INodeProperties[];
|
||||
populateVectorStore: (
|
||||
context: IExecuteFunctions,
|
||||
context: ISupplyDataFunctions,
|
||||
embeddings: Embeddings,
|
||||
documents: Array<Document<Record<string, unknown>>>,
|
||||
itemIndex: number,
|
||||
) => Promise<void>;
|
||||
getVectorStoreClient: (
|
||||
context: IExecuteFunctions,
|
||||
context: ISupplyDataFunctions,
|
||||
filter: Record<string, never> | undefined,
|
||||
embeddings: Embeddings,
|
||||
itemIndex: number,
|
||||
|
@ -281,7 +282,7 @@ export const createVectorStoreNode = (args: VectorStoreNodeConstructorArgs) =>
|
|||
});
|
||||
|
||||
resultData.push(...serializedDocs);
|
||||
void logAiEvent(this, 'ai-vector-store-searched', { query: prompt });
|
||||
logAiEvent(this, 'ai-vector-store-searched', { query: prompt });
|
||||
}
|
||||
|
||||
return [resultData];
|
||||
|
@ -311,7 +312,7 @@ export const createVectorStoreNode = (args: VectorStoreNodeConstructorArgs) =>
|
|||
try {
|
||||
await args.populateVectorStore(this, embeddings, processedDocuments, itemIndex);
|
||||
|
||||
void logAiEvent(this, 'ai-vector-store-populated');
|
||||
logAiEvent(this, 'ai-vector-store-populated');
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
@ -365,7 +366,7 @@ export const createVectorStoreNode = (args: VectorStoreNodeConstructorArgs) =>
|
|||
ids: [documentId],
|
||||
});
|
||||
|
||||
void logAiEvent(this, 'ai-vector-store-updated');
|
||||
logAiEvent(this, 'ai-vector-store-updated');
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
@ -380,7 +381,7 @@ export const createVectorStoreNode = (args: VectorStoreNodeConstructorArgs) =>
|
|||
);
|
||||
}
|
||||
|
||||
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||
const mode = this.getNodeParameter('mode', 0) as 'load' | 'insert' | 'retrieve';
|
||||
const filter = getMetadataFiltersValues(this, itemIndex);
|
||||
const embeddings = (await this.getInputConnectionData(
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import { pipeline } from 'stream/promises';
|
||||
import { createWriteStream } from 'fs';
|
||||
import type { IBinaryData, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
|
||||
import type {
|
||||
IBinaryData,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
ISupplyDataFunctions,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeOperationError, BINARY_ENCODING } from 'n8n-workflow';
|
||||
|
||||
import type { TextSplitter } from '@langchain/textsplitters';
|
||||
|
@ -26,25 +31,12 @@ const SUPPORTED_MIME_TYPES = {
|
|||
};
|
||||
|
||||
export class N8nBinaryLoader {
|
||||
private context: IExecuteFunctions;
|
||||
|
||||
private optionsPrefix: string;
|
||||
|
||||
private binaryDataKey: string;
|
||||
|
||||
private textSplitter?: TextSplitter;
|
||||
|
||||
constructor(
|
||||
context: IExecuteFunctions,
|
||||
optionsPrefix = '',
|
||||
binaryDataKey = '',
|
||||
textSplitter?: TextSplitter,
|
||||
) {
|
||||
this.context = context;
|
||||
this.textSplitter = textSplitter;
|
||||
this.optionsPrefix = optionsPrefix;
|
||||
this.binaryDataKey = binaryDataKey;
|
||||
}
|
||||
private context: IExecuteFunctions | ISupplyDataFunctions,
|
||||
private optionsPrefix = '',
|
||||
private binaryDataKey = '',
|
||||
private textSplitter?: TextSplitter,
|
||||
) {}
|
||||
|
||||
async processAll(items?: INodeExecutionData[]): Promise<Document[]> {
|
||||
const docs: Document[] = [];
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
import { type IExecuteFunctions, type INodeExecutionData, NodeOperationError } from 'n8n-workflow';
|
||||
import {
|
||||
type IExecuteFunctions,
|
||||
type INodeExecutionData,
|
||||
type ISupplyDataFunctions,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import type { TextSplitter } from '@langchain/textsplitters';
|
||||
import type { Document } from '@langchain/core/documents';
|
||||
|
@ -7,17 +12,11 @@ import { TextLoader } from 'langchain/document_loaders/fs/text';
|
|||
import { getMetadataFiltersValues } from './helpers';
|
||||
|
||||
export class N8nJsonLoader {
|
||||
private context: IExecuteFunctions;
|
||||
|
||||
private optionsPrefix: string;
|
||||
|
||||
private textSplitter?: TextSplitter;
|
||||
|
||||
constructor(context: IExecuteFunctions, optionsPrefix = '', textSplitter?: TextSplitter) {
|
||||
this.context = context;
|
||||
this.textSplitter = textSplitter;
|
||||
this.optionsPrefix = optionsPrefix;
|
||||
}
|
||||
constructor(
|
||||
private context: IExecuteFunctions | ISupplyDataFunctions,
|
||||
private optionsPrefix = '',
|
||||
private textSplitter?: TextSplitter,
|
||||
) {}
|
||||
|
||||
async processAll(items?: INodeExecutionData[]): Promise<Document[]> {
|
||||
const docs: Document[] = [];
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { DynamicStructuredToolInput } from '@langchain/core/tools';
|
||||
import { DynamicStructuredTool, DynamicTool } from '@langchain/core/tools';
|
||||
import type { IExecuteFunctions, IDataObject } from 'n8n-workflow';
|
||||
import type { ISupplyDataFunctions, IDataObject } from 'n8n-workflow';
|
||||
import { NodeConnectionType, jsonParse, NodeOperationError } from 'n8n-workflow';
|
||||
import { StructuredOutputParser } from 'langchain/output_parsers';
|
||||
import type { ZodTypeAny } from 'zod';
|
||||
|
@ -45,12 +45,11 @@ ALL parameters marked as required must be provided`;
|
|||
};
|
||||
|
||||
export class N8nTool extends DynamicStructuredTool {
|
||||
private context: IExecuteFunctions;
|
||||
|
||||
constructor(context: IExecuteFunctions, fields: DynamicStructuredToolInput) {
|
||||
constructor(
|
||||
private context: ISupplyDataFunctions,
|
||||
fields: DynamicStructuredToolInput,
|
||||
) {
|
||||
super(fields);
|
||||
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
asDynamicTool(): DynamicTool {
|
||||
|
|
|
@ -5,7 +5,13 @@ import type { BaseMessage } from '@langchain/core/messages';
|
|||
import type { Tool } from '@langchain/core/tools';
|
||||
import type { BaseChatMemory } from 'langchain/memory';
|
||||
import { NodeConnectionType, NodeOperationError, jsonStringify } from 'n8n-workflow';
|
||||
import type { AiEvent, IDataObject, IExecuteFunctions, IWebhookFunctions } from 'n8n-workflow';
|
||||
import type {
|
||||
AiEvent,
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
ISupplyDataFunctions,
|
||||
IWebhookFunctions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { N8nTool } from './N8nTool';
|
||||
|
||||
|
@ -20,7 +26,7 @@ function hasMethods<T>(obj: unknown, ...methodNames: Array<string | symbol>): ob
|
|||
}
|
||||
|
||||
export function getMetadataFiltersValues(
|
||||
ctx: IExecuteFunctions,
|
||||
ctx: IExecuteFunctions | ISupplyDataFunctions,
|
||||
itemIndex: number,
|
||||
): Record<string, never> | undefined {
|
||||
const options = ctx.getNodeParameter('options', itemIndex, {});
|
||||
|
@ -93,7 +99,7 @@ export function getPromptInputByType(options: {
|
|||
}
|
||||
|
||||
export function getSessionId(
|
||||
ctx: IExecuteFunctions | IWebhookFunctions,
|
||||
ctx: ISupplyDataFunctions | IWebhookFunctions,
|
||||
itemIndex: number,
|
||||
selectorKey = 'sessionIdType',
|
||||
autoSelect = 'fromInput',
|
||||
|
@ -133,13 +139,13 @@ export function getSessionId(
|
|||
return sessionId;
|
||||
}
|
||||
|
||||
export async function logAiEvent(
|
||||
executeFunctions: IExecuteFunctions,
|
||||
export function logAiEvent(
|
||||
executeFunctions: IExecuteFunctions | ISupplyDataFunctions,
|
||||
event: AiEvent,
|
||||
data?: IDataObject,
|
||||
) {
|
||||
try {
|
||||
await executeFunctions.logAiEvent(event, data ? jsonStringify(data) : undefined);
|
||||
executeFunctions.logAiEvent(event, data ? jsonStringify(data) : undefined);
|
||||
} catch (error) {
|
||||
executeFunctions.logger.debug(`Error logging AI event: ${event}`);
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import type { Tool } from '@langchain/core/tools';
|
|||
import { VectorStore } from '@langchain/core/vectorstores';
|
||||
import { TextSplitter } from '@langchain/textsplitters';
|
||||
import type { BaseDocumentLoader } from 'langchain/dist/document_loaders/base';
|
||||
import type { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
|
||||
import type { IExecuteFunctions, INodeExecutionData, ISupplyDataFunctions } from 'n8n-workflow';
|
||||
import { NodeOperationError, NodeConnectionType } from 'n8n-workflow';
|
||||
|
||||
import { logAiEvent, isToolsInstance, isBaseChatMemory, isBaseChatMessageHistory } from './helpers';
|
||||
|
@ -27,7 +27,7 @@ const errorsMap: { [key: string]: { message: string; description: string } } = {
|
|||
export async function callMethodAsync<T>(
|
||||
this: T,
|
||||
parameters: {
|
||||
executeFunctions: IExecuteFunctions;
|
||||
executeFunctions: IExecuteFunctions | ISupplyDataFunctions;
|
||||
connectionType: NodeConnectionType;
|
||||
currentNodeRunIndex: number;
|
||||
method: (...args: any[]) => Promise<unknown>;
|
||||
|
@ -113,7 +113,7 @@ export function logWrapper(
|
|||
| VectorStore
|
||||
| N8nBinaryLoader
|
||||
| N8nJsonLoader,
|
||||
executeFunctions: IExecuteFunctions,
|
||||
executeFunctions: IExecuteFunctions | ISupplyDataFunctions,
|
||||
) {
|
||||
return new Proxy(originalInstance, {
|
||||
get: (target, prop) => {
|
||||
|
@ -190,7 +190,7 @@ export function logWrapper(
|
|||
const payload = { action: 'getMessages', response };
|
||||
executeFunctions.addOutputData(connectionType, index, [[{ json: payload }]]);
|
||||
|
||||
void logAiEvent(executeFunctions, 'ai-messages-retrieved-from-memory', { response });
|
||||
logAiEvent(executeFunctions, 'ai-messages-retrieved-from-memory', { response });
|
||||
return response;
|
||||
};
|
||||
} else if (prop === 'addMessage' && 'addMessage' in target) {
|
||||
|
@ -207,7 +207,7 @@ export function logWrapper(
|
|||
arguments: [message],
|
||||
});
|
||||
|
||||
void logAiEvent(executeFunctions, 'ai-message-added-to-memory', { message });
|
||||
logAiEvent(executeFunctions, 'ai-message-added-to-memory', { message });
|
||||
executeFunctions.addOutputData(connectionType, index, [[{ json: payload }]]);
|
||||
};
|
||||
}
|
||||
|
@ -233,7 +233,7 @@ export function logWrapper(
|
|||
arguments: [query, config],
|
||||
})) as Array<Document<Record<string, any>>>;
|
||||
|
||||
void logAiEvent(executeFunctions, 'ai-documents-retrieved', { query });
|
||||
logAiEvent(executeFunctions, 'ai-documents-retrieved', { query });
|
||||
executeFunctions.addOutputData(connectionType, index, [[{ json: { response } }]]);
|
||||
return response;
|
||||
};
|
||||
|
@ -258,7 +258,7 @@ export function logWrapper(
|
|||
arguments: [documents],
|
||||
})) as number[][];
|
||||
|
||||
void logAiEvent(executeFunctions, 'ai-document-embedded');
|
||||
logAiEvent(executeFunctions, 'ai-document-embedded');
|
||||
executeFunctions.addOutputData(connectionType, index, [[{ json: { response } }]]);
|
||||
return response;
|
||||
};
|
||||
|
@ -278,7 +278,7 @@ export function logWrapper(
|
|||
method: target[prop],
|
||||
arguments: [query],
|
||||
})) as number[];
|
||||
void logAiEvent(executeFunctions, 'ai-query-embedded');
|
||||
logAiEvent(executeFunctions, 'ai-query-embedded');
|
||||
executeFunctions.addOutputData(connectionType, index, [[{ json: { response } }]]);
|
||||
return response;
|
||||
};
|
||||
|
@ -323,7 +323,7 @@ export function logWrapper(
|
|||
arguments: [item, itemIndex],
|
||||
})) as number[];
|
||||
|
||||
void logAiEvent(executeFunctions, 'ai-document-processed');
|
||||
logAiEvent(executeFunctions, 'ai-document-processed');
|
||||
executeFunctions.addOutputData(connectionType, index, [
|
||||
[{ json: { response }, pairedItem: { item: itemIndex } }],
|
||||
]);
|
||||
|
@ -349,7 +349,7 @@ export function logWrapper(
|
|||
arguments: [text],
|
||||
})) as string[];
|
||||
|
||||
void logAiEvent(executeFunctions, 'ai-text-split');
|
||||
logAiEvent(executeFunctions, 'ai-text-split');
|
||||
executeFunctions.addOutputData(connectionType, index, [[{ json: { response } }]]);
|
||||
return response;
|
||||
};
|
||||
|
@ -373,7 +373,7 @@ export function logWrapper(
|
|||
arguments: [query],
|
||||
})) as string;
|
||||
|
||||
void logAiEvent(executeFunctions, 'ai-tool-called', { query, response });
|
||||
logAiEvent(executeFunctions, 'ai-tool-called', { query, response });
|
||||
executeFunctions.addOutputData(connectionType, index, [[{ json: { response } }]]);
|
||||
return response;
|
||||
};
|
||||
|
@ -403,7 +403,7 @@ export function logWrapper(
|
|||
arguments: [query, k, filter, _callbacks],
|
||||
})) as Array<Document<Record<string, any>>>;
|
||||
|
||||
void logAiEvent(executeFunctions, 'ai-vector-store-searched', { query });
|
||||
logAiEvent(executeFunctions, 'ai-vector-store-searched', { query });
|
||||
executeFunctions.addOutputData(connectionType, index, [[{ json: { response } }]]);
|
||||
|
||||
return response;
|
||||
|
|
|
@ -2,7 +2,7 @@ import type { Callbacks } from '@langchain/core/callbacks/manager';
|
|||
import type { BaseLanguageModel } from '@langchain/core/language_models/base';
|
||||
import type { AIMessage } from '@langchain/core/messages';
|
||||
import { BaseOutputParser } from '@langchain/core/output_parsers';
|
||||
import type { IExecuteFunctions } from 'n8n-workflow';
|
||||
import type { ISupplyDataFunctions } from 'n8n-workflow';
|
||||
import { NodeConnectionType } from 'n8n-workflow';
|
||||
|
||||
import type { N8nStructuredOutputParser } from './N8nStructuredOutputParser';
|
||||
|
@ -10,23 +10,14 @@ import { NAIVE_FIX_PROMPT } from './prompt';
|
|||
import { logAiEvent } from '../helpers';
|
||||
|
||||
export class N8nOutputFixingParser extends BaseOutputParser {
|
||||
private context: IExecuteFunctions;
|
||||
|
||||
private model: BaseLanguageModel;
|
||||
|
||||
private outputParser: N8nStructuredOutputParser;
|
||||
|
||||
lc_namespace = ['langchain', 'output_parsers', 'fix'];
|
||||
|
||||
constructor(
|
||||
context: IExecuteFunctions,
|
||||
model: BaseLanguageModel,
|
||||
outputParser: N8nStructuredOutputParser,
|
||||
private context: ISupplyDataFunctions,
|
||||
private model: BaseLanguageModel,
|
||||
private outputParser: N8nStructuredOutputParser,
|
||||
) {
|
||||
super();
|
||||
this.context = context;
|
||||
this.model = model;
|
||||
this.outputParser = outputParser;
|
||||
}
|
||||
|
||||
getRetryChain() {
|
||||
|
@ -48,7 +39,7 @@ export class N8nOutputFixingParser extends BaseOutputParser {
|
|||
try {
|
||||
// First attempt to parse the completion
|
||||
const response = await this.outputParser.parse(completion, callbacks, (e) => e);
|
||||
void logAiEvent(this.context, 'ai-output-parsed', { text: completion, response });
|
||||
logAiEvent(this.context, 'ai-output-parsed', { text: completion, response });
|
||||
|
||||
this.context.addOutputData(NodeConnectionType.AiOutputParser, index, [
|
||||
[{ json: { action: 'parse', response } }],
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type { Callbacks } from '@langchain/core/callbacks/manager';
|
||||
import { StructuredOutputParser } from 'langchain/output_parsers';
|
||||
import get from 'lodash/get';
|
||||
import type { IExecuteFunctions } from 'n8n-workflow';
|
||||
import type { ISupplyDataFunctions } from 'n8n-workflow';
|
||||
import { NodeConnectionType, NodeOperationError } from 'n8n-workflow';
|
||||
import { z } from 'zod';
|
||||
|
||||
|
@ -14,11 +14,11 @@ const STRUCTURED_OUTPUT_ARRAY_KEY = '__structured__output__array';
|
|||
export class N8nStructuredOutputParser extends StructuredOutputParser<
|
||||
z.ZodType<object, z.ZodTypeDef, object>
|
||||
> {
|
||||
context: IExecuteFunctions;
|
||||
|
||||
constructor(context: IExecuteFunctions, zodSchema: z.ZodSchema<object>) {
|
||||
constructor(
|
||||
private context: ISupplyDataFunctions,
|
||||
zodSchema: z.ZodSchema<object>,
|
||||
) {
|
||||
super(zodSchema);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
lc_namespace = ['langchain', 'output_parsers', 'structured'];
|
||||
|
@ -39,7 +39,7 @@ export class N8nStructuredOutputParser extends StructuredOutputParser<
|
|||
get(parsed, STRUCTURED_OUTPUT_KEY) ??
|
||||
parsed) as Record<string, unknown>;
|
||||
|
||||
void logAiEvent(this.context, 'ai-output-parsed', { text, response: result });
|
||||
logAiEvent(this.context, 'ai-output-parsed', { text, response: result });
|
||||
|
||||
this.context.addOutputData(NodeConnectionType.AiOutputParser, index, [
|
||||
[{ json: { action: 'parse', response: result } }],
|
||||
|
@ -56,7 +56,7 @@ export class N8nStructuredOutputParser extends StructuredOutputParser<
|
|||
},
|
||||
);
|
||||
|
||||
void logAiEvent(this.context, 'ai-output-parsed', {
|
||||
logAiEvent(this.context, 'ai-output-parsed', {
|
||||
text,
|
||||
response: e.message ?? e,
|
||||
});
|
||||
|
@ -73,7 +73,7 @@ export class N8nStructuredOutputParser extends StructuredOutputParser<
|
|||
static async fromZodJsonSchema(
|
||||
zodSchema: z.ZodSchema<object>,
|
||||
nodeVersion: number,
|
||||
context: IExecuteFunctions,
|
||||
context: ISupplyDataFunctions,
|
||||
): Promise<N8nStructuredOutputParser> {
|
||||
let returnSchema: z.ZodType<object, z.ZodTypeDef, object>;
|
||||
if (nodeVersion === 1) {
|
||||
|
|
|
@ -22,9 +22,11 @@
|
|||
"dist/**/*"
|
||||
],
|
||||
"dependencies": {
|
||||
"@n8n/config": "workspace:*",
|
||||
"n8n-workflow": "workspace:*",
|
||||
"n8n-core": "workspace:*",
|
||||
"nanoid": "^3.3.6",
|
||||
"typedi": "catalog:",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
16
packages/@n8n/task-runner/src/config/base-runner-config.ts
Normal file
16
packages/@n8n/task-runner/src/config/base-runner-config.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { Config, Env } from '@n8n/config';
|
||||
|
||||
@Config
|
||||
export class BaseRunnerConfig {
|
||||
@Env('N8N_RUNNERS_N8N_URI')
|
||||
n8nUri: string = '127.0.0.1:5679';
|
||||
|
||||
@Env('N8N_RUNNERS_GRANT_TOKEN')
|
||||
grantToken: string = '';
|
||||
|
||||
@Env('N8N_RUNNERS_MAX_PAYLOAD')
|
||||
maxPayloadSize: number = 1024 * 1024 * 1024;
|
||||
|
||||
@Env('N8N_RUNNERS_MAX_CONCURRENCY')
|
||||
maxConcurrency: number = 5;
|
||||
}
|
10
packages/@n8n/task-runner/src/config/js-runner-config.ts
Normal file
10
packages/@n8n/task-runner/src/config/js-runner-config.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { Config, Env } from '@n8n/config';
|
||||
|
||||
@Config
|
||||
export class JsRunnerConfig {
|
||||
@Env('NODE_FUNCTION_ALLOW_BUILTIN')
|
||||
allowedBuiltInModules: string = '';
|
||||
|
||||
@Env('NODE_FUNCTION_ALLOW_EXTERNAL')
|
||||
allowedExternalModules: string = '';
|
||||
}
|
13
packages/@n8n/task-runner/src/config/main-config.ts
Normal file
13
packages/@n8n/task-runner/src/config/main-config.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { Config, Nested } from '@n8n/config';
|
||||
|
||||
import { BaseRunnerConfig } from './base-runner-config';
|
||||
import { JsRunnerConfig } from './js-runner-config';
|
||||
|
||||
@Config
|
||||
export class MainConfig {
|
||||
@Nested
|
||||
baseRunnerConfig!: BaseRunnerConfig;
|
||||
|
||||
@Nested
|
||||
jsRunnerConfig!: JsRunnerConfig;
|
||||
}
|
|
@ -4,7 +4,6 @@ import fs from 'node:fs';
|
|||
import { builtinModules } from 'node:module';
|
||||
|
||||
import { ValidationError } from '@/js-task-runner/errors/validation-error';
|
||||
import type { JsTaskRunnerOpts } from '@/js-task-runner/js-task-runner';
|
||||
import {
|
||||
JsTaskRunner,
|
||||
type AllCodeTaskData,
|
||||
|
@ -13,17 +12,27 @@ import {
|
|||
import type { Task } from '@/task-runner';
|
||||
|
||||
import { newAllCodeTaskData, newTaskWithSettings, withPairedItem, wrapIntoJson } from './test-data';
|
||||
import type { JsRunnerConfig } from '../../config/js-runner-config';
|
||||
import { MainConfig } from '../../config/main-config';
|
||||
import { ExecutionError } from '../errors/execution-error';
|
||||
|
||||
jest.mock('ws');
|
||||
|
||||
const defaultConfig = new MainConfig();
|
||||
|
||||
describe('JsTaskRunner', () => {
|
||||
const createRunnerWithOpts = (opts: Partial<JsTaskRunnerOpts> = {}) =>
|
||||
const createRunnerWithOpts = (opts: Partial<JsRunnerConfig> = {}) =>
|
||||
new JsTaskRunner({
|
||||
wsUrl: 'ws://localhost',
|
||||
grantToken: 'grantToken',
|
||||
maxConcurrency: 1,
|
||||
...opts,
|
||||
baseRunnerConfig: {
|
||||
...defaultConfig.baseRunnerConfig,
|
||||
grantToken: 'grantToken',
|
||||
maxConcurrency: 1,
|
||||
n8nUri: 'localhost',
|
||||
},
|
||||
jsRunnerConfig: {
|
||||
...defaultConfig.jsRunnerConfig,
|
||||
...opts,
|
||||
},
|
||||
});
|
||||
|
||||
const defaultTaskRunner = createRunnerWithOpts();
|
||||
|
|
|
@ -30,6 +30,7 @@ import { makeSerializable } from './errors/serializable-error';
|
|||
import type { RequireResolver } from './require-resolver';
|
||||
import { createRequireResolver } from './require-resolver';
|
||||
import { validateRunForAllItemsOutput, validateRunForEachItemOutput } from './result-validation';
|
||||
import type { MainConfig } from '../config/main-config';
|
||||
|
||||
export interface JSExecSettings {
|
||||
code: string;
|
||||
|
@ -76,23 +77,6 @@ export interface AllCodeTaskData {
|
|||
additionalData: PartialAdditionalData;
|
||||
}
|
||||
|
||||
export interface JsTaskRunnerOpts {
|
||||
wsUrl: string;
|
||||
grantToken: string;
|
||||
maxConcurrency: number;
|
||||
name?: string;
|
||||
/**
|
||||
* List of built-in nodejs modules that are allowed to be required in the
|
||||
* execution sandbox. Asterisk (*) can be used to allow all.
|
||||
*/
|
||||
allowedBuiltInModules?: string;
|
||||
/**
|
||||
* List of npm modules that are allowed to be required in the execution
|
||||
* sandbox. Asterisk (*) can be used to allow all.
|
||||
*/
|
||||
allowedExternalModules?: string;
|
||||
}
|
||||
|
||||
type CustomConsole = {
|
||||
log: (...args: unknown[]) => void;
|
||||
};
|
||||
|
@ -100,22 +84,20 @@ type CustomConsole = {
|
|||
export class JsTaskRunner extends TaskRunner {
|
||||
private readonly requireResolver: RequireResolver;
|
||||
|
||||
constructor({
|
||||
grantToken,
|
||||
maxConcurrency,
|
||||
wsUrl,
|
||||
name = 'JS Task Runner',
|
||||
allowedBuiltInModules,
|
||||
allowedExternalModules,
|
||||
}: JsTaskRunnerOpts) {
|
||||
super('javascript', wsUrl, grantToken, maxConcurrency, name);
|
||||
constructor(config: MainConfig, name = 'JS Task Runner') {
|
||||
super({
|
||||
taskType: 'javascript',
|
||||
name,
|
||||
...config.baseRunnerConfig,
|
||||
});
|
||||
const { jsRunnerConfig } = config;
|
||||
|
||||
const parseModuleAllowList = (moduleList: string) =>
|
||||
moduleList === '*' ? null : new Set(moduleList.split(',').map((x) => x.trim()));
|
||||
|
||||
this.requireResolver = createRequireResolver({
|
||||
allowedBuiltInModules: parseModuleAllowList(allowedBuiltInModules ?? ''),
|
||||
allowedExternalModules: parseModuleAllowList(allowedExternalModules ?? ''),
|
||||
allowedBuiltInModules: parseModuleAllowList(jsRunnerConfig.allowedBuiltInModules ?? ''),
|
||||
allowedExternalModules: parseModuleAllowList(jsRunnerConfig.allowedExternalModules ?? ''),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,27 +1,12 @@
|
|||
import { ApplicationError, ensureError } from 'n8n-workflow';
|
||||
import { ensureError } from 'n8n-workflow';
|
||||
import Container from 'typedi';
|
||||
|
||||
import { MainConfig } from './config/main-config';
|
||||
import { JsTaskRunner } from './js-task-runner/js-task-runner';
|
||||
|
||||
let runner: JsTaskRunner | undefined;
|
||||
let isShuttingDown = false;
|
||||
|
||||
type Config = {
|
||||
n8nUri: string;
|
||||
grantToken: string;
|
||||
};
|
||||
|
||||
function readAndParseConfig(): Config {
|
||||
const grantToken = process.env.N8N_RUNNERS_GRANT_TOKEN;
|
||||
if (!grantToken) {
|
||||
throw new ApplicationError('Missing N8N_RUNNERS_GRANT_TOKEN environment variable');
|
||||
}
|
||||
|
||||
return {
|
||||
n8nUri: process.env.N8N_RUNNERS_N8N_URI ?? '127.0.0.1:5679',
|
||||
grantToken,
|
||||
};
|
||||
}
|
||||
|
||||
function createSignalHandler(signal: string) {
|
||||
return async function onSignal() {
|
||||
if (isShuttingDown) {
|
||||
|
@ -46,16 +31,9 @@ function createSignalHandler(signal: string) {
|
|||
}
|
||||
|
||||
void (async function start() {
|
||||
const config = readAndParseConfig();
|
||||
const config = Container.get(MainConfig);
|
||||
|
||||
const wsUrl = `ws://${config.n8nUri}/runners/_ws`;
|
||||
runner = new JsTaskRunner({
|
||||
wsUrl,
|
||||
grantToken: config.grantToken,
|
||||
maxConcurrency: 5,
|
||||
allowedBuiltInModules: process.env.NODE_FUNCTION_ALLOW_BUILTIN,
|
||||
allowedExternalModules: process.env.NODE_FUNCTION_ALLOW_EXTERNAL,
|
||||
});
|
||||
runner = new JsTaskRunner(config);
|
||||
|
||||
process.on('SIGINT', createSignalHandler('SIGINT'));
|
||||
process.on('SIGTERM', createSignalHandler('SIGTERM'));
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { ApplicationError, type INodeTypeDescription } from 'n8n-workflow';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { URL } from 'node:url';
|
||||
import { type MessageEvent, WebSocket } from 'ws';
|
||||
|
||||
import type { BaseRunnerConfig } from './config/base-runner-config';
|
||||
import { TaskRunnerNodeTypes } from './node-types';
|
||||
import {
|
||||
RPC_ALLOW_LIST,
|
||||
|
@ -42,7 +42,10 @@ export interface RPCCallObject {
|
|||
const VALID_TIME_MS = 1000;
|
||||
const VALID_EXTRA_MS = 100;
|
||||
|
||||
const DEFAULT_MAX_PAYLOAD_SIZE = 1024 * 1024 * 1024;
|
||||
export interface TaskRunnerOpts extends BaseRunnerConfig {
|
||||
taskType: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export abstract class TaskRunner {
|
||||
id: string = nanoid();
|
||||
|
@ -63,22 +66,23 @@ export abstract class TaskRunner {
|
|||
|
||||
nodeTypes: TaskRunnerNodeTypes = new TaskRunnerNodeTypes([]);
|
||||
|
||||
constructor(
|
||||
public taskType: string,
|
||||
wsUrl: string,
|
||||
grantToken: string,
|
||||
private maxConcurrency: number,
|
||||
public name?: string,
|
||||
) {
|
||||
const url = new URL(wsUrl);
|
||||
url.searchParams.append('id', this.id);
|
||||
this.ws = new WebSocket(url.toString(), {
|
||||
taskType: string;
|
||||
|
||||
maxConcurrency: number;
|
||||
|
||||
name: string;
|
||||
|
||||
constructor(opts: TaskRunnerOpts) {
|
||||
this.taskType = opts.taskType;
|
||||
this.name = opts.name ?? 'Node.js Task Runner SDK';
|
||||
this.maxConcurrency = opts.maxConcurrency;
|
||||
|
||||
const wsUrl = `ws://${opts.n8nUri}/runners/_ws?id=${this.id}`;
|
||||
this.ws = new WebSocket(wsUrl, {
|
||||
headers: {
|
||||
authorization: `Bearer ${grantToken}`,
|
||||
authorization: `Bearer ${opts.grantToken}`,
|
||||
},
|
||||
maxPayload: process.env.N8N_RUNNERS_MAX_PAYLOAD
|
||||
? parseInt(process.env.N8N_RUNNERS_MAX_PAYLOAD)
|
||||
: DEFAULT_MAX_PAYLOAD_SIZE,
|
||||
maxPayload: opts.maxPayloadSize,
|
||||
});
|
||||
this.ws.addEventListener('message', this.receiveMessage);
|
||||
this.ws.addEventListener('close', this.stopTaskOffers);
|
||||
|
@ -145,7 +149,7 @@ export abstract class TaskRunner {
|
|||
case 'broker:inforequest':
|
||||
this.send({
|
||||
type: 'runner:info',
|
||||
name: this.name ?? 'Node.js Task Runner SDK',
|
||||
name: this.name,
|
||||
types: [this.taskType],
|
||||
});
|
||||
break;
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
"extends": ["../../../tsconfig.json", "../../../tsconfig.backend.json"],
|
||||
"compilerOptions": {
|
||||
"rootDir": ".",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"baseUrl": "src",
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
|
|
|
@ -17,14 +17,16 @@ const MOCK_ACTIVATION_KEY = 'activation-key';
|
|||
const MOCK_FEATURE_FLAG = 'feat:sharing';
|
||||
const MOCK_MAIN_PLAN_ID = '1b765dc4-d39d-4ffe-9885-c56dd67c4b26';
|
||||
|
||||
describe('License', () => {
|
||||
beforeAll(() => {
|
||||
config.set('license.serverUrl', MOCK_SERVER_URL);
|
||||
config.set('license.autoRenewEnabled', true);
|
||||
config.set('license.autoRenewOffset', MOCK_RENEW_OFFSET);
|
||||
config.set('license.tenantId', 1);
|
||||
});
|
||||
const licenseConfig: GlobalConfig['license'] = {
|
||||
serverUrl: MOCK_SERVER_URL,
|
||||
autoRenewalEnabled: true,
|
||||
autoRenewOffset: MOCK_RENEW_OFFSET,
|
||||
activationKey: MOCK_ACTIVATION_KEY,
|
||||
tenantId: 1,
|
||||
cert: '',
|
||||
};
|
||||
|
||||
describe('License', () => {
|
||||
let license: License;
|
||||
const instanceSettings = mock<InstanceSettings>({
|
||||
instanceId: MOCK_INSTANCE_ID,
|
||||
|
@ -32,7 +34,10 @@ describe('License', () => {
|
|||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
const globalConfig = mock<GlobalConfig>({ multiMainSetup: { enabled: false } });
|
||||
const globalConfig = mock<GlobalConfig>({
|
||||
license: licenseConfig,
|
||||
multiMainSetup: { enabled: false },
|
||||
});
|
||||
license = new License(mockLogger(), instanceSettings, mock(), mock(), mock(), globalConfig);
|
||||
await license.init();
|
||||
});
|
||||
|
@ -66,7 +71,7 @@ describe('License', () => {
|
|||
mock(),
|
||||
mock(),
|
||||
mock(),
|
||||
mock(),
|
||||
mock<GlobalConfig>({ license: licenseConfig }),
|
||||
);
|
||||
await license.init();
|
||||
expect(LicenseManager).toHaveBeenCalledWith(
|
||||
|
@ -192,17 +197,23 @@ describe('License', () => {
|
|||
});
|
||||
|
||||
describe('License', () => {
|
||||
beforeEach(() => {
|
||||
config.load(config.default);
|
||||
});
|
||||
|
||||
describe('init', () => {
|
||||
describe('in single-main setup', () => {
|
||||
describe('with `license.autoRenewEnabled` enabled', () => {
|
||||
it('should enable renewal', async () => {
|
||||
const globalConfig = mock<GlobalConfig>({ multiMainSetup: { enabled: false } });
|
||||
const globalConfig = mock<GlobalConfig>({
|
||||
license: licenseConfig,
|
||||
multiMainSetup: { enabled: false },
|
||||
});
|
||||
|
||||
await new License(mockLogger(), mock(), mock(), mock(), mock(), globalConfig).init();
|
||||
await new License(
|
||||
mockLogger(),
|
||||
mock<InstanceSettings>({ instanceType: 'main' }),
|
||||
mock(),
|
||||
mock(),
|
||||
mock(),
|
||||
globalConfig,
|
||||
).init();
|
||||
|
||||
expect(LicenseManager).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ autoRenewEnabled: true, renewOnInit: true }),
|
||||
|
@ -212,9 +223,14 @@ describe('License', () => {
|
|||
|
||||
describe('with `license.autoRenewEnabled` disabled', () => {
|
||||
it('should disable renewal', async () => {
|
||||
config.set('license.autoRenewEnabled', false);
|
||||
|
||||
await new License(mockLogger(), mock(), mock(), mock(), mock(), mock()).init();
|
||||
await new License(
|
||||
mockLogger(),
|
||||
mock<InstanceSettings>({ instanceType: 'main' }),
|
||||
mock(),
|
||||
mock(),
|
||||
mock(),
|
||||
mock(),
|
||||
).init();
|
||||
|
||||
expect(LicenseManager).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ autoRenewEnabled: false, renewOnInit: false }),
|
||||
|
@ -228,9 +244,11 @@ describe('License', () => {
|
|||
test.each(['unset', 'leader', 'follower'])(
|
||||
'if %s status, should disable removal',
|
||||
async (status) => {
|
||||
const globalConfig = mock<GlobalConfig>({ multiMainSetup: { enabled: true } });
|
||||
const globalConfig = mock<GlobalConfig>({
|
||||
license: { ...licenseConfig, autoRenewalEnabled: false },
|
||||
multiMainSetup: { enabled: true },
|
||||
});
|
||||
config.set('multiMainSetup.instanceType', status);
|
||||
config.set('license.autoRenewEnabled', false);
|
||||
|
||||
await new License(mockLogger(), mock(), mock(), mock(), mock(), globalConfig).init();
|
||||
|
||||
|
@ -243,9 +261,11 @@ describe('License', () => {
|
|||
|
||||
describe('with `license.autoRenewEnabled` enabled', () => {
|
||||
test.each(['unset', 'follower'])('if %s status, should disable removal', async (status) => {
|
||||
const globalConfig = mock<GlobalConfig>({ multiMainSetup: { enabled: true } });
|
||||
const globalConfig = mock<GlobalConfig>({
|
||||
license: { ...licenseConfig, autoRenewalEnabled: false },
|
||||
multiMainSetup: { enabled: true },
|
||||
});
|
||||
config.set('multiMainSetup.instanceType', status);
|
||||
config.set('license.autoRenewEnabled', false);
|
||||
|
||||
await new License(mockLogger(), mock(), mock(), mock(), mock(), globalConfig).init();
|
||||
|
||||
|
@ -255,7 +275,10 @@ describe('License', () => {
|
|||
});
|
||||
|
||||
it('if leader status, should enable renewal', async () => {
|
||||
const globalConfig = mock<GlobalConfig>({ multiMainSetup: { enabled: true } });
|
||||
const globalConfig = mock<GlobalConfig>({
|
||||
license: licenseConfig,
|
||||
multiMainSetup: { enabled: true },
|
||||
});
|
||||
config.set('multiMainSetup.instanceType', 'leader');
|
||||
|
||||
await new License(mockLogger(), mock(), mock(), mock(), mock(), globalConfig).init();
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { SecurityConfig } from '@n8n/config';
|
||||
import { Flags } from '@oclif/core';
|
||||
import { ApplicationError } from 'n8n-workflow';
|
||||
import { Container } from 'typedi';
|
||||
|
||||
import config from '@/config';
|
||||
import { RISK_CATEGORIES } from '@/security-audit/constants';
|
||||
import { SecurityAuditService } from '@/security-audit/security-audit.service';
|
||||
import type { Risk } from '@/security-audit/types';
|
||||
|
@ -26,7 +26,7 @@ export class SecurityAudit extends BaseCommand {
|
|||
}),
|
||||
|
||||
'days-abandoned-workflow': Flags.integer({
|
||||
default: config.getEnv('security.audit.daysAbandonedWorkflow'),
|
||||
default: Container.get(SecurityConfig).daysAbandonedWorkflow,
|
||||
description: 'Days for a workflow to be considered abandoned if not executed',
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -274,7 +274,7 @@ export abstract class BaseCommand extends Command {
|
|||
this.license = Container.get(License);
|
||||
await this.license.init();
|
||||
|
||||
const activationKey = config.getEnv('license.activationKey');
|
||||
const { activationKey } = this.globalConfig.license;
|
||||
|
||||
if (activationKey) {
|
||||
const hasCert = (await this.license.loadCertStr()).length > 0;
|
||||
|
|
|
@ -199,7 +199,7 @@ export class Start extends BaseCommand {
|
|||
await this.initOrchestration();
|
||||
this.logger.debug('Orchestration init complete');
|
||||
|
||||
if (!config.getEnv('license.autoRenewEnabled') && this.instanceSettings.isLeader) {
|
||||
if (!this.globalConfig.license.autoRenewalEnabled && this.instanceSettings.isLeader) {
|
||||
this.logger.warn(
|
||||
'Automatic license renewal is disabled. The license will not renew automatically, and access to licensed features may be lost!',
|
||||
);
|
||||
|
|
|
@ -187,29 +187,6 @@ export const schema = {
|
|||
doc: 'Public URL where the editor is accessible. Also used for emails sent from n8n.',
|
||||
},
|
||||
|
||||
security: {
|
||||
restrictFileAccessTo: {
|
||||
doc: 'If set only files in that directories can be accessed. Multiple directories can be separated by semicolon (";").',
|
||||
format: String,
|
||||
default: '',
|
||||
env: 'N8N_RESTRICT_FILE_ACCESS_TO',
|
||||
},
|
||||
blockFileAccessToN8nFiles: {
|
||||
doc: 'If set to true it will block access to all files in the ".n8n" directory, the static cache dir at ~/.cache/n8n/public, and user defined config files.',
|
||||
format: Boolean,
|
||||
default: true,
|
||||
env: 'N8N_BLOCK_FILE_ACCESS_TO_N8N_FILES',
|
||||
},
|
||||
audit: {
|
||||
daysAbandonedWorkflow: {
|
||||
doc: 'Days for a workflow to be considered abandoned if not executed',
|
||||
format: Number,
|
||||
default: 90,
|
||||
env: 'N8N_SECURITY_AUDIT_DAYS_ABANDONED_WORKFLOW',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
workflowTagsDisabled: {
|
||||
format: Boolean,
|
||||
default: false,
|
||||
|
@ -411,45 +388,6 @@ export const schema = {
|
|||
env: 'N8N_DEFAULT_LOCALE',
|
||||
},
|
||||
|
||||
license: {
|
||||
serverUrl: {
|
||||
format: String,
|
||||
default: 'https://license.n8n.io/v1',
|
||||
env: 'N8N_LICENSE_SERVER_URL',
|
||||
doc: 'License server url to retrieve license.',
|
||||
},
|
||||
autoRenewEnabled: {
|
||||
format: Boolean,
|
||||
default: true,
|
||||
env: 'N8N_LICENSE_AUTO_RENEW_ENABLED',
|
||||
doc: 'Whether auto renewal for licenses is enabled.',
|
||||
},
|
||||
autoRenewOffset: {
|
||||
format: Number,
|
||||
default: 60 * 60 * 72, // 72 hours
|
||||
env: 'N8N_LICENSE_AUTO_RENEW_OFFSET',
|
||||
doc: 'How many seconds before expiry a license should get automatically renewed. ',
|
||||
},
|
||||
activationKey: {
|
||||
format: String,
|
||||
default: '',
|
||||
env: 'N8N_LICENSE_ACTIVATION_KEY',
|
||||
doc: 'Activation key to initialize license',
|
||||
},
|
||||
tenantId: {
|
||||
format: Number,
|
||||
default: 1,
|
||||
env: 'N8N_LICENSE_TENANT_ID',
|
||||
doc: 'Tenant id used by the license manager',
|
||||
},
|
||||
cert: {
|
||||
format: String,
|
||||
default: '',
|
||||
env: 'N8N_LICENSE_CERT',
|
||||
doc: 'Ephemeral license certificate',
|
||||
},
|
||||
},
|
||||
|
||||
hideUsagePage: {
|
||||
format: Boolean,
|
||||
default: false,
|
||||
|
|
|
@ -1061,6 +1061,7 @@ describe('TelemetryEventRelay', () => {
|
|||
describe('Community+ registered', () => {
|
||||
it('should track `license-community-plus-registered` event', () => {
|
||||
const event: RelayEventMap['license-community-plus-registered'] = {
|
||||
userId: 'user123',
|
||||
email: 'user@example.com',
|
||||
licenseKey: 'license123',
|
||||
};
|
||||
|
@ -1068,6 +1069,7 @@ describe('TelemetryEventRelay', () => {
|
|||
eventService.emit('license-community-plus-registered', event);
|
||||
|
||||
expect(telemetry.track).toHaveBeenCalledWith('User registered for license community plus', {
|
||||
user_id: 'user123',
|
||||
email: 'user@example.com',
|
||||
licenseKey: 'license123',
|
||||
});
|
||||
|
|
|
@ -8,7 +8,7 @@ import type {
|
|||
|
||||
import type { AuthProviderType } from '@/databases/entities/auth-identity';
|
||||
import type { ProjectRole } from '@/databases/entities/project-relation';
|
||||
import type { GlobalRole } from '@/databases/entities/user';
|
||||
import type { GlobalRole, User } from '@/databases/entities/user';
|
||||
import type { IWorkflowDb } from '@/interfaces';
|
||||
|
||||
import type { AiEventMap } from './ai.event-map';
|
||||
|
@ -421,6 +421,7 @@ export type RelayEventMap = {
|
|||
};
|
||||
|
||||
'license-community-plus-registered': {
|
||||
userId: User['id'];
|
||||
email: string;
|
||||
licenseKey: string;
|
||||
};
|
||||
|
|
|
@ -236,10 +236,12 @@ export class TelemetryEventRelay extends EventRelay {
|
|||
}
|
||||
|
||||
private licenseCommunityPlusRegistered({
|
||||
userId,
|
||||
email,
|
||||
licenseKey,
|
||||
}: RelayEventMap['license-community-plus-registered']) {
|
||||
this.telemetry.track('User registered for license community plus', {
|
||||
user_id: userId,
|
||||
email,
|
||||
licenseKey,
|
||||
});
|
||||
|
@ -778,7 +780,7 @@ export class TelemetryEventRelay extends EventRelay {
|
|||
ldap_allowed: authenticationMethod === 'ldap',
|
||||
saml_enabled: authenticationMethod === 'saml',
|
||||
license_plan_name: this.license.getPlanName(),
|
||||
license_tenant_id: config.getEnv('license.tenantId'),
|
||||
license_tenant_id: this.globalConfig.license.tenantId,
|
||||
binary_data_s3: isS3Available && isS3Selected && isS3Licensed,
|
||||
multi_main_setup_enabled: this.globalConfig.multiMainSetup.enabled,
|
||||
metrics: {
|
||||
|
|
|
@ -48,8 +48,7 @@ export class License {
|
|||
*/
|
||||
private renewalEnabled() {
|
||||
if (this.instanceSettings.instanceType !== 'main') return false;
|
||||
|
||||
const autoRenewEnabled = config.getEnv('license.autoRenewEnabled');
|
||||
const autoRenewEnabled = this.globalConfig.license.autoRenewalEnabled;
|
||||
|
||||
/**
|
||||
* In multi-main setup, all mains start off with `unset` status and so renewal disabled.
|
||||
|
@ -75,9 +74,9 @@ export class License {
|
|||
|
||||
const { instanceType } = this.instanceSettings;
|
||||
const isMainInstance = instanceType === 'main';
|
||||
const server = config.getEnv('license.serverUrl');
|
||||
const server = this.globalConfig.license.serverUrl;
|
||||
const offlineMode = !isMainInstance;
|
||||
const autoRenewOffset = config.getEnv('license.autoRenewOffset');
|
||||
const autoRenewOffset = this.globalConfig.license.autoRenewOffset;
|
||||
const saveCertStr = isMainInstance
|
||||
? async (value: TLicenseBlock) => await this.saveCertStr(value)
|
||||
: async () => {};
|
||||
|
@ -96,7 +95,7 @@ export class License {
|
|||
try {
|
||||
this.manager = new LicenseManager({
|
||||
server,
|
||||
tenantId: config.getEnv('license.tenantId'),
|
||||
tenantId: this.globalConfig.license.tenantId,
|
||||
productIdentifier: `n8n-${N8N_VERSION}`,
|
||||
autoRenewEnabled: renewalEnabled,
|
||||
renewOnInit: renewalEnabled,
|
||||
|
@ -122,7 +121,7 @@ export class License {
|
|||
|
||||
async loadCertStr(): Promise<TLicenseBlock> {
|
||||
// if we have an ephemeral license, we don't want to load it from the database
|
||||
const ephemeralLicense = config.get('license.cert');
|
||||
const ephemeralLicense = this.globalConfig.license.cert;
|
||||
if (ephemeralLicense) {
|
||||
return ephemeralLicense;
|
||||
}
|
||||
|
@ -179,7 +178,7 @@ export class License {
|
|||
|
||||
async saveCertStr(value: TLicenseBlock): Promise<void> {
|
||||
// if we have an ephemeral license, we don't want to save it to the database
|
||||
if (config.get('license.cert')) return;
|
||||
if (this.globalConfig.license.cert) return;
|
||||
await this.settingsRepository.upsert(
|
||||
{
|
||||
key: SETTINGS_LICENSE_CERT_KEY,
|
||||
|
|
|
@ -94,6 +94,7 @@ describe('LicenseService', () => {
|
|||
.spyOn(axios, 'post')
|
||||
.mockResolvedValueOnce({ data: { title: 'Title', text: 'Text', licenseKey: 'abc-123' } });
|
||||
const data = await licenseService.registerCommunityEdition({
|
||||
userId: '123',
|
||||
email: 'test@ema.il',
|
||||
instanceId: '123',
|
||||
instanceUrl: 'http://localhost',
|
||||
|
@ -102,6 +103,7 @@ describe('LicenseService', () => {
|
|||
|
||||
expect(data).toEqual({ title: 'Title', text: 'Text' });
|
||||
expect(eventService.emit).toHaveBeenCalledWith('license-community-plus-registered', {
|
||||
userId: '123',
|
||||
email: 'test@ema.il',
|
||||
licenseKey: 'abc-123',
|
||||
});
|
||||
|
@ -111,6 +113,7 @@ describe('LicenseService', () => {
|
|||
jest.spyOn(axios, 'post').mockRejectedValueOnce(new AxiosError('Failed'));
|
||||
await expect(
|
||||
licenseService.registerCommunityEdition({
|
||||
userId: '123',
|
||||
email: 'test@ema.il',
|
||||
instanceId: '123',
|
||||
instanceUrl: 'http://localhost',
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue