Merge branch 'ADO-3305' into ADO-3304

This commit is contained in:
Charlie Kolb 2025-03-24 10:02:31 +01:00
commit 8531f9cbe5
No known key found for this signature in database
1599 changed files with 51463 additions and 65710 deletions

View File

@ -161,7 +161,6 @@ jobs:
env:
NODE_OPTIONS: --dns-result-order=ipv4first
CYPRESS_NODE_VIEW_VERSION: 2
N8N_FOLDERS_ENABLED: true
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
E2E_TESTS: true

View File

@ -22,6 +22,31 @@ export function getFolderCards() {
export function getFolderCard(name: string) {
return cy.getByTestId('folder-card-name').contains(name).closest('[data-test-id="folder-card"]');
}
export function getWorkflowCards() {
return cy.getByTestId('resources-list-item-workflow');
}
export function getWorkflowCard(name: string) {
return cy
.getByTestId('workflow-card-name')
.contains(name)
.closest('[data-test-id="resources-list-item-workflow"]');
}
export function getWorkflowCardActions(name: string) {
return getWorkflowCard(name).find('[data-test-id="workflow-card-actions"]');
}
export function getWorkflowCardActionItem(workflowName: string, actionName: string) {
return getWorkflowCardActions(workflowName)
.find('span[aria-controls]')
.invoke('attr', 'aria-controls')
.then((popperId) => {
return cy.get(`#${popperId}`).find(`[data-test-id="action-${actionName}"]`);
});
}
export function getAddFolderButton() {
return cy.getByTestId('add-folder-button');
}
@ -34,6 +59,10 @@ export function getHomeProjectBreadcrumb() {
return getListBreadcrumbs().findChildByTestId('home-project');
}
export function getListBreadcrumbItem(name: string) {
return getListBreadcrumbs().findChildByTestId('breadcrumbs-item').contains(name);
}
export function getVisibleListBreadcrumbs() {
return getListBreadcrumbs().findChildByTestId('breadcrumbs-item');
}
@ -94,13 +123,14 @@ export function getFolderCardActionToggle(folderName: string) {
return getFolderCard(folderName).find('[data-test-id="folder-card-actions"]');
}
export function getFolderCardActionItem(name: string) {
return cy
.getByTestId('folder-card-actions')
export function getFolderCardActionItem(folderName: string, actionName: string) {
return getFolderCard(folderName)
.findChildByTestId('folder-card-actions')
.filter(':visible')
.find('span[aria-controls]')
.invoke('attr', 'aria-controls')
.then((popperId) => {
return cy.get(`#${popperId}`).find(`[data-test-id="action-${name}"]`);
return cy.get(`#${popperId}`).find(`[data-test-id="action-${actionName}"]`);
});
}
@ -108,10 +138,18 @@ export function getFolderDeleteModal() {
return cy.getByTestId('deleteFolder-modal');
}
export function getMoveFolderModal() {
return cy.getByTestId('moveFolder-modal');
}
export function getDeleteRadioButton() {
return cy.getByTestId('delete-content-radio');
}
export function getTransferContentRadioButton() {
return cy.getByTestId('transfer-content-radio');
}
export function getConfirmDeleteInput() {
return getFolderDeleteModal().findChildByTestId('delete-data-input').find('input');
}
@ -119,6 +157,61 @@ export function getConfirmDeleteInput() {
export function getDeleteFolderModalConfirmButton() {
return getFolderDeleteModal().findChildByTestId('confirm-delete-folder-button');
}
export function getProjectEmptyState() {
return cy.getByTestId('list-empty-state');
}
export function getFolderEmptyState() {
return cy.getByTestId('empty-folder-container');
}
export function getProjectMenuItem(name: string) {
if (name.toLowerCase() === 'personal') {
return getPersonalProjectMenuItem();
}
return cy.getByTestId('project-menu-item').contains(name);
}
export function getMoveToFolderDropdown() {
return cy.getByTestId('move-to-folder-dropdown');
}
export function getMoveToFolderOption(name: string) {
return cy.getByTestId('move-to-folder-option').contains(name);
}
export function getMoveToFolderInput() {
return getMoveToFolderDropdown().find('input');
}
export function getEmptyFolderDropdownMessage(text: string) {
return cy.get('.el-select-dropdown__empty').contains(text);
}
export function getMoveFolderConfirmButton() {
return cy.getByTestId('confirm-move-folder-button');
}
export function getMoveWorkflowModal() {
return cy.getByTestId('moveFolder-modal');
}
export function getWorkflowCardBreadcrumbs(workflowName: string) {
return getWorkflowCard(workflowName).find('[data-test-id="workflow-card-breadcrumbs"]');
}
export function getWorkflowCardBreadcrumbsEllipsis(workflowName: string) {
return getWorkflowCardBreadcrumbs(workflowName).find('[data-test-id="ellipsis"]');
}
export function getNewFolderNameInput() {
return cy.get('.add-folder-modal').filter(':visible').find('input.el-input__inner');
}
export function getNewFolderModalErrorMessage() {
return cy.get('.el-message-box__errormsg').filter(':visible');
}
/**
* Actions
*/
@ -136,8 +229,46 @@ export function createFolderFromListHeaderButton(folderName: string) {
createNewFolder(folderName);
}
export function createWorkflowFromEmptyState(workflowName?: string) {
getFolderEmptyState().find('button').contains('Create Workflow').click();
if (workflowName) {
cy.getByTestId('workflow-name-input').type(`{selectAll}{backspace}${workflowName}`, {
delay: 50,
});
}
cy.getByTestId('workflow-save-button').click();
successToast().should('exist');
}
export function createWorkflowFromProjectHeader(folderName?: string, workflowName?: string) {
cy.getByTestId('add-resource-workflow').click();
if (workflowName) {
cy.getByTestId('workflow-name-input').type(`{selectAll}{backspace}${workflowName}`, {
delay: 50,
});
}
cy.getByTestId('workflow-save-button').click();
if (folderName) {
successToast().should(
'contain.text',
`Workflow successfully created in "Personal", within "${folderName}"`,
);
}
}
export function createWorkflowFromListDropdown(workflowName?: string) {
getListActionsToggle().click();
getListActionItem('create_workflow').click();
if (workflowName) {
cy.getByTestId('workflow-name-input').type(`{selectAll}{backspace}${workflowName}`, {
delay: 50,
});
}
cy.getByTestId('workflow-save-button').click();
successToast().should('exist');
}
export function createFolderFromProjectHeader(folderName: string) {
getPersonalProjectMenuItem().click();
getAddResourceDropdown().click();
cy.getByTestId('action-folder').click();
createNewFolder(folderName);
@ -151,7 +282,7 @@ export function createFolderFromListDropdown(folderName: string) {
export function createFolderFromCardActions(parentName: string, folderName: string) {
getFolderCardActionToggle(parentName).click();
getFolderCardActionItem('create').click();
getFolderCardActionItem(parentName, 'create').click();
createNewFolder(folderName);
}
@ -164,7 +295,7 @@ export function renameFolderFromListActions(folderName: string, newName: string)
export function renameFolderFromCardActions(folderName: string, newName: string) {
getFolderCardActionToggle(folderName).click();
getFolderCardActionItem('rename').click();
getFolderCardActionItem(folderName, 'rename').click();
renameFolder(newName);
}
@ -194,9 +325,63 @@ export function deleteFolderWithContentsFromListDropdown(folderName: string) {
export function deleteFolderWithContentsFromCardDropdown(folderName: string) {
getFolderCardActionToggle(folderName).click();
getFolderCardActionItem('delete').click();
getFolderCardActionItem(folderName, 'delete').click();
confirmFolderDelete(folderName);
}
export function deleteAndTransferFolderContentsFromCardDropdown(
folderName: string,
destinationName: string,
) {
getFolderCardActionToggle(folderName).click();
getFolderCardActionItem(folderName, 'delete').click();
deleteFolderAndMoveContents(folderName, destinationName);
}
export function deleteAndTransferFolderContentsFromListDropdown(destinationName: string) {
getListActionsToggle().click();
getListActionItem('delete').click();
getCurrentBreadcrumb()
.find('span')
.invoke('text')
.then((currentFolderName) => {
deleteFolderAndMoveContents(currentFolderName, destinationName);
});
}
export function createNewProject(projectName: string, options: { openAfterCreate?: boolean } = {}) {
cy.getByTestId('universal-add').should('exist').click();
cy.getByTestId('navigation-menu-item').contains('Project').click();
cy.getByTestId('project-settings-name-input').type(projectName, { delay: 50 });
cy.getByTestId('project-settings-save-button').click();
successToast().should('exist');
if (options.openAfterCreate) {
getProjectMenuItem(projectName).click();
}
}
export function moveFolderFromFolderCardActions(folderName: string, destinationName: string) {
getFolderCardActionToggle(folderName).click();
getFolderCardActionItem(folderName, 'move').click();
moveFolder(folderName, destinationName);
}
export function moveFolderFromListActions(folderName: string, destinationName: string) {
getFolderCard(folderName).click();
getListActionsToggle().click();
getListActionItem('move').click();
moveFolder(folderName, destinationName);
}
export function moveWorkflowToFolder(workflowName: string, folderName: string) {
getWorkflowCardActions(workflowName).click();
getWorkflowCardActionItem(workflowName, 'moveToFolder').click();
getMoveFolderModal().should('be.visible');
getMoveToFolderDropdown().click();
getMoveToFolderInput().type(folderName, { delay: 50 });
getMoveToFolderOption(folderName).should('be.visible').click();
getMoveFolderConfirmButton().should('be.enabled').click();
}
/**
* Utils
*/
@ -240,3 +425,34 @@ function confirmFolderDelete(folderName: string) {
cy.wait('@deleteFolder');
successToast().contains('Folder deleted').should('exist');
}
function deleteFolderAndMoveContents(folderName: string, destinationName: string) {
cy.intercept('DELETE', '/rest/projects/**').as('deleteFolder');
getFolderDeleteModal().should('be.visible');
getFolderDeleteModal().find('h1').first().contains(`Delete "${folderName}"`);
getTransferContentRadioButton().should('be.visible').click();
getMoveToFolderDropdown().click();
getMoveToFolderInput().type(destinationName);
getMoveToFolderOption(destinationName).click();
getDeleteFolderModalConfirmButton().should('be.enabled').click();
cy.wait('@deleteFolder');
successToast().should('contain.text', `Data transferred to "${destinationName}"`);
}
function moveFolder(folderName: string, destinationName: string) {
cy.intercept('PATCH', '/rest/projects/**').as('moveFolder');
getMoveFolderModal().should('be.visible');
getMoveFolderModal().find('h1').first().contains(`Move "${folderName}" to another folder`);
getMoveToFolderDropdown().click();
// Try to find current folder in the dropdown
getMoveToFolderInput().type(folderName, { delay: 50 });
// Should not be available
getEmptyFolderDropdownMessage('No folders found').should('exist');
// Select destination folder
getMoveToFolderInput().type(`{selectall}{backspace}${destinationName}`, {
delay: 50,
});
getMoveToFolderOption(destinationName).should('be.visible').click();
getMoveFolderConfirmButton().should('be.enabled').click();
cy.wait('@moveFolder');
}

View File

@ -105,11 +105,13 @@ export function getNodeOutputHint() {
}
export function getWorkflowCards() {
return cy.getByTestId('resources-list-item');
return cy.getByTestId('resources-list-item-workflow');
}
export function getWorkflowCard(workflowName: string) {
return getWorkflowCards().contains(workflowName).parents('[data-test-id="resources-list-item"]');
return getWorkflowCards()
.contains(workflowName)
.parents('[data-test-id="resources-list-item-workflow"]');
}
export function getWorkflowCardContent(workflowName: string) {

View File

@ -21,7 +21,7 @@ const switchBetweenEditorAndWorkflowlist = () => {
cy.getByTestId('menu-item').first().click();
cy.wait(['@getUsers', '@getWorkflows', '@getActiveWorkflows', '@getProjects']);
cy.getByTestId('resources-list-item').first().click();
cy.getByTestId('resources-list-item-workflow').first().click();
workflowPage.getters.canvasNodes().first().should('be.visible');
workflowPage.getters.canvasNodes().last().should('be.visible');

View File

@ -514,7 +514,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
workflowsPage.getters.workflowCards().should('have.length', 3);
workflowsPage.getters
.workflowCards()
.filter(':has(.n8n-badge:contains("Project"))')
.filter(':has([data-test-id="workflow-card-breadcrumbs"]:contains("Project"))')
.should('have.length', 2);
workflowsPage.getters.workflowCardActions('Workflow in Home project').click();
workflowsPage.getters.workflowMoveButton().click();

View File

@ -4,6 +4,12 @@ import {
createFolderFromListHeaderButton,
createFolderFromProjectHeader,
createFolderInsideFolder,
createNewProject,
createWorkflowFromEmptyState,
createWorkflowFromListDropdown,
createWorkflowFromProjectHeader,
deleteAndTransferFolderContentsFromCardDropdown,
deleteAndTransferFolderContentsFromListDropdown,
deleteEmptyFolderFromCardDropdown,
deleteEmptyFolderFromListDropdown,
deleteFolderWithContentsFromCardDropdown,
@ -14,14 +20,27 @@ import {
getFolderCardActionItem,
getFolderCardActionToggle,
getFolderCards,
getFolderEmptyState,
getHomeProjectBreadcrumb,
getListBreadcrumbItem,
getListBreadcrumbs,
getMainBreadcrumbsEllipsis,
getMainBreadcrumbsEllipsisMenuItems,
getNewFolderModalErrorMessage,
getNewFolderNameInput,
getOverviewMenuItem,
getPersonalProjectMenuItem,
getProjectEmptyState,
getProjectMenuItem,
getVisibleListBreadcrumbs,
getWorkflowCard,
getWorkflowCardBreadcrumbs,
getWorkflowCardBreadcrumbsEllipsis,
getWorkflowCards,
goToPersonalProject,
moveFolderFromFolderCardActions,
moveFolderFromListActions,
moveWorkflowToFolder,
renameFolderFromCardActions,
renameFolderFromListActions,
} from '../composables/folders';
@ -32,6 +51,7 @@ describe('Folders', () => {
before(() => {
cy.resetDatabase();
cy.enableFeature('sharing');
cy.enableFeature('folders');
cy.enableFeature('advancedPermissions');
cy.enableFeature('projectRole:admin');
cy.enableFeature('projectRole:editor');
@ -44,6 +64,7 @@ describe('Folders', () => {
describe('Create and navigate folders', () => {
it('should create folder from the project header', () => {
getPersonalProjectMenuItem().click();
createFolderFromProjectHeader('My Folder');
getFolderCards().should('have.length.greaterThan', 0);
// Clicking on the success toast should navigate to the folder
@ -51,6 +72,33 @@ describe('Folders', () => {
getCurrentBreadcrumb().should('contain.text', 'My Folder');
});
it('should not allow illegal folder names', () => {
// Validation logic is thoroughly tested in unit tests
// Here we just make sure everything is working in the full UI
const ILLEGAL_CHARACTERS_NAME = 'hello[';
const ONLY_DOTS_NAME = '...';
const REGULAR_NAME = 'My Folder';
getPersonalProjectMenuItem().click();
getAddResourceDropdown().click();
cy.getByTestId('action-folder').click();
getNewFolderNameInput().type(ILLEGAL_CHARACTERS_NAME, { delay: 50 });
getNewFolderModalErrorMessage().should(
'contain.text',
'Folder name cannot contain the following characters',
);
getNewFolderNameInput().clear();
getNewFolderNameInput().type(ONLY_DOTS_NAME, { delay: 50 });
getNewFolderModalErrorMessage().should(
'contain.text',
'Folder name cannot contain only dots',
);
getNewFolderNameInput().clear();
getNewFolderModalErrorMessage().should('contain.text', 'Folder name cannot be empty');
getNewFolderNameInput().type(REGULAR_NAME, { delay: 50 });
getNewFolderModalErrorMessage().should('not.exist');
});
it('should create folder from the list header button', () => {
goToPersonalProject();
// First create a folder so list appears
@ -78,9 +126,9 @@ describe('Folders', () => {
getFolderCard('Created from card dropdown').should('exist');
createFolderFromCardActions('Created from card dropdown', 'Child Folder');
successToast().should('exist');
// Open parent folder to see the new child folder
getFolderCard('Created from card dropdown').click();
// Should be automatically navigated to the new folder
getFolderCard('Child Folder').should('exist');
getCurrentBreadcrumb().should('contain.text', 'Created from card dropdown');
});
it('should navigate folders using breadcrumbs and dropdown menu', () => {
@ -88,7 +136,7 @@ describe('Folders', () => {
createFolderFromProjectHeader('Navigate Test');
// Open folder using menu item
getFolderCardActionToggle('Navigate Test').click();
getFolderCardActionItem('open').click();
getFolderCardActionItem('Navigate Test', 'open').click();
getCurrentBreadcrumb().should('contain.text', 'Navigate Test');
// Create new child folder and navigate to it
createFolderFromListHeaderButton('Child Folder');
@ -165,12 +213,72 @@ describe('Folders', () => {
// In personal, we should see previously created folders
getPersonalProjectMenuItem().click();
getAddResourceDropdown().click();
cy.getByTestId('action-folder').should('exist');
createFolderFromProjectHeader('Personal Folder');
getFolderCards().should('exist');
});
});
describe('Empty State', () => {
it('should show project empty state when no folders exist', () => {
createNewProject('Test empty project', { openAfterCreate: true });
getProjectEmptyState().should('exist');
});
it('should toggle folder empty state correctly', () => {
createNewProject('Test empty folder', { openAfterCreate: true });
createFolderFromProjectHeader('My Folder');
getProjectEmptyState().should('not.exist');
getFolderCard('My Folder').should('exist');
getFolderCard('My Folder').click();
getFolderEmptyState().should('exist');
// Create a new workflow from the empty state
createWorkflowFromEmptyState('My Workflow');
// Toast should inform that the workflow was created in the folder
successToast().should(
'contain.text',
'Workflow successfully created in "Test empty folder", within "My Folder"',
);
// Go back to the folder
getProjectMenuItem('Test empty folder').click();
getFolderCard('My Folder').should('exist');
getFolderCard('My Folder').click();
// Should not show empty state anymore
getFolderEmptyState().should('not.exist');
getWorkflowCards().should('have.length.greaterThan', 0);
// Also when filtering and there are no results, empty state CTA should not show
cy.getByTestId('resources-list-search').type('non-existing', { delay: 20 });
getWorkflowCards().should('not.exist');
getFolderEmptyState().should('not.exist');
// But there should be a message saying that no results were found
cy.getByTestId('resources-list-empty').should('exist');
});
});
describe('Create workflows inside folders', () => {
it('should create workflows in folders in all supported ways', () => {
goToPersonalProject();
createFolderFromProjectHeader('Workflows go here');
// 1. From empty state
getFolderCard('Workflows go here').should('exist').click();
createWorkflowFromEmptyState('Created from empty state');
goToPersonalProject();
getFolderCard('Workflows go here').click();
getWorkflowCard('Created from empty state').should('exist');
// 2. From the project header
createWorkflowFromProjectHeader('Workflows go here', 'Created from project header');
goToPersonalProject();
getFolderCard('Workflows go here').click();
getWorkflowCard('Created from project header').should('exist');
// 3. From list breadcrumbs
createWorkflowFromListDropdown('Created from list breadcrumbs');
goToPersonalProject();
getFolderCard('Workflows go here').click();
getWorkflowCard('Created from list breadcrumbs').should('exist');
});
});
describe('Rename and delete folders', () => {
it('should rename folder from main dropdown', () => {
goToPersonalProject();
@ -224,6 +332,175 @@ describe('Folders', () => {
deleteFolderWithContentsFromCardDropdown('I also have family');
});
// TODO: Once we have backend endpoint that lists project folders, test transfer when deleting
it('should transfer contents when deleting non-empty folder - from card dropdown', () => {
goToPersonalProject();
createFolderFromProjectHeader('Move my contents');
createFolderFromProjectHeader('Destination');
createFolderInsideFolder('Child 1', 'Move my contents');
getHomeProjectBreadcrumb().click();
getFolderCard('Move my contents').should('exist');
deleteAndTransferFolderContentsFromCardDropdown('Move my contents', 'Destination');
getFolderCard('Destination').click();
// Should show the contents of the moved folder
getFolderCard('Child 1').should('exist');
});
it('should transfer contents when deleting non-empty folder - from list breadcrumbs', () => {
goToPersonalProject();
createFolderFromProjectHeader('Move me too');
createFolderFromProjectHeader('Destination 2');
createFolderInsideFolder('Child 1', 'Move me too');
deleteAndTransferFolderContentsFromListDropdown('Destination 2');
getFolderCard('Destination').click();
// Should show the contents of the moved folder
getFolderCard('Child 1').should('exist');
});
});
describe('Move folders and workflows', () => {
it('should move empty folder to another folder - from folder card action', () => {
goToPersonalProject();
createFolderFromProjectHeader('Move me - I am empty');
createFolderFromProjectHeader('Destination 3');
moveFolderFromFolderCardActions('Move me - I am empty', 'Destination 3');
getFolderCard('Destination 3').click();
getFolderCard('Move me - I am empty').should('exist');
getFolderCard('Move me - I am empty').click();
getFolderEmptyState().should('exist');
successToast().should('contain.text', 'Move me - I am empty has been moved to Destination 3');
// Breadcrumbs should show the destination folder
getListBreadcrumbItem('Destination 3').should('exist');
});
it('should move folder with contents to another folder - from folder card action', () => {
goToPersonalProject();
createFolderFromProjectHeader('Move me - I have family');
createFolderFromProjectHeader('Destination 4');
// Create a workflow and a folder inside the folder
createFolderInsideFolder('Child 1', 'Move me - I have family');
createWorkflowFromProjectHeader('Move me - I have family');
goToPersonalProject();
// Move the folder
moveFolderFromFolderCardActions('Move me - I have family', 'Destination 4');
successToast().should(
'contain.text',
'Move me - I have family has been moved to Destination 4',
);
// Go to destination folder and check if contents are there
getFolderCard('Destination 4').click();
// Moved folder should be there
getFolderCard('Move me - I have family').should('exist').click();
// Both the workflow and the folder should be there
getFolderCards().should('have.length', 1);
getWorkflowCards().should('have.length', 1);
// Breadcrumbs should show the destination folder
getListBreadcrumbItem('Destination 4').should('exist');
});
it('should move empty folder to another folder - from list breadcrumbs', () => {
goToPersonalProject();
createFolderFromProjectHeader('Move me too - I am empty');
createFolderFromProjectHeader('Destination 5');
moveFolderFromListActions('Move me too - I am empty', 'Destination 5');
// Since we moved the current folder, we should be in the destination folder
getCurrentBreadcrumb().should('contain.text', 'Destination 5');
});
it('should move folder with contents to another folder - from list dropdown', () => {
goToPersonalProject();
createFolderFromProjectHeader('Move me - I have family 2');
createFolderFromProjectHeader('Destination 6');
// Create a workflow and a folder inside the folder
createFolderInsideFolder('Child 1', 'Move me - I have family 2');
createWorkflowFromProjectHeader('Move me - I have family 2');
// Navigate back to folder
goToPersonalProject();
getFolderCard('Move me - I have family 2').should('exist');
// Move the folder
moveFolderFromListActions('Move me - I have family 2', 'Destination 6');
// Since we moved the current folder, we should be in the destination folder
getCurrentBreadcrumb().should('contain.text', 'Destination 6');
// Moved folder should be there
getFolderCard('Move me - I have family 2').should('exist').click();
// After navigating to the moved folder, both the workflow and the folder should be there
getFolderCards().should('have.length', 1);
getWorkflowCards().should('have.length', 1);
// Breadcrumbs should show the destination folder
getListBreadcrumbItem('Destination 6').should('exist');
});
it('should move folder to project root - from folder card action', () => {
goToPersonalProject();
createFolderFromProjectHeader('Test parent');
createFolderInsideFolder('Move me to root', 'Test parent');
moveFolderFromFolderCardActions('Move me to root', 'Personal');
// Parent folder should be empty
getFolderEmptyState().should('exist');
// Child folder should be in the root
goToPersonalProject();
getFolderCard('Move me to root').should('exist');
// Navigate to the moved folder and check breadcrumbs
getFolderCard('Move me to root').click();
getHomeProjectBreadcrumb().should('contain.text', 'Personal');
getListBreadcrumbs().findChildByTestId('breadcrumbs-item').should('not.exist');
getCurrentBreadcrumb().should('contain.text', 'Move me to root');
});
it('should move workflow from project root to folder', () => {
goToPersonalProject();
createWorkflowFromProjectHeader(undefined, 'Move me');
goToPersonalProject();
createFolderFromProjectHeader('Workflow destination');
moveWorkflowToFolder('Move me', 'Workflow destination');
successToast().should('contain.text', 'Move me has been moved to Workflow destination');
// Navigate to the destination folder
getFolderCard('Workflow destination').click();
// Moved workflow should be there
getWorkflowCards().should('have.length', 1);
getWorkflowCard('Move me').should('exist');
});
it('should move workflow to another folder', () => {
goToPersonalProject();
createFolderFromProjectHeader('Moving workflow from here');
createFolderFromProjectHeader('Moving workflow to here');
getFolderCard('Moving workflow from here').click();
createWorkflowFromProjectHeader(undefined, 'Move me');
goToPersonalProject();
getFolderCard('Moving workflow from here').click();
getWorkflowCard('Move me').should('exist');
moveWorkflowToFolder('Move me', 'Moving workflow to here');
// Now folder should be empty
getFolderEmptyState().should('exist');
// Navigate to the destination folder
getHomeProjectBreadcrumb().click();
getFolderCard('Moving workflow to here').click();
// Moved workflow should be there
getWorkflowCards().should('have.length', 1);
getWorkflowCard('Move me').should('exist');
});
});
describe('Workflow card breadcrumbs', () => {
it('should correctly show workflow card breadcrumbs', () => {
createNewProject('Test workflow breadcrumbs', { openAfterCreate: true });
createFolderFromProjectHeader('Parent Folder');
createFolderInsideFolder('Child Folder', 'Parent Folder');
getFolderCard('Child Folder').click();
createFolderFromListHeaderButton('Child Folder 2');
getFolderCard('Child Folder 2').click();
createWorkflowFromEmptyState('Breadcrumbs Test');
// Go to overview page
getOverviewMenuItem().click();
getWorkflowCard('Breadcrumbs Test').should('exist');
getWorkflowCardBreadcrumbs('Breadcrumbs Test').should('exist');
getWorkflowCardBreadcrumbsEllipsis('Breadcrumbs Test').should('exist');
getWorkflowCardBreadcrumbsEllipsis('Breadcrumbs Test').realHover({ position: 'topLeft' });
cy.get('[role=tooltip]').should('exist');
cy.get('[role=tooltip]').should(
'contain.text',
'est workflow breadcrumbs / Parent Folder / Child Folder / Child Folder 2',
);
});
});
});

View File

@ -19,12 +19,12 @@ export class WorkflowsPage extends BasePage {
cy.getByTestId('add-resource-workflow').should('be.visible');
return cy.getByTestId('add-resource-workflow');
},
workflowCards: () => cy.getByTestId('resources-list-item'),
workflowCards: () => cy.getByTestId('resources-list-item-workflow'),
workflowCard: (workflowName: string) =>
this.getters
.workflowCards()
.contains(workflowName)
.parents('[data-test-id="resources-list-item"]'),
.parents('[data-test-id="resources-list-item-workflow"]'),
workflowTags: (workflowName: string) =>
this.getters.workflowCard(workflowName).findChildByTestId('workflow-card-tags'),
workflowCardContent: (workflowName: string) =>

View File

@ -47,7 +47,6 @@ switch (scenario) {
testCommand: 'cypress open',
customEnv: {
CYPRESS_NODE_VIEW_VERSION: 2,
N8N_FOLDERS_ENABLED: true,
},
});
break;
@ -59,7 +58,6 @@ switch (scenario) {
customEnv: {
CYPRESS_NODE_VIEW_VERSION: 1,
CYPRESS_BASE_URL: 'http://localhost:8080',
N8N_FOLDERS_ENABLED: true,
},
});
break;
@ -71,7 +69,6 @@ switch (scenario) {
customEnv: {
CYPRESS_NODE_VIEW_VERSION: 2,
CYPRESS_BASE_URL: 'http://localhost:8080',
N8N_FOLDERS_ENABLED: true,
},
});
break;
@ -85,7 +82,6 @@ switch (scenario) {
testCommand: `cypress run --headless ${specParam}`,
customEnv: {
CYPRESS_NODE_VIEW_VERSION: 2,
N8N_FOLDERS_ENABLED: true,
},
});
break;

View File

@ -1,6 +1,6 @@
import { z } from 'zod';
const FileTypeSchema = z.enum(['credential', 'workflow', 'tags', 'variables', 'file']);
const FileTypeSchema = z.enum(['credential', 'workflow', 'tags', 'variables', 'file', 'folders']);
export const SOURCE_CONTROL_FILE_TYPE = FileTypeSchema.Values;
const FileStatusSchema = z.enum([

View File

@ -1,14 +1,6 @@
import { ColonSeparatedStringArray } from '../custom-types';
import { Config, Env } from '../decorators';
class ColonSeparatedStringArray<T extends string = string> extends Array<T> {
constructor(str: string) {
super();
const parsed = str.split(':') as this;
const filtered = parsed.filter((i) => typeof i === 'string' && i.length);
return filtered.length ? filtered : [];
}
}
@Config
export class ExternalHooksConfig {
/** Files containing external hooks. Multiple files can be separated by colon (":") */

View File

@ -1,5 +1,5 @@
import { CommaSeperatedStringArray } from '../custom-types';
import { Config, Env, Nested } from '../decorators';
import { StringArray } from '../utils';
/** Scopes (areas of functionality) to filter logs by. */
export const LOG_SCOPES = [
@ -14,6 +14,7 @@ export const LOG_SCOPES = [
'scaling',
'waiting-executions',
'task-runner',
'insights',
] as const;
export type LogScope = (typeof LOG_SCOPES)[number];
@ -57,7 +58,7 @@ export class LoggingConfig {
* @example `N8N_LOG_OUTPUT=console,file` will output to both console and file.
*/
@Env('N8N_LOG_OUTPUT')
outputs: StringArray<'console' | 'file'> = ['console'];
outputs: CommaSeperatedStringArray<'console' | 'file'> = ['console'];
@Nested
file: FileLoggingConfig;
@ -84,5 +85,5 @@ export class LoggingConfig {
* `N8N_LOG_SCOPES=license,waiting-executions`
*/
@Env('N8N_LOG_SCOPES')
scopes: StringArray<LogScope> = [];
scopes: CommaSeperatedStringArray<LogScope> = [];
}

View File

@ -0,0 +1,19 @@
abstract class StringArray<T extends string> extends Array<T> {
constructor(str: string, delimiter: string) {
super();
const parsed = str.split(delimiter) as this;
return parsed.filter((i) => typeof i === 'string' && i.length);
}
}
export class CommaSeperatedStringArray<T extends string> extends StringArray<T> {
constructor(str: string) {
super(str, ',');
}
}
export class ColonSeparatedStringArray<T extends string = string> extends StringArray<T> {
constructor(str: string) {
super(str, ':');
}
}

View File

@ -36,6 +36,7 @@ export { S3Config } from './configs/external-storage.config';
export { LOG_SCOPES } from './configs/logging.config';
export type { LogScope } from './configs/logging.config';
export { WorkflowsConfig } from './configs/workflows.config';
export * from './custom-types';
@Config
export class GlobalConfig {

View File

@ -1,7 +0,0 @@
export class StringArray<T extends string> extends Array<T> {
constructor(str: string) {
super();
const parsed = str.split(',') as StringArray<T>;
return parsed.every((i) => typeof i === 'string') ? parsed : [];
}
}

View File

@ -0,0 +1,25 @@
import { CommaSeperatedStringArray, ColonSeparatedStringArray } from '../src/custom-types';
describe('CommaSeperatedStringArray', () => {
it('should parse comma-separated string into array', () => {
const result = new CommaSeperatedStringArray('a,b,c');
expect(result).toEqual(['a', 'b', 'c']);
});
it('should handle empty strings', () => {
const result = new CommaSeperatedStringArray('a,b,,,');
expect(result).toEqual(['a', 'b']);
});
});
describe('ColonSeparatedStringArray', () => {
it('should parse colon-separated string into array', () => {
const result = new ColonSeparatedStringArray('a:b:c');
expect(result).toEqual(['a', 'b', 'c']);
});
it('should handle empty strings', () => {
const result = new ColonSeparatedStringArray('a::b:::');
expect(result).toEqual(['a', 'b']);
});
});

View File

@ -1,4 +1,4 @@
import { NodeConnectionType, NodeOperationError } from 'n8n-workflow';
import { NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';
import type {
INodeInputConfiguration,
INodeInputFilter,
@ -7,6 +7,7 @@ import type {
INodeType,
INodeTypeDescription,
INodeProperties,
NodeConnectionType,
} from 'n8n-workflow';
import { promptTypeOptions, textFromPreviousNode, textInput } from '@utils/descriptions';
@ -46,14 +47,14 @@ function getInputs(
inputs: SpecialInput[],
): Array<NodeConnectionType | INodeInputConfiguration> => {
const displayNames: { [key: string]: string } = {
[NodeConnectionType.AiLanguageModel]: 'Model',
[NodeConnectionType.AiMemory]: 'Memory',
[NodeConnectionType.AiTool]: 'Tool',
[NodeConnectionType.AiOutputParser]: 'Output Parser',
ai_languageModel: 'Model',
ai_memory: 'Memory',
ai_tool: 'Tool',
ai_outputParser: 'Output Parser',
};
return inputs.map(({ type, filter }) => {
const isModelType = type === NodeConnectionType.AiLanguageModel;
const isModelType = type === ('ai_languageModel' as NodeConnectionType);
let displayName = type in displayNames ? displayNames[type] : undefined;
if (
isModelType &&
@ -65,11 +66,9 @@ function getInputs(
type,
displayName,
required: isModelType,
maxConnections: [
NodeConnectionType.AiLanguageModel,
NodeConnectionType.AiMemory,
NodeConnectionType.AiOutputParser,
].includes(type as NodeConnectionType)
maxConnections: ['ai_languageModel', 'ai_memory', 'ai_outputParser'].includes(
type as NodeConnectionType,
)
? 1
: undefined,
};
@ -87,7 +86,7 @@ function getInputs(
if (agent === 'conversationalAgent') {
specialInputs = [
{
type: NodeConnectionType.AiLanguageModel,
type: 'ai_languageModel',
filter: {
nodes: [
'@n8n/n8n-nodes-langchain.lmChatAnthropic',
@ -105,19 +104,19 @@ function getInputs(
},
},
{
type: NodeConnectionType.AiMemory,
type: 'ai_memory',
},
{
type: NodeConnectionType.AiTool,
type: 'ai_tool',
},
{
type: NodeConnectionType.AiOutputParser,
type: 'ai_outputParser',
},
];
} else if (agent === 'toolsAgent') {
specialInputs = [
{
type: NodeConnectionType.AiLanguageModel,
type: 'ai_languageModel',
filter: {
nodes: [
'@n8n/n8n-nodes-langchain.lmChatAnthropic',
@ -135,20 +134,20 @@ function getInputs(
},
},
{
type: NodeConnectionType.AiMemory,
type: 'ai_memory',
},
{
type: NodeConnectionType.AiTool,
type: 'ai_tool',
required: true,
},
{
type: NodeConnectionType.AiOutputParser,
type: 'ai_outputParser',
},
];
} else if (agent === 'openAiFunctionsAgent') {
specialInputs = [
{
type: NodeConnectionType.AiLanguageModel,
type: 'ai_languageModel',
filter: {
nodes: [
'@n8n/n8n-nodes-langchain.lmChatOpenAi',
@ -157,57 +156,55 @@ function getInputs(
},
},
{
type: NodeConnectionType.AiMemory,
type: 'ai_memory',
},
{
type: NodeConnectionType.AiTool,
type: 'ai_tool',
required: true,
},
{
type: NodeConnectionType.AiOutputParser,
type: 'ai_outputParser',
},
];
} else if (agent === 'reActAgent') {
specialInputs = [
{
type: NodeConnectionType.AiLanguageModel,
type: 'ai_languageModel',
},
{
type: NodeConnectionType.AiTool,
type: 'ai_tool',
},
{
type: NodeConnectionType.AiOutputParser,
type: 'ai_outputParser',
},
];
} else if (agent === 'sqlAgent') {
specialInputs = [
{
type: NodeConnectionType.AiLanguageModel,
type: 'ai_languageModel',
},
{
type: NodeConnectionType.AiMemory,
type: 'ai_memory',
},
];
} else if (agent === 'planAndExecuteAgent') {
specialInputs = [
{
type: NodeConnectionType.AiLanguageModel,
type: 'ai_languageModel',
},
{
type: NodeConnectionType.AiTool,
type: 'ai_tool',
},
{
type: NodeConnectionType.AiOutputParser,
type: 'ai_outputParser',
},
];
}
if (hasOutputParser === false) {
specialInputs = specialInputs.filter(
(input) => input.type !== NodeConnectionType.AiOutputParser,
);
specialInputs = specialInputs.filter((input) => input.type !== 'ai_outputParser');
}
return [NodeConnectionType.Main, ...getInputData(specialInputs)];
return ['main', ...getInputData(specialInputs)];
}
const agentTypeProperty: INodeProperties = {
@ -292,7 +289,7 @@ export class Agent implements INodeType {
return getInputs(agent, hasOutputParser)
})($parameter.agent, $parameter.hasOutputParser === undefined || $parameter.hasOutputParser === true)
}}`,
outputs: [NodeConnectionType.Main],
outputs: [NodeConnectionTypes.Main],
credentials: [
{
// eslint-disable-next-line n8n-nodes-base/node-class-description-credentials-name-unsuffixed
@ -432,7 +429,7 @@ export class Agent implements INodeType {
},
},
{
displayName: `Connect an <a data-action='openSelectiveNodeCreator' data-action-parameter-connectiontype='${NodeConnectionType.AiOutputParser}'>output parser</a> on the canvas to specify the output format you require`,
displayName: `Connect an <a data-action='openSelectiveNodeCreator' data-action-parameter-connectiontype='${NodeConnectionTypes.AiOutputParser}'>output parser</a> on the canvas to specify the output format you require`,
name: 'notice',
type: 'notice',
default: '',

View File

@ -2,7 +2,7 @@ import type { BaseChatMemory } from '@langchain/community/memory/chat_memory';
import { PromptTemplate } from '@langchain/core/prompts';
import { initializeAgentExecutorWithOptions } from 'langchain/agents';
import type { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { NodeConnectionType, NodeOperationError } from 'n8n-workflow';
import { NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';
import { isChatInstance, getPromptInputByType, getConnectedTools } from '@utils/helpers';
import { getOptionalOutputParser } from '@utils/output_parsers/N8nOutputParser';
@ -16,13 +16,13 @@ export async function conversationalAgentExecute(
nodeVersion: number,
): Promise<INodeExecutionData[][]> {
this.logger.debug('Executing Conversational Agent');
const model = await this.getInputConnectionData(NodeConnectionType.AiLanguageModel, 0);
const model = await this.getInputConnectionData(NodeConnectionTypes.AiLanguageModel, 0);
if (!isChatInstance(model)) {
throw new NodeOperationError(this.getNode(), 'Conversational Agent requires Chat Model');
}
const memory = (await this.getInputConnectionData(NodeConnectionType.AiMemory, 0)) as
const memory = (await this.getInputConnectionData(NodeConnectionTypes.AiMemory, 0)) as
| BaseChatMemory
| undefined;

View File

@ -6,7 +6,7 @@ import { BufferMemory, type BaseChatMemory } from 'langchain/memory';
import {
type IExecuteFunctions,
type INodeExecutionData,
NodeConnectionType,
NodeConnectionTypes,
NodeOperationError,
} from 'n8n-workflow';
@ -22,7 +22,7 @@ export async function openAiFunctionsAgentExecute(
): Promise<INodeExecutionData[][]> {
this.logger.debug('Executing OpenAi Functions Agent');
const model = (await this.getInputConnectionData(
NodeConnectionType.AiLanguageModel,
NodeConnectionTypes.AiLanguageModel,
0,
)) as ChatOpenAI;
@ -32,7 +32,7 @@ export async function openAiFunctionsAgentExecute(
'OpenAI Functions Agent requires OpenAI Chat Model',
);
}
const memory = (await this.getInputConnectionData(NodeConnectionType.AiMemory, 0)) as
const memory = (await this.getInputConnectionData(NodeConnectionTypes.AiMemory, 0)) as
| BaseChatMemory
| undefined;
const tools = await getConnectedTools(this, nodeVersion >= 1.5, false);

View File

@ -4,7 +4,7 @@ import { PlanAndExecuteAgentExecutor } from 'langchain/experimental/plan_and_exe
import {
type IExecuteFunctions,
type INodeExecutionData,
NodeConnectionType,
NodeConnectionTypes,
NodeOperationError,
} from 'n8n-workflow';
@ -21,7 +21,7 @@ export async function planAndExecuteAgentExecute(
): Promise<INodeExecutionData[][]> {
this.logger.debug('Executing PlanAndExecute Agent');
const model = (await this.getInputConnectionData(
NodeConnectionType.AiLanguageModel,
NodeConnectionTypes.AiLanguageModel,
0,
)) as BaseChatModel;

View File

@ -5,7 +5,7 @@ import { AgentExecutor, ChatAgent, ZeroShotAgent } from 'langchain/agents';
import {
type IExecuteFunctions,
type INodeExecutionData,
NodeConnectionType,
NodeConnectionTypes,
NodeOperationError,
} from 'n8n-workflow';
@ -22,7 +22,7 @@ export async function reActAgentAgentExecute(
): Promise<INodeExecutionData[][]> {
this.logger.debug('Executing ReAct Agent');
const model = (await this.getInputConnectionData(NodeConnectionType.AiLanguageModel, 0)) as
const model = (await this.getInputConnectionData(NodeConnectionTypes.AiLanguageModel, 0)) as
| BaseLanguageModel
| BaseChatModel;

View File

@ -7,7 +7,7 @@ import { SqlDatabase } from 'langchain/sql_db';
import {
type IExecuteFunctions,
type INodeExecutionData,
NodeConnectionType,
NodeConnectionTypes,
NodeOperationError,
type IDataObject,
} from 'n8n-workflow';
@ -32,7 +32,7 @@ export async function sqlAgentAgentExecute(
this.logger.debug('Executing SQL Agent');
const model = (await this.getInputConnectionData(
NodeConnectionType.AiLanguageModel,
NodeConnectionTypes.AiLanguageModel,
0,
)) as BaseLanguageModel;
const items = this.getInputData();
@ -113,7 +113,7 @@ export async function sqlAgentAgentExecute(
const toolkit = new SqlToolkit(dbInstance, model);
const agentExecutor = createSqlAgent(model, toolkit, agentOptions);
const memory = (await this.getInputConnectionData(NodeConnectionType.AiMemory, 0)) as
const memory = (await this.getInputConnectionData(NodeConnectionTypes.AiMemory, 0)) as
| BaseChatMemory
| undefined;

View File

@ -11,7 +11,7 @@ import type { AgentAction, AgentFinish } from 'langchain/agents';
import { AgentExecutor, createToolCallingAgent } from 'langchain/agents';
import type { ToolsAgentAction } from 'langchain/dist/agents/tool_calling/output_parser';
import { omit } from 'lodash';
import { BINARY_ENCODING, jsonParse, NodeConnectionType, NodeOperationError } from 'n8n-workflow';
import { BINARY_ENCODING, jsonParse, NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';
import type { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import type { ZodObject } from 'zod';
import { z } from 'zod';
@ -275,7 +275,7 @@ export const getAgentStepsParser =
* @returns The validated chat model
*/
export async function getChatModel(ctx: IExecuteFunctions): Promise<BaseChatModel> {
const model = await ctx.getInputConnectionData(NodeConnectionType.AiLanguageModel, 0);
const model = await ctx.getInputConnectionData(NodeConnectionTypes.AiLanguageModel, 0);
if (!isChatInstance(model) || !model.bindTools) {
throw new NodeOperationError(
ctx.getNode(),
@ -294,7 +294,7 @@ export async function getChatModel(ctx: IExecuteFunctions): Promise<BaseChatMode
export async function getOptionalMemory(
ctx: IExecuteFunctions,
): Promise<BaseChatMemory | undefined> {
return (await ctx.getInputConnectionData(NodeConnectionType.AiMemory, 0)) as
return (await ctx.getInputConnectionData(NodeConnectionTypes.AiMemory, 0)) as
| BaseChatMemory
| undefined;
}

View File

@ -1,7 +1,7 @@
import { AgentExecutor } from 'langchain/agents';
import type { OpenAIToolType } from 'langchain/dist/experimental/openai_assistant/schema';
import { OpenAIAssistantRunnable } from 'langchain/experimental/openai_assistant';
import { NodeConnectionType, NodeOperationError } from 'n8n-workflow';
import { NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';
import type {
IExecuteFunctions,
INodeExecutionData,
@ -44,10 +44,10 @@ export class OpenAiAssistant implements INodeType {
},
},
inputs: [
{ type: NodeConnectionType.Main },
{ type: NodeConnectionType.AiTool, displayName: 'Tools' },
{ type: NodeConnectionTypes.Main },
{ type: NodeConnectionTypes.AiTool, displayName: 'Tools' },
],
outputs: [NodeConnectionType.Main],
outputs: [NodeConnectionTypes.Main],
credentials: [
{
name: 'openAiApi',

View File

@ -5,7 +5,7 @@ import type {
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
import { NodeApiError, NodeConnectionType, NodeOperationError } from 'n8n-workflow';
import { NodeApiError, NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';
import { getPromptInputByType } from '@utils/helpers';
import { getOptionalOutputParser } from '@utils/output_parsers/N8nOutputParser';
@ -55,7 +55,7 @@ export class ChainLlm implements INodeType {
},
},
inputs: `={{ ((parameter) => { ${getInputs.toString()}; return getInputs(parameter) })($parameter) }}`,
outputs: [NodeConnectionType.Main],
outputs: [NodeConnectionTypes.Main],
credentials: [],
properties: nodeProperties,
};
@ -73,7 +73,7 @@ export class ChainLlm implements INodeType {
try {
// Get the language model
const llm = (await this.getInputConnectionData(
NodeConnectionType.AiLanguageModel,
NodeConnectionTypes.AiLanguageModel,
0,
)) as BaseLanguageModel;

View File

@ -3,8 +3,8 @@ import {
HumanMessagePromptTemplate,
SystemMessagePromptTemplate,
} from '@langchain/core/prompts';
import type { IDataObject, INodeProperties } from 'n8n-workflow';
import { NodeConnectionType } from 'n8n-workflow';
import type { IDataObject, INodeInputConfiguration, INodeProperties } from 'n8n-workflow';
import { NodeConnectionTypes } from 'n8n-workflow';
import { promptTypeOptions, textFromPreviousNode } from '@utils/descriptions';
import { getTemplateNoticeField } from '@utils/sharedFields';
@ -13,12 +13,12 @@ import { getTemplateNoticeField } from '@utils/sharedFields';
* Dynamic input configuration generation based on node parameters
*/
export function getInputs(parameters: IDataObject) {
const inputs = [
{ displayName: '', type: NodeConnectionType.Main },
const inputs: INodeInputConfiguration[] = [
{ displayName: '', type: 'main' },
{
displayName: 'Model',
maxConnections: 1,
type: NodeConnectionType.AiLanguageModel,
type: 'ai_languageModel',
required: true,
},
];
@ -29,7 +29,7 @@ export function getInputs(parameters: IDataObject) {
if (hasOutputParser === undefined || hasOutputParser === true) {
inputs.push({
displayName: 'Output Parser',
type: NodeConnectionType.AiOutputParser,
type: 'ai_outputParser',
maxConnections: 1,
required: false,
});
@ -260,7 +260,7 @@ export const nodeProperties: INodeProperties[] = [
],
},
{
displayName: `Connect an <a data-action='openSelectiveNodeCreator' data-action-parameter-connectiontype='${NodeConnectionType.AiOutputParser}'>output parser</a> on the canvas to specify the output format you require`,
displayName: `Connect an <a data-action='openSelectiveNodeCreator' data-action-parameter-connectiontype='${NodeConnectionTypes.AiOutputParser}'>output parser</a> on the canvas to specify the output format you require`,
name: 'notice',
type: 'notice',
default: '',

View File

@ -3,7 +3,7 @@ import { HumanMessage } from '@langchain/core/messages';
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
import { ChatOllama } from '@langchain/ollama';
import type { IExecuteFunctions, IBinaryData } from 'n8n-workflow';
import { NodeOperationError, NodeConnectionType, OperationalError } from 'n8n-workflow';
import { NodeOperationError, NodeConnectionTypes, OperationalError } from 'n8n-workflow';
import type { MessageTemplate } from './types';
@ -69,7 +69,7 @@ export async function createImageMessage({
const bufferData = await context.helpers.getBinaryDataBuffer(itemIndex, binaryDataKey);
const model = (await context.getInputConnectionData(
NodeConnectionType.AiLanguageModel,
NodeConnectionTypes.AiLanguageModel,
0,
)) as BaseLanguageModel;

View File

@ -3,7 +3,7 @@
import { FakeChatModel } from '@langchain/core/utils/testing';
import { mock } from 'jest-mock-extended';
import type { IExecuteFunctions, INode } from 'n8n-workflow';
import { NodeConnectionType } from 'n8n-workflow';
import { NodeConnectionTypes } from 'n8n-workflow';
import * as helperModule from '@utils/helpers';
import * as outputParserModule from '@utils/output_parsers/N8nOutputParser';
@ -64,7 +64,7 @@ describe('ChainLlm Node', () => {
expect(node.description.version).toContain(1.5);
expect(node.description.properties).toBeDefined();
expect(node.description.inputs).toBeDefined();
expect(node.description.outputs).toEqual([NodeConnectionType.Main]);
expect(node.description.outputs).toEqual([NodeConnectionTypes.Main]);
});
});

View File

@ -1,4 +1,4 @@
import { NodeConnectionType } from 'n8n-workflow';
import { NodeConnectionTypes } from 'n8n-workflow';
import { getInputs, nodeProperties } from '../methods/config';
@ -8,24 +8,24 @@ describe('config', () => {
const inputs = getInputs({});
expect(inputs).toHaveLength(3);
expect(inputs[0].type).toBe(NodeConnectionType.Main);
expect(inputs[1].type).toBe(NodeConnectionType.AiLanguageModel);
expect(inputs[2].type).toBe(NodeConnectionType.AiOutputParser);
expect(inputs[0].type).toBe(NodeConnectionTypes.Main);
expect(inputs[1].type).toBe(NodeConnectionTypes.AiLanguageModel);
expect(inputs[2].type).toBe(NodeConnectionTypes.AiOutputParser);
});
it('should exclude the OutputParser when hasOutputParser is false', () => {
const inputs = getInputs({ hasOutputParser: false });
expect(inputs).toHaveLength(2);
expect(inputs[0].type).toBe(NodeConnectionType.Main);
expect(inputs[1].type).toBe(NodeConnectionType.AiLanguageModel);
expect(inputs[0].type).toBe(NodeConnectionTypes.Main);
expect(inputs[1].type).toBe(NodeConnectionTypes.AiLanguageModel);
});
it('should include the OutputParser when hasOutputParser is true', () => {
const inputs = getInputs({ hasOutputParser: true });
expect(inputs).toHaveLength(3);
expect(inputs[2].type).toBe(NodeConnectionType.AiOutputParser);
expect(inputs[2].type).toBe(NodeConnectionTypes.AiOutputParser);
});
});

View File

@ -8,7 +8,7 @@ import {
import type { BaseRetriever } from '@langchain/core/retrievers';
import { createStuffDocumentsChain } from 'langchain/chains/combine_documents';
import { createRetrievalChain } from 'langchain/chains/retrieval';
import { NodeConnectionType, NodeOperationError, parseErrorMetadata } from 'n8n-workflow';
import { NodeConnectionTypes, NodeOperationError, parseErrorMetadata } from 'n8n-workflow';
import {
type INodeProperties,
type IExecuteFunctions,
@ -70,21 +70,21 @@ export class ChainRetrievalQa implements INodeType {
},
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [
NodeConnectionType.Main,
NodeConnectionTypes.Main,
{
displayName: 'Model',
maxConnections: 1,
type: NodeConnectionType.AiLanguageModel,
type: NodeConnectionTypes.AiLanguageModel,
required: true,
},
{
displayName: 'Retriever',
maxConnections: 1,
type: NodeConnectionType.AiRetriever,
type: NodeConnectionTypes.AiRetriever,
required: true,
},
],
outputs: [NodeConnectionType.Main],
outputs: [NodeConnectionTypes.Main],
credentials: [],
properties: [
getTemplateNoticeField(1960),
@ -192,12 +192,12 @@ export class ChainRetrievalQa implements INodeType {
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
try {
const model = (await this.getInputConnectionData(
NodeConnectionType.AiLanguageModel,
NodeConnectionTypes.AiLanguageModel,
0,
)) as BaseLanguageModel;
const retriever = (await this.getInputConnectionData(
NodeConnectionType.AiRetriever,
NodeConnectionTypes.AiRetriever,
0,
)) as BaseRetriever;

View File

@ -3,8 +3,8 @@ import type { BaseLanguageModel } from '@langchain/core/language_models/base';
import type { BaseRetriever } from '@langchain/core/retrievers';
import { FakeChatModel, FakeLLM, FakeRetriever } from '@langchain/core/utils/testing';
import get from 'lodash/get';
import type { IDataObject, IExecuteFunctions } from 'n8n-workflow';
import { NodeConnectionType, NodeOperationError, UnexpectedError } from 'n8n-workflow';
import type { IDataObject, IExecuteFunctions, NodeConnectionType } from 'n8n-workflow';
import { NodeConnectionTypes, NodeOperationError, UnexpectedError } from 'n8n-workflow';
import { ChainRetrievalQa } from '../ChainRetrievalQa.node';
@ -27,10 +27,10 @@ const createExecuteFunctionsMock = (
};
},
getInputConnectionData(type: NodeConnectionType) {
if (type === NodeConnectionType.AiLanguageModel) {
if (type === NodeConnectionTypes.AiLanguageModel) {
return fakeLlm;
}
if (type === NodeConnectionType.AiRetriever) {
if (type === NodeConnectionTypes.AiRetriever) {
return fakeRetriever;
}
return null;

View File

@ -4,7 +4,7 @@ import { PromptTemplate } from '@langchain/core/prompts';
import type { SummarizationChainParams } from 'langchain/chains';
import { loadSummarizationChain } from 'langchain/chains';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeTypeBaseDescription,
type IExecuteFunctions,
type INodeExecutionData,
@ -31,21 +31,21 @@ export class ChainSummarizationV1 implements INodeType {
},
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [
NodeConnectionType.Main,
NodeConnectionTypes.Main,
{
displayName: 'Model',
maxConnections: 1,
type: NodeConnectionType.AiLanguageModel,
type: NodeConnectionTypes.AiLanguageModel,
required: true,
},
{
displayName: 'Document',
maxConnections: 1,
type: NodeConnectionType.AiDocument,
type: NodeConnectionTypes.AiDocument,
required: true,
},
],
outputs: [NodeConnectionType.Main],
outputs: [NodeConnectionTypes.Main],
credentials: [],
properties: [
getTemplateNoticeField(1951),
@ -167,11 +167,11 @@ export class ChainSummarizationV1 implements INodeType {
const type = this.getNodeParameter('type', 0) as 'map_reduce' | 'stuff' | 'refine';
const model = (await this.getInputConnectionData(
NodeConnectionType.AiLanguageModel,
NodeConnectionTypes.AiLanguageModel,
0,
)) as BaseLanguageModel;
const documentInput = (await this.getInputConnectionData(NodeConnectionType.AiDocument, 0)) as
const documentInput = (await this.getInputConnectionData(NodeConnectionTypes.AiDocument, 0)) as
| N8nJsonLoader
| Array<Document<Record<string, unknown>>>;

View File

@ -10,8 +10,9 @@ import type {
INodeType,
INodeTypeDescription,
IDataObject,
INodeInputConfiguration,
} from 'n8n-workflow';
import { NodeConnectionType } from 'n8n-workflow';
import { NodeConnectionTypes } from 'n8n-workflow';
import { N8nBinaryLoader } from '@utils/N8nBinaryLoader';
import { N8nJsonLoader } from '@utils/N8nJsonLoader';
@ -24,12 +25,12 @@ import { REFINE_PROMPT_TEMPLATE, DEFAULT_PROMPT_TEMPLATE } from '../prompt';
function getInputs(parameters: IDataObject) {
const chunkingMode = parameters?.chunkingMode;
const operationMode = parameters?.operationMode;
const inputs = [
{ displayName: '', type: NodeConnectionType.Main },
const inputs: INodeInputConfiguration[] = [
{ displayName: '', type: 'main' },
{
displayName: 'Model',
maxConnections: 1,
type: NodeConnectionType.AiLanguageModel,
type: 'ai_languageModel',
required: true,
},
];
@ -37,7 +38,7 @@ function getInputs(parameters: IDataObject) {
if (operationMode === 'documentLoader') {
inputs.push({
displayName: 'Document',
type: NodeConnectionType.AiDocument,
type: 'ai_document',
required: true,
maxConnections: 1,
});
@ -47,7 +48,7 @@ function getInputs(parameters: IDataObject) {
if (chunkingMode === 'advanced') {
inputs.push({
displayName: 'Text Splitter',
type: NodeConnectionType.AiTextSplitter,
type: 'ai_textSplitter',
required: false,
maxConnections: 1,
});
@ -69,7 +70,7 @@ export class ChainSummarizationV2 implements INodeType {
},
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: `={{ ((parameter) => { ${getInputs.toString()}; return getInputs(parameter) })($parameter) }}`,
outputs: [NodeConnectionType.Main],
outputs: [NodeConnectionTypes.Main],
credentials: [],
properties: [
getTemplateNoticeField(1951),
@ -327,7 +328,7 @@ export class ChainSummarizationV2 implements INodeType {
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
try {
const model = (await this.getInputConnectionData(
NodeConnectionType.AiLanguageModel,
NodeConnectionTypes.AiLanguageModel,
0,
)) as BaseLanguageModel;
@ -356,7 +357,7 @@ export class ChainSummarizationV2 implements INodeType {
// Use dedicated document loader input to load documents
if (operationMode === 'documentLoader') {
const documentInput = (await this.getInputConnectionData(
NodeConnectionType.AiDocument,
NodeConnectionTypes.AiDocument,
0,
)) as N8nJsonLoader | Array<Document<Record<string, unknown>>>;
@ -390,7 +391,7 @@ export class ChainSummarizationV2 implements INodeType {
// In advanced mode user can connect text splitter node so we just retrieve it
case 'advanced':
textSplitter = (await this.getInputConnectionData(
NodeConnectionType.AiTextSplitter,
NodeConnectionTypes.AiTextSplitter,
0,
)) as TextSplitter | undefined;
break;

View File

@ -3,7 +3,7 @@ import { HumanMessage } from '@langchain/core/messages';
import { ChatPromptTemplate, SystemMessagePromptTemplate } from '@langchain/core/prompts';
import type { JSONSchema7 } from 'json-schema';
import { OutputFixingParser, StructuredOutputParser } from 'langchain/output_parsers';
import { jsonParse, NodeConnectionType, NodeOperationError } from 'n8n-workflow';
import { jsonParse, NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';
import type {
INodeType,
INodeTypeDescription,
@ -51,15 +51,15 @@ export class InformationExtractor implements INodeType {
name: 'Information Extractor',
},
inputs: [
{ displayName: '', type: NodeConnectionType.Main },
{ displayName: '', type: NodeConnectionTypes.Main },
{
displayName: 'Model',
maxConnections: 1,
type: NodeConnectionType.AiLanguageModel,
type: NodeConnectionTypes.AiLanguageModel,
required: true,
},
],
outputs: [NodeConnectionType.Main],
outputs: [NodeConnectionTypes.Main],
properties: [
{
displayName: 'Text',
@ -222,7 +222,7 @@ export class InformationExtractor implements INodeType {
const items = this.getInputData();
const llm = (await this.getInputConnectionData(
NodeConnectionType.AiLanguageModel,
NodeConnectionTypes.AiLanguageModel,
0,
)) as BaseLanguageModel;

View File

@ -2,7 +2,7 @@ import type { BaseLanguageModel } from '@langchain/core/language_models/base';
import { HumanMessage } from '@langchain/core/messages';
import { SystemMessagePromptTemplate, ChatPromptTemplate } from '@langchain/core/prompts';
import { OutputFixingParser, StructuredOutputParser } from 'langchain/output_parsers';
import { NodeConnectionType, NodeOperationError } from 'n8n-workflow';
import { NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';
import type {
IDataObject,
IExecuteFunctions,
@ -24,7 +24,7 @@ const configuredOutputs = (parameters: INodeParameters, defaultCategories: strin
const categories = (options?.categories as string) ?? defaultCategories;
const categoriesArray = categories.split(',').map((cat) => cat.trim());
const ret = categoriesArray.map((cat) => ({ type: NodeConnectionType.Main, displayName: cat }));
const ret = categoriesArray.map((cat) => ({ type: NodeConnectionTypes.Main, displayName: cat }));
return ret;
};
@ -54,11 +54,11 @@ export class SentimentAnalysis implements INodeType {
name: 'Sentiment Analysis',
},
inputs: [
{ displayName: '', type: NodeConnectionType.Main },
{ displayName: '', type: NodeConnectionTypes.Main },
{
displayName: 'Model',
maxConnections: 1,
type: NodeConnectionType.AiLanguageModel,
type: NodeConnectionTypes.AiLanguageModel,
required: true,
},
],
@ -140,7 +140,7 @@ export class SentimentAnalysis implements INodeType {
const items = this.getInputData();
const llm = (await this.getInputConnectionData(
NodeConnectionType.AiLanguageModel,
NodeConnectionTypes.AiLanguageModel,
0,
)) as BaseLanguageModel;

View File

@ -2,7 +2,7 @@ import type { BaseLanguageModel } from '@langchain/core/language_models/base';
import { HumanMessage } from '@langchain/core/messages';
import { SystemMessagePromptTemplate, ChatPromptTemplate } from '@langchain/core/prompts';
import { OutputFixingParser, StructuredOutputParser } from 'langchain/output_parsers';
import { NodeOperationError, NodeConnectionType } from 'n8n-workflow';
import { NodeOperationError, NodeConnectionTypes } from 'n8n-workflow';
import type {
IDataObject,
IExecuteFunctions,
@ -22,9 +22,9 @@ const configuredOutputs = (parameters: INodeParameters) => {
const categories = ((parameters.categories as IDataObject)?.categories as IDataObject[]) ?? [];
const fallback = (parameters.options as IDataObject)?.fallback as string;
const ret = categories.map((cat) => {
return { type: NodeConnectionType.Main, displayName: cat.category };
return { type: NodeConnectionTypes.Main, displayName: cat.category };
});
if (fallback === 'other') ret.push({ type: NodeConnectionType.Main, displayName: 'Other' });
if (fallback === 'other') ret.push({ type: NodeConnectionTypes.Main, displayName: 'Other' });
return ret;
};
@ -54,11 +54,11 @@ export class TextClassifier implements INodeType {
name: 'Text Classifier',
},
inputs: [
{ displayName: '', type: NodeConnectionType.Main },
{ displayName: '', type: NodeConnectionTypes.Main },
{
displayName: 'Model',
maxConnections: 1,
type: NodeConnectionType.AiLanguageModel,
type: NodeConnectionTypes.AiLanguageModel,
required: true,
},
],
@ -167,7 +167,7 @@ export class TextClassifier implements INodeType {
const items = this.getInputData();
const llm = (await this.getInputConnectionData(
NodeConnectionType.AiLanguageModel,
NodeConnectionTypes.AiLanguageModel,
0,
)) as BaseLanguageModel;

View File

@ -4,7 +4,7 @@ import { makeResolverFromLegacyOptions } from '@n8n/vm2';
import { JavaScriptSandbox } from 'n8n-nodes-base/dist/nodes/Code/JavaScriptSandbox';
import { getSandboxContext } from 'n8n-nodes-base/dist/nodes/Code/Sandbox';
import { standardizeOutput } from 'n8n-nodes-base/dist/nodes/Code/utils';
import { NodeOperationError, NodeConnectionType } from 'n8n-workflow';
import { NodeOperationError, NodeConnectionTypes } from 'n8n-workflow';
import type {
IExecuteFunctions,
INodeExecutionData,
@ -24,16 +24,16 @@ const { NODE_FUNCTION_ALLOW_BUILTIN: builtIn, NODE_FUNCTION_ALLOW_EXTERNAL: exte
// TODO: Replace
const connectorTypes = {
[NodeConnectionType.AiChain]: 'Chain',
[NodeConnectionType.AiDocument]: 'Document',
[NodeConnectionType.AiEmbedding]: 'Embedding',
[NodeConnectionType.AiLanguageModel]: 'Language Model',
[NodeConnectionType.AiMemory]: 'Memory',
[NodeConnectionType.AiOutputParser]: 'Output Parser',
[NodeConnectionType.AiTextSplitter]: 'Text Splitter',
[NodeConnectionType.AiTool]: 'Tool',
[NodeConnectionType.AiVectorStore]: 'Vector Store',
[NodeConnectionType.Main]: 'Main',
[NodeConnectionTypes.AiChain]: 'Chain',
[NodeConnectionTypes.AiDocument]: 'Document',
[NodeConnectionTypes.AiEmbedding]: 'Embedding',
[NodeConnectionTypes.AiLanguageModel]: 'Language Model',
[NodeConnectionTypes.AiMemory]: 'Memory',
[NodeConnectionTypes.AiOutputParser]: 'Output Parser',
[NodeConnectionTypes.AiTextSplitter]: 'Text Splitter',
[NodeConnectionTypes.AiTool]: 'Tool',
[NodeConnectionTypes.AiVectorStore]: 'Vector Store',
[NodeConnectionTypes.Main]: 'Main',
};
const defaultCodeExecute = `const { PromptTemplate } = require('@langchain/core/prompts');
@ -304,7 +304,7 @@ export class Code implements INodeType {
const outputs = this.getNodeOutputs();
const mainOutputs: INodeOutputConfiguration[] = outputs.filter(
(output) => output.type === NodeConnectionType.Main,
(output) => output.type === NodeConnectionTypes.Main,
);
const options = { multiOutput: mainOutputs.length !== 1 };

View File

@ -1,7 +1,7 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import type { TextSplitter } from '@langchain/textsplitters';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -51,15 +51,15 @@ export class DocumentBinaryInputLoader implements INodeType {
{
displayName: 'Text Splitter',
maxConnections: 1,
type: NodeConnectionType.AiTextSplitter,
type: NodeConnectionTypes.AiTextSplitter,
required: true,
},
],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiDocument],
outputs: [NodeConnectionTypes.AiDocument],
outputNames: ['Document'],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiVectorStore]),
getConnectionHintNoticeField([NodeConnectionTypes.AiVectorStore]),
{
displayName: 'Loader Type',
name: 'loader',
@ -179,7 +179,7 @@ export class DocumentBinaryInputLoader implements INodeType {
async supplyData(this: ISupplyDataFunctions): Promise<SupplyData> {
this.logger.debug('Supply Data for Binary Input Loader');
const textSplitter = (await this.getInputConnectionData(
NodeConnectionType.AiTextSplitter,
NodeConnectionTypes.AiTextSplitter,
0,
)) as TextSplitter | undefined;

View File

@ -1,7 +1,7 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import type { TextSplitter } from '@langchain/textsplitters';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -49,12 +49,12 @@ export class DocumentDefaultDataLoader implements INodeType {
{
displayName: 'Text Splitter',
maxConnections: 1,
type: NodeConnectionType.AiTextSplitter,
type: NodeConnectionTypes.AiTextSplitter,
required: true,
},
],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiDocument],
outputs: [NodeConnectionTypes.AiDocument],
outputNames: ['Document'],
properties: [
{
@ -286,7 +286,7 @@ export class DocumentDefaultDataLoader implements INodeType {
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,
NodeConnectionTypes.AiTextSplitter,
0,
)) as TextSplitter | undefined;
const binaryDataKey = this.getNodeParameter('binaryDataKey', itemIndex, '') as string;

View File

@ -2,7 +2,7 @@
import { GithubRepoLoader } from '@langchain/community/document_loaders/web/github';
import type { CharacterTextSplitter } from '@langchain/textsplitters';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -47,15 +47,15 @@ export class DocumentGithubLoader implements INodeType {
{
displayName: 'Text Splitter',
maxConnections: 1,
type: NodeConnectionType.AiTextSplitter,
type: NodeConnectionTypes.AiTextSplitter,
},
],
inputNames: ['Text Splitter'],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiDocument],
outputs: [NodeConnectionTypes.AiDocument],
outputNames: ['Document'],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiVectorStore]),
getConnectionHintNoticeField([NodeConnectionTypes.AiVectorStore]),
{
displayName: 'Repository Link',
name: 'repository',
@ -106,11 +106,11 @@ export class DocumentGithubLoader implements INodeType {
};
const textSplitter = (await this.getInputConnectionData(
NodeConnectionType.AiTextSplitter,
NodeConnectionTypes.AiTextSplitter,
0,
)) as CharacterTextSplitter | undefined;
const { index } = this.addInputData(NodeConnectionType.AiDocument, [
const { index } = this.addInputData(NodeConnectionTypes.AiDocument, [
[{ json: { repository, branch, ignorePaths, recursive } }],
]);
const docs = new GithubRepoLoader(repository, {
@ -125,7 +125,7 @@ export class DocumentGithubLoader implements INodeType {
? await textSplitter.splitDocuments(await docs.load())
: await docs.load();
this.addOutputData(NodeConnectionType.AiDocument, index, [[{ json: { loadedDocs } }]]);
this.addOutputData(NodeConnectionTypes.AiDocument, index, [[{ json: { loadedDocs } }]]);
return {
response: logWrapper(loadedDocs, this),
};

View File

@ -1,7 +1,7 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import type { TextSplitter } from '@langchain/textsplitters';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -44,15 +44,15 @@ export class DocumentJsonInputLoader implements INodeType {
{
displayName: 'Text Splitter',
maxConnections: 1,
type: NodeConnectionType.AiTextSplitter,
type: NodeConnectionTypes.AiTextSplitter,
},
],
inputNames: ['Text Splitter'],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiDocument],
outputs: [NodeConnectionTypes.AiDocument],
outputNames: ['Document'],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiVectorStore]),
getConnectionHintNoticeField([NodeConnectionTypes.AiVectorStore]),
{
displayName: 'Pointers',
name: 'pointers',
@ -82,7 +82,7 @@ export class DocumentJsonInputLoader implements INodeType {
async supplyData(this: ISupplyDataFunctions): Promise<SupplyData> {
this.logger.debug('Supply Data for JSON Input Loader');
const textSplitter = (await this.getInputConnectionData(
NodeConnectionType.AiTextSplitter,
NodeConnectionTypes.AiTextSplitter,
0,
)) as TextSplitter | undefined;

View File

@ -1,7 +1,7 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import { BedrockEmbeddings } from '@langchain/aws';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -45,14 +45,14 @@ export class EmbeddingsAwsBedrock implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiEmbedding],
outputs: [NodeConnectionTypes.AiEmbedding],
outputNames: ['Embeddings'],
requestDefaults: {
ignoreHttpStatusErrors: true,
baseURL: '=https://bedrock.{{$credentials?.region ?? "eu-central-1"}}.amazonaws.com',
},
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiVectorStore]),
getConnectionHintNoticeField([NodeConnectionTypes.AiVectorStore]),
{
displayName: 'Model',
name: 'model',

View File

@ -1,7 +1,7 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import { OpenAIEmbeddings } from '@langchain/openai';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -45,10 +45,10 @@ export class EmbeddingsAzureOpenAi implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiEmbedding],
outputs: [NodeConnectionTypes.AiEmbedding],
outputNames: ['Embeddings'],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiVectorStore]),
getConnectionHintNoticeField([NodeConnectionTypes.AiVectorStore]),
{
displayName: 'Model (Deployment) Name',
name: 'model',

View File

@ -1,7 +1,7 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import { CohereEmbeddings } from '@langchain/cohere';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -48,10 +48,10 @@ export class EmbeddingsCohere implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiEmbedding],
outputs: [NodeConnectionTypes.AiEmbedding],
outputNames: ['Embeddings'],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiVectorStore]),
getConnectionHintNoticeField([NodeConnectionTypes.AiVectorStore]),
{
displayName:
'Each model is using different dimensional density for embeddings. Please make sure to use the same dimensionality for your vector store. The default model is using 768-dimensional embeddings.',

View File

@ -1,7 +1,7 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import { GoogleGenerativeAIEmbeddings } from '@langchain/google-genai';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -48,10 +48,10 @@ export class EmbeddingsGoogleGemini implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiEmbedding],
outputs: [NodeConnectionTypes.AiEmbedding],
outputNames: ['Embeddings'],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiVectorStore]),
getConnectionHintNoticeField([NodeConnectionTypes.AiVectorStore]),
{
displayName:
'Each model is using different dimensional density for embeddings. Please make sure to use the same dimensionality for your vector store. The default model is using 768-dimensional embeddings.',

View File

@ -1,7 +1,7 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import { HuggingFaceInferenceEmbeddings } from '@langchain/community/embeddings/hf';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -44,10 +44,10 @@ export class EmbeddingsHuggingFaceInference implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiEmbedding],
outputs: [NodeConnectionTypes.AiEmbedding],
outputNames: ['Embeddings'],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiVectorStore]),
getConnectionHintNoticeField([NodeConnectionTypes.AiVectorStore]),
{
displayName:
'Each model is using different dimensional density for embeddings. Please make sure to use the same dimensionality for your vector store. The default model is using 768-dimensional embeddings.',

View File

@ -2,7 +2,7 @@
import type { MistralAIEmbeddingsParams } from '@langchain/mistralai';
import { MistralAIEmbeddings } from '@langchain/mistralai';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -46,14 +46,14 @@ export class EmbeddingsMistralCloud implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiEmbedding],
outputs: [NodeConnectionTypes.AiEmbedding],
outputNames: ['Embeddings'],
requestDefaults: {
ignoreHttpStatusErrors: true,
baseURL: 'https://api.mistral.ai/v1',
},
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiVectorStore]),
getConnectionHintNoticeField([NodeConnectionTypes.AiVectorStore]),
{
displayName: 'Model',
name: 'model',

View File

@ -1,7 +1,7 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import { OllamaEmbeddings } from '@langchain/ollama';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -41,9 +41,9 @@ export class EmbeddingsOllama implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiEmbedding],
outputs: [NodeConnectionTypes.AiEmbedding],
outputNames: ['Embeddings'],
properties: [getConnectionHintNoticeField([NodeConnectionType.AiVectorStore]), ollamaModel],
properties: [getConnectionHintNoticeField([NodeConnectionTypes.AiVectorStore]), ollamaModel],
};
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {

View File

@ -1,7 +1,7 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import { OpenAIEmbeddings } from '@langchain/openai';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type SupplyData,
@ -101,7 +101,7 @@ export class EmbeddingsOpenAi implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiEmbedding],
outputs: [NodeConnectionTypes.AiEmbedding],
outputNames: ['Embeddings'],
requestDefaults: {
ignoreHttpStatusErrors: true,
@ -109,7 +109,7 @@ export class EmbeddingsOpenAi implements INodeType {
'={{ $parameter.options?.baseURL?.split("/").slice(0,-1).join("/") || $credentials.url?.split("/").slice(0,-1).join("/") || "https://api.openai.com" }}',
},
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiVectorStore]),
getConnectionHintNoticeField([NodeConnectionTypes.AiVectorStore]),
{
...modelParameter,
default: 'text-embedding-ada-002',

View File

@ -3,7 +3,7 @@
import { ChatAnthropic } from '@langchain/anthropic';
import type { LLMResult } from '@langchain/core/outputs';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodePropertyOptions,
type INodeProperties,
type ISupplyDataFunctions,
@ -109,7 +109,7 @@ export class LmChatAnthropic implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiLanguageModel],
outputs: [NodeConnectionTypes.AiLanguageModel],
outputNames: ['Model'],
credentials: [
{
@ -118,7 +118,7 @@ export class LmChatAnthropic implements INodeType {
},
],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiChain, NodeConnectionType.AiChain]),
getConnectionHintNoticeField([NodeConnectionTypes.AiChain, NodeConnectionTypes.AiChain]),
{
...modelField,
displayOptions: {

View File

@ -3,7 +3,7 @@
import type { ChatOllamaInput } from '@langchain/ollama';
import { ChatOllama } from '@langchain/ollama';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -45,11 +45,11 @@ export class LmChatOllama implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiLanguageModel],
outputs: [NodeConnectionTypes.AiLanguageModel],
outputNames: ['Model'],
...ollamaDescription,
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiChain, NodeConnectionType.AiAgent]),
getConnectionHintNoticeField([NodeConnectionTypes.AiChain, NodeConnectionTypes.AiAgent]),
ollamaModel,
ollamaOptions,
],

View File

@ -2,7 +2,7 @@
import { ChatOpenAI, type ClientOptions } from '@langchain/openai';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -51,7 +51,7 @@ export class LmChatOpenAi implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiLanguageModel],
outputs: [NodeConnectionTypes.AiLanguageModel],
outputNames: ['Model'],
credentials: [
{
@ -65,7 +65,7 @@ export class LmChatOpenAi implements INodeType {
'={{ $parameter.options?.baseURL?.split("/").slice(0,-1).join("/") || $credentials?.url?.split("/").slice(0,-1).join("/") || "https://api.openai.com" }}',
},
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiChain, NodeConnectionType.AiAgent]),
getConnectionHintNoticeField([NodeConnectionTypes.AiChain, NodeConnectionTypes.AiAgent]),
{
displayName:
'If using JSON response format, you must include word "json" in the prompt in your chain or agent. Also, make sure to select latest models released post November 2023.',

View File

@ -1,7 +1,7 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import { Cohere } from '@langchain/cohere';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -42,7 +42,7 @@ export class LmCohere implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiLanguageModel],
outputs: [NodeConnectionTypes.AiLanguageModel],
outputNames: ['Model'],
credentials: [
{
@ -51,7 +51,7 @@ export class LmCohere implements INodeType {
},
],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiChain, NodeConnectionType.AiAgent]),
getConnectionHintNoticeField([NodeConnectionTypes.AiChain, NodeConnectionTypes.AiAgent]),
{
displayName: 'Options',
name: 'options',

View File

@ -2,7 +2,7 @@
import { Ollama } from '@langchain/community/llms/ollama';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -44,11 +44,11 @@ export class LmOllama implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiLanguageModel],
outputs: [NodeConnectionTypes.AiLanguageModel],
outputNames: ['Model'],
...ollamaDescription,
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiChain, NodeConnectionType.AiAgent]),
getConnectionHintNoticeField([NodeConnectionTypes.AiChain, NodeConnectionTypes.AiAgent]),
ollamaModel,
ollamaOptions,
],

View File

@ -1,6 +1,6 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import { OpenAI, type ClientOptions } from '@langchain/openai';
import { NodeConnectionType } from 'n8n-workflow';
import { NodeConnectionTypes } from 'n8n-workflow';
import type {
INodeType,
INodeTypeDescription,
@ -53,7 +53,7 @@ export class LmOpenAi implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiLanguageModel],
outputs: [NodeConnectionTypes.AiLanguageModel],
outputNames: ['Model'],
credentials: [
{

View File

@ -1,7 +1,7 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import { HuggingFaceInference } from '@langchain/community/llms/hf';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -42,7 +42,7 @@ export class LmOpenHuggingFaceInference implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiLanguageModel],
outputs: [NodeConnectionTypes.AiLanguageModel],
outputNames: ['Model'],
credentials: [
{
@ -51,7 +51,7 @@ export class LmOpenHuggingFaceInference implements INodeType {
},
],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiChain, NodeConnectionType.AiAgent]),
getConnectionHintNoticeField([NodeConnectionTypes.AiChain, NodeConnectionTypes.AiAgent]),
{
displayName: 'Model',
name: 'model',

View File

@ -1,7 +1,7 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import { ChatBedrockConverse } from '@langchain/aws';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -42,7 +42,7 @@ export class LmChatAwsBedrock implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiLanguageModel],
outputs: [NodeConnectionTypes.AiLanguageModel],
outputNames: ['Model'],
credentials: [
{
@ -56,7 +56,7 @@ export class LmChatAwsBedrock implements INodeType {
baseURL: '=https://bedrock.{{$credentials?.region ?? "eu-central-1"}}.amazonaws.com',
},
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiChain, NodeConnectionType.AiChain]),
getConnectionHintNoticeField([NodeConnectionTypes.AiChain, NodeConnectionTypes.AiChain]),
{
displayName: 'Model',
name: 'model',

View File

@ -1,7 +1,7 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import { AzureChatOpenAI } from '@langchain/openai';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -42,7 +42,7 @@ export class LmChatAzureOpenAi implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiLanguageModel],
outputs: [NodeConnectionTypes.AiLanguageModel],
outputNames: ['Model'],
credentials: [
{
@ -51,7 +51,7 @@ export class LmChatAzureOpenAi implements INodeType {
},
],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiChain, NodeConnectionType.AiAgent]),
getConnectionHintNoticeField([NodeConnectionTypes.AiChain, NodeConnectionTypes.AiAgent]),
{
displayName:
'If using JSON response format, you must include word "json" in the prompt in your chain or agent. Also, make sure to select latest models released post November 2023.',

View File

@ -2,7 +2,7 @@
import { ChatOpenAI, type ClientOptions } from '@langchain/openai';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -44,7 +44,7 @@ export class LmChatDeepSeek implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiLanguageModel],
outputs: [NodeConnectionTypes.AiLanguageModel],
outputNames: ['Model'],
credentials: [
{
@ -57,7 +57,7 @@ export class LmChatDeepSeek implements INodeType {
baseURL: '={{ $credentials?.url }}',
},
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiChain, NodeConnectionType.AiAgent]),
getConnectionHintNoticeField([NodeConnectionTypes.AiChain, NodeConnectionTypes.AiAgent]),
{
displayName:
'If using JSON response format, you must include word "json" in the prompt in your chain or agent. Also, make sure to select latest models released post November 2023.',

View File

@ -1,7 +1,7 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import type { SafetySetting } from '@google/generative-ai';
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
import { NodeConnectionType } from 'n8n-workflow';
import { NodeConnectionTypes } from 'n8n-workflow';
import type {
NodeError,
INodeType,
@ -52,7 +52,7 @@ export class LmChatGoogleGemini implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiLanguageModel],
outputs: [NodeConnectionTypes.AiLanguageModel],
outputNames: ['Model'],
credentials: [
{
@ -65,7 +65,7 @@ export class LmChatGoogleGemini implements INodeType {
baseURL: '={{ $credentials.host }}',
},
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiChain, NodeConnectionType.AiAgent]),
getConnectionHintNoticeField([NodeConnectionTypes.AiChain, NodeConnectionTypes.AiAgent]),
{
displayName: 'Model',
name: 'modelName',

View File

@ -4,7 +4,7 @@ import { ProjectsClient } from '@google-cloud/resource-manager';
import { ChatVertexAI } from '@langchain/google-vertexai';
import { formatPrivateKey } from 'n8n-nodes-base/dist/utils/utilities';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -50,7 +50,7 @@ export class LmChatGoogleVertex implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiLanguageModel],
outputs: [NodeConnectionTypes.AiLanguageModel],
outputNames: ['Model'],
credentials: [
{
@ -59,7 +59,7 @@ export class LmChatGoogleVertex implements INodeType {
},
],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiChain, NodeConnectionType.AiAgent]),
getConnectionHintNoticeField([NodeConnectionTypes.AiChain, NodeConnectionTypes.AiAgent]),
{
displayName: 'Project ID',
name: 'projectId',

View File

@ -1,7 +1,7 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import { ChatGroq } from '@langchain/groq';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -42,7 +42,7 @@ export class LmChatGroq implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiLanguageModel],
outputs: [NodeConnectionTypes.AiLanguageModel],
outputNames: ['Model'],
credentials: [
{
@ -54,7 +54,7 @@ export class LmChatGroq implements INodeType {
baseURL: 'https://api.groq.com/openai/v1',
},
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiChain, NodeConnectionType.AiChain]),
getConnectionHintNoticeField([NodeConnectionTypes.AiChain, NodeConnectionTypes.AiChain]),
{
displayName: 'Model',
name: 'model',

View File

@ -3,7 +3,7 @@
import type { ChatMistralAIInput } from '@langchain/mistralai';
import { ChatMistralAI } from '@langchain/mistralai';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -44,7 +44,7 @@ export class LmChatMistralCloud implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiLanguageModel],
outputs: [NodeConnectionTypes.AiLanguageModel],
outputNames: ['Model'],
credentials: [
{
@ -57,7 +57,7 @@ export class LmChatMistralCloud implements INodeType {
baseURL: 'https://api.mistral.ai/v1',
},
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiChain, NodeConnectionType.AiAgent]),
getConnectionHintNoticeField([NodeConnectionTypes.AiChain, NodeConnectionTypes.AiAgent]),
{
displayName: 'Model',
name: 'model',

View File

@ -2,7 +2,7 @@
import { ChatOpenAI, type ClientOptions } from '@langchain/openai';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -43,7 +43,7 @@ export class LmChatOpenRouter implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiLanguageModel],
outputs: [NodeConnectionTypes.AiLanguageModel],
outputNames: ['Model'],
credentials: [
{
@ -56,7 +56,7 @@ export class LmChatOpenRouter implements INodeType {
baseURL: '={{ $credentials?.url }}',
},
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiChain, NodeConnectionType.AiAgent]),
getConnectionHintNoticeField([NodeConnectionTypes.AiChain, NodeConnectionTypes.AiAgent]),
{
displayName:
'If using JSON response format, you must include word "json" in the prompt in your chain or agent. Also, make sure to select latest models released post November 2023.',

View File

@ -11,7 +11,7 @@ import type { LLMResult } from '@langchain/core/outputs';
import { encodingForModel } from '@langchain/core/utils/tiktoken';
import { pick } from 'lodash';
import type { IDataObject, ISupplyDataFunctions, JsonObject } from 'n8n-workflow';
import { NodeConnectionType, NodeError, NodeOperationError } from 'n8n-workflow';
import { NodeConnectionTypes, NodeError, NodeOperationError } from 'n8n-workflow';
import { logAiEvent } from '@utils/helpers';
@ -35,7 +35,7 @@ export class N8nLlmTracing extends BaseCallbackHandler {
// This is crucial for the handleLLMError handler to work correctly (it should be called before the error is propagated to the root node)
awaitHandlers = true;
connectionType = NodeConnectionType.AiLanguageModel;
connectionType = NodeConnectionTypes.AiLanguageModel;
promptTokensEstimate = 0;

View File

@ -2,7 +2,7 @@
import type { BufferWindowMemoryInput } from 'langchain/memory';
import { BufferWindowMemory } from 'langchain/memory';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -101,10 +101,10 @@ export class MemoryBufferWindow implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiMemory],
outputs: [NodeConnectionTypes.AiMemory],
outputNames: ['Memory'],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiAgent]),
getConnectionHintNoticeField([NodeConnectionTypes.AiAgent]),
{
displayName: 'Session Key',
name: 'sessionKey',

View File

@ -2,7 +2,7 @@
import type { BaseChatMemory } from '@langchain/community/memory/chat_memory';
import type { BaseMessage } from '@langchain/core/messages';
import {
NodeConnectionType,
NodeConnectionTypes,
type IDataObject,
type IExecuteFunctions,
type INodeExecutionData,
@ -61,16 +61,16 @@ export class MemoryChatRetriever implements INodeType {
},
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [
NodeConnectionType.Main,
NodeConnectionTypes.Main,
{
displayName: 'Memory',
maxConnections: 1,
type: NodeConnectionType.AiMemory,
type: NodeConnectionTypes.AiMemory,
required: true,
},
],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.Main],
outputs: [NodeConnectionTypes.Main],
properties: [
{
displayName: "This node is deprecated. Use 'Chat Memory Manager' node instead.",
@ -91,7 +91,7 @@ export class MemoryChatRetriever implements INodeType {
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
this.logger.debug('Executing Chat Memory Retriever');
const memory = (await this.getInputConnectionData(NodeConnectionType.AiMemory, 0)) as
const memory = (await this.getInputConnectionData(NodeConnectionTypes.AiMemory, 0)) as
| BaseChatMemory
| undefined;
const simplifyOutput = this.getNodeParameter('simplifyOutput', 0) as boolean;

View File

@ -1,7 +1,7 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import type { BaseChatMemory } from '@langchain/community/memory/chat_memory';
import { AIMessage, SystemMessage, HumanMessage, type BaseMessage } from '@langchain/core/messages';
import { NodeConnectionType } from 'n8n-workflow';
import { NodeConnectionTypes } from 'n8n-workflow';
import type {
IDataObject,
IExecuteFunctions,
@ -92,11 +92,11 @@ export class MemoryManager implements INodeType {
inputs: [
{
displayName: '',
type: NodeConnectionType.Main,
type: NodeConnectionTypes.Main,
},
{
displayName: 'Memory',
type: NodeConnectionType.AiMemory,
type: NodeConnectionTypes.AiMemory,
required: true,
maxConnections: 1,
},
@ -105,7 +105,7 @@ export class MemoryManager implements INodeType {
outputs: [
{
displayName: '',
type: NodeConnectionType.Main,
type: NodeConnectionTypes.Main,
},
],
properties: [
@ -297,7 +297,7 @@ export class MemoryManager implements INodeType {
const items = this.getInputData();
const mode = this.getNodeParameter('mode', 0, 'load') as 'load' | 'insert' | 'delete';
const memory = (await this.getInputConnectionData(
NodeConnectionType.AiMemory,
NodeConnectionTypes.AiMemory,
0,
)) as BaseChatMemory;

View File

@ -1,7 +1,7 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import { MotorheadMemory } from '@langchain/community/memory/motorhead_memory';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -42,7 +42,7 @@ export class MemoryMotorhead implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiMemory],
outputs: [NodeConnectionTypes.AiMemory],
outputNames: ['Memory'],
credentials: [
{
@ -51,7 +51,7 @@ export class MemoryMotorhead implements INodeType {
},
],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiAgent]),
getConnectionHintNoticeField([NodeConnectionTypes.AiAgent]),
{
displayName: 'Session ID',
name: 'sessionId',

View File

@ -10,7 +10,7 @@ import type {
INodeTypeDescription,
SupplyData,
} from 'n8n-workflow';
import { NodeConnectionType } from 'n8n-workflow';
import { NodeConnectionTypes } from 'n8n-workflow';
import type pg from 'pg';
import { getSessionId } from '@utils/helpers';
@ -58,10 +58,10 @@ export class MemoryPostgresChat implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiMemory],
outputs: [NodeConnectionTypes.AiMemory],
outputNames: ['Memory'],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiAgent]),
getConnectionHintNoticeField([NodeConnectionTypes.AiAgent]),
sessionIdOption,
expressionSessionKeyProperty(1.2),
sessionKeyProperty,

View File

@ -8,7 +8,7 @@ import {
type INodeTypeDescription,
type ISupplyDataFunctions,
type SupplyData,
NodeConnectionType,
NodeConnectionTypes,
} from 'n8n-workflow';
import type { RedisClientOptions } from 'redis';
import { createClient } from 'redis';
@ -57,10 +57,10 @@ export class MemoryRedisChat implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiMemory],
outputs: [NodeConnectionTypes.AiMemory],
outputNames: ['Memory'],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiAgent]),
getConnectionHintNoticeField([NodeConnectionTypes.AiAgent]),
{
displayName: 'Session Key',
name: 'sessionKey',

View File

@ -2,7 +2,7 @@
import { XataChatMessageHistory } from '@langchain/community/stores/message/xata';
import { BaseClient } from '@xata.io/client';
import { BufferMemory, BufferWindowMemory } from 'langchain/memory';
import { NodeConnectionType, NodeOperationError } from 'n8n-workflow';
import { NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';
import type {
ISupplyDataFunctions,
INodeType,
@ -50,7 +50,7 @@ export class MemoryXata implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiMemory],
outputs: [NodeConnectionTypes.AiMemory],
outputNames: ['Memory'],
credentials: [
{
@ -59,7 +59,7 @@ export class MemoryXata implements INodeType {
},
],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiAgent]),
getConnectionHintNoticeField([NodeConnectionTypes.AiAgent]),
{
displayName: 'Session ID',
name: 'sessionId',

View File

@ -5,7 +5,7 @@ import { ZepCloudMemory } from '@langchain/community/memory/zep_cloud';
import type { InputValues, MemoryVariables } from '@langchain/core/memory';
import type { BaseMessage } from '@langchain/core/messages';
import {
NodeConnectionType,
NodeConnectionTypes,
type ISupplyDataFunctions,
type INodeType,
type INodeTypeDescription,
@ -58,7 +58,7 @@ export class MemoryZep implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiMemory],
outputs: [NodeConnectionTypes.AiMemory],
outputNames: ['Memory'],
credentials: [
{
@ -67,7 +67,7 @@ export class MemoryZep implements INodeType {
},
],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiAgent]),
getConnectionHintNoticeField([NodeConnectionTypes.AiAgent]),
{
displayName: 'Only works with Zep Cloud and Community edition <= v0.27.2',
name: 'supportedVersions',

View File

@ -1,6 +1,6 @@
import type { BaseLanguageModel } from '@langchain/core/language_models/base';
import { PromptTemplate } from '@langchain/core/prompts';
import { NodeConnectionType, NodeOperationError } from 'n8n-workflow';
import { NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';
import type {
ISupplyDataFunctions,
INodeType,
@ -47,18 +47,18 @@ export class OutputParserAutofixing implements INodeType {
{
displayName: 'Model',
maxConnections: 1,
type: NodeConnectionType.AiLanguageModel,
type: NodeConnectionTypes.AiLanguageModel,
required: true,
},
{
displayName: 'Output Parser',
maxConnections: 1,
required: true,
type: NodeConnectionType.AiOutputParser,
type: NodeConnectionTypes.AiOutputParser,
},
],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiOutputParser],
outputs: [NodeConnectionTypes.AiOutputParser],
outputNames: ['Output Parser'],
properties: [
{
@ -68,7 +68,7 @@ export class OutputParserAutofixing implements INodeType {
type: 'notice',
default: '',
},
getConnectionHintNoticeField([NodeConnectionType.AiChain, NodeConnectionType.AiAgent]),
getConnectionHintNoticeField([NodeConnectionTypes.AiChain, NodeConnectionTypes.AiAgent]),
{
displayName: 'Options',
name: 'options',
@ -95,11 +95,11 @@ export class OutputParserAutofixing implements INodeType {
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
const model = (await this.getInputConnectionData(
NodeConnectionType.AiLanguageModel,
NodeConnectionTypes.AiLanguageModel,
itemIndex,
)) as BaseLanguageModel;
const outputParser = (await this.getInputConnectionData(
NodeConnectionType.AiOutputParser,
NodeConnectionTypes.AiOutputParser,
itemIndex,
)) as N8nStructuredOutputParser;
const prompt = this.getNodeParameter('options.prompt', itemIndex, NAIVE_FIX_PROMPT) as string;

View File

@ -5,8 +5,12 @@ import { OutputParserException } from '@langchain/core/output_parsers';
import type { MockProxy } from 'jest-mock-extended';
import { mock } from 'jest-mock-extended';
import { normalizeItems } from 'n8n-core';
import type { ISupplyDataFunctions, IWorkflowDataProxyData } from 'n8n-workflow';
import { ApplicationError, NodeConnectionType, NodeOperationError } from 'n8n-workflow';
import type {
ISupplyDataFunctions,
IWorkflowDataProxyData,
NodeConnectionType,
} from 'n8n-workflow';
import { ApplicationError, NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';
import type {
N8nOutputFixingParser,
@ -34,8 +38,8 @@ describe('OutputParserAutofixing', () => {
thisArg.addInputData.mockReturnValue({ index: 0 });
thisArg.addOutputData.mockReturnValue();
thisArg.getInputConnectionData.mockImplementation(async (type: NodeConnectionType) => {
if (type === NodeConnectionType.AiLanguageModel) return mockModel;
if (type === NodeConnectionType.AiOutputParser) return mockStructuredOutputParser;
if (type === NodeConnectionTypes.AiLanguageModel) return mockModel;
if (type === NodeConnectionTypes.AiOutputParser) return mockStructuredOutputParser;
throw new ApplicationError('Unexpected connection type');
});

View File

@ -1,6 +1,6 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -39,10 +39,10 @@ export class OutputParserItemList implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiOutputParser],
outputs: [NodeConnectionTypes.AiOutputParser],
outputNames: ['Output Parser'],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiChain, NodeConnectionType.AiAgent]),
getConnectionHintNoticeField([NodeConnectionTypes.AiChain, NodeConnectionTypes.AiAgent]),
{
displayName: 'Options',
name: 'options',

View File

@ -6,7 +6,7 @@ import {
type ISupplyDataFunctions,
type SupplyData,
NodeOperationError,
NodeConnectionType,
NodeConnectionTypes,
} from 'n8n-workflow';
import type { z } from 'zod';
@ -46,10 +46,10 @@ export class OutputParserStructured implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiOutputParser],
outputs: [NodeConnectionTypes.AiOutputParser],
outputNames: ['Output Parser'],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiChain, NodeConnectionType.AiAgent]),
getConnectionHintNoticeField([NodeConnectionTypes.AiChain, NodeConnectionTypes.AiAgent]),
{ ...schemaTypeField, displayOptions: { show: { '@version': [{ _cnd: { gte: 1.2 } }] } } },
{
...jsonSchemaExampleField,

View File

@ -5,7 +5,7 @@ import type { BaseRetriever } from '@langchain/core/retrievers';
import { ContextualCompressionRetriever } from 'langchain/retrievers/contextual_compression';
import { LLMChainExtractor } from 'langchain/retrievers/document_compressors/chain_extract';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -44,13 +44,13 @@ export class RetrieverContextualCompression implements INodeType {
{
displayName: 'Model',
maxConnections: 1,
type: NodeConnectionType.AiLanguageModel,
type: NodeConnectionTypes.AiLanguageModel,
required: true,
},
{
displayName: 'Retriever',
maxConnections: 1,
type: NodeConnectionType.AiRetriever,
type: NodeConnectionTypes.AiRetriever,
required: true,
},
],
@ -58,7 +58,7 @@ export class RetrieverContextualCompression implements INodeType {
{
displayName: 'Retriever',
maxConnections: 1,
type: NodeConnectionType.AiRetriever,
type: NodeConnectionTypes.AiRetriever,
},
],
properties: [],
@ -68,12 +68,12 @@ export class RetrieverContextualCompression implements INodeType {
this.logger.debug('Supplying data for Contextual Compression Retriever');
const model = (await this.getInputConnectionData(
NodeConnectionType.AiLanguageModel,
NodeConnectionTypes.AiLanguageModel,
itemIndex,
)) as BaseLanguageModel;
const baseRetriever = (await this.getInputConnectionData(
NodeConnectionType.AiRetriever,
NodeConnectionTypes.AiRetriever,
itemIndex,
)) as BaseRetriever;

View File

@ -4,7 +4,7 @@ import type { BaseLanguageModel } from '@langchain/core/language_models/base';
import type { BaseRetriever } from '@langchain/core/retrievers';
import { MultiQueryRetriever } from 'langchain/retrievers/multi_query';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -44,13 +44,13 @@ export class RetrieverMultiQuery implements INodeType {
{
displayName: 'Model',
maxConnections: 1,
type: NodeConnectionType.AiLanguageModel,
type: NodeConnectionTypes.AiLanguageModel,
required: true,
},
{
displayName: 'Retriever',
maxConnections: 1,
type: NodeConnectionType.AiRetriever,
type: NodeConnectionTypes.AiRetriever,
required: true,
},
],
@ -58,7 +58,7 @@ export class RetrieverMultiQuery implements INodeType {
{
displayName: 'Retriever',
maxConnections: 1,
type: NodeConnectionType.AiRetriever,
type: NodeConnectionTypes.AiRetriever,
},
],
properties: [
@ -89,12 +89,12 @@ export class RetrieverMultiQuery implements INodeType {
const options = this.getNodeParameter('options', itemIndex, {}) as { queryCount?: number };
const model = (await this.getInputConnectionData(
NodeConnectionType.AiLanguageModel,
NodeConnectionTypes.AiLanguageModel,
itemIndex,
)) as BaseLanguageModel;
const baseRetriever = (await this.getInputConnectionData(
NodeConnectionType.AiRetriever,
NodeConnectionTypes.AiRetriever,
itemIndex,
)) as BaseRetriever;

View File

@ -1,7 +1,7 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import type { VectorStore } from '@langchain/core/vectorstores';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -40,12 +40,12 @@ export class RetrieverVectorStore implements INodeType {
{
displayName: 'Vector Store',
maxConnections: 1,
type: NodeConnectionType.AiVectorStore,
type: NodeConnectionTypes.AiVectorStore,
required: true,
},
],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiRetriever],
outputs: [NodeConnectionTypes.AiRetriever],
outputNames: ['Retriever'],
properties: [
{
@ -63,7 +63,7 @@ export class RetrieverVectorStore implements INodeType {
const topK = this.getNodeParameter('topK', itemIndex, 4) as number;
const vectorStore = (await this.getInputConnectionData(
NodeConnectionType.AiVectorStore,
NodeConnectionTypes.AiVectorStore,
itemIndex,
)) as VectorStore;

View File

@ -4,7 +4,7 @@ import { Document } from '@langchain/core/documents';
import { BaseRetriever, type BaseRetrieverInput } from '@langchain/core/retrievers';
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 { NodeConnectionType, NodeOperationError } from 'n8n-workflow';
import { NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';
import type {
IDataObject,
IExecuteWorkflowInfo,
@ -66,7 +66,7 @@ export class RetrieverWorkflow implements INodeType {
{
displayName: 'Retriever',
maxConnections: 1,
type: NodeConnectionType.AiRetriever,
type: NodeConnectionTypes.AiRetriever,
},
],
properties: [

View File

@ -2,7 +2,7 @@
import type { CharacterTextSplitterParams } from '@langchain/textsplitters';
import { CharacterTextSplitter } from '@langchain/textsplitters';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -40,10 +40,10 @@ export class TextSplitterCharacterTextSplitter implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiTextSplitter],
outputs: [NodeConnectionTypes.AiTextSplitter],
outputNames: ['Text Splitter'],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiDocument]),
getConnectionHintNoticeField([NodeConnectionTypes.AiDocument]),
{
displayName: 'Separator',
name: 'separator',

View File

@ -5,7 +5,7 @@ import type {
} from '@langchain/textsplitters';
import { RecursiveCharacterTextSplitter } from '@langchain/textsplitters';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -60,10 +60,10 @@ export class TextSplitterRecursiveCharacterTextSplitter implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiTextSplitter],
outputs: [NodeConnectionTypes.AiTextSplitter],
outputNames: ['Text Splitter'],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiDocument]),
getConnectionHintNoticeField([NodeConnectionTypes.AiDocument]),
{
displayName: 'Chunk Size',
name: 'chunkSize',

View File

@ -1,7 +1,7 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import { TokenTextSplitter } from '@langchain/textsplitters';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -39,10 +39,10 @@ export class TextSplitterTokenSplitter implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiTextSplitter],
outputs: [NodeConnectionTypes.AiTextSplitter],
outputNames: ['Text Splitter'],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiDocument]),
getConnectionHintNoticeField([NodeConnectionTypes.AiDocument]),
{
displayName: 'Chunk Size',
name: 'chunkSize',

View File

@ -1,7 +1,7 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import { Calculator } from '@langchain/community/tools/calculator';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -40,9 +40,9 @@ export class ToolCalculator implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiTool],
outputs: [NodeConnectionTypes.AiTool],
outputNames: ['Tool'],
properties: [getConnectionHintNoticeField([NodeConnectionType.AiAgent])],
properties: [getConnectionHintNoticeField([NodeConnectionTypes.AiAgent])],
};
async supplyData(this: ISupplyDataFunctions): Promise<SupplyData> {

View File

@ -13,7 +13,7 @@ import type {
ExecutionError,
IDataObject,
} from 'n8n-workflow';
import { jsonParse, NodeConnectionType, NodeOperationError } from 'n8n-workflow';
import { jsonParse, NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';
import {
buildInputSchemaField,
@ -54,10 +54,10 @@ export class ToolCode implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiTool],
outputs: [NodeConnectionTypes.AiTool],
outputNames: ['Tool'],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiAgent]),
getConnectionHintNoticeField([NodeConnectionTypes.AiAgent]),
{
displayName:
'See an example of a conversational agent with custom tool written in JavaScript <a href="/templates/1963" target="_blank">here</a>.',
@ -221,7 +221,7 @@ export class ToolCode implements INodeType {
};
const toolHandler = async (query: string | IDataObject): Promise<string> => {
const { index } = this.addInputData(NodeConnectionType.AiTool, [[{ json: { query } }]]);
const { index } = this.addInputData(NodeConnectionTypes.AiTool, [[{ json: { query } }]]);
let response: string = '';
let executionError: ExecutionError | undefined;
@ -245,9 +245,9 @@ export class ToolCode implements INodeType {
}
if (executionError) {
void this.addOutputData(NodeConnectionType.AiTool, index, executionError);
void this.addOutputData(NodeConnectionTypes.AiTool, index, executionError);
} else {
void this.addOutputData(NodeConnectionType.AiTool, index, [[{ json: { response } }]]);
void this.addOutputData(NodeConnectionTypes.AiTool, index, [[{ json: { response } }]]);
}
return response;

View File

@ -8,7 +8,11 @@ import type {
IHttpRequestMethods,
IHttpRequestOptions,
} from 'n8n-workflow';
import { NodeConnectionType, NodeOperationError, tryToParseAlphanumericString } from 'n8n-workflow';
import {
NodeConnectionTypes,
NodeOperationError,
tryToParseAlphanumericString,
} from 'n8n-workflow';
import { N8nTool } from '@utils/N8nTool';
import { getConnectionHintNoticeField } from '@utils/sharedFields';
@ -62,10 +66,10 @@ export class ToolHttpRequest implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiTool],
outputs: [NodeConnectionTypes.AiTool],
outputNames: ['Tool'],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiAgent]),
getConnectionHintNoticeField([NodeConnectionTypes.AiAgent]),
{
displayName: 'Description',
name: 'toolDescription',

View File

@ -14,7 +14,7 @@ import type {
NodeApiError,
ISupplyDataFunctions,
} from 'n8n-workflow';
import { NodeConnectionType, NodeOperationError, jsonParse } from 'n8n-workflow';
import { NodeConnectionTypes, NodeOperationError, jsonParse } from 'n8n-workflow';
import { z } from 'zod';
import type {
@ -585,7 +585,7 @@ export const configureToolFunction = (
optimizeResponse: (response: string) => string,
) => {
return async (query: string | IDataObject): Promise<string> => {
const { index } = ctx.addInputData(NodeConnectionType.AiTool, [[{ json: { query } }]]);
const { index } = ctx.addInputData(NodeConnectionTypes.AiTool, [[{ json: { query } }]]);
// Clone options and rawRequestOptions to avoid mutating the original objects
const options: IHttpRequestOptions | null = structuredClone(requestOptions);
@ -792,9 +792,9 @@ export const configureToolFunction = (
}
if (executionError) {
void ctx.addOutputData(NodeConnectionType.AiTool, index, executionError as ExecutionError);
void ctx.addOutputData(NodeConnectionTypes.AiTool, index, executionError as ExecutionError);
} else {
void ctx.addOutputData(NodeConnectionType.AiTool, index, [[{ json: { response } }]]);
void ctx.addOutputData(NodeConnectionTypes.AiTool, index, [[{ json: { response } }]]);
}
return response;

View File

@ -1,7 +1,7 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import { SerpAPI } from '@langchain/community/tools/serpapi';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -39,7 +39,7 @@ export class ToolSerpApi implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiTool],
outputs: [NodeConnectionTypes.AiTool],
outputNames: ['Tool'],
credentials: [
{
@ -48,7 +48,7 @@ export class ToolSerpApi implements INodeType {
},
],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiAgent]),
getConnectionHintNoticeField([NodeConnectionTypes.AiAgent]),
{
displayName: 'Options',
name: 'options',

View File

@ -8,7 +8,7 @@ import type {
ISupplyDataFunctions,
SupplyData,
} from 'n8n-workflow';
import { NodeConnectionType } from 'n8n-workflow';
import { NodeConnectionTypes } from 'n8n-workflow';
import { logWrapper } from '@utils/logWrapper';
import { getConnectionHintNoticeField } from '@utils/sharedFields';
@ -44,21 +44,21 @@ export class ToolVectorStore implements INodeType {
{
displayName: 'Vector Store',
maxConnections: 1,
type: NodeConnectionType.AiVectorStore,
type: NodeConnectionTypes.AiVectorStore,
required: true,
},
{
displayName: 'Model',
maxConnections: 1,
type: NodeConnectionType.AiLanguageModel,
type: NodeConnectionTypes.AiLanguageModel,
required: true,
},
],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiTool],
outputs: [NodeConnectionTypes.AiTool],
outputNames: ['Tool'],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiAgent]),
getConnectionHintNoticeField([NodeConnectionTypes.AiAgent]),
{
displayName: 'Data Name',
name: 'name',
@ -97,12 +97,12 @@ export class ToolVectorStore implements INodeType {
const topK = this.getNodeParameter('topK', itemIndex, 4) as number;
const vectorStore = (await this.getInputConnectionData(
NodeConnectionType.AiVectorStore,
NodeConnectionTypes.AiVectorStore,
itemIndex,
)) as VectorStore;
const llm = (await this.getInputConnectionData(
NodeConnectionType.AiLanguageModel,
NodeConnectionTypes.AiLanguageModel,
0,
)) as BaseLanguageModel;

View File

@ -1,7 +1,7 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import { WikipediaQueryRun } from '@langchain/community/tools/wikipedia_query_run';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -39,9 +39,9 @@ export class ToolWikipedia implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiTool],
outputs: [NodeConnectionTypes.AiTool],
outputNames: ['Tool'],
properties: [getConnectionHintNoticeField([NodeConnectionType.AiAgent])],
properties: [getConnectionHintNoticeField([NodeConnectionTypes.AiAgent])],
};
async supplyData(this: ISupplyDataFunctions): Promise<SupplyData> {

View File

@ -1,7 +1,7 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import { WolframAlphaTool } from '@langchain/community/tools/wolframalpha';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -45,9 +45,9 @@ export class ToolWolframAlpha implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiTool],
outputs: [NodeConnectionTypes.AiTool],
outputNames: ['Tool'],
properties: [getConnectionHintNoticeField([NodeConnectionType.AiAgent])],
properties: [getConnectionHintNoticeField([NodeConnectionTypes.AiAgent])],
};
async supplyData(this: ISupplyDataFunctions): Promise<SupplyData> {

View File

@ -20,7 +20,7 @@ import type {
ITaskMetadata,
INodeTypeBaseDescription,
} from 'n8n-workflow';
import { NodeConnectionType, NodeOperationError, jsonParse } from 'n8n-workflow';
import { NodeConnectionTypes, NodeOperationError, jsonParse } from 'n8n-workflow';
import { versionDescription } from './versionDescription';
import type { DynamicZodObject } from '../../../../types/zod.types';
@ -148,7 +148,7 @@ export class ToolWorkflowV1 implements INodeType {
query: string | IDataObject,
runManager?: CallbackManagerForToolRun,
): Promise<string> => {
const { index } = this.addInputData(NodeConnectionType.AiTool, [[{ json: { query } }]]);
const { index } = this.addInputData(NodeConnectionTypes.AiTool, [[{ json: { query } }]]);
let response: string = '';
let executionError: ExecutionError | undefined;
@ -189,12 +189,12 @@ export class ToolWorkflowV1 implements INodeType {
}
if (executionError) {
void this.addOutputData(NodeConnectionType.AiTool, index, executionError, metadata);
void this.addOutputData(NodeConnectionTypes.AiTool, index, executionError, metadata);
} else {
// Output always needs to be an object
// so we try to parse the response as JSON and if it fails we just return the string wrapped in an object
const json = jsonParse<IDataObject>(response, { fallbackValue: { response } });
void this.addOutputData(NodeConnectionType.AiTool, index, [[{ json }]], metadata);
void this.addOutputData(NodeConnectionTypes.AiTool, index, [[{ json }]], metadata);
}
return response;
};

View File

@ -1,7 +1,7 @@
/* eslint-disable n8n-nodes-base/node-filename-against-convention */
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import type { INodeTypeDescription } from 'n8n-workflow';
import { NodeConnectionType } from 'n8n-workflow';
import { NodeConnectionTypes } from 'n8n-workflow';
import {
inputSchemaField,
@ -36,10 +36,10 @@ export const versionDescription: INodeTypeDescription = {
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiTool],
outputs: [NodeConnectionTypes.AiTool],
outputNames: ['Tool'],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiAgent]),
getConnectionHintNoticeField([NodeConnectionTypes.AiAgent]),
{
displayName:
'See an example of a workflow to suggest meeting slots using AI <a href="/templates/1953" target="_blank">here</a>.',

View File

@ -21,7 +21,7 @@ import type {
import {
generateZodSchema,
jsonParse,
NodeConnectionType,
NodeConnectionTypes,
NodeOperationError,
parseErrorMetadata,
traverseNodeParameters,
@ -113,7 +113,7 @@ export class WorkflowToolService {
}
void context.addOutputData(
NodeConnectionType.AiTool,
NodeConnectionTypes.AiTool,
localRunIndex,
[responseData],
metadata,
@ -126,7 +126,7 @@ export class WorkflowToolService {
const metadata = parseErrorMetadata(error);
void context.addOutputData(
NodeConnectionType.AiTool,
NodeConnectionTypes.AiTool,
localRunIndex,
executionError,
metadata,

View File

@ -1,6 +1,6 @@
/* eslint-disable n8n-nodes-base/node-filename-against-convention */
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import { NodeConnectionType, type INodeTypeDescription } from 'n8n-workflow';
import { NodeConnectionTypes, type INodeTypeDescription } from 'n8n-workflow';
import { getConnectionHintNoticeField } from '../../../../utils/sharedFields';
@ -14,10 +14,10 @@ export const versionDescription: INodeTypeDescription = {
},
version: [2, 2.1],
inputs: [],
outputs: [NodeConnectionType.AiTool],
outputs: [NodeConnectionTypes.AiTool],
outputNames: ['Tool'],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiAgent]),
getConnectionHintNoticeField([NodeConnectionTypes.AiAgent]),
{
displayName:
'See an example of a workflow to suggest meeting slots using AI <a href="/templates/1953" target="_blank">here</a>.',

View File

@ -1,6 +1,6 @@
import type { BaseChatMemory } from '@langchain/community/memory/chat_memory';
import { pick } from 'lodash';
import { Node, NodeConnectionType } from 'n8n-workflow';
import { Node, NodeConnectionTypes } from 'n8n-workflow';
import type {
IDataObject,
IWebhookFunctions,
@ -70,12 +70,12 @@ export class ChatTrigger extends Node {
{
displayName: 'Memory',
maxConnections: 1,
type: '${NodeConnectionType.AiMemory}',
type: '${NodeConnectionTypes.AiMemory}',
required: true,
}
];
})() }}`,
outputs: [NodeConnectionType.Main],
outputs: [NodeConnectionTypes.Main],
credentials: [
{
// eslint-disable-next-line n8n-nodes-base/node-class-description-credentials-name-unsuffixed
@ -554,7 +554,7 @@ ${cssVariables}
if (bodyData.action === 'loadPreviousSession') {
if (options?.loadPreviousSession === 'memory') {
const memory = (await ctx.getInputConnectionData(NodeConnectionType.AiMemory, 0)) as
const memory = (await ctx.getInputConnectionData(NodeConnectionTypes.AiMemory, 0)) as
| BaseChatMemory
| undefined;
const messages = ((await memory?.chatHistory.getMessages()) ?? [])

View File

@ -3,7 +3,7 @@ import {
type INodeType,
type INodeTypeDescription,
type ITriggerResponse,
NodeConnectionType,
NodeConnectionTypes,
} from 'n8n-workflow';
export class ManualChatTrigger implements INodeType {
@ -35,7 +35,7 @@ export class ManualChatTrigger implements INodeType {
},
},
inputs: [],
outputs: [NodeConnectionType.Main],
outputs: [NodeConnectionTypes.Main],
properties: [
{
displayName:

View File

@ -2,12 +2,12 @@ import type { MemoryVectorStore } from 'langchain/vectorstores/memory';
import type { INodeProperties } from 'n8n-workflow';
import { createVectorStoreNode } from '../shared/createVectorStoreNode/createVectorStoreNode';
import { MemoryVectorStoreManager } from '../shared/MemoryVectorStoreManager';
import { MemoryVectorStoreManager } from '../shared/MemoryManager/MemoryVectorStoreManager';
const insertFields: INodeProperties[] = [
{
displayName:
'The embedded data are stored in the server memory, so they will be lost when the server is restarted. Additionally, if the amount of data is too large, it may cause the server to crash due to insufficient memory.',
'<strong>For experimental use only</strong>: Data is stored in memory and will be lost if n8n restarts. Data may also be cleared if available memory gets low. <a href="https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.vectorstoreinmemory/">More info</a>',
name: 'notice',
type: 'notice',
default: '',
@ -48,7 +48,7 @@ export class VectorStoreInMemory extends createVectorStoreNode<MemoryVectorStore
async getVectorStoreClient(context, _filter, embeddings, itemIndex) {
const workflowId = context.getWorkflow().id;
const memoryKey = context.getNodeParameter('memoryKey', itemIndex) as string;
const vectorStoreSingleton = MemoryVectorStoreManager.getInstance(embeddings);
const vectorStoreSingleton = MemoryVectorStoreManager.getInstance(embeddings, context.logger);
return await vectorStoreSingleton.getVectorStore(`${workflowId}__${memoryKey}`);
},
@ -56,7 +56,7 @@ export class VectorStoreInMemory extends createVectorStoreNode<MemoryVectorStore
const memoryKey = context.getNodeParameter('memoryKey', itemIndex) as string;
const clearStore = context.getNodeParameter('clearStore', itemIndex) as boolean;
const workflowId = context.getWorkflow().id;
const vectorStoreInstance = MemoryVectorStoreManager.getInstance(embeddings);
const vectorStoreInstance = MemoryVectorStoreManager.getInstance(embeddings, context.logger);
await vectorStoreInstance.addDocuments(`${workflowId}__${memoryKey}`, documents, clearStore);
},

View File

@ -2,7 +2,7 @@
import type { Embeddings } from '@langchain/core/embeddings';
import type { Document } from 'langchain/document';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeExecutionData,
type IExecuteFunctions,
type INodeType,
@ -11,7 +11,7 @@ import {
import type { N8nJsonLoader } from '@utils/N8nJsonLoader';
import { MemoryVectorStoreManager } from '../shared/MemoryVectorStoreManager';
import { MemoryVectorStoreManager } from '../shared/MemoryManager/MemoryVectorStoreManager';
import { processDocuments } from '../shared/processDocuments';
// This node is deprecated. Use VectorStoreInMemory instead.
@ -42,21 +42,21 @@ export class VectorStoreInMemoryInsert implements INodeType {
},
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [
NodeConnectionType.Main,
NodeConnectionTypes.Main,
{
displayName: 'Document',
maxConnections: 1,
type: NodeConnectionType.AiDocument,
type: NodeConnectionTypes.AiDocument,
required: true,
},
{
displayName: 'Embedding',
maxConnections: 1,
type: NodeConnectionType.AiEmbedding,
type: NodeConnectionTypes.AiEmbedding,
required: true,
},
],
outputs: [NodeConnectionType.Main],
outputs: [NodeConnectionTypes.Main],
properties: [
{
displayName:
@ -86,13 +86,13 @@ export class VectorStoreInMemoryInsert implements INodeType {
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData(0);
const embeddings = (await this.getInputConnectionData(
NodeConnectionType.AiEmbedding,
NodeConnectionTypes.AiEmbedding,
0,
)) as Embeddings;
const memoryKey = this.getNodeParameter('memoryKey', 0) as string;
const clearStore = this.getNodeParameter('clearStore', 0) as boolean;
const documentInput = (await this.getInputConnectionData(NodeConnectionType.AiDocument, 0)) as
const documentInput = (await this.getInputConnectionData(NodeConnectionTypes.AiDocument, 0)) as
| N8nJsonLoader
| Array<Document<Record<string, unknown>>>;
@ -103,7 +103,7 @@ export class VectorStoreInMemoryInsert implements INodeType {
const workflowId = this.getWorkflow().id;
const vectorStoreInstance = MemoryVectorStoreManager.getInstance(embeddings);
const vectorStoreInstance = MemoryVectorStoreManager.getInstance(embeddings, this.logger);
await vectorStoreInstance.addDocuments(
`${workflowId}__${memoryKey}`,
processedDocuments,

View File

@ -1,7 +1,7 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import type { Embeddings } from '@langchain/core/embeddings';
import {
NodeConnectionType,
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
@ -10,7 +10,7 @@ import {
import { logWrapper } from '@utils/logWrapper';
import { MemoryVectorStoreManager } from '../shared/MemoryVectorStoreManager';
import { MemoryVectorStoreManager } from '../shared/MemoryManager/MemoryVectorStoreManager';
// This node is deprecated. Use VectorStoreInMemory instead.
export class VectorStoreInMemoryLoad implements INodeType {
@ -43,11 +43,11 @@ export class VectorStoreInMemoryLoad implements INodeType {
{
displayName: 'Embedding',
maxConnections: 1,
type: NodeConnectionType.AiEmbedding,
type: NodeConnectionTypes.AiEmbedding,
required: true,
},
],
outputs: [NodeConnectionType.AiVectorStore],
outputs: [NodeConnectionTypes.AiVectorStore],
outputNames: ['Vector Store'],
properties: [
{
@ -63,14 +63,14 @@ export class VectorStoreInMemoryLoad implements INodeType {
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
const embeddings = (await this.getInputConnectionData(
NodeConnectionType.AiEmbedding,
NodeConnectionTypes.AiEmbedding,
itemIndex,
)) as Embeddings;
const workflowId = this.getWorkflow().id;
const memoryKey = this.getNodeParameter('memoryKey', 0) as string;
const vectorStoreSingleton = MemoryVectorStoreManager.getInstance(embeddings);
const vectorStoreSingleton = MemoryVectorStoreManager.getInstance(embeddings, this.logger);
const vectorStoreInstance = await vectorStoreSingleton.getVectorStore(
`${workflowId}__${memoryKey}`,
);

Some files were not shown because too many files have changed in this diff Show More