Skip to content

Commit 076d7b2

Browse files
feat(core): Send AI Node SDK version to Strapi community nodes API (#25760)
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent a270892 commit 076d7b2

14 files changed

+84
-31
lines changed

packages/@n8n/ai-utilities/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"name": "@n8n/ai-utilities",
33
"version": "0.3.0",
44
"description": "Utilities for building AI nodes in n8n",
5+
"aiNodeSdkVersion": 1,
56
"main": "dist/index.js",
67
"module": "src/index.ts",
78
"types": "dist/index.d.ts",
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { readFileSync } from 'fs';
2+
import { jsonParse } from 'n8n-workflow';
3+
import { resolve } from 'path';
4+
5+
function readAiNodeSdkVersion(): number {
6+
const pkgPath = resolve(__dirname, '..', 'package.json');
7+
const pkg = jsonParse<{ aiNodeSdkVersion: number }>(readFileSync(pkgPath, 'utf-8'));
8+
return pkg.aiNodeSdkVersion;
9+
}
10+
11+
export const AI_NODE_SDK_VERSION: number = readAiNodeSdkVersion();

packages/@n8n/ai-utilities/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// AI Node SDK version
2+
export { AI_NODE_SDK_VERSION } from './ai-node-sdk-version';
3+
14
// Log wrapper and related utilities
25
export { logWrapper } from './utils/log-wrapper';
36
export { logAiEvent } from './utils/log-ai-event';

packages/cli/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
9898
"@azure/identity": "catalog:",
9999
"@azure/keyvault-secrets": "4.8.0",
100100
"@google-cloud/secret-manager": "5.6.0",
101+
"@n8n/ai-utilities": "workspace:*",
101102
"@n8n/ai-workflow-builder": "workspace:*",
102103
"@n8n/api-types": "workspace:*",
103104
"@n8n/backend-common": "workspace:*",

packages/cli/src/modules/community-packages/__tests__/community-node-types-utils.test.ts

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ jest.mock('../strapi-utils', () => ({
77

88
const mockPaginatedRequest = paginatedRequest as jest.MockedFunction<typeof paginatedRequest>;
99

10+
const AI_SDK_VERSION = 1;
11+
1012
describe('community-node-types-utils', () => {
1113
beforeEach(() => {
1214
jest.clearAllMocks();
@@ -16,9 +18,10 @@ describe('community-node-types-utils', () => {
1618
it('should call paginatedRequest with correct URL for production', async () => {
1719
mockPaginatedRequest.mockResolvedValue([]);
1820

19-
await getCommunityNodeTypes('production');
21+
await getCommunityNodeTypes('production', {}, AI_SDK_VERSION);
2022

2123
expect(mockPaginatedRequest).toHaveBeenCalledWith('https://api.n8n.io/api/community-nodes', {
24+
includeAiNodesSdkVersion: AI_SDK_VERSION,
2225
pagination: {
2326
page: 1,
2427
pageSize: 25,
@@ -29,11 +32,12 @@ describe('community-node-types-utils', () => {
2932
it('should call paginatedRequest with correct URL for staging', async () => {
3033
mockPaginatedRequest.mockResolvedValue([]);
3134

32-
await getCommunityNodeTypes('staging');
35+
await getCommunityNodeTypes('staging', {}, AI_SDK_VERSION);
3336

3437
expect(mockPaginatedRequest).toHaveBeenCalledWith(
3538
'https://api-staging.n8n.io/api/community-nodes',
3639
{
40+
includeAiNodesSdkVersion: AI_SDK_VERSION,
3741
pagination: {
3842
page: 1,
3943
pageSize: 25,
@@ -50,11 +54,12 @@ describe('community-node-types-utils', () => {
5054
fields: ['name', 'version'],
5155
};
5256

53-
await getCommunityNodeTypes('production', qs);
57+
await getCommunityNodeTypes('production', qs, AI_SDK_VERSION);
5458

5559
expect(mockPaginatedRequest).toHaveBeenCalledWith('https://api.n8n.io/api/community-nodes', {
5660
filters: { packageName: { $eq: 'test-package' } },
5761
fields: ['name', 'version'],
62+
includeAiNodesSdkVersion: AI_SDK_VERSION,
5863
pagination: {
5964
page: 1,
6065
pageSize: 25,
@@ -69,7 +74,7 @@ describe('community-node-types-utils', () => {
6974
];
7075
mockPaginatedRequest.mockResolvedValue(mockData as any);
7176

72-
const result = await getCommunityNodeTypes('production');
77+
const result = await getCommunityNodeTypes('production', {}, AI_SDK_VERSION);
7378

7479
expect(result).toEqual(mockData);
7580
});
@@ -79,12 +84,13 @@ describe('community-node-types-utils', () => {
7984
it('should call paginatedRequest with correct URL for production', async () => {
8085
mockPaginatedRequest.mockResolvedValue([]);
8186

82-
await getCommunityNodesMetadata('production');
87+
await getCommunityNodesMetadata('production', AI_SDK_VERSION);
8388

8489
expect(mockPaginatedRequest).toHaveBeenCalledWith(
8590
'https://api.n8n.io/api/community-nodes',
8691
{
8792
fields: ['npmVersion', 'name', 'updatedAt'],
93+
includeAiNodesSdkVersion: AI_SDK_VERSION,
8894
pagination: {
8995
page: 1,
9096
pageSize: 500,
@@ -97,12 +103,13 @@ describe('community-node-types-utils', () => {
97103
it('should call paginatedRequest with correct URL for staging', async () => {
98104
mockPaginatedRequest.mockResolvedValue([]);
99105

100-
await getCommunityNodesMetadata('staging');
106+
await getCommunityNodesMetadata('staging', AI_SDK_VERSION);
101107

102108
expect(mockPaginatedRequest).toHaveBeenCalledWith(
103109
'https://api-staging.n8n.io/api/community-nodes',
104110
{
105111
fields: ['npmVersion', 'name', 'updatedAt'],
112+
includeAiNodesSdkVersion: AI_SDK_VERSION,
106113
pagination: {
107114
page: 1,
108115
pageSize: 500,
@@ -115,14 +122,14 @@ describe('community-node-types-utils', () => {
115122
it('should use larger pageSize than getCommunityNodeTypes', async () => {
116123
mockPaginatedRequest.mockResolvedValue([]);
117124

118-
await getCommunityNodesMetadata('production');
125+
await getCommunityNodesMetadata('production', AI_SDK_VERSION);
119126

120127
const metadataCall = mockPaginatedRequest.mock.calls[0];
121128
expect(metadataCall[1].pagination.pageSize).toBe(500);
122129

123130
mockPaginatedRequest.mockClear();
124131

125-
await getCommunityNodeTypes('production');
132+
await getCommunityNodeTypes('production', {}, AI_SDK_VERSION);
126133

127134
const nodeTypesCall = mockPaginatedRequest.mock.calls[0];
128135
expect(nodeTypesCall[1].pagination.pageSize).toBe(25);
@@ -131,7 +138,7 @@ describe('community-node-types-utils', () => {
131138
it('should request only specific fields', async () => {
132139
mockPaginatedRequest.mockResolvedValue([]);
133140

134-
await getCommunityNodesMetadata('production');
141+
await getCommunityNodesMetadata('production', AI_SDK_VERSION);
135142

136143
expect(mockPaginatedRequest).toHaveBeenCalledWith(
137144
expect.any(String),
@@ -149,7 +156,7 @@ describe('community-node-types-utils', () => {
149156
];
150157
mockPaginatedRequest.mockResolvedValue(mockMetadata as any);
151158

152-
const result = await getCommunityNodesMetadata('production');
159+
const result = await getCommunityNodesMetadata('production', AI_SDK_VERSION);
153160

154161
expect(result).toEqual(mockMetadata);
155162
});

packages/cli/src/modules/community-packages/__tests__/community-node-types.service.test.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ describe('CommunityNodeTypesService', () => {
3131
configMock = {
3232
enabled: true,
3333
verifiedEnabled: true,
34+
aiNodeSdkVersion: 1,
3435
};
3536
communityPackagesServiceMock = {};
3637

@@ -51,26 +52,26 @@ describe('CommunityNodeTypesService', () => {
5152
it('should use staging environment when ENVIRONMENT=staging', async () => {
5253
process.env.ENVIRONMENT = 'staging';
5354
await (service as any).fetchNodeTypes();
54-
expect(getCommunityNodeTypes).toHaveBeenCalledWith('staging');
55+
expect(getCommunityNodeTypes).toHaveBeenCalledWith('staging', {}, 1);
5556
});
5657

5758
it('should use production environment when inProduction=true', async () => {
5859
(inProduction as unknown as jest.Mock).mockReturnValue(true);
5960
await (service as any).fetchNodeTypes();
60-
expect(getCommunityNodeTypes).toHaveBeenCalledWith('production');
61+
expect(getCommunityNodeTypes).toHaveBeenCalledWith('production', {}, 1);
6162
});
6263

6364
it('should use production environment when ENVIRONMENT=production', async () => {
6465
process.env.ENVIRONMENT = 'production';
6566
await (service as any).fetchNodeTypes();
66-
expect(getCommunityNodeTypes).toHaveBeenCalledWith('production');
67+
expect(getCommunityNodeTypes).toHaveBeenCalledWith('production', {}, 1);
6768
});
6869

6970
it('should prioritize ENVIRONMENT=staging over inProduction=true', async () => {
7071
process.env.ENVIRONMENT = 'staging';
7172
(inProduction as unknown as jest.Mock).mockReturnValue(true);
7273
await (service as any).fetchNodeTypes();
73-
expect(getCommunityNodeTypes).toHaveBeenCalledWith('staging');
74+
expect(getCommunityNodeTypes).toHaveBeenCalledWith('staging', {}, 1);
7475
});
7576

7677
it('should call setTimestampForRetry when detectUpdates returns scheduleRetry', async () => {

packages/cli/src/modules/community-packages/__tests__/community-packages.service.test.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -804,10 +804,14 @@ describe('CommunityPackagesService', () => {
804804

805805
await communityPackagesService.checkForMissingPackages();
806806

807-
expect(getCommunityNodeTypes).toHaveBeenCalledWith('production', {
808-
filters: { packageName: { $in: ['package-1'] } },
809-
fields: ['packageName', 'npmVersion', 'checksum', 'nodeVersions'],
810-
});
807+
expect(getCommunityNodeTypes).toHaveBeenCalledWith(
808+
'production',
809+
{
810+
filters: { packageName: { $in: ['package-1'] } },
811+
fields: ['packageName', 'npmVersion', 'checksum', 'nodeVersions'],
812+
},
813+
config.aiNodeSdkVersion,
814+
);
811815
});
812816

813817
test('should use staging environment when ENVIRONMENT is set to staging', async () => {
@@ -825,10 +829,14 @@ describe('CommunityPackagesService', () => {
825829
try {
826830
await communityPackagesService.checkForMissingPackages();
827831

828-
expect(getCommunityNodeTypes).toHaveBeenCalledWith('staging', {
829-
filters: { packageName: { $in: ['package-1'] } },
830-
fields: ['packageName', 'npmVersion', 'checksum', 'nodeVersions'],
831-
});
832+
expect(getCommunityNodeTypes).toHaveBeenCalledWith(
833+
'staging',
834+
{
835+
filters: { packageName: { $in: ['package-1'] } },
836+
fields: ['packageName', 'npmVersion', 'checksum', 'nodeVersions'],
837+
},
838+
config.aiNodeSdkVersion,
839+
);
832840
} finally {
833841
// Restore original environment
834842
if (originalEnv === undefined) {

packages/cli/src/modules/community-packages/community-node-types-utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export type StrapiCommunityNodeType = {
2020
companyName?: string;
2121
nodeDescription: INodeTypeDescription;
2222
nodeVersions?: Array<{ npmVersion: string; checksum: string }>;
23+
aiNodeSdkVersion?: number;
2324
};
2425

2526
export type CommunityNodesMetadata = Pick<
@@ -39,10 +40,12 @@ function getUrl(environment: 'staging' | 'production'): string {
3940
export async function getCommunityNodeTypes(
4041
environment: 'staging' | 'production',
4142
qs: { filters?: StrapiFilters; fields?: string[] } = {},
43+
aiNodeSdkVersion: number,
4244
): Promise<StrapiCommunityNodeType[]> {
4345
const url = getUrl(environment);
4446
const params = {
4547
...qs,
48+
includeAiNodesSdkVersion: aiNodeSdkVersion,
4649
pagination: {
4750
page: 1,
4851
pageSize: 25,
@@ -53,10 +56,12 @@ export async function getCommunityNodeTypes(
5356

5457
export async function getCommunityNodesMetadata(
5558
environment: 'staging' | 'production',
59+
aiNodeSdkVersion: number,
5660
): Promise<CommunityNodesMetadata[]> {
5761
const url = getUrl(environment);
5862
const params = {
5963
fields: ['npmVersion', 'name', 'updatedAt'],
64+
includeAiNodesSdkVersion: aiNodeSdkVersion,
6065
pagination: {
6166
page: 1,
6267
pageSize: 500,

packages/cli/src/modules/community-packages/community-node-types.service.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ export class CommunityNodeTypesService {
3434
): Promise<{ typesToUpdate?: number[]; scheduleRetry?: boolean }> {
3535
let communityNodesMetadata: CommunityNodesMetadata[] = [];
3636
try {
37-
communityNodesMetadata = await getCommunityNodesMetadata(environment);
37+
communityNodesMetadata = await getCommunityNodesMetadata(
38+
environment,
39+
this.config.aiNodeSdkVersion,
40+
);
3841
} catch (error) {
3942
this.logger.error('Failed to fetch community nodes metadata', {
4043
error: ensureError(error),
@@ -82,7 +85,7 @@ export class CommunityNodeTypesService {
8285
let data: StrapiCommunityNodeType[] = [];
8386
if (this.config.enabled && this.config.verifiedEnabled) {
8487
if (this.communityNodeTypes.size === 0) {
85-
data = await getCommunityNodeTypes(environment);
88+
data = await getCommunityNodeTypes(environment, {}, this.config.aiNodeSdkVersion);
8689
this.updateCommunityNodeTypes(data);
8790
return;
8891
}
@@ -99,7 +102,7 @@ export class CommunityNodeTypesService {
99102
}
100103

101104
const qs = buildStrapiUpdateQuery(typesToUpdate);
102-
data = await getCommunityNodeTypes(environment, qs);
105+
data = await getCommunityNodeTypes(environment, qs, this.config.aiNodeSdkVersion);
103106
}
104107

105108
this.updateCommunityNodeTypes(data);

packages/cli/src/modules/community-packages/community-packages.config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { AI_NODE_SDK_VERSION } from '@n8n/ai-utilities';
12
import { Config, Env } from '@n8n/config';
23

34
@Config
@@ -25,4 +26,7 @@ export class CommunityPackagesConfig {
2526
/** Whether to load community packages */
2627
@Env('N8N_COMMUNITY_PACKAGES_PREVENT_LOADING')
2728
preventLoading: boolean = false;
29+
30+
/** Current AI Node SDK version from @n8n/ai-utilities, sent to Strapi API */
31+
readonly aiNodeSdkVersion: number = AI_NODE_SDK_VERSION;
2832
}

0 commit comments

Comments
 (0)