@kairos-sdk/core 0.1.0

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/dist/index.cjs ADDED
@@ -0,0 +1,1456 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ ApiError: () => ApiError,
34
+ DEFAULT_REGISTRY: () => DEFAULT_REGISTRY,
35
+ FileLibrary: () => FileLibrary,
36
+ GenerationError: () => GenerationError,
37
+ GuardError: () => GuardError,
38
+ Kairos: () => Kairos,
39
+ KairosError: () => KairosError,
40
+ N8nApiClient: () => N8nApiClient,
41
+ N8nFieldStripper: () => N8nFieldStripper,
42
+ N8nProvider: () => N8nProvider,
43
+ N8nValidator: () => N8nValidator,
44
+ NodeRegistry: () => NodeRegistry,
45
+ NullLibrary: () => NullLibrary,
46
+ ProviderError: () => ProviderError,
47
+ ResponseParseError: () => ResponseParseError,
48
+ TelemetryCollector: () => TelemetryCollector,
49
+ ValidationError: () => ValidationError,
50
+ nullLogger: () => nullLogger
51
+ });
52
+ module.exports = __toCommonJS(index_exports);
53
+
54
+ // src/client.ts
55
+ var import_sdk = __toESM(require("@anthropic-ai/sdk"), 1);
56
+
57
+ // src/utils/uuid.ts
58
+ function generateUUID() {
59
+ return crypto.randomUUID();
60
+ }
61
+
62
+ // src/library/null-library.ts
63
+ var NullLibrary = class {
64
+ async initialize() {
65
+ }
66
+ async search(_description, _options) {
67
+ return [];
68
+ }
69
+ async save(_workflow, _metadata) {
70
+ return generateUUID();
71
+ }
72
+ async recordDeployment(_id) {
73
+ }
74
+ async get(_id) {
75
+ return null;
76
+ }
77
+ async list(_filters) {
78
+ return [];
79
+ }
80
+ };
81
+
82
+ // src/errors/base.ts
83
+ var KairosError = class extends Error {
84
+ constructor(message, cause) {
85
+ super(message);
86
+ this.cause = cause;
87
+ this.name = "KairosError";
88
+ }
89
+ cause;
90
+ };
91
+
92
+ // src/errors/api-error.ts
93
+ var ApiError = class extends KairosError {
94
+ constructor(message, statusCode, cause) {
95
+ super(message, cause);
96
+ this.statusCode = statusCode;
97
+ this.name = "ApiError";
98
+ }
99
+ statusCode;
100
+ };
101
+
102
+ // src/errors/provider-error.ts
103
+ var ProviderError = class extends KairosError {
104
+ constructor(message, cause) {
105
+ super(message, cause);
106
+ this.name = "ProviderError";
107
+ }
108
+ };
109
+
110
+ // src/providers/n8n/api-client.ts
111
+ var EXECUTION_LIMIT_CAP = 100;
112
+ var N8nApiClient = class {
113
+ constructor(baseUrl, apiKey, logger) {
114
+ this.baseUrl = baseUrl;
115
+ this.apiKey = apiKey;
116
+ this.logger = logger;
117
+ }
118
+ baseUrl;
119
+ apiKey;
120
+ logger;
121
+ async request(method, path, body) {
122
+ const url = `${this.baseUrl.replace(/\/$/, "")}/api/v1${path}`;
123
+ this.logger.debug(`n8n ${method} ${path}`);
124
+ let response;
125
+ try {
126
+ response = await fetch(url, {
127
+ method,
128
+ headers: {
129
+ "X-N8N-API-KEY": this.apiKey,
130
+ "Content-Type": "application/json",
131
+ Accept: "application/json"
132
+ },
133
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {}
134
+ });
135
+ } catch (err) {
136
+ throw new ProviderError(`Network error calling n8n API: ${path}`, err);
137
+ }
138
+ if (!response.ok) {
139
+ let errorBody;
140
+ try {
141
+ errorBody = await response.json();
142
+ } catch {
143
+ errorBody = await response.text().catch(() => "");
144
+ }
145
+ this.logger.error(`n8n API error ${response.status} on ${method} ${path}`, {
146
+ status: response.status,
147
+ body: String(errorBody)
148
+ });
149
+ throw new ApiError(
150
+ `n8n API returned ${response.status} for ${method} ${path}: ${JSON.stringify(errorBody)}`,
151
+ response.status,
152
+ errorBody
153
+ );
154
+ }
155
+ if (response.status === 204) return void 0;
156
+ return response.json();
157
+ }
158
+ async createWorkflow(workflow) {
159
+ return this.request("POST", "/workflows", workflow);
160
+ }
161
+ async updateWorkflow(id, workflow) {
162
+ return this.request("PUT", `/workflows/${id}`, workflow);
163
+ }
164
+ async getWorkflow(id) {
165
+ return this.request("GET", `/workflows/${id}`);
166
+ }
167
+ async listWorkflows() {
168
+ const response = await this.request("GET", "/workflows?limit=250");
169
+ return response.data.map((w) => ({
170
+ id: w.id,
171
+ name: w.name,
172
+ active: w.active,
173
+ createdAt: w.createdAt,
174
+ updatedAt: w.updatedAt,
175
+ ...w.tags !== void 0 ? { tags: w.tags } : {}
176
+ }));
177
+ }
178
+ async deleteWorkflow(id) {
179
+ await this.request("DELETE", `/workflows/${id}`);
180
+ }
181
+ async activateWorkflow(id) {
182
+ await this.request("POST", `/workflows/${id}/activate`);
183
+ }
184
+ async deactivateWorkflow(id) {
185
+ await this.request("POST", `/workflows/${id}/deactivate`);
186
+ }
187
+ async getExecutions(workflowId, filter) {
188
+ const params = new URLSearchParams();
189
+ if (workflowId) params.set("workflowId", workflowId);
190
+ if (filter?.status) params.set("status", filter.status);
191
+ const limit = Math.min(filter?.limit ?? 20, EXECUTION_LIMIT_CAP);
192
+ params.set("limit", String(limit));
193
+ if (filter?.cursor) params.set("cursor", filter.cursor);
194
+ const qs = params.toString();
195
+ const response = await this.request("GET", `/executions${qs ? `?${qs}` : ""}`);
196
+ return response.data.map(this.mapExecution);
197
+ }
198
+ async getExecution(id) {
199
+ const response = await this.request("GET", `/executions/${id}`);
200
+ return { ...this.mapExecution(response), data: response.data, workflowData: response.workflowData };
201
+ }
202
+ async listTags() {
203
+ const response = await this.request("GET", "/tags");
204
+ return response.data.map((t) => ({ id: t.id, name: t.name }));
205
+ }
206
+ async createTag(name) {
207
+ const response = await this.request("POST", "/tags", { name });
208
+ return { id: response.id, name: response.name };
209
+ }
210
+ async tagWorkflow(workflowId, tagIds) {
211
+ await this.request("PUT", `/workflows/${workflowId}/tags`, tagIds.map((id) => ({ id })));
212
+ }
213
+ async untagWorkflow(workflowId, tagIds) {
214
+ const current = await this.getWorkflow(workflowId);
215
+ const remaining = (current.tags ?? []).filter((t) => !tagIds.includes(t.id)).map((t) => ({ id: t.id }));
216
+ await this.request("PUT", `/workflows/${workflowId}/tags`, remaining);
217
+ }
218
+ mapExecution(e) {
219
+ return {
220
+ id: e.id,
221
+ workflowId: e.workflowId,
222
+ status: e.status,
223
+ startedAt: e.startedAt,
224
+ ...e.stoppedAt !== void 0 ? { stoppedAt: e.stoppedAt } : {},
225
+ mode: e.mode
226
+ };
227
+ }
228
+ };
229
+
230
+ // src/providers/n8n/types.ts
231
+ var FORBIDDEN_ON_CREATE = [
232
+ "id",
233
+ "createdAt",
234
+ "updatedAt",
235
+ "versionId",
236
+ "meta",
237
+ "isArchived",
238
+ "activeVersionId",
239
+ "activeVersion",
240
+ "active",
241
+ "pinData",
242
+ "triggerCount",
243
+ "shared"
244
+ ];
245
+ var FORBIDDEN_ON_UPDATE = FORBIDDEN_ON_CREATE.filter((f) => f !== "id");
246
+
247
+ // src/providers/n8n/stripper.ts
248
+ var N8nFieldStripper = class {
249
+ stripForCreate(workflow) {
250
+ return this.strip(workflow, FORBIDDEN_ON_CREATE);
251
+ }
252
+ stripForUpdate(workflow) {
253
+ return this.strip(workflow, FORBIDDEN_ON_UPDATE);
254
+ }
255
+ strip(workflow, forbidden) {
256
+ const result = { ...workflow };
257
+ for (const field of forbidden) {
258
+ delete result[field];
259
+ }
260
+ return result;
261
+ }
262
+ };
263
+
264
+ // src/errors/guard-error.ts
265
+ var GuardError = class extends KairosError {
266
+ constructor(message) {
267
+ super(message);
268
+ this.name = "GuardError";
269
+ }
270
+ };
271
+
272
+ // src/providers/n8n/provider.ts
273
+ var N8nProvider = class {
274
+ constructor(client, stripper) {
275
+ this.client = client;
276
+ this.stripper = stripper;
277
+ }
278
+ client;
279
+ stripper;
280
+ platform = "n8n";
281
+ async deploy(workflow) {
282
+ const stripped = this.stripper.stripForCreate(workflow);
283
+ const response = await this.client.createWorkflow(stripped);
284
+ return { workflowId: response.id, name: response.name };
285
+ }
286
+ async update(id, workflow) {
287
+ const stripped = this.stripper.stripForUpdate(workflow);
288
+ const response = await this.client.updateWorkflow(id, stripped);
289
+ return { workflowId: response.id, name: response.name };
290
+ }
291
+ async get(id) {
292
+ const response = await this.client.getWorkflow(id);
293
+ return {
294
+ name: response.name,
295
+ nodes: response.nodes,
296
+ connections: response.connections,
297
+ ...response.settings !== void 0 ? { settings: response.settings } : {},
298
+ ...response.tags !== void 0 ? { tags: response.tags } : {}
299
+ };
300
+ }
301
+ async list() {
302
+ return this.client.listWorkflows();
303
+ }
304
+ async activate(id) {
305
+ await this.client.activateWorkflow(id);
306
+ }
307
+ async deactivate(id) {
308
+ await this.client.deactivateWorkflow(id);
309
+ }
310
+ async delete(id, options) {
311
+ if (options.confirm !== true) {
312
+ throw new GuardError("delete() requires { confirm: true } to prevent accidental deletion");
313
+ }
314
+ await this.client.deleteWorkflow(id);
315
+ }
316
+ async executions(workflowId, filter) {
317
+ return this.client.getExecutions(workflowId, filter);
318
+ }
319
+ async execution(id) {
320
+ return this.client.getExecution(id);
321
+ }
322
+ async listTags() {
323
+ return this.client.listTags();
324
+ }
325
+ async createTag(name) {
326
+ return this.client.createTag(name);
327
+ }
328
+ async tag(workflowId, tagIds) {
329
+ await this.client.tagWorkflow(workflowId, tagIds);
330
+ }
331
+ async untag(workflowId, tagIds) {
332
+ await this.client.untagWorkflow(workflowId, tagIds);
333
+ }
334
+ };
335
+
336
+ // src/validation/registry.ts
337
+ var DEFAULT_REGISTRY = [
338
+ // Trigger nodes
339
+ { type: "n8n-nodes-base.manualTrigger", safeTypeVersions: [1], requiredParams: [], isTrigger: true },
340
+ { type: "n8n-nodes-base.scheduleTrigger", safeTypeVersions: [1, 1.1, 1.2], requiredParams: [], isTrigger: true },
341
+ { type: "n8n-nodes-base.webhook", safeTypeVersions: [1, 1.1, 2], requiredParams: ["httpMethod", "path"], isTrigger: true },
342
+ { type: "n8n-nodes-base.formTrigger", safeTypeVersions: [1, 2, 2.1, 2.2], requiredParams: [], isTrigger: true },
343
+ { type: "n8n-nodes-base.emailReadImap", safeTypeVersions: [2], requiredParams: [], credentialType: "imap", isTrigger: true },
344
+ { type: "n8n-nodes-base.errorTrigger", safeTypeVersions: [1], requiredParams: [], isTrigger: true },
345
+ { type: "n8n-nodes-base.executeWorkflowTrigger", safeTypeVersions: [1, 1.1], requiredParams: [], isTrigger: true },
346
+ { type: "n8n-nodes-base.gmailTrigger", safeTypeVersions: [1, 1.1, 1.2], requiredParams: [], credentialType: "gmailOAuth2", isTrigger: true },
347
+ { type: "n8n-nodes-base.googleDriveTrigger", safeTypeVersions: [1], requiredParams: [], credentialType: "googleDriveOAuth2Api", isTrigger: true },
348
+ { type: "n8n-nodes-base.googleSheetsTrigger", safeTypeVersions: [1], requiredParams: [], credentialType: "googleSheetsTriggerOAuth2Api", isTrigger: true },
349
+ { type: "n8n-nodes-base.slackTrigger", safeTypeVersions: [1], requiredParams: [], credentialType: "slackApi", isTrigger: true },
350
+ { type: "n8n-nodes-base.telegramTrigger", safeTypeVersions: [1, 1.1, 1.2], requiredParams: [], credentialType: "telegramApi", isTrigger: true },
351
+ { type: "n8n-nodes-base.githubTrigger", safeTypeVersions: [1], requiredParams: [], credentialType: "githubApi", isTrigger: true },
352
+ { type: "n8n-nodes-base.stripeTrigger", safeTypeVersions: [1], requiredParams: [], credentialType: "stripeApi", isTrigger: true },
353
+ { type: "n8n-nodes-base.airtableTrigger", safeTypeVersions: [1], requiredParams: [], credentialType: "airtableTokenApi", isTrigger: true },
354
+ { type: "n8n-nodes-base.notionTrigger", safeTypeVersions: [1], requiredParams: [], credentialType: "notionApi", isTrigger: true },
355
+ { type: "@n8n/n8n-nodes-langchain.chatTrigger", safeTypeVersions: [1, 1.1], requiredParams: [], isTrigger: true },
356
+ // Core logic nodes
357
+ { type: "n8n-nodes-base.code", safeTypeVersions: [1, 2], requiredParams: [] },
358
+ { type: "n8n-nodes-base.httpRequest", safeTypeVersions: [1, 2, 3, 4, 4.1, 4.2], requiredParams: ["url"] },
359
+ { type: "n8n-nodes-base.set", safeTypeVersions: [1, 2, 3, 3.1, 3.2, 3.3, 3.4], requiredParams: [] },
360
+ { type: "n8n-nodes-base.if", safeTypeVersions: [1, 2, 2.1, 2.2], requiredParams: [] },
361
+ { type: "n8n-nodes-base.switch", safeTypeVersions: [1, 2, 3, 3.1, 3.2], requiredParams: [] },
362
+ { type: "n8n-nodes-base.filter", safeTypeVersions: [1, 2, 2.1, 2.2], requiredParams: [] },
363
+ { type: "n8n-nodes-base.merge", safeTypeVersions: [1, 2, 2.1, 3], requiredParams: [] },
364
+ { type: "n8n-nodes-base.splitInBatches", safeTypeVersions: [1, 2, 3], requiredParams: [] },
365
+ { type: "n8n-nodes-base.wait", safeTypeVersions: [1, 1.1], requiredParams: [] },
366
+ { type: "n8n-nodes-base.executeWorkflow", safeTypeVersions: [1, 1.1, 1.2], requiredParams: [] },
367
+ { type: "n8n-nodes-base.respondToWebhook", safeTypeVersions: [1, 1.1], requiredParams: [] },
368
+ { type: "n8n-nodes-base.noOp", safeTypeVersions: [1], requiredParams: [] },
369
+ { type: "n8n-nodes-base.stopAndError", safeTypeVersions: [1], requiredParams: [] },
370
+ { type: "n8n-nodes-base.splitOut", safeTypeVersions: [1], requiredParams: [] },
371
+ { type: "n8n-nodes-base.aggregate", safeTypeVersions: [1], requiredParams: [] },
372
+ { type: "n8n-nodes-base.stickyNote", safeTypeVersions: [1], requiredParams: [] },
373
+ // Email / messaging
374
+ { type: "n8n-nodes-base.emailSend", safeTypeVersions: [1, 2, 2.1], requiredParams: [], credentialType: "smtp" },
375
+ { type: "n8n-nodes-base.slack", safeTypeVersions: [1, 2, 2.1, 2.2], requiredParams: [], credentialType: "slackOAuth2Api" },
376
+ { type: "n8n-nodes-base.telegram", safeTypeVersions: [1, 1.1, 1.2], requiredParams: [], credentialType: "telegramApi" },
377
+ { type: "n8n-nodes-base.discord", safeTypeVersions: [1, 2], requiredParams: [], credentialType: "discordWebhookApi" },
378
+ // Google
379
+ { type: "n8n-nodes-base.gmail", safeTypeVersions: [1, 2, 2.1], requiredParams: [], credentialType: "gmailOAuth2" },
380
+ { type: "n8n-nodes-base.googleSheets", safeTypeVersions: [1, 2, 3, 4, 4.1, 4.2, 4.3, 4.4, 4.5], requiredParams: [], credentialType: "googleSheetsOAuth2Api" },
381
+ { type: "n8n-nodes-base.googleDrive", safeTypeVersions: [1, 2, 3], requiredParams: [], credentialType: "googleDriveOAuth2Api" },
382
+ { type: "n8n-nodes-base.googleCalendar", safeTypeVersions: [1, 1.1, 1.2, 1.3], requiredParams: [], credentialType: "googleCalendarOAuth2Api" },
383
+ // Project management / CRM
384
+ { type: "n8n-nodes-base.notion", safeTypeVersions: [1, 2, 2.1, 2.2], requiredParams: [], credentialType: "notionApi" },
385
+ { type: "n8n-nodes-base.airtable", safeTypeVersions: [1, 2, 2.1], requiredParams: [], credentialType: "airtableTokenApi" },
386
+ { type: "n8n-nodes-base.github", safeTypeVersions: [1, 1.1], requiredParams: [], credentialType: "githubApi" },
387
+ { type: "n8n-nodes-base.jira", safeTypeVersions: [1], requiredParams: [], credentialType: "jiraSoftwareCloudApi" },
388
+ { type: "n8n-nodes-base.hubspot", safeTypeVersions: [1, 2, 2.1], requiredParams: [], credentialType: "hubspotOAuth2Api" },
389
+ // Databases
390
+ { type: "n8n-nodes-base.postgres", safeTypeVersions: [1, 2, 2.1, 2.2, 2.3, 2.4, 2.5], requiredParams: [], credentialType: "postgres" },
391
+ { type: "n8n-nodes-base.mySql", safeTypeVersions: [1, 2, 2.1, 2.2, 2.3, 2.4], requiredParams: [], credentialType: "mySql" },
392
+ { type: "n8n-nodes-base.redis", safeTypeVersions: [1], requiredParams: [], credentialType: "redis" },
393
+ { type: "n8n-nodes-base.supabase", safeTypeVersions: [1], requiredParams: [], credentialType: "supabaseApi" },
394
+ // Cloud
395
+ { type: "n8n-nodes-base.awsS3", safeTypeVersions: [1, 2], requiredParams: [], credentialType: "aws" },
396
+ // Payment / commerce
397
+ { type: "n8n-nodes-base.stripe", safeTypeVersions: [1], requiredParams: [], credentialType: "stripeApi" },
398
+ // AI / LangChain root nodes
399
+ { type: "@n8n/n8n-nodes-langchain.agent", safeTypeVersions: [1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9], requiredParams: [] },
400
+ { type: "@n8n/n8n-nodes-langchain.chainLlm", safeTypeVersions: [1, 1.1, 1.2, 1.3, 1.4, 1.5], requiredParams: [] },
401
+ { type: "@n8n/n8n-nodes-langchain.chainRetrievalQa", safeTypeVersions: [1, 1.1, 1.2, 1.3, 1.4], requiredParams: [] },
402
+ { type: "@n8n/n8n-nodes-langchain.informationExtractor", safeTypeVersions: [1], requiredParams: [] },
403
+ { type: "@n8n/n8n-nodes-langchain.textClassifier", safeTypeVersions: [1], requiredParams: [] },
404
+ // AI / LangChain sub-nodes (models)
405
+ { type: "@n8n/n8n-nodes-langchain.lmChatOpenAi", safeTypeVersions: [1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7], requiredParams: [], credentialType: "openAiApi" },
406
+ { type: "@n8n/n8n-nodes-langchain.lmChatAnthropic", safeTypeVersions: [1, 1.1, 1.2, 1.3], requiredParams: [], credentialType: "anthropicApi" },
407
+ { type: "@n8n/n8n-nodes-langchain.lmChatGoogleGemini", safeTypeVersions: [1], requiredParams: [], credentialType: "googlePalmApi" },
408
+ // AI / LangChain sub-nodes (memory, tools, etc.)
409
+ { type: "@n8n/n8n-nodes-langchain.memoryBufferWindow", safeTypeVersions: [1, 1.1, 1.2, 1.3], requiredParams: [] },
410
+ { type: "@n8n/n8n-nodes-langchain.toolWorkflow", safeTypeVersions: [1, 1.1, 1.2, 1.3], requiredParams: [] },
411
+ { type: "@n8n/n8n-nodes-langchain.toolCode", safeTypeVersions: [1, 1.1], requiredParams: [] },
412
+ { type: "@n8n/n8n-nodes-langchain.toolHttpRequest", safeTypeVersions: [1, 1.1], requiredParams: [] }
413
+ ];
414
+ var NodeRegistry = class {
415
+ byType;
416
+ constructor(definitions = DEFAULT_REGISTRY) {
417
+ this.byType = new Map(definitions.map((d) => [d.type, d]));
418
+ }
419
+ get(type) {
420
+ return this.byType.get(type);
421
+ }
422
+ isTrigger(type) {
423
+ return this.byType.get(type)?.isTrigger === true;
424
+ }
425
+ isVersionSafe(type, version) {
426
+ const def = this.byType.get(type);
427
+ if (!def) return true;
428
+ return def.safeTypeVersions.includes(version);
429
+ }
430
+ };
431
+
432
+ // src/validation/validator.ts
433
+ var AI_CONNECTION_TYPES = [
434
+ "ai_languageModel",
435
+ "ai_memory",
436
+ "ai_tool",
437
+ "ai_outputParser",
438
+ "ai_embedding",
439
+ "ai_document",
440
+ "ai_textSplitter",
441
+ "ai_retriever",
442
+ "ai_vectorStore"
443
+ ];
444
+ var TRIGGER_TYPE_PATTERNS = [/trigger/i, /Trigger$/];
445
+ var NODE_TYPE_PATTERN = /^(@[a-z0-9-]+\/[a-z0-9-]+\.|n8n-nodes-[a-z0-9-]+\.)[a-zA-Z][a-zA-Z0-9]+$/;
446
+ var N8nValidator = class {
447
+ registry;
448
+ constructor(registry = new NodeRegistry(DEFAULT_REGISTRY)) {
449
+ this.registry = registry;
450
+ }
451
+ validate(workflow) {
452
+ const issues = [];
453
+ this.checkRule1(workflow, issues);
454
+ this.checkRule2(workflow, issues);
455
+ this.checkRule3(workflow, issues);
456
+ this.checkRule4(workflow, issues);
457
+ this.checkRule5(workflow, issues);
458
+ this.checkRule6(workflow, issues);
459
+ this.checkRule7(workflow, issues);
460
+ this.checkRule8(workflow, issues);
461
+ this.checkRule9(workflow, issues);
462
+ this.checkRule10(workflow, issues);
463
+ this.checkRule11(workflow, issues);
464
+ this.checkRule12(workflow, issues);
465
+ this.checkRule13(workflow, issues);
466
+ this.checkRule14(workflow, issues);
467
+ this.checkRule15(workflow, issues);
468
+ this.checkRule16(workflow, issues);
469
+ this.checkRule17(workflow, issues);
470
+ this.checkRule18(workflow, issues);
471
+ this.checkRule19(workflow, issues);
472
+ const errors = issues.filter((i) => i.severity === "error");
473
+ return { valid: errors.length === 0, issues };
474
+ }
475
+ err(issues, rule, message, nodeId) {
476
+ const issue = { rule, severity: "error", message };
477
+ if (nodeId !== void 0) issue.nodeId = nodeId;
478
+ issues.push(issue);
479
+ }
480
+ warn(issues, rule, message, nodeId) {
481
+ const issue = { rule, severity: "warn", message };
482
+ if (nodeId !== void 0) issue.nodeId = nodeId;
483
+ issues.push(issue);
484
+ }
485
+ isTriggerNode(node) {
486
+ if (this.registry.isTrigger(node.type)) return true;
487
+ return TRIGGER_TYPE_PATTERNS.some((p) => p.test(node.type));
488
+ }
489
+ // Rule 1: name is a non-empty string
490
+ checkRule1(w, issues) {
491
+ if (typeof w.name !== "string" || w.name.trim() === "") {
492
+ this.err(issues, 1, "Workflow name is required and must be a non-empty string");
493
+ }
494
+ }
495
+ // Rule 2: nodes is an array with at least one element
496
+ checkRule2(w, issues) {
497
+ if (!Array.isArray(w.nodes) || w.nodes.length === 0) {
498
+ this.err(issues, 2, "Workflow must have at least one node");
499
+ }
500
+ }
501
+ // Rule 3: every node has a non-empty id
502
+ checkRule3(w, issues) {
503
+ if (!Array.isArray(w.nodes)) return;
504
+ for (const node of w.nodes) {
505
+ if (typeof node.id !== "string" || node.id.trim() === "") {
506
+ this.err(issues, 3, `Node "${node.name ?? "unknown"}" is missing a valid id`, node.id);
507
+ }
508
+ }
509
+ }
510
+ // Rule 4: node ids are unique
511
+ checkRule4(w, issues) {
512
+ if (!Array.isArray(w.nodes)) return;
513
+ const seen = /* @__PURE__ */ new Set();
514
+ for (const node of w.nodes) {
515
+ if (!node.id) continue;
516
+ if (seen.has(node.id)) {
517
+ this.err(issues, 4, `Duplicate node id: "${node.id}"`, node.id);
518
+ }
519
+ seen.add(node.id);
520
+ }
521
+ }
522
+ // Rule 5: every node has a non-empty type string
523
+ checkRule5(w, issues) {
524
+ if (!Array.isArray(w.nodes)) return;
525
+ for (const node of w.nodes) {
526
+ if (typeof node.type !== "string" || node.type.trim() === "") {
527
+ this.err(issues, 5, `Node "${node.name ?? node.id}" is missing a type`, node.id);
528
+ }
529
+ }
530
+ }
531
+ // Rule 6: every node has a positive typeVersion number
532
+ checkRule6(w, issues) {
533
+ if (!Array.isArray(w.nodes)) return;
534
+ for (const node of w.nodes) {
535
+ if (typeof node.typeVersion !== "number" || node.typeVersion <= 0) {
536
+ this.err(issues, 6, `Node "${node.name}" has invalid typeVersion: ${String(node.typeVersion)}`, node.id);
537
+ }
538
+ }
539
+ }
540
+ // Rule 7: every node has a valid [x, y] position
541
+ checkRule7(w, issues) {
542
+ if (!Array.isArray(w.nodes)) return;
543
+ for (const node of w.nodes) {
544
+ const pos = node.position;
545
+ if (!Array.isArray(pos) || pos.length !== 2 || typeof pos[0] !== "number" || typeof pos[1] !== "number") {
546
+ this.err(issues, 7, `Node "${node.name}" has invalid position (must be [x, y])`, node.id);
547
+ }
548
+ }
549
+ }
550
+ // Rule 8: every node has a non-empty name
551
+ checkRule8(w, issues) {
552
+ if (!Array.isArray(w.nodes)) return;
553
+ for (const node of w.nodes) {
554
+ if (typeof node.name !== "string" || node.name.trim() === "") {
555
+ this.err(issues, 8, `Node with id "${node.id}" is missing a name`, node.id);
556
+ }
557
+ }
558
+ }
559
+ // Rule 9: connections is a plain object
560
+ checkRule9(w, issues) {
561
+ if (typeof w.connections !== "object" || w.connections === null || Array.isArray(w.connections)) {
562
+ this.err(issues, 9, "connections must be a plain object (use {} for single-node workflows)");
563
+ }
564
+ }
565
+ // Rule 10: every connection target node name exists in nodes
566
+ checkRule10(w, issues) {
567
+ if (!Array.isArray(w.nodes) || typeof w.connections !== "object" || w.connections === null) return;
568
+ const nodeNames = new Set(w.nodes.map((n) => n.name));
569
+ for (const [sourceName, outputs] of Object.entries(w.connections)) {
570
+ if (!nodeNames.has(sourceName)) {
571
+ this.err(issues, 10, `Connection source "${sourceName}" does not exist in nodes`);
572
+ continue;
573
+ }
574
+ if (typeof outputs !== "object" || outputs === null) continue;
575
+ for (const portGroup of Object.values(outputs)) {
576
+ if (!Array.isArray(portGroup)) continue;
577
+ for (const targets of portGroup) {
578
+ if (!Array.isArray(targets)) continue;
579
+ for (const target of targets) {
580
+ const t = target;
581
+ if (typeof t?.node === "string" && !nodeNames.has(t.node)) {
582
+ this.err(issues, 10, `Connection target "${t.node}" does not exist in nodes`);
583
+ }
584
+ }
585
+ }
586
+ }
587
+ }
588
+ }
589
+ // Rule 11 (WARN): every non-trigger node has at least one incoming connection
590
+ checkRule11(w, issues) {
591
+ if (!Array.isArray(w.nodes) || typeof w.connections !== "object" || w.connections === null) return;
592
+ const reachable = /* @__PURE__ */ new Set();
593
+ for (const [, outputs] of Object.entries(w.connections)) {
594
+ if (typeof outputs !== "object" || outputs === null) continue;
595
+ for (const portGroup of Object.values(outputs)) {
596
+ if (!Array.isArray(portGroup)) continue;
597
+ for (const targets of portGroup) {
598
+ if (!Array.isArray(targets)) continue;
599
+ for (const target of targets) {
600
+ const t = target;
601
+ if (typeof t?.node === "string") reachable.add(t.node);
602
+ }
603
+ }
604
+ }
605
+ }
606
+ for (const node of w.nodes) {
607
+ if (!this.isTriggerNode(node) && !reachable.has(node.name)) {
608
+ this.warn(issues, 11, `Node "${node.name}" has no incoming connections and may never execute`, node.id);
609
+ }
610
+ }
611
+ }
612
+ // Rule 12: forbidden fields absent from workflow root
613
+ checkRule12(w, issues) {
614
+ const wObj = w;
615
+ for (const field of FORBIDDEN_ON_CREATE) {
616
+ if (field in wObj) {
617
+ this.err(issues, 12, `Forbidden field "${field}" present in workflow \u2014 remove it before deploying`);
618
+ }
619
+ }
620
+ }
621
+ // Rule 13: settings, if present, is a plain object
622
+ checkRule13(w, issues) {
623
+ if (w.settings !== void 0) {
624
+ if (typeof w.settings !== "object" || w.settings === null || Array.isArray(w.settings)) {
625
+ this.err(issues, 13, "workflow.settings must be a plain object");
626
+ }
627
+ }
628
+ }
629
+ // Rule 14: at least one trigger node is present
630
+ checkRule14(w, issues) {
631
+ if (!Array.isArray(w.nodes)) return;
632
+ const hasTrigger = w.nodes.some((n) => this.isTriggerNode(n));
633
+ if (!hasTrigger) {
634
+ this.err(issues, 14, "Workflow must contain at least one trigger node");
635
+ }
636
+ }
637
+ // Rule 15: node type string matches expected format
638
+ checkRule15(w, issues) {
639
+ if (!Array.isArray(w.nodes)) return;
640
+ for (const node of w.nodes) {
641
+ if (typeof node.type !== "string") continue;
642
+ if (!NODE_TYPE_PATTERN.test(node.type)) {
643
+ this.err(issues, 15, `Node "${node.name}" has malformed type string: "${node.type}"`, node.id);
644
+ }
645
+ }
646
+ }
647
+ // Rule 16: node names are unique within the workflow
648
+ checkRule16(w, issues) {
649
+ if (!Array.isArray(w.nodes)) return;
650
+ const seen = /* @__PURE__ */ new Set();
651
+ for (const node of w.nodes) {
652
+ if (!node.name) continue;
653
+ if (seen.has(node.name)) {
654
+ this.err(issues, 16, `Duplicate node name: "${node.name}"`, node.id);
655
+ }
656
+ seen.add(node.name);
657
+ }
658
+ }
659
+ // Rule 17: credentials shape — each entry has id and name
660
+ checkRule17(w, issues) {
661
+ if (!Array.isArray(w.nodes)) return;
662
+ for (const node of w.nodes) {
663
+ if (!node.credentials) continue;
664
+ for (const [credType, credRef] of Object.entries(node.credentials)) {
665
+ if (typeof credRef !== "object" || credRef === null) {
666
+ this.err(issues, 17, `Node "${node.name}" credential "${credType}" must be an object with id and name`, node.id);
667
+ continue;
668
+ }
669
+ const ref = credRef;
670
+ if (typeof ref["id"] !== "string" || ref["id"].trim() === "" || typeof ref["name"] !== "string" || ref["name"].trim() === "") {
671
+ this.err(issues, 17, `Node "${node.name}" credential "${credType}" must have non-empty string id and name fields`, node.id);
672
+ }
673
+ }
674
+ }
675
+ }
676
+ // Rule 18 (WARN): AI connections originate from sub-nodes, not the agent/chain root
677
+ checkRule18(w, issues) {
678
+ if (typeof w.connections !== "object" || w.connections === null) return;
679
+ const agentTypes = /* @__PURE__ */ new Set([
680
+ "@n8n/n8n-nodes-langchain.agent",
681
+ "@n8n/n8n-nodes-langchain.chainLlm",
682
+ "@n8n/n8n-nodes-langchain.chainRetrievalQa",
683
+ "@n8n/n8n-nodes-langchain.chainSummarization"
684
+ ]);
685
+ if (!Array.isArray(w.nodes)) return;
686
+ const nodesByName = new Map(w.nodes.map((n) => [n.name, n]));
687
+ for (const [sourceName, outputs] of Object.entries(w.connections)) {
688
+ const sourceNode = nodesByName.get(sourceName);
689
+ if (!sourceNode) continue;
690
+ if (!agentTypes.has(sourceNode.type)) continue;
691
+ if (typeof outputs !== "object" || outputs === null) continue;
692
+ for (const connType of AI_CONNECTION_TYPES) {
693
+ if (connType in outputs) {
694
+ this.warn(
695
+ issues,
696
+ 18,
697
+ `Node "${sourceName}" uses AI connection type "${connType}" as a SOURCE \u2014 AI sub-nodes should be the source, not the agent/chain root`,
698
+ sourceNode.id
699
+ );
700
+ }
701
+ }
702
+ }
703
+ }
704
+ // Rule 19 (WARN): typeVersion is within known safe range for registered node types
705
+ checkRule19(w, issues) {
706
+ if (!Array.isArray(w.nodes)) return;
707
+ for (const node of w.nodes) {
708
+ if (typeof node.type !== "string" || typeof node.typeVersion !== "number") continue;
709
+ if (!this.registry.isVersionSafe(node.type, node.typeVersion)) {
710
+ this.warn(
711
+ issues,
712
+ 19,
713
+ `Node "${node.name}" uses typeVersion ${node.typeVersion} for type "${node.type}" which is not in the known safe list`,
714
+ node.id
715
+ );
716
+ }
717
+ }
718
+ }
719
+ };
720
+
721
+ // src/errors/generation-error.ts
722
+ var GenerationError = class extends KairosError {
723
+ constructor(message, cause) {
724
+ super(message, cause);
725
+ this.name = "GenerationError";
726
+ }
727
+ };
728
+
729
+ // src/errors/response-parse-error.ts
730
+ var ResponseParseError = class extends KairosError {
731
+ constructor(message, cause) {
732
+ super(message, cause);
733
+ this.name = "ResponseParseError";
734
+ }
735
+ };
736
+
737
+ // src/errors/validation-error.ts
738
+ var ValidationError = class extends KairosError {
739
+ constructor(message, issues) {
740
+ super(message);
741
+ this.issues = issues;
742
+ this.name = "ValidationError";
743
+ }
744
+ issues;
745
+ };
746
+
747
+ // src/generation/prompts/v1.ts
748
+ var SYSTEM_PROMPT_V1 = `You are a workflow generation engine for n8n. Your only output is a generate_workflow tool call containing valid n8n workflow JSON. You never respond with prose, explanations, or markdown. If you cannot fulfill the request, set the error field in the tool call.
749
+
750
+ ## HARD RULES \u2014 violating any of these causes immediate deployment failure
751
+
752
+ ### Forbidden fields \u2014 NEVER include these in the workflow object:
753
+ id, active, createdAt, updatedAt, versionId, meta, isArchived, activeVersionId, activeVersion, pinData, triggerCount, shared, staticData
754
+
755
+ ### Required top-level structure:
756
+ {
757
+ "name": "<descriptive name>",
758
+ "nodes": [...],
759
+ "connections": {...},
760
+ "settings": {
761
+ "saveExecutionProgress": true,
762
+ "saveManualExecutions": true,
763
+ "saveDataErrorExecution": "all",
764
+ "saveDataSuccessExecution": "all",
765
+ "executionTimeout": 3600,
766
+ "timezone": "America/New_York",
767
+ "executionOrder": "v1"
768
+ }
769
+ }
770
+
771
+ ### Node IDs:
772
+ - Every node.id must be a valid UUID v4 (random hex, format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx)
773
+ - Never reuse IDs, never use sequential fake IDs like "node-1"
774
+
775
+ ### Credentials:
776
+ - Only reference credentials with exact type names (see catalog below)
777
+ - If credential ID is unknown, OMIT the credentials block entirely \u2014 never invent credential IDs
778
+ - Never put API keys or tokens in parameters when a credential type exists
779
+
780
+ ### Node names:
781
+ - All node names must be unique within the workflow
782
+ - Use descriptive names: "Fetch Open Invoices" not "HTTP Request 2"
783
+
784
+ ### Positioning:
785
+ - Trigger node: [250, 300]
786
+ - Each subsequent step: x + 220 minimum
787
+ - Parallel branches: offset y by \xB1150
788
+ - AI sub-nodes: place below their root node (y + 200)
789
+
790
+ ---
791
+
792
+ ## CONNECTION RULES \u2014 the most common source of errors
793
+
794
+ ### Standard connections (main data flow):
795
+ "NodeA": { "main": [ [ { "node": "NodeB", "type": "main", "index": 0 } ] ] }
796
+
797
+ ### AI connections \u2014 CRITICAL: the SUB-NODE is the SOURCE, NOT the agent/chain:
798
+ "OpenAI Chat Model": { "ai_languageModel": [ [ { "node": "AI Agent", "type": "ai_languageModel", "index": 0 } ] ] }
799
+ "Simple Memory": { "ai_memory": [ [ { "node": "AI Agent", "type": "ai_memory", "index": 0 } ] ] }
800
+ "Calculator Tool": { "ai_tool": [ [ { "node": "AI Agent", "type": "ai_tool", "index": 0 } ] ] }
801
+
802
+ The AI Agent node does NOT appear in connections as a source for ai_* types.
803
+ Every AI Agent must have at least one ai_languageModel sub-node connected.
804
+
805
+ ### IF node \u2014 two output ports (0 = true, 1 = false):
806
+ "IF Check": { "main": [ [{ "node": "True Path", "type": "main", "index": 0 }], [{ "node": "False Path", "type": "main", "index": 0 }] ] }
807
+
808
+ ### Triggers have no incoming connections.
809
+ ### Connection keys are NODE NAMES, never node IDs.
810
+
811
+ ---
812
+
813
+ ## NODE CATALOG \u2014 exact type strings and safe typeVersions
814
+
815
+ ### Triggers (always at least one required):
816
+ n8n-nodes-base.manualTrigger typeVersion: 1 \u2014 testing only
817
+ n8n-nodes-base.scheduleTrigger typeVersion: 1.2 \u2014 params: rule.interval[{field, ...}]
818
+ n8n-nodes-base.webhook typeVersion: 2 \u2014 params: httpMethod, path, responseMode
819
+ n8n-nodes-base.formTrigger typeVersion: 2.2
820
+ n8n-nodes-base.emailReadImap typeVersion: 2 \u2014 cred: imap
821
+ n8n-nodes-base.errorTrigger typeVersion: 1
822
+ n8n-nodes-base.executeWorkflowTrigger typeVersion: 1.1
823
+ n8n-nodes-base.gmailTrigger typeVersion: 1.2 \u2014 cred: gmailOAuth2
824
+ n8n-nodes-base.slackTrigger typeVersion: 1 \u2014 cred: slackApi
825
+ n8n-nodes-base.telegramTrigger typeVersion: 1.2 \u2014 cred: telegramApi
826
+ n8n-nodes-base.githubTrigger typeVersion: 1 \u2014 cred: githubApi
827
+ n8n-nodes-base.airtableTrigger typeVersion: 1 \u2014 cred: airtableTokenApi
828
+ n8n-nodes-base.notionTrigger typeVersion: 1 \u2014 cred: notionApi
829
+ @n8n/n8n-nodes-langchain.chatTrigger typeVersion: 1.1 \u2014 pairs with AI Agent
830
+
831
+ ### Core logic:
832
+ n8n-nodes-base.code typeVersion: 2 \u2014 params: mode, jsCode
833
+ n8n-nodes-base.httpRequest typeVersion: 4.2 \u2014 params: method, url, [sendBody, jsonBody, sendHeaders, headerParameters]
834
+ n8n-nodes-base.set typeVersion: 3.4 \u2014 params: assignments.assignments[{id, name, value, type}]
835
+ n8n-nodes-base.if typeVersion: 2.2 \u2014 params: conditions.conditions[{id, leftValue, rightValue, operator}], combinator
836
+ n8n-nodes-base.switch typeVersion: 3.2 \u2014 multi-branch routing
837
+ n8n-nodes-base.filter typeVersion: 2.2 \u2014 params: conditions (same as IF), 1 output
838
+ n8n-nodes-base.merge typeVersion: 3 \u2014 modes: append/combine/chooseBranch
839
+ n8n-nodes-base.splitInBatches typeVersion: 3 \u2014 output 0=done, output 1=loop body
840
+ n8n-nodes-base.wait typeVersion: 1.1
841
+ n8n-nodes-base.executeWorkflow typeVersion: 1.2
842
+ n8n-nodes-base.respondToWebhook typeVersion: 1.1 \u2014 required when webhook responseMode is "responseNode"
843
+ n8n-nodes-base.noOp typeVersion: 1
844
+ n8n-nodes-base.splitOut typeVersion: 1
845
+ n8n-nodes-base.aggregate typeVersion: 1
846
+ n8n-nodes-base.stickyNote typeVersion: 1 \u2014 never connected, canvas annotation only
847
+
848
+ ### Email / messaging:
849
+ n8n-nodes-base.emailSend typeVersion: 2.1 \u2014 cred: smtp
850
+ n8n-nodes-base.slack typeVersion: 2.2 \u2014 cred: slackOAuth2Api \u2014 params: resource, operation, select, channelId{__rl}, text
851
+ n8n-nodes-base.telegram typeVersion: 1.2 \u2014 cred: telegramApi
852
+ n8n-nodes-base.discord typeVersion: 2 \u2014 cred: discordWebhookApi
853
+
854
+ ### Google:
855
+ n8n-nodes-base.gmail typeVersion: 2.1 \u2014 cred: gmailOAuth2 \u2014 params: resource, operation
856
+ n8n-nodes-base.googleSheets typeVersion: 4.5 \u2014 cred: googleSheetsOAuth2Api \u2014 params: resource, operation, documentId{__rl}, sheetName{__rl}
857
+ n8n-nodes-base.googleDrive typeVersion: 3 \u2014 cred: googleDriveOAuth2Api
858
+ n8n-nodes-base.googleCalendar typeVersion: 1.3 \u2014 cred: googleCalendarOAuth2Api
859
+
860
+ ### Productivity:
861
+ n8n-nodes-base.notion typeVersion: 2.2 \u2014 cred: notionApi
862
+ n8n-nodes-base.airtable typeVersion: 2.1 \u2014 cred: airtableTokenApi
863
+ n8n-nodes-base.github typeVersion: 1.1 \u2014 cred: githubApi
864
+ n8n-nodes-base.jira typeVersion: 1 \u2014 cred: jiraSoftwareCloudApi
865
+ n8n-nodes-base.hubspot typeVersion: 2.1 \u2014 cred: hubspotOAuth2Api
866
+
867
+ ### Databases:
868
+ n8n-nodes-base.postgres typeVersion: 2.5 \u2014 cred: postgres
869
+ n8n-nodes-base.mySql typeVersion: 2.4 \u2014 cred: mySql
870
+ n8n-nodes-base.redis typeVersion: 1 \u2014 cred: redis
871
+ n8n-nodes-base.supabase typeVersion: 1 \u2014 cred: supabaseApi
872
+ n8n-nodes-base.awsS3 typeVersion: 2 \u2014 cred: aws
873
+
874
+ ### AI \u2014 Root nodes (sit on main data flow):
875
+ @n8n/n8n-nodes-langchain.agent typeVersion: 1.9 \u2014 params: promptType, text (if define), options.systemMessage
876
+ @n8n/n8n-nodes-langchain.chainLlm typeVersion: 1.5
877
+ @n8n/n8n-nodes-langchain.chainRetrievalQa typeVersion: 1.4
878
+ @n8n/n8n-nodes-langchain.openAi typeVersion: 1.8 \u2014 cred: openAiApi
879
+ @n8n/n8n-nodes-langchain.anthropic typeVersion: 1 \u2014 cred: anthropicApi
880
+
881
+ ### AI \u2014 Sub-nodes (sources of ai_* connections):
882
+ @n8n/n8n-nodes-langchain.lmChatOpenAi typeVersion: 1.7 \u2014 cred: openAiApi \u2014 ai_languageModel
883
+ @n8n/n8n-nodes-langchain.lmChatAnthropic typeVersion: 1.3 \u2014 cred: anthropicApi \u2014 ai_languageModel
884
+ @n8n/n8n-nodes-langchain.lmChatGoogleGemini typeVersion: 1 \u2014 cred: googlePalmApi \u2014 ai_languageModel
885
+ @n8n/n8n-nodes-langchain.memoryBufferWindow typeVersion: 1.3 \u2014 \u2014 ai_memory
886
+ @n8n/n8n-nodes-langchain.toolWorkflow typeVersion: 2 \u2014 \u2014 ai_tool
887
+ @n8n/n8n-nodes-langchain.toolCode typeVersion: 1.1 \u2014 \u2014 ai_tool
888
+ @n8n/n8n-nodes-langchain.toolHttpRequest typeVersion: 1.1 \u2014 \u2014 ai_tool
889
+ @n8n/n8n-nodes-langchain.toolCalculator typeVersion: 1 \u2014 \u2014 ai_tool
890
+
891
+ ### Resource locator (__rl) format (Google / Slack / Notion modern nodes):
892
+ { "__rl": true, "mode": "id", "value": "ACTUAL_ID" }
893
+ { "__rl": true, "mode": "name", "value": "#channel-name" }
894
+
895
+ ### App node parameter pattern:
896
+ { "resource": "message", "operation": "send", ...operation-specific fields }
897
+
898
+ ### Schedule Trigger \u2014 daily at 9am example:
899
+ { "rule": { "interval": [{ "field": "days", "daysInterval": 1, "triggerAtHour": 9, "triggerAtMinute": 0 }] } }
900
+ Cron: { "rule": { "interval": [{ "field": "cronExpression", "expression": "0 9 * * 1-5" }] } }
901
+
902
+ ---
903
+
904
+ ## PRE-DELIVERY SELF-CHECK (do this before calling the tool):
905
+ 1. Every connection source/target name exists in nodes array
906
+ 2. No duplicate node names
907
+ 3. No duplicate node IDs
908
+ 4. No forbidden fields at the workflow root
909
+ 5. At least one trigger node present
910
+ 6. Every AI Agent has an ai_languageModel sub-node
911
+ 7. settings block is complete with executionOrder: "v1"
912
+
913
+ ---
914
+
915
+ Respond ONLY with a generate_workflow tool call. No prose. No markdown outside the tool call.
916
+ If the request is impossible or unclear, set the error field instead of generating a workflow.`;
917
+
918
+ // src/generation/prompt-builder.ts
919
+ var PromptBuilder = class {
920
+ build(request, matches) {
921
+ const mode = this.resolveMode(matches);
922
+ const system = this.buildSystem(matches, mode);
923
+ const userMessage = this.buildUserMessage(request, matches, mode);
924
+ return { system, userMessage, mode, matches };
925
+ }
926
+ buildCorrectionMessage(request, matches, allIssues, attempt) {
927
+ const base = this.buildUserMessage(request, matches, this.resolveMode(matches));
928
+ return `${base}
929
+
930
+ IMPORTANT: A previous generation attempt (attempt ${attempt}) failed validation with these issues:
931
+ ${allIssues.join("\n")}
932
+
933
+ Fix ALL of the above issues in your new response. Do not repeat any of these mistakes.`;
934
+ }
935
+ resolveMode(matches) {
936
+ if (matches.length === 0) return "scratch";
937
+ const top = matches[0];
938
+ if (!top) return "scratch";
939
+ if (top.score >= 0.92) return "direct";
940
+ if (top.score >= 0.72) return "reference";
941
+ return "scratch";
942
+ }
943
+ buildSystem(matches, mode) {
944
+ const blocks = [
945
+ {
946
+ type: "text",
947
+ text: SYSTEM_PROMPT_V1,
948
+ cache_control: { type: "ephemeral" }
949
+ }
950
+ ];
951
+ if (mode === "reference" && matches.length > 0) {
952
+ const refText = matches.slice(0, 3).map((m) => {
953
+ const nodes = m.workflow.workflow.nodes.map((n) => ` - ${n.name} (${n.type} v${n.typeVersion})`).join("\n");
954
+ return `Reference workflow: "${m.workflow.description}" (similarity: ${m.score.toFixed(2)})
955
+ Nodes:
956
+ ${nodes}`;
957
+ }).join("\n\n");
958
+ blocks.push({
959
+ type: "text",
960
+ text: `## Similar Workflows From Library (for reference only \u2014 adapt, do not copy verbatim)
961
+
962
+ ${refText}`
963
+ });
964
+ }
965
+ if (mode === "direct" && matches[0]) {
966
+ const match = matches[0];
967
+ blocks.push({
968
+ type: "text",
969
+ text: `## Closely Matched Workflow (score: ${match.score.toFixed(2)}) \u2014 adapt this structure:
970
+
971
+ ${JSON.stringify(match.workflow.workflow, null, 2)}`
972
+ });
973
+ }
974
+ return blocks;
975
+ }
976
+ buildUserMessage(request, _matches, _mode) {
977
+ const namePart = request.name ? `
978
+ Workflow name: "${request.name}"` : "";
979
+ return `Build a workflow that: ${request.description}${namePart}`;
980
+ }
981
+ };
982
+
983
+ // src/generation/designer.ts
984
+ var MAX_ATTEMPTS = 3;
985
+ var BASE_TEMPERATURE = 0.2;
986
+ var FINAL_TEMPERATURE = 0.1;
987
+ var GENERATE_WORKFLOW_TOOL = {
988
+ name: "generate_workflow",
989
+ description: "Generate a valid n8n workflow JSON object",
990
+ input_schema: {
991
+ type: "object",
992
+ properties: {
993
+ workflow: {
994
+ type: "object",
995
+ description: "The complete n8n workflow object",
996
+ properties: {
997
+ name: { type: "string" },
998
+ nodes: { type: "array" },
999
+ connections: { type: "object" },
1000
+ settings: { type: "object" }
1001
+ },
1002
+ required: ["name", "nodes", "connections"]
1003
+ },
1004
+ credentialsNeeded: {
1005
+ type: "array",
1006
+ description: "List of credentials the user must configure before activating",
1007
+ items: {
1008
+ type: "object",
1009
+ properties: {
1010
+ service: { type: "string" },
1011
+ credentialType: { type: "string" },
1012
+ description: { type: "string" }
1013
+ },
1014
+ required: ["service", "credentialType", "description"]
1015
+ }
1016
+ },
1017
+ error: {
1018
+ type: "string",
1019
+ description: "Set this if the request cannot be fulfilled \u2014 explain why"
1020
+ }
1021
+ },
1022
+ required: ["workflow"]
1023
+ }
1024
+ };
1025
+ var WorkflowDesigner = class {
1026
+ constructor(anthropic, model, logger) {
1027
+ this.anthropic = anthropic;
1028
+ this.model = model;
1029
+ this.logger = logger;
1030
+ this.validator = new N8nValidator();
1031
+ this.promptBuilder = new PromptBuilder();
1032
+ }
1033
+ anthropic;
1034
+ model;
1035
+ logger;
1036
+ validator;
1037
+ promptBuilder;
1038
+ async design(request, matches) {
1039
+ const allIssues = [];
1040
+ const attemptMetadata = [];
1041
+ let attempts = 0;
1042
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
1043
+ attempts = attempt;
1044
+ const temperature = attempt === MAX_ATTEMPTS ? FINAL_TEMPERATURE : BASE_TEMPERATURE;
1045
+ const built = this.promptBuilder.build(request, matches);
1046
+ let userMessage;
1047
+ if (attempt === 1) {
1048
+ userMessage = built.userMessage;
1049
+ this.logger.debug("WorkflowDesigner: attempt 1", { description: request.description });
1050
+ } else {
1051
+ const issueLines = allIssues.map(
1052
+ (i) => `- [Rule ${i.rule}] ${i.message}${i.nodeId ? ` (node: ${i.nodeId})` : ""}`
1053
+ );
1054
+ userMessage = this.promptBuilder.buildCorrectionMessage(request, matches, issueLines, attempt - 1);
1055
+ this.logger.debug(`WorkflowDesigner: correction attempt ${attempt}`, { issueCount: allIssues.length });
1056
+ }
1057
+ const start = Date.now();
1058
+ const message = await this.callClaude(built.system, userMessage, temperature);
1059
+ const durationMs = Date.now() - start;
1060
+ const parsed = this.extractToolUse(message);
1061
+ if (parsed.error) {
1062
+ throw new GenerationError(`Claude declined to generate workflow: ${parsed.error}`);
1063
+ }
1064
+ const validation = this.validator.validate(parsed.workflow);
1065
+ const errors = validation.issues.filter((i) => i.severity === "error");
1066
+ attemptMetadata.push({
1067
+ attempt,
1068
+ temperature,
1069
+ durationMs,
1070
+ tokensInput: message.usage.input_tokens,
1071
+ tokensOutput: message.usage.output_tokens,
1072
+ validationPassed: validation.valid,
1073
+ issues: errors
1074
+ });
1075
+ if (validation.valid) {
1076
+ return { workflow: parsed.workflow, credentialsNeeded: parsed.credentialsNeeded, attempts, attemptMetadata };
1077
+ }
1078
+ allIssues.push(...errors);
1079
+ this.logger.warn(`WorkflowDesigner: validation failed on attempt ${attempt}`, {
1080
+ newErrors: errors.length,
1081
+ totalErrors: allIssues.length
1082
+ });
1083
+ }
1084
+ throw new ValidationError(
1085
+ `Workflow failed validation after ${MAX_ATTEMPTS} attempts (${allIssues.length} total errors)`,
1086
+ allIssues
1087
+ );
1088
+ }
1089
+ async callClaude(system, userMessage, temperature) {
1090
+ try {
1091
+ return await this.anthropic.messages.create({
1092
+ model: this.model,
1093
+ max_tokens: 8192,
1094
+ temperature,
1095
+ system,
1096
+ messages: [{ role: "user", content: userMessage }],
1097
+ tools: [GENERATE_WORKFLOW_TOOL],
1098
+ tool_choice: { type: "tool", name: "generate_workflow" }
1099
+ });
1100
+ } catch (err) {
1101
+ throw new GenerationError("Anthropic API call failed", err);
1102
+ }
1103
+ }
1104
+ extractToolUse(message) {
1105
+ const toolUseBlock = message.content.find(
1106
+ (block) => block.type === "tool_use"
1107
+ );
1108
+ if (!toolUseBlock) {
1109
+ throw new ResponseParseError(
1110
+ "Claude response contained no tool_use block \u2014 forced tool_choice failed unexpectedly"
1111
+ );
1112
+ }
1113
+ const input = toolUseBlock.input;
1114
+ if (typeof input["error"] === "string") {
1115
+ return {
1116
+ workflow: { name: "", nodes: [], connections: {} },
1117
+ credentialsNeeded: [],
1118
+ error: input["error"]
1119
+ };
1120
+ }
1121
+ if (!input["workflow"] || typeof input["workflow"] !== "object") {
1122
+ throw new ResponseParseError("generate_workflow tool call missing workflow field");
1123
+ }
1124
+ const workflow = input["workflow"];
1125
+ const credentialsNeeded = input["credentialsNeeded"] ?? [];
1126
+ return { workflow, credentialsNeeded };
1127
+ }
1128
+ };
1129
+
1130
+ // src/telemetry/collector.ts
1131
+ var import_promises = require("fs/promises");
1132
+ var import_node_path = require("path");
1133
+ var import_node_os = require("os");
1134
+ var TelemetryCollector = class {
1135
+ dir;
1136
+ sessionId;
1137
+ constructor(dir) {
1138
+ this.dir = dir ?? (0, import_node_path.join)((0, import_node_os.homedir)(), ".kairos", "telemetry");
1139
+ this.sessionId = generateUUID();
1140
+ }
1141
+ async emit(eventType, data) {
1142
+ const event = {
1143
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1144
+ sessionId: this.sessionId,
1145
+ eventType,
1146
+ data
1147
+ };
1148
+ await (0, import_promises.mkdir)(this.dir, { recursive: true });
1149
+ const filename = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10) + ".jsonl";
1150
+ const filepath = (0, import_node_path.join)(this.dir, filename);
1151
+ await (0, import_promises.appendFile)(filepath, JSON.stringify(event) + "\n", "utf-8");
1152
+ }
1153
+ };
1154
+
1155
+ // src/utils/logger.ts
1156
+ var nullLogger = {
1157
+ debug() {
1158
+ },
1159
+ info() {
1160
+ },
1161
+ warn() {
1162
+ },
1163
+ error() {
1164
+ }
1165
+ };
1166
+
1167
+ // src/client.ts
1168
+ var DEFAULT_MODEL = "claude-sonnet-4-6";
1169
+ var Kairos = class {
1170
+ provider;
1171
+ designer;
1172
+ validator;
1173
+ library;
1174
+ logger;
1175
+ telemetry;
1176
+ model;
1177
+ constructor(options) {
1178
+ const logger = options.logger ?? nullLogger;
1179
+ this.model = options.model ?? DEFAULT_MODEL;
1180
+ const anthropic = new import_sdk.default({ apiKey: options.anthropicApiKey });
1181
+ const apiClient = new N8nApiClient(options.n8nBaseUrl, options.n8nApiKey, logger);
1182
+ const stripper = new N8nFieldStripper();
1183
+ this.provider = new N8nProvider(apiClient, stripper);
1184
+ this.designer = new WorkflowDesigner(anthropic, this.model, logger);
1185
+ this.validator = new N8nValidator();
1186
+ this.library = options.library ?? new NullLibrary();
1187
+ this.logger = logger;
1188
+ if (options.telemetry === true) {
1189
+ this.telemetry = new TelemetryCollector();
1190
+ } else if (typeof options.telemetry === "string") {
1191
+ this.telemetry = new TelemetryCollector(options.telemetry);
1192
+ } else {
1193
+ this.telemetry = null;
1194
+ }
1195
+ }
1196
+ async build(description, options) {
1197
+ this.logger.info("Kairos.build", { description, dryRun: options?.dryRun });
1198
+ const buildStart = Date.now();
1199
+ await this.telemetry?.emit("build_start", {
1200
+ description,
1201
+ model: this.model,
1202
+ dryRun: options?.dryRun ?? false
1203
+ });
1204
+ await this.library.initialize();
1205
+ const matches = await this.library.search(description);
1206
+ const designResult = await this.designer.design(
1207
+ { description, ...options?.name ? { name: options.name } : {} },
1208
+ matches
1209
+ );
1210
+ for (const meta of designResult.attemptMetadata) {
1211
+ await this.telemetry?.emit("generation_attempt", {
1212
+ description,
1213
+ attempt: meta.attempt,
1214
+ temperature: meta.temperature,
1215
+ durationMs: meta.durationMs,
1216
+ tokensInput: meta.tokensInput,
1217
+ tokensOutput: meta.tokensOutput,
1218
+ validationPassed: meta.validationPassed,
1219
+ issueCount: meta.issues.length,
1220
+ issues: meta.issues.map((i) => ({ rule: i.rule, message: i.message }))
1221
+ });
1222
+ }
1223
+ const workflow = options?.name ? { ...designResult.workflow, name: options.name } : designResult.workflow;
1224
+ if (options?.dryRun) {
1225
+ const result2 = {
1226
+ workflowId: null,
1227
+ name: workflow.name,
1228
+ credentialsNeeded: designResult.credentialsNeeded,
1229
+ activationRequired: true,
1230
+ generationAttempts: designResult.attempts,
1231
+ dryRun: true
1232
+ };
1233
+ const totalTokensInput2 = designResult.attemptMetadata.reduce((s, m) => s + m.tokensInput, 0);
1234
+ const totalTokensOutput2 = designResult.attemptMetadata.reduce((s, m) => s + m.tokensOutput, 0);
1235
+ await this.telemetry?.emit("build_complete", {
1236
+ description,
1237
+ success: true,
1238
+ totalAttempts: designResult.attempts,
1239
+ totalDurationMs: Date.now() - buildStart,
1240
+ totalTokensInput: totalTokensInput2,
1241
+ totalTokensOutput: totalTokensOutput2,
1242
+ workflowName: workflow.name,
1243
+ workflowId: null,
1244
+ dryRun: true,
1245
+ credentialsNeeded: designResult.credentialsNeeded.length
1246
+ });
1247
+ return result2;
1248
+ }
1249
+ const deployed = await this.provider.deploy(workflow);
1250
+ this.library.save(workflow, { description }).catch((err) => {
1251
+ this.logger.warn("Failed to save workflow to library (non-fatal)", { err: String(err) });
1252
+ });
1253
+ if (options?.activate) {
1254
+ await this.provider.activate(deployed.workflowId);
1255
+ }
1256
+ const result = {
1257
+ workflowId: deployed.workflowId,
1258
+ name: deployed.name,
1259
+ credentialsNeeded: designResult.credentialsNeeded,
1260
+ activationRequired: !options?.activate,
1261
+ generationAttempts: designResult.attempts,
1262
+ dryRun: false
1263
+ };
1264
+ const totalTokensInput = designResult.attemptMetadata.reduce((s, m) => s + m.tokensInput, 0);
1265
+ const totalTokensOutput = designResult.attemptMetadata.reduce((s, m) => s + m.tokensOutput, 0);
1266
+ await this.telemetry?.emit("build_complete", {
1267
+ description,
1268
+ success: true,
1269
+ totalAttempts: designResult.attempts,
1270
+ totalDurationMs: Date.now() - buildStart,
1271
+ totalTokensInput,
1272
+ totalTokensOutput,
1273
+ workflowName: deployed.name,
1274
+ workflowId: deployed.workflowId,
1275
+ dryRun: false,
1276
+ credentialsNeeded: designResult.credentialsNeeded.length
1277
+ });
1278
+ return result;
1279
+ }
1280
+ async update(id, description) {
1281
+ this.logger.info("Kairos.update", { id, description });
1282
+ const matches = await this.library.search(description);
1283
+ const designResult = await this.designer.design({ description }, matches);
1284
+ const deployed = await this.provider.update(id, designResult.workflow);
1285
+ return {
1286
+ workflowId: deployed.workflowId,
1287
+ name: deployed.name,
1288
+ credentialsNeeded: designResult.credentialsNeeded,
1289
+ activationRequired: true,
1290
+ generationAttempts: designResult.attempts,
1291
+ dryRun: false
1292
+ };
1293
+ }
1294
+ async get(id) {
1295
+ return this.provider.get(id);
1296
+ }
1297
+ async list() {
1298
+ return this.provider.list();
1299
+ }
1300
+ async activate(id) {
1301
+ await this.provider.activate(id);
1302
+ }
1303
+ async deactivate(id) {
1304
+ await this.provider.deactivate(id);
1305
+ }
1306
+ async delete(id, options) {
1307
+ await this.provider.delete(id, options);
1308
+ }
1309
+ async executions(workflowId, filter) {
1310
+ return this.provider.executions(workflowId, filter);
1311
+ }
1312
+ async execution(id) {
1313
+ return this.provider.execution(id);
1314
+ }
1315
+ async listTags() {
1316
+ return this.provider.listTags();
1317
+ }
1318
+ async createTag(name) {
1319
+ return this.provider.createTag(name);
1320
+ }
1321
+ async tag(workflowId, tagIds) {
1322
+ await this.provider.tag(workflowId, tagIds);
1323
+ }
1324
+ async untag(workflowId, tagIds) {
1325
+ await this.provider.untag(workflowId, tagIds);
1326
+ }
1327
+ };
1328
+
1329
+ // src/library/file-library.ts
1330
+ var import_promises2 = require("fs/promises");
1331
+ var import_node_path2 = require("path");
1332
+ var import_node_os2 = require("os");
1333
+ function tokenize(text) {
1334
+ return text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length > 2);
1335
+ }
1336
+ function computeTfIdf(queryTokens, docTokens, idf) {
1337
+ if (docTokens.length === 0) return 0;
1338
+ let score = 0;
1339
+ const docFreq = /* @__PURE__ */ new Map();
1340
+ for (const t of docTokens) {
1341
+ docFreq.set(t, (docFreq.get(t) ?? 0) + 1);
1342
+ }
1343
+ for (const qt of queryTokens) {
1344
+ const tf = (docFreq.get(qt) ?? 0) / docTokens.length;
1345
+ const idfVal = idf.get(qt) ?? 0;
1346
+ score += tf * idfVal;
1347
+ }
1348
+ return score;
1349
+ }
1350
+ var FileLibrary = class {
1351
+ dir;
1352
+ workflows = [];
1353
+ initialized = false;
1354
+ constructor(dir) {
1355
+ this.dir = dir ?? (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".kairos", "library");
1356
+ }
1357
+ async initialize() {
1358
+ if (this.initialized) return;
1359
+ await (0, import_promises2.mkdir)(this.dir, { recursive: true });
1360
+ const indexPath = (0, import_node_path2.join)(this.dir, "index.json");
1361
+ try {
1362
+ const raw = await (0, import_promises2.readFile)(indexPath, "utf-8");
1363
+ this.workflows = JSON.parse(raw);
1364
+ } catch {
1365
+ this.workflows = [];
1366
+ }
1367
+ this.initialized = true;
1368
+ }
1369
+ async search(description, options) {
1370
+ if (this.workflows.length === 0) return [];
1371
+ const limit = options?.limit ?? 3;
1372
+ const queryTokens = tokenize(description);
1373
+ if (queryTokens.length === 0) return [];
1374
+ const docTokenSets = this.workflows.map(
1375
+ (w) => tokenize(`${w.description} ${w.workflow.name} ${w.tags.join(" ")}`)
1376
+ );
1377
+ const docCount = this.workflows.length;
1378
+ const idf = /* @__PURE__ */ new Map();
1379
+ const allTokens = new Set(queryTokens);
1380
+ for (const token of allTokens) {
1381
+ const docsWithToken = docTokenSets.filter((d) => d.includes(token)).length;
1382
+ idf.set(token, Math.log((docCount + 1) / (docsWithToken + 1)) + 1);
1383
+ }
1384
+ const scored = this.workflows.map((w, i) => ({
1385
+ workflow: w,
1386
+ score: computeTfIdf(queryTokens, docTokenSets[i], idf)
1387
+ })).filter((m) => m.score > 0).sort((a, b) => b.score - a.score).slice(0, limit);
1388
+ return scored.map((m) => ({
1389
+ workflow: m.workflow,
1390
+ score: m.score,
1391
+ mode: m.score > 0.5 ? "direct" : "reference"
1392
+ }));
1393
+ }
1394
+ async save(workflow, metadata) {
1395
+ const id = generateUUID();
1396
+ const stored = {
1397
+ id,
1398
+ workflow,
1399
+ description: metadata.description,
1400
+ tags: metadata.tags ?? [],
1401
+ platform: metadata.platform ?? "n8n",
1402
+ deployCount: 1,
1403
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1404
+ };
1405
+ this.workflows.push(stored);
1406
+ await this.persist();
1407
+ return id;
1408
+ }
1409
+ async recordDeployment(id) {
1410
+ const w = this.workflows.find((w2) => w2.id === id);
1411
+ if (w) {
1412
+ w.deployCount++;
1413
+ w.lastDeployedAt = (/* @__PURE__ */ new Date()).toISOString();
1414
+ await this.persist();
1415
+ }
1416
+ }
1417
+ async get(id) {
1418
+ return this.workflows.find((w) => w.id === id) ?? null;
1419
+ }
1420
+ async list(filters) {
1421
+ let result = this.workflows;
1422
+ if (filters?.platform) {
1423
+ result = result.filter((w) => w.platform === filters.platform);
1424
+ }
1425
+ if (filters?.tags && filters.tags.length > 0) {
1426
+ result = result.filter((w) => filters.tags.some((t) => w.tags.includes(t)));
1427
+ }
1428
+ return result;
1429
+ }
1430
+ async persist() {
1431
+ const indexPath = (0, import_node_path2.join)(this.dir, "index.json");
1432
+ await (0, import_promises2.writeFile)(indexPath, JSON.stringify(this.workflows, null, 2), "utf-8");
1433
+ }
1434
+ };
1435
+ // Annotate the CommonJS export names for ESM import in node:
1436
+ 0 && (module.exports = {
1437
+ ApiError,
1438
+ DEFAULT_REGISTRY,
1439
+ FileLibrary,
1440
+ GenerationError,
1441
+ GuardError,
1442
+ Kairos,
1443
+ KairosError,
1444
+ N8nApiClient,
1445
+ N8nFieldStripper,
1446
+ N8nProvider,
1447
+ N8nValidator,
1448
+ NodeRegistry,
1449
+ NullLibrary,
1450
+ ProviderError,
1451
+ ResponseParseError,
1452
+ TelemetryCollector,
1453
+ ValidationError,
1454
+ nullLogger
1455
+ });
1456
+ //# sourceMappingURL=index.cjs.map