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