n8n-nodes-tembory 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Node de memoria operacional da Tembory para agentes de IA no n8n.
4
4
 
5
- Versao atual: `1.0.0`.
5
+ Versao atual: `1.0.1`.
6
6
 
7
7
  O Tembory entrega contexto rico para o AI Agent sem depender apenas do historico textual da conversa. Ele combina memoria semantica, working memory, decision state, fatos estaveis do lead, historico de tools, estado operacional, action ledger, timeline de entidades, compressao de memoria, grafo, mensagens recentes e diagnosticos.
8
8
 
@@ -19,7 +19,7 @@ O smoke simula 12 trocas com tools de agenda, valida tool history, working memor
19
19
  ## Principais recursos
20
20
 
21
21
  - Porta `ai_memory` para conectar direto no AI Agent do n8n.
22
- - Campo `Projeto` para isolar memórias por cliente/workspace mesmo quando a origem repete o mesmo ID de conversa.
22
+ - Campo `Projeto` para isolar memórias por cliente/workspace mesmo quando a origem repete o mesmo ID de conversa. Ele e um namespace operacional do workflow; nao e o Project ID da credencial.
23
23
  - Entradas opcionais `ai_languageModel` e `ai_embedding` para usar os modelos conectados pelo cliente no proprio n8n.
24
24
  - Profile facts: nome, empresa, email, telefone, preferencias e contexto comercial.
25
25
  - Tool history: quais tools foram chamadas, quando, com qual status e resultado.
@@ -36,6 +36,10 @@ O smoke simula 12 trocas com tools de agenda, valida tool history, working memor
36
36
 
37
37
  O node nao fornece LLM nem embedding gerenciados pela Tembory. Todo uso de LLM e embedding deve vir dos nodes/modelos conectados no cluster do n8n do cliente. O backend do Tembory armazena e consulta os vetores recebidos prontos.
38
38
 
39
+ ## Credencial
40
+
41
+ Use apenas a credencial `Tembory API` com a API key gerada no SaaS. A API key ja identifica a conta e o projeto comercial no Tembory; por isso o node nao pede Organization ID, Project ID nem URL self-hosted.
42
+
39
43
  ## Presets operacionais
40
44
 
41
45
  O campo avancado `Preset Operacional` preenche defaults sem sobrescrever valores configurados manualmente:
@@ -4,7 +4,7 @@ exports.Mem0Api = void 0;
4
4
  class Mem0Api {
5
5
  constructor() {
6
6
  this.name = 'mem0Api';
7
- this.displayName = 'Tembory Cloud API';
7
+ this.displayName = 'Tembory API';
8
8
  this.documentationUrl = 'https://tembory.com';
9
9
  this.properties = [
10
10
  {
@@ -14,39 +14,24 @@ class Mem0Api {
14
14
  typeOptions: { password: true },
15
15
  default: '',
16
16
  required: true,
17
- description: 'API key for the managed Tembory memory backend.',
18
- },
19
- {
20
- displayName: 'Organization ID',
21
- name: 'orgId',
22
- type: 'string',
23
- default: '',
24
- description: 'Optional: Organization ID',
25
- },
26
- {
27
- displayName: 'Project ID',
28
- name: 'projectId',
29
- type: 'string',
30
- default: '',
31
- description: 'Optional: Project ID',
17
+ description: 'API key generated in the Tembory SaaS dashboard. It already identifies the account and project.',
32
18
  },
33
19
  ];
34
20
  this.authenticate = {
35
21
  type: 'generic',
36
22
  properties: {
37
23
  headers: {
38
- 'Authorization': '=Token {{$credentials.apiKey}}',
24
+ 'x-tembory-api-key': '={{$credentials.apiKey}}',
39
25
  },
40
26
  },
41
27
  };
42
28
  this.test = {
43
29
  request: {
44
- baseURL: 'https://api.mem0.ai',
45
- url: '/v1/entities/',
30
+ baseURL: 'https://api.tembory.com',
31
+ url: '/healthz',
46
32
  method: 'GET',
47
33
  },
48
34
  };
49
35
  }
50
36
  }
51
37
  exports.Mem0Api = Mem0Api;
52
-
@@ -3,46 +3,47 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.mem0ApiRequest = mem0ApiRequest;
4
4
  const n8n_workflow_1 = require("n8n-workflow");
5
5
  async function mem0ApiRequest(method, endpoint, body = {}, qs = {}) {
6
- // Support both Portuguese parameter name used in CRUD node and English used in Memory node
7
- let authenticationType;
8
- try {
9
- authenticationType = this.getNodeParameter('tipoAutenticacao', 0);
10
- }
11
- catch (e) {
12
- authenticationType = this.getNodeParameter('authType', 0) || 'nuvem';
13
- // Map to legacy values used below
14
- if (authenticationType === 'cloud')
15
- authenticationType = 'nuvem';
16
- if (authenticationType === 'selfHosted')
17
- authenticationType = 'selfHosted';
18
- }
19
- let credentials;
20
- let baseUrl;
21
- if (authenticationType === 'nuvem') {
22
- credentials = await this.getCredentials('mem0Api');
23
- baseUrl = 'https://api.mem0.ai';
24
- }
25
- else {
26
- credentials = await this.getCredentials('mem0SelfHostedApi');
27
- baseUrl = credentials.baseUrl.replace(/\/$/, ''); // Remove trailing slash
28
- }
6
+ const credentials = await this.getCredentials('mem0Api');
7
+ const baseUrl = 'https://api.tembory.com';
8
+ const operation = resolveGatewayOperation(method, endpoint);
9
+ const payload = {
10
+ ...(qs && typeof qs === 'object' ? qs : {}),
11
+ ...(body && typeof body === 'object' ? body : {}),
12
+ operation,
13
+ };
29
14
  const options = {
30
- method,
31
- body,
32
- qs,
33
- url: `${baseUrl}${endpoint}`,
15
+ method: 'POST',
16
+ body: payload,
17
+ url: `${baseUrl}/api/tembory/memory`,
34
18
  json: true,
35
19
  };
36
20
  if (credentials.apiKey) {
37
21
  options.headers = {
38
- 'Authorization': `Token ${credentials.apiKey}`,
22
+ 'x-tembory-api-key': credentials.apiKey,
39
23
  };
40
24
  }
41
25
  try {
42
- return await this.helpers.request(options);
26
+ const response = await this.helpers.request(options);
27
+ return unwrapGatewayResponse(response);
43
28
  }
44
29
  catch (error) {
45
30
  throw new n8n_workflow_1.NodeApiError(this.getNode(), error);
46
31
  }
47
32
  }
48
-
33
+ function resolveGatewayOperation(method, endpoint) {
34
+ const normalizedMethod = String(method || '').toUpperCase();
35
+ const path = String(endpoint || '').toLowerCase();
36
+ if (path.includes('search') || normalizedMethod === 'GET')
37
+ return 'memory.search';
38
+ if (path.includes('delete'))
39
+ return 'memory.delete';
40
+ if (path.includes('relation'))
41
+ return 'memory.search';
42
+ return 'memory.add';
43
+ }
44
+ function unwrapGatewayResponse(response) {
45
+ if (response && typeof response === 'object' && response.memoryCore && typeof response.memoryCore === 'object' && 'response' in response.memoryCore) {
46
+ return response.memoryCore.response;
47
+ }
48
+ return response;
49
+ }
@@ -112,8 +112,12 @@ const memoryText = (memory) => {
112
112
  if (!memory)
113
113
  return '';
114
114
  if (typeof memory === 'string')
115
- return memory;
116
- return memory.memory || memory.text || memory.value || memory.content || memory.data || safeStringify(memory);
115
+ return parseRecentMessageMarker(memory)?.content || memory;
116
+ const meta = metadataOf(memory);
117
+ if (meta.kind === 'recent_message' && meta.content)
118
+ return String(meta.content);
119
+ const text = memory.memory || memory.text || memory.value || memory.content || memory.data || safeStringify(memory);
120
+ return parseRecentMessageMarker(text)?.content || text;
117
121
  };
118
122
  const normalizeResults = (value) => {
119
123
  if (!value)
@@ -1512,20 +1516,6 @@ class Mem0Memory {
1512
1516
  {
1513
1517
  name: 'mem0Api',
1514
1518
  required: true,
1515
- displayOptions: {
1516
- show: {
1517
- authType: ['cloud'],
1518
- },
1519
- },
1520
- },
1521
- {
1522
- name: 'mem0SelfHostedApi',
1523
- required: true,
1524
- displayOptions: {
1525
- show: {
1526
- authType: ['selfHosted'],
1527
- },
1528
- },
1529
1519
  },
1530
1520
  ],
1531
1521
  codex: {
@@ -1536,16 +1526,6 @@ class Mem0Memory {
1536
1526
  },
1537
1527
  },
1538
1528
  properties: [
1539
- {
1540
- displayName: 'Autenticação',
1541
- name: 'authType',
1542
- type: 'options',
1543
- options: [
1544
- { name: 'Tembory Cloud', value: 'cloud' },
1545
- { name: 'Self-hosted', value: 'selfHosted' },
1546
- ],
1547
- default: 'cloud',
1548
- },
1549
1529
  {
1550
1530
  displayName: 'ID da Thread',
1551
1531
  name: 'threadId',
@@ -1558,7 +1538,7 @@ class Mem0Memory {
1558
1538
  name: 'project',
1559
1539
  type: 'string',
1560
1540
  default: '={{ $json.project || $json.projectId || $json.accountId || $json.body?.account?.id || "" }}',
1561
- description: 'Namespace estável do cliente/projeto. Quando preenchido, isola memórias mesmo que duas conversas usem o mesmo ID.',
1541
+ description: 'Namespace operacional do workflow para separar clientes/workspaces. Nao e o Project ID da credencial; a API key do Tembory ja identifica o projeto comercial.',
1562
1542
  },
1563
1543
  {
1564
1544
  displayName: 'Modo de Recuperação de Contexto',
@@ -2192,38 +2172,31 @@ class Mem0Memory {
2192
2172
  }
2193
2173
  let persistedRecentMessages = [];
2194
2174
  let persistedToolHistory = [];
2195
- if (adv.includeRecentMessages !== false) {
2175
+ let persistedMemoryItems = [];
2176
+ if (adv.includeRecentMessages !== false || adv.includeToolHistory !== false) {
2196
2177
  try {
2197
2178
  const qs = { user_id: key };
2198
2179
  if (adv.agentId)
2199
2180
  qs.agent_id = String(adv.agentId);
2200
2181
  if (adv.runId)
2201
2182
  qs.run_id = String(adv.runId);
2202
- const recentRes = await GenericFunctions_1.mem0ApiRequest.call(this, 'GET', '/v1/memories/', {}, qs);
2203
- persistedRecentMessages = normalizeResults(recentRes)
2204
- .map(recentMessageFromMemory)
2205
- .filter(Boolean)
2206
- .sort((a, b) => new Date(a.at || 0).getTime() - new Date(b.at || 0).getTime());
2183
+ const persistedRes = await GenericFunctions_1.mem0ApiRequest.call(this, 'GET', '/v1/memories/', {}, qs);
2184
+ persistedMemoryItems = normalizeResults(persistedRes);
2207
2185
  }
2208
2186
  catch (error) {
2209
- connectedAi.errors.push(`recentMessages.load: ${error.message || String(error)}`);
2187
+ connectedAi.errors.push(`persistedContext.load: ${error.message || String(error)}`);
2210
2188
  }
2211
2189
  }
2190
+ if (adv.includeRecentMessages !== false) {
2191
+ persistedRecentMessages = persistedMemoryItems
2192
+ .map(recentMessageFromMemory)
2193
+ .filter(Boolean)
2194
+ .sort((a, b) => new Date(a.at || 0).getTime() - new Date(b.at || 0).getTime());
2195
+ }
2212
2196
  if (adv.includeToolHistory !== false) {
2213
- try {
2214
- const qs = { user_id: key };
2215
- if (adv.agentId)
2216
- qs.agent_id = String(adv.agentId);
2217
- if (adv.runId)
2218
- qs.run_id = String(adv.runId);
2219
- const toolRes = await GenericFunctions_1.mem0ApiRequest.call(this, 'GET', '/v1/memories/', {}, qs);
2220
- persistedToolHistory = normalizeResults(toolRes)
2221
- .flatMap(toolHistoryItemsFromMemory)
2222
- .sort((a, b) => new Date(a.at || 0).getTime() - new Date(b.at || 0).getTime());
2223
- }
2224
- catch (error) {
2225
- connectedAi.errors.push(`toolHistory.load: ${error.message || String(error)}`);
2226
- }
2197
+ persistedToolHistory = persistedMemoryItems
2198
+ .flatMap(toolHistoryItemsFromMemory)
2199
+ .sort((a, b) => new Date(a.at || 0).getTime() - new Date(b.at || 0).getTime());
2227
2200
  }
2228
2201
  const allRecentMessages = dedupeRecentMessages((store.recentMessages[key] || []).concat(persistedRecentMessages));
2229
2202
  const recentMessages = adv.includeRecentMessages === false ? [] : pruneByLimit(allRecentMessages, adv.recentMessagesLastN || 8);
@@ -2354,7 +2327,7 @@ class Mem0Memory {
2354
2327
  superseded_by: enriched.superseded_by,
2355
2328
  id: m.id || m.uuid || m.memory_id,
2356
2329
  created_at: m.created_at || m.createdAt,
2357
- raw: m,
2330
+ raw: adv.includeDiagnostics ? m : undefined,
2358
2331
  };
2359
2332
  });
2360
2333
  const summary = vectorMemories.slice(0, Number(adv.summaryMaxFacts || 4)).map(memoryText).filter(Boolean);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-tembory",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Tembory node for n8n AI Agents with profile, tools, timeline, graph and semantic memory",
5
5
  "license": "MIT",
6
6
  "homepage": "https://tembory.com",
@@ -36,8 +36,7 @@
36
36
  "n8n": {
37
37
  "n8nNodesApiVersion": 1,
38
38
  "credentials": [
39
- "dist/credentials/Mem0Api.credentials.js",
40
- "dist/credentials/Mem0SelfHostedApi.credentials.js"
39
+ "dist/credentials/Mem0Api.credentials.js"
41
40
  ],
42
41
  "nodes": [
43
42
  "dist/nodes/Mem0/Mem0Memory.node.js"
@@ -1,10 +0,0 @@
1
- import { IAuthenticateGeneric, ICredentialTestRequest, ICredentialType, INodeProperties } from 'n8n-workflow';
2
- export declare class Mem0SelfHostedApi implements ICredentialType {
3
- name: string;
4
- displayName: string;
5
- documentationUrl: string;
6
- properties: INodeProperties[];
7
- authenticate: IAuthenticateGeneric;
8
- test: ICredentialTestRequest;
9
- }
10
-
@@ -1,45 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Mem0SelfHostedApi = void 0;
4
- class Mem0SelfHostedApi {
5
- constructor() {
6
- this.name = 'mem0SelfHostedApi';
7
- this.displayName = 'Tembory Self-Hosted API';
8
- this.documentationUrl = 'https://tembory.com';
9
- this.properties = [
10
- {
11
- displayName: 'Base URL',
12
- name: 'baseUrl',
13
- type: 'string',
14
- default: 'http://localhost:8000',
15
- required: true,
16
- description: 'The base URL of your self-hosted Tembory memory backend',
17
- },
18
- {
19
- displayName: 'API Key',
20
- name: 'apiKey',
21
- type: 'string',
22
- typeOptions: { password: true },
23
- default: '',
24
- description: 'API key if authentication enabled',
25
- },
26
- ];
27
- this.authenticate = {
28
- type: 'generic',
29
- properties: {
30
- headers: {
31
- 'Authorization': '={{$credentials.apiKey ? "Token " + $credentials.apiKey : ""}}',
32
- },
33
- },
34
- };
35
- this.test = {
36
- request: {
37
- baseURL: '={{$credentials.baseUrl}}',
38
- url: '/v1/entities/',
39
- method: 'GET',
40
- },
41
- };
42
- }
43
- }
44
- exports.Mem0SelfHostedApi = Mem0SelfHostedApi;
45
-