first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled

This commit is contained in:
2026-03-17 16:22:57 +03:30
commit 3d5eaf9445
15349 changed files with 2847338 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
dist
+351
View File
@@ -0,0 +1,351 @@
# @n8n/ai-node-sdk
> **Preview:** This package is in preview. The API may change without notice. AI nodes are not yet accepted for verification.
Public SDK for building AI nodes in n8n. This package provides a simplified API for creating chat model and memory nodes without LangChain dependencies.
## Installation in node packages
Include the package in your node packages by updating `peerDependencies`:
```json
{
"peerDependencies": {
"n8n-workflow": "*",
"@n8n/ai-node-sdk": "*"
}
}
```
## Development
```bash
# Build the package
pnpm build
# Run tests
pnpm test
# Run in watch mode
pnpm dev
```
## Chat Model Nodes
Chat model nodes implement the `INodeType` interface and use `supplyModel` to provide model instances.
### Simple Pattern: OpenAI-Compatible Providers
For OpenAI-compatible providers, use the config object pattern with `supplyModel`:
```typescript
import { supplyModel } from '@n8n/ai-node-sdk';
import {
type INodeType,
type INodeTypeDescription,
NodeConnectionTypes,
type SupplyData,
type ISupplyDataFunctions,
} from 'n8n-workflow';
export class LmChatMyProvider implements INodeType {
description: INodeTypeDescription = {
displayName: 'MyProvider Chat Model',
name: 'lmChatMyProvider',
icon: 'fa:robot',
group: ['transform'],
version: [1],
description: 'For advanced usage with an AI chain',
defaults: {
name: 'MyProvider Chat Model',
},
inputs: [],
outputs: [NodeConnectionTypes.AiLanguageModel],
credentials: [{ name: 'myProviderApi', required: true }],
properties: [
{
displayName: 'Model',
name: 'model',
type: 'string',
default: 'my-model',
},
{
displayName: 'Temperature',
name: 'temperature',
type: 'number',
default: 0.7,
},
],
};
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
const credentials = await this.getCredentials('myProviderApi');
const model = this.getNodeParameter('model', itemIndex) as string;
const temperature = this.getNodeParameter('temperature', itemIndex) as number;
// Return config for OpenAI-compatible providers
return supplyModel(this, {
type: 'openai',
baseUrl: credentials.url as string,
apiKey: credentials.apiKey as string,
model,
temperature,
});
}
}
```
### Advanced Pattern: Custom Model Class
For providers with custom APIs, extend `BaseChatModel` and pass an instance to `supplyModel`:
```typescript
import {
BaseChatModel,
supplyModel,
type Message,
type GenerateResult,
type StreamChunk,
type ChatModelConfig,
} from '@n8n/ai-node-sdk';
import {
type INodeType,
type INodeTypeDescription,
NodeConnectionTypes,
type IHttpRequestMethods,
type ISupplyDataFunctions,
type SupplyData,
} from 'n8n-workflow';
import type Stream from 'node:stream';
import { Readable } from 'node:stream';
// Custom model implementation
class MyProviderChatModel extends BaseChatModel {
constructor(
modelId: string,
private requests: {
httpRequest: (
method: IHttpRequestMethods,
url: string,
body?: object,
headers?: Record<string, string>,
) => Promise<{ body: unknown }>;
openStream: (
method: IHttpRequestMethods,
url: string,
body?: object,
headers?: Record<string, string>,
) => Promise<{ body: ReadableStream<Uint8Array> }>;
},
config?: ChatModelConfig,
) {
super('my-provider', modelId, config);
}
async generate(messages: Message[], config?: ChatModelConfig): Promise<GenerateResult> {
// Convert n8n messages to provider format
const providerMessages = messages.map(m => ({
role: m.role,
content: m.content.find(c => c.type === 'text')?.text ?? '',
}));
// Call the provider API
const response = await this.requests.httpRequest('POST', '/chat', {
model: this.modelId,
messages: providerMessages,
temperature: config?.temperature,
});
const body = response.body as any;
return {
finishReason: 'stop',
message: {
id: body.id,
role: 'assistant',
content: [{ type: 'text', text: body.content }],
},
usage: {
promptTokens: body.usage.prompt_tokens,
completionTokens: body.usage.completion_tokens,
totalTokens: body.usage.total_tokens,
},
};
}
async *stream(messages: Message[], config?: ChatModelConfig): AsyncIterable<StreamChunk> {
// Implement streaming...
yield { type: 'text-delta', delta: 'response text' };
yield { type: 'finish', finishReason: 'stop' };
}
}
// Node definition
export class LmChatMyProvider implements INodeType {
description: INodeTypeDescription = {
displayName: 'MyProvider Chat Model',
name: 'lmChatMyProvider',
icon: 'fa:robot',
group: ['transform'],
version: [1],
description: 'For advanced usage with an AI chain',
defaults: {
name: 'MyProvider Chat Model',
},
inputs: [],
outputs: [NodeConnectionTypes.AiLanguageModel],
credentials: [{ name: 'myProviderApi', required: true }],
properties: [
{ displayName: 'Model', name: 'model', type: 'string', default: 'my-model' },
{ displayName: 'Temperature', name: 'temperature', type: 'number', default: 0.7 },
],
};
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
const model = this.getNodeParameter('model', itemIndex) as string;
const temperature = this.getNodeParameter('temperature', itemIndex) as number;
const chatModel = new MyProviderChatModel(
model,
{
httpRequest: async (method, url, body, headers) => {
const response = await this.helpers.httpRequestWithAuthentication.call(
this,
'myProviderApi',
{ method, url, body, headers },
);
return { body: response };
},
openStream: async (method, url, body, headers) => {
const response = (await this.helpers.httpRequestWithAuthentication.call(
this,
'myProviderApi',
{ method, url, body, headers, encoding: 'stream' },
)) as Stream.Readable;
return { body: Readable.toWeb(response) as ReadableStream<Uint8Array> };
},
},
{ temperature },
);
return supplyModel(this, chatModel);
}
}
```
## Memory Nodes
Memory nodes implement the `INodeType` interface and use `supplyMemory` to provide memory instances.
### Pattern: Custom Storage with Windowed Memory
Extend `BaseChatHistory` to implement storage, then wrap it with `WindowedChatMemory` and pass to `supplyMemory`:
```typescript
import {
BaseChatHistory,
WindowedChatMemory,
supplyMemory,
type Message,
} from '@n8n/ai-node-sdk';
import {
type INodeType,
type INodeTypeDescription,
NodeConnectionTypes,
type ISupplyDataFunctions,
type SupplyData,
} from 'n8n-workflow';
// Custom storage implementation
class MyDbChatHistory extends BaseChatHistory {
constructor(
private sessionId: string,
private apiKey: string,
private httpRequest: any,
) {
super();
}
async getMessages(): Promise<Message[]> {
const data = await this.httpRequest({
method: 'GET',
url: `/sessions/${this.sessionId}/messages`,
headers: { Authorization: `Bearer ${this.apiKey}` },
json: true,
});
return data.messages.map((m: any) => ({
role: m.role,
content: [{ type: 'text', text: m.content }],
}));
}
async addMessage(message: Message): Promise<void> {
const text = message.content.find(c => c.type === 'text')?.text ?? '';
await this.httpRequest({
method: 'POST',
url: `/sessions/${this.sessionId}/messages`,
headers: { Authorization: `Bearer ${this.apiKey}` },
body: { role: message.role, content: text },
json: true,
});
}
async clear(): Promise<void> {
await this.httpRequest({
method: 'DELETE',
url: `/sessions/${this.sessionId}`,
headers: { Authorization: `Bearer ${this.apiKey}` },
});
}
}
// Memory node
export class MemoryMyDb implements INodeType {
description: INodeTypeDescription = {
displayName: 'MyDB Memory',
name: 'memoryMyDb',
icon: 'fa:database',
group: ['transform'],
version: [1],
description: 'Store conversation history in MyDB',
defaults: {
name: 'MyDB Memory',
},
inputs: [],
outputs: [NodeConnectionTypes.AiMemory],
credentials: [{ name: 'myDbApi', required: true }],
properties: [
{
displayName: 'Session ID',
name: 'sessionId',
type: 'string',
default: '={{ $json.sessionId }}',
},
{
displayName: 'Window Size',
name: 'windowSize',
type: 'number',
default: 10,
description: 'Number of recent message pairs to keep',
},
],
};
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
const credentials = await this.getCredentials('myDbApi');
const sessionId = this.getNodeParameter('sessionId', itemIndex) as string;
const windowSize = this.getNodeParameter('windowSize', itemIndex) as number;
const history = new MyDbChatHistory(
sessionId,
credentials.apiKey as string,
this.helpers.httpRequest,
);
const memory = new WindowedChatMemory(history, { windowSize });
return supplyMemory(this, memory);
}
}
```
@@ -0,0 +1,13 @@
import { defineConfig, globalIgnores } from 'eslint/config';
import { nodeConfig } from '@n8n/eslint-config/node';
export default defineConfig(nodeConfig, globalIgnores(['dist/**']), {
rules: {
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-unsafe-assignment': 'warn',
'@typescript-eslint/no-unsafe-call': 'warn',
'@typescript-eslint/no-unsafe-member-access': 'warn',
'@typescript-eslint/no-unsafe-return': 'warn',
'@typescript-eslint/naming-convention': 'warn',
},
});
+6
View File
@@ -0,0 +1,6 @@
/** @type {import('jest').Config} */
module.exports = {
...require('../../../jest.config'),
collectCoverageFrom: ['src/**/*.ts'],
coveragePathIgnorePatterns: ['src/index.ts'],
};
+39
View File
@@ -0,0 +1,39 @@
{
"name": "@n8n/ai-node-sdk",
"version": "0.3.0",
"description": "SDK for building AI nodes in n8n",
"types": "dist/esm/index.d.ts",
"module": "dist/esm/index.js",
"main": "dist/cjs/index.js",
"exports": {
".": {
"types": "./dist/esm/index.d.ts",
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.js"
},
"./*": "./*"
},
"scripts": {
"clean": "rimraf dist .turbo",
"dev": "pnpm watch",
"typecheck": "tsc --noEmit",
"build": "tsc --build tsconfig.build.esm.json tsconfig.build.cjs.json",
"format": "biome format --write .",
"format:check": "biome ci .",
"lint": "eslint . --quiet",
"lint:fix": "eslint . --fix",
"watch": "tsc --build tsconfig.build.esm.json tsconfig.build.cjs.json --watch",
"test": "jest --passWithNoTests",
"test:unit": "jest --passWithNoTests",
"test:dev": "jest --watch"
},
"files": [
"dist"
],
"dependencies": {
"@n8n/ai-utilities": "workspace:*"
},
"devDependencies": {
"@n8n/typescript-config": "workspace:*"
}
}
+37
View File
@@ -0,0 +1,37 @@
export { parseSSEStream } from '@n8n/ai-utilities';
export type { GenerateResult, StreamChunk, TokenUsage, FinishReason } from '@n8n/ai-utilities';
export type { Tool, ToolResult, ToolCall, ProviderTool } from '@n8n/ai-utilities';
export type {
Message,
ContentFile,
ContentMetadata,
ContentReasoning,
ContentText,
ContentToolCall,
ContentToolResult,
MessageContent,
MessageRole,
} from '@n8n/ai-utilities';
export type { JSONArray, JSONObject, JSONValue } from '@n8n/ai-utilities';
export type { ServerSentEventMessage } from '@n8n/ai-utilities';
export { getParametersJsonSchema } from '@n8n/ai-utilities';
// Chat model types
export type { ChatModel, ChatModelConfig } from '@n8n/ai-utilities';
// Chat model base classes
export { BaseChatModel } from '@n8n/ai-utilities';
// Memory types
export type { ChatHistory, ChatMemory } from '@n8n/ai-utilities';
// Memory base classes
export { BaseChatHistory } from '@n8n/ai-utilities';
export { BaseChatMemory } from '@n8n/ai-utilities';
// Memory implementations
export { WindowedChatMemory, type WindowedChatMemoryConfig } from '@n8n/ai-utilities';
// Suppliers
export { supplyMemory, type SupplyMemoryOptions } from '@n8n/ai-utilities';
export { supplyModel, type SupplyModelOptions, type OpenAiModel } from '@n8n/ai-utilities';
@@ -0,0 +1,11 @@
{
"extends": ["./tsconfig.json", "@n8n/typescript-config/modern/tsconfig.cjs.json"],
"compilerOptions": {
"rootDir": "src",
"outDir": "dist/cjs",
"tsBuildInfoFile": "dist/cjs/typecheck.tsbuildinfo",
"declaration": true
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "src/**/__tests__/**", "src/**/*.test.ts"]
}
@@ -0,0 +1,11 @@
{
"extends": ["./tsconfig.json"],
"compilerOptions": {
"rootDir": "src",
"outDir": "dist/esm",
"tsBuildInfoFile": "dist/esm/typecheck.tsbuildinfo",
"declaration": true
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "src/**/__tests__/**", "src/**/*.test.ts"]
}
@@ -0,0 +1,11 @@
{
"extends": ["./tsconfig.json", "@n8n/typescript-config/tsconfig.build.json"],
"compilerOptions": {
"composite": true,
"rootDir": "src",
"outDir": "dist",
"tsBuildInfoFile": "dist/build.tsbuildinfo"
},
"include": ["src/**/*.ts"],
"exclude": ["src/**/__tests__/**", "src/**/*.test.ts"]
}
+12
View File
@@ -0,0 +1,12 @@
{
"extends": [
"@n8n/typescript-config/tsconfig.common.json",
"@n8n/typescript-config/tsconfig.backend.json"
],
"compilerOptions": {
"rootDir": ".",
"baseUrl": ".",
"tsBuildInfoFile": "dist/typecheck.tsbuildinfo"
},
"include": ["src/**/*.ts"]
}