@kairos-sdk/core 0.2.0 → 0.3.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.
@@ -1,30 +1,3 @@
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 recordOutcome(_id, _outcome) {
19
- }
20
- async get(_id) {
21
- return null;
22
- }
23
- async list(_filters) {
24
- return [];
25
- }
26
- };
27
-
28
1
  // src/errors/base.ts
29
2
  var KairosError = class extends Error {
30
3
  constructor(message, cause) {
@@ -267,78 +240,6 @@ var N8nFieldStripper = class {
267
240
  }
268
241
  };
269
242
 
270
- // src/errors/guard-error.ts
271
- var GuardError = class extends KairosError {
272
- constructor(message) {
273
- super(message);
274
- this.name = "GuardError";
275
- }
276
- };
277
-
278
- // src/providers/n8n/provider.ts
279
- var N8nProvider = class {
280
- constructor(client, stripper) {
281
- this.client = client;
282
- this.stripper = stripper;
283
- }
284
- client;
285
- stripper;
286
- platform = "n8n";
287
- async deploy(workflow) {
288
- const stripped = this.stripper.stripForCreate(workflow);
289
- const response = await this.client.createWorkflow(stripped);
290
- return { workflowId: response.id, name: response.name };
291
- }
292
- async update(id, workflow) {
293
- const stripped = this.stripper.stripForUpdate(workflow);
294
- const response = await this.client.updateWorkflow(id, stripped);
295
- return { workflowId: response.id, name: response.name };
296
- }
297
- async get(id) {
298
- const response = await this.client.getWorkflow(id);
299
- return {
300
- name: response.name,
301
- nodes: response.nodes,
302
- connections: response.connections,
303
- ...response.settings !== void 0 ? { settings: response.settings } : {},
304
- ...response.tags !== void 0 ? { tags: response.tags } : {}
305
- };
306
- }
307
- async list() {
308
- return this.client.listWorkflows();
309
- }
310
- async activate(id) {
311
- await this.client.activateWorkflow(id);
312
- }
313
- async deactivate(id) {
314
- await this.client.deactivateWorkflow(id);
315
- }
316
- async delete(id, options) {
317
- if (options.confirm !== true) {
318
- throw new GuardError("delete() requires { confirm: true } to prevent accidental deletion");
319
- }
320
- await this.client.deleteWorkflow(id);
321
- }
322
- async executions(workflowId, filter) {
323
- return this.client.getExecutions(workflowId, filter);
324
- }
325
- async execution(id) {
326
- return this.client.getExecution(id);
327
- }
328
- async listTags() {
329
- return this.client.listTags();
330
- }
331
- async createTag(name) {
332
- return this.client.createTag(name);
333
- }
334
- async tag(workflowId, tagIds) {
335
- await this.client.tagWorkflow(workflowId, tagIds);
336
- }
337
- async untag(workflowId, tagIds) {
338
- await this.client.untagWorkflow(workflowId, tagIds);
339
- }
340
- };
341
-
342
243
  // src/validation/registry.ts
343
244
  var DEFAULT_REGISTRY = [
344
245
  // Trigger nodes
@@ -843,170 +744,6 @@ var N8nValidator = class {
843
744
  }
844
745
  };
845
746
 
846
- // src/errors/generation-error.ts
847
- var GenerationError = class extends KairosError {
848
- constructor(message, cause) {
849
- super(message, cause);
850
- this.name = "GenerationError";
851
- }
852
- };
853
-
854
- // src/errors/response-parse-error.ts
855
- var ResponseParseError = class extends KairosError {
856
- constructor(message, cause) {
857
- super(message, cause);
858
- this.name = "ResponseParseError";
859
- }
860
- };
861
-
862
- // src/errors/validation-error.ts
863
- var ValidationError = class extends KairosError {
864
- constructor(message, issues) {
865
- super(message);
866
- this.issues = issues;
867
- this.name = "ValidationError";
868
- }
869
- issues;
870
- };
871
-
872
- // src/telemetry/collector.ts
873
- import { appendFile, mkdir } from "fs/promises";
874
- import { join } from "path";
875
- import { homedir } from "os";
876
-
877
- // src/telemetry/types.ts
878
- var TELEMETRY_SCHEMA_VERSION = 2;
879
-
880
- // src/telemetry/collector.ts
881
- var TelemetryCollector = class {
882
- dir;
883
- sessionId;
884
- dirReady = null;
885
- constructor(dir) {
886
- this.dir = dir ?? join(homedir(), ".kairos", "telemetry");
887
- this.sessionId = generateUUID();
888
- }
889
- async emit(eventType, data) {
890
- const event = {
891
- schemaVersion: TELEMETRY_SCHEMA_VERSION,
892
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
893
- sessionId: this.sessionId,
894
- eventType,
895
- data
896
- };
897
- if (!this.dirReady) {
898
- this.dirReady = mkdir(this.dir, { recursive: true }).then(() => {
899
- });
900
- }
901
- await this.dirReady;
902
- const filename = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10) + ".jsonl";
903
- const filepath = join(this.dir, filename);
904
- await appendFile(filepath, JSON.stringify(event) + "\n", "utf-8");
905
- }
906
- };
907
-
908
- // src/telemetry/reader.ts
909
- import { readFile, readdir } from "fs/promises";
910
- import { join as join2 } from "path";
911
- import { homedir as homedir2 } from "os";
912
- var TelemetryReader = class {
913
- dir;
914
- cache = null;
915
- cacheTime = 0;
916
- constructor(dir) {
917
- this.dir = dir ?? join2(homedir2(), ".kairos", "telemetry");
918
- }
919
- async getFailureRates(days = 30) {
920
- const now = Date.now();
921
- if (this.cache && now - this.cacheTime < 5 * 60 * 1e3) {
922
- return this.cache;
923
- }
924
- const events = await this.readRecentEvents(days);
925
- const buildSessions = new Set(
926
- events.filter((e) => e.eventType === "build_complete" && !e.data.dryRun).map((e) => e.sessionId)
927
- );
928
- if (buildSessions.size === 0) return [];
929
- const ruleSessions = /* @__PURE__ */ new Map();
930
- for (const event of events) {
931
- if (event.eventType !== "generation_attempt") continue;
932
- if (!buildSessions.has(event.sessionId)) continue;
933
- const data = event.data;
934
- if (data.validationPassed || !data.issues) continue;
935
- for (const issue of data.issues) {
936
- const entry = ruleSessions.get(issue.rule) ?? { sessions: /* @__PURE__ */ new Set(), messages: /* @__PURE__ */ new Map() };
937
- entry.sessions.add(event.sessionId);
938
- entry.messages.set(issue.message, (entry.messages.get(issue.message) ?? 0) + 1);
939
- ruleSessions.set(issue.rule, entry);
940
- }
941
- }
942
- const rates = [];
943
- for (const [rule, entry] of ruleSessions) {
944
- let topMessage = "";
945
- let topCount = 0;
946
- for (const [msg, count] of entry.messages) {
947
- if (count > topCount) {
948
- topMessage = msg;
949
- topCount = count;
950
- }
951
- }
952
- rates.push({
953
- rule,
954
- failureCount: entry.sessions.size,
955
- totalBuilds: buildSessions.size,
956
- rate: entry.sessions.size / buildSessions.size,
957
- commonMessage: topMessage
958
- });
959
- }
960
- rates.sort((a, b) => b.rate - a.rate);
961
- this.cache = rates;
962
- this.cacheTime = now;
963
- return rates;
964
- }
965
- async readRecentEvents(days) {
966
- let files;
967
- try {
968
- files = await readdir(this.dir);
969
- } catch {
970
- return [];
971
- }
972
- const cutoff = /* @__PURE__ */ new Date();
973
- cutoff.setDate(cutoff.getDate() - days);
974
- const cutoffStr = cutoff.toISOString().slice(0, 10);
975
- const datePattern = /^\d{4}-\d{2}-\d{2}\.jsonl$/;
976
- const recentFiles = files.filter((f) => datePattern.test(f) && f >= cutoffStr).sort();
977
- const events = [];
978
- for (const file of recentFiles) {
979
- try {
980
- const content = await readFile(join2(this.dir, file), "utf-8");
981
- for (const line of content.split("\n")) {
982
- if (!line.trim()) continue;
983
- try {
984
- events.push(JSON.parse(line));
985
- } catch {
986
- }
987
- }
988
- } catch {
989
- }
990
- }
991
- return events;
992
- }
993
- };
994
-
995
- // src/utils/logger.ts
996
- var nullLogger = {
997
- debug() {
998
- },
999
- info() {
1000
- },
1001
- warn() {
1002
- },
1003
- error() {
1004
- }
1005
- };
1006
-
1007
- // src/client.ts
1008
- import Anthropic from "@anthropic-ai/sdk";
1009
-
1010
747
  // src/generation/prompts/v1.ts
1011
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.
1012
749
 
@@ -1332,454 +1069,102 @@ Workflow name: "${request.name}"` : "";
1332
1069
  }
1333
1070
  };
1334
1071
 
1335
- // src/generation/designer.ts
1336
- var MAX_ATTEMPTS = 3;
1337
- var BASE_TEMPERATURE = 0.2;
1338
- var FINAL_TEMPERATURE = 0.1;
1339
- var GENERATE_WORKFLOW_TOOL = {
1340
- name: "generate_workflow",
1341
- description: "Generate a valid n8n workflow JSON object",
1342
- input_schema: {
1343
- type: "object",
1344
- properties: {
1345
- workflow: {
1346
- type: "object",
1347
- description: "The complete n8n workflow object",
1348
- properties: {
1349
- name: { type: "string" },
1350
- nodes: { type: "array" },
1351
- connections: { type: "object" },
1352
- settings: { type: "object" }
1353
- },
1354
- required: ["name", "nodes", "connections"]
1355
- },
1356
- credentialsNeeded: {
1357
- type: "array",
1358
- description: "List of credentials the user must configure before activating",
1359
- items: {
1360
- type: "object",
1361
- properties: {
1362
- service: { type: "string" },
1363
- credentialType: { type: "string" },
1364
- description: { type: "string" }
1365
- },
1366
- required: ["service", "credentialType", "description"]
1367
- }
1368
- },
1369
- error: {
1370
- type: "string",
1371
- description: "Set this if the request cannot be fulfilled \u2014 explain why"
1372
- }
1373
- },
1374
- required: []
1375
- }
1376
- };
1377
- var WorkflowDesigner = class {
1378
- constructor(anthropic, model, logger) {
1379
- this.anthropic = anthropic;
1380
- this.model = model;
1381
- this.logger = logger;
1382
- this.validator = new N8nValidator();
1383
- this.promptBuilder = new PromptBuilder();
1072
+ // src/telemetry/reader.ts
1073
+ import { readFile, readdir } from "fs/promises";
1074
+ import { join } from "path";
1075
+ import { homedir } from "os";
1076
+ var TelemetryReader = class {
1077
+ dir;
1078
+ cache = null;
1079
+ cacheTime = 0;
1080
+ constructor(dir) {
1081
+ this.dir = dir ?? join(homedir(), ".kairos", "telemetry");
1384
1082
  }
1385
- anthropic;
1386
- model;
1387
- logger;
1388
- validator;
1389
- promptBuilder;
1390
- async design(request, matches, globalFailureRates = []) {
1391
- const attemptMetadata = [];
1392
- let lastErrors = [];
1393
- let attempts = 0;
1394
- const built = this.promptBuilder.build(request, matches, globalFailureRates);
1395
- for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
1396
- attempts = attempt;
1397
- const temperature = attempt === MAX_ATTEMPTS ? FINAL_TEMPERATURE : BASE_TEMPERATURE;
1398
- let userMessage;
1399
- if (attempt === 1) {
1400
- userMessage = built.userMessage;
1401
- this.logger.debug("WorkflowDesigner: attempt 1", { description: request.description });
1402
- } else {
1403
- const issueLines = lastErrors.map(
1404
- (i) => `- [Rule ${i.rule}] ${i.message}${i.nodeId ? ` (node: ${i.nodeId})` : ""}`
1405
- );
1406
- userMessage = this.promptBuilder.buildCorrectionMessage(request, matches, issueLines, attempt - 1);
1407
- this.logger.debug(`WorkflowDesigner: correction attempt ${attempt}`, { issueCount: lastErrors.length });
1408
- }
1409
- const start = Date.now();
1410
- const message = await this.callClaude(built.system, userMessage, temperature);
1411
- const durationMs = Date.now() - start;
1412
- const parsed = this.extractToolUse(message);
1413
- if (parsed.error) {
1414
- throw new GenerationError(`Claude declined to generate workflow: ${parsed.error}`);
1083
+ async getFailureRates(days = 30) {
1084
+ const now = Date.now();
1085
+ if (this.cache && now - this.cacheTime < 5 * 60 * 1e3) {
1086
+ return this.cache;
1087
+ }
1088
+ const events = await this.readRecentEvents(days);
1089
+ const buildSessions = new Set(
1090
+ events.filter((e) => e.eventType === "build_complete" && !e.data.dryRun).map((e) => e.sessionId)
1091
+ );
1092
+ if (buildSessions.size === 0) return [];
1093
+ const ruleSessions = /* @__PURE__ */ new Map();
1094
+ for (const event of events) {
1095
+ if (event.eventType !== "generation_attempt") continue;
1096
+ if (!buildSessions.has(event.sessionId)) continue;
1097
+ const data = event.data;
1098
+ if (data.validationPassed || !data.issues) continue;
1099
+ for (const issue of data.issues) {
1100
+ const entry = ruleSessions.get(issue.rule) ?? { sessions: /* @__PURE__ */ new Set(), messages: /* @__PURE__ */ new Map() };
1101
+ entry.sessions.add(event.sessionId);
1102
+ entry.messages.set(issue.message, (entry.messages.get(issue.message) ?? 0) + 1);
1103
+ ruleSessions.set(issue.rule, entry);
1415
1104
  }
1416
- const validation = this.validator.validate(parsed.workflow);
1417
- const errors = validation.issues.filter((i) => i.severity === "error");
1418
- attemptMetadata.push({
1419
- attempt,
1420
- temperature,
1421
- durationMs,
1422
- tokensInput: message.usage.input_tokens,
1423
- tokensOutput: message.usage.output_tokens,
1424
- validationPassed: validation.valid,
1425
- issues: validation.issues
1426
- });
1427
- if (validation.valid) {
1428
- return { workflow: parsed.workflow, credentialsNeeded: parsed.credentialsNeeded, attempts, attemptMetadata };
1105
+ }
1106
+ const rates = [];
1107
+ for (const [rule, entry] of ruleSessions) {
1108
+ let topMessage = "";
1109
+ let topCount = 0;
1110
+ for (const [msg, count] of entry.messages) {
1111
+ if (count > topCount) {
1112
+ topMessage = msg;
1113
+ topCount = count;
1114
+ }
1429
1115
  }
1430
- lastErrors = errors;
1431
- this.logger.warn(`WorkflowDesigner: validation failed on attempt ${attempt}`, {
1432
- errorCount: errors.length
1116
+ rates.push({
1117
+ rule,
1118
+ failureCount: entry.sessions.size,
1119
+ totalBuilds: buildSessions.size,
1120
+ rate: entry.sessions.size / buildSessions.size,
1121
+ commonMessage: topMessage
1433
1122
  });
1434
1123
  }
1435
- const finalIssues = attemptMetadata.at(-1)?.issues ?? lastErrors;
1436
- throw new ValidationError(
1437
- `Workflow failed validation after ${MAX_ATTEMPTS} attempts`,
1438
- finalIssues
1439
- );
1124
+ rates.sort((a, b) => b.rate - a.rate);
1125
+ this.cache = rates;
1126
+ this.cacheTime = now;
1127
+ return rates;
1440
1128
  }
1441
- async callClaude(system, userMessage, temperature) {
1442
- const controller = new AbortController();
1443
- const timer = setTimeout(() => controller.abort(), 12e4);
1129
+ async readRecentEvents(days) {
1130
+ let files;
1444
1131
  try {
1445
- return await this.anthropic.messages.create(
1446
- {
1447
- model: this.model,
1448
- max_tokens: 8192,
1449
- temperature,
1450
- system: system.map((b) => ({ type: b.type, text: b.text, ...b.cache_control ? { cache_control: b.cache_control } : {} })),
1451
- messages: [{ role: "user", content: userMessage }],
1452
- tools: [GENERATE_WORKFLOW_TOOL],
1453
- tool_choice: { type: "tool", name: "generate_workflow" }
1454
- },
1455
- { signal: controller.signal }
1456
- );
1457
- } catch (err) {
1458
- const detail = err instanceof Error ? err.message : String(err);
1459
- throw new GenerationError(`Anthropic API call failed: ${detail}`, err);
1460
- } finally {
1461
- clearTimeout(timer);
1462
- }
1463
- }
1464
- extractToolUse(message) {
1465
- const toolUseBlock = message.content.find(
1466
- (block) => block.type === "tool_use"
1467
- );
1468
- if (!toolUseBlock) {
1469
- throw new ResponseParseError(
1470
- "Claude response contained no tool_use block \u2014 forced tool_choice failed unexpectedly"
1471
- );
1472
- }
1473
- const input = toolUseBlock.input;
1474
- if (typeof input["error"] === "string") {
1475
- return {
1476
- workflow: { name: "", nodes: [], connections: {} },
1477
- credentialsNeeded: [],
1478
- error: input["error"]
1479
- };
1480
- }
1481
- if (!input["workflow"] || typeof input["workflow"] !== "object") {
1482
- throw new ResponseParseError("generate_workflow tool call missing workflow field");
1132
+ files = await readdir(this.dir);
1133
+ } catch {
1134
+ return [];
1483
1135
  }
1484
- const workflow = input["workflow"];
1485
- const credentialsNeeded = input["credentialsNeeded"] ?? [];
1486
- return { workflow, credentialsNeeded };
1487
- }
1488
- };
1489
-
1490
- // src/client.ts
1491
- var DEFAULT_MODEL = "claude-sonnet-4-6";
1492
- var Kairos = class {
1493
- provider;
1494
- designer;
1495
- validator;
1496
- library;
1497
- logger;
1498
- telemetry;
1499
- telemetryReader;
1500
- model;
1501
- saveQueue = Promise.resolve(null);
1502
- constructor(options) {
1503
- const logger = options.logger ?? nullLogger;
1504
- this.model = options.model ?? DEFAULT_MODEL;
1505
- if (options.n8nBaseUrl && options.n8nApiKey) {
1136
+ const cutoff = /* @__PURE__ */ new Date();
1137
+ cutoff.setDate(cutoff.getDate() - days);
1138
+ const cutoffStr = cutoff.toISOString().slice(0, 10);
1139
+ const datePattern = /^\d{4}-\d{2}-\d{2}\.jsonl$/;
1140
+ const recentFiles = files.filter((f) => datePattern.test(f) && f >= cutoffStr).sort();
1141
+ const events = [];
1142
+ for (const file of recentFiles) {
1506
1143
  try {
1507
- new URL(options.n8nBaseUrl);
1144
+ const content = await readFile(join(this.dir, file), "utf-8");
1145
+ for (const line of content.split("\n")) {
1146
+ if (!line.trim()) continue;
1147
+ try {
1148
+ events.push(JSON.parse(line));
1149
+ } catch {
1150
+ }
1151
+ }
1508
1152
  } catch {
1509
- throw new GuardError(`Invalid n8nBaseUrl: "${options.n8nBaseUrl}" \u2014 must be a valid URL`);
1510
1153
  }
1511
- const apiClient = new N8nApiClient(options.n8nBaseUrl, options.n8nApiKey, logger);
1512
- const stripper = new N8nFieldStripper();
1513
- this.provider = new N8nProvider(apiClient, stripper);
1514
- } else {
1515
- this.provider = null;
1516
- }
1517
- const anthropic = new Anthropic({ apiKey: options.anthropicApiKey });
1518
- this.designer = new WorkflowDesigner(anthropic, this.model, logger);
1519
- this.validator = new N8nValidator();
1520
- this.library = options.library ?? new NullLibrary();
1521
- this.logger = logger;
1522
- if (options.telemetry === true) {
1523
- this.telemetry = new TelemetryCollector();
1524
- this.telemetryReader = new TelemetryReader();
1525
- } else if (typeof options.telemetry === "string") {
1526
- this.telemetry = new TelemetryCollector(options.telemetry);
1527
- this.telemetryReader = new TelemetryReader(options.telemetry);
1528
- } else {
1529
- this.telemetry = null;
1530
- this.telemetryReader = null;
1531
1154
  }
1155
+ return events;
1532
1156
  }
1533
- requireProvider() {
1534
- if (!this.provider) {
1535
- throw new GuardError("n8nBaseUrl and n8nApiKey are required for this operation \u2014 set them in the Kairos constructor, or use { dryRun: true } for generation-only mode");
1536
- }
1537
- return this.provider;
1538
- }
1539
- validateDescription(description) {
1540
- if (!description || description.trim().length === 0) {
1541
- throw new GuardError("Description is required and must be non-empty");
1542
- }
1543
- }
1544
- async build(description, options) {
1545
- this.validateDescription(description);
1546
- this.logger.info("Kairos.build", { description, dryRun: options?.dryRun });
1547
- const buildStart = Date.now();
1548
- await this.telemetry?.emit("build_start", {
1549
- description,
1550
- model: this.model,
1551
- dryRun: options?.dryRun ?? false
1552
- });
1553
- await this.library.initialize();
1554
- const matches = await this.library.search(description);
1555
- if (matches.length > 0) {
1556
- const top = matches[0];
1557
- this.logger.info(`Library: ${matches.length} match(es), top="${top.workflow.description.slice(0, 50)}" score=${top.score.toFixed(2)} mode=${top.mode}`);
1558
- } else {
1559
- this.logger.info("Library: no matches (scratch mode)");
1560
- }
1561
- const globalFailureRates = await this.telemetryReader?.getFailureRates() ?? [];
1562
- if (globalFailureRates.length > 0) {
1563
- const highFreq = globalFailureRates.filter((r) => r.rate >= 0.15);
1564
- if (highFreq.length > 0) {
1565
- this.logger.info(`Telemetry: ${highFreq.length} high-frequency failure rule(s) will be warned about`);
1566
- }
1567
- }
1568
- const designResult = await this.designer.design(
1569
- { description, ...options?.name ? { name: options.name } : {} },
1570
- matches,
1571
- globalFailureRates
1572
- );
1573
- await this.emitAttemptTelemetry(description, designResult);
1574
- const workflow = options?.name ? { ...designResult.workflow, name: options.name } : designResult.workflow;
1575
- this.saveToLibrary(workflow, description, designResult, matches);
1576
- if (options?.dryRun) {
1577
- const totalTokensInput2 = designResult.attemptMetadata.reduce((s, m) => s + m.tokensInput, 0);
1578
- const totalTokensOutput2 = designResult.attemptMetadata.reduce((s, m) => s + m.tokensOutput, 0);
1579
- await this.telemetry?.emit("build_complete", {
1580
- description,
1581
- success: true,
1582
- totalAttempts: designResult.attempts,
1583
- totalDurationMs: Date.now() - buildStart,
1584
- totalTokensInput: totalTokensInput2,
1585
- totalTokensOutput: totalTokensOutput2,
1586
- workflowName: workflow.name,
1587
- workflowId: null,
1588
- dryRun: true,
1589
- credentialsNeeded: designResult.credentialsNeeded.length
1590
- });
1591
- return {
1592
- workflowId: null,
1593
- name: workflow.name,
1594
- workflow,
1595
- credentialsNeeded: designResult.credentialsNeeded,
1596
- activationRequired: true,
1597
- generationAttempts: designResult.attempts,
1598
- dryRun: true
1599
- };
1600
- }
1601
- const provider = this.requireProvider();
1602
- const deployed = await provider.deploy(workflow);
1603
- this.recordDeploy();
1604
- if (options?.activate) {
1605
- await provider.activate(deployed.workflowId);
1606
- }
1607
- const totalTokensInput = designResult.attemptMetadata.reduce((s, m) => s + m.tokensInput, 0);
1608
- const totalTokensOutput = designResult.attemptMetadata.reduce((s, m) => s + m.tokensOutput, 0);
1609
- await this.telemetry?.emit("build_complete", {
1610
- description,
1611
- success: true,
1612
- totalAttempts: designResult.attempts,
1613
- totalDurationMs: Date.now() - buildStart,
1614
- totalTokensInput,
1615
- totalTokensOutput,
1616
- workflowName: deployed.name,
1617
- workflowId: deployed.workflowId,
1618
- dryRun: false,
1619
- credentialsNeeded: designResult.credentialsNeeded.length
1620
- });
1621
- return {
1622
- workflowId: deployed.workflowId,
1623
- name: deployed.name,
1624
- workflow,
1625
- credentialsNeeded: designResult.credentialsNeeded,
1626
- activationRequired: !options?.activate,
1627
- generationAttempts: designResult.attempts,
1628
- dryRun: false
1629
- };
1630
- }
1631
- async replace(id, description) {
1632
- this.validateDescription(description);
1633
- this.logger.info("Kairos.update", { id, description });
1634
- const buildStart = Date.now();
1635
- await this.telemetry?.emit("build_start", {
1636
- description,
1637
- model: this.model,
1638
- dryRun: false
1639
- });
1640
- await this.library.initialize();
1641
- const matches = await this.library.search(description);
1642
- const globalFailureRates = await this.telemetryReader?.getFailureRates() ?? [];
1643
- const designResult = await this.designer.design({ description }, matches, globalFailureRates);
1644
- await this.emitAttemptTelemetry(description, designResult);
1645
- const provider = this.requireProvider();
1646
- const deployed = await provider.update(id, designResult.workflow);
1647
- this.saveToLibrary(designResult.workflow, description, designResult, matches);
1648
- this.recordDeploy();
1649
- const totalTokensInput = designResult.attemptMetadata.reduce((s, m) => s + m.tokensInput, 0);
1650
- const totalTokensOutput = designResult.attemptMetadata.reduce((s, m) => s + m.tokensOutput, 0);
1651
- await this.telemetry?.emit("build_complete", {
1652
- description,
1653
- success: true,
1654
- totalAttempts: designResult.attempts,
1655
- totalDurationMs: Date.now() - buildStart,
1656
- totalTokensInput,
1657
- totalTokensOutput,
1658
- workflowName: deployed.name,
1659
- workflowId: deployed.workflowId,
1660
- dryRun: false,
1661
- credentialsNeeded: designResult.credentialsNeeded.length
1662
- });
1663
- return {
1664
- workflowId: deployed.workflowId,
1665
- name: deployed.name,
1666
- workflow: designResult.workflow,
1667
- credentialsNeeded: designResult.credentialsNeeded,
1668
- activationRequired: true,
1669
- generationAttempts: designResult.attempts,
1670
- dryRun: false
1671
- };
1672
- }
1673
- async drain() {
1674
- await this.saveQueue.catch(() => {
1675
- });
1676
- }
1677
- async emitAttemptTelemetry(description, designResult) {
1678
- for (const meta of designResult.attemptMetadata) {
1679
- await this.telemetry?.emit("generation_attempt", {
1680
- description,
1681
- attempt: meta.attempt,
1682
- temperature: meta.temperature,
1683
- durationMs: meta.durationMs,
1684
- tokensInput: meta.tokensInput,
1685
- tokensOutput: meta.tokensOutput,
1686
- validationPassed: meta.validationPassed,
1687
- issueCount: meta.issues.length,
1688
- issues: meta.issues.map((i) => ({ rule: i.rule, message: i.message }))
1689
- });
1690
- }
1691
- }
1692
- recordDeploy() {
1693
- this.saveQueue = this.saveQueue.then(async (savedId) => {
1694
- if (savedId) {
1695
- await this.library.recordDeployment(savedId);
1696
- }
1697
- return savedId;
1698
- }).catch((err) => {
1699
- this.logger.warn("Failed to record deployment (non-fatal)", { err: String(err) });
1700
- return null;
1701
- });
1702
- }
1703
- saveToLibrary(workflow, description, designResult, matches) {
1704
- const failedAttempts = designResult.attemptMetadata.filter((m) => !m.validationPassed);
1705
- const failurePatterns = failedAttempts.flatMap(
1706
- (m) => m.issues.map((i) => ({ rule: i.rule, message: i.message }))
1707
- );
1708
- const topMatch = matches[0];
1709
- const generationMode = topMatch ? scoreToMode(topMatch.score) : "scratch";
1710
- const autoTags = Array.from(new Set(
1711
- workflow.nodes.flatMap((n) => {
1712
- const bare = n.type.split(".").pop() ?? "";
1713
- const tags = [bare];
1714
- if (n.type.includes("Trigger") || n.type.includes("trigger")) tags.push(`trigger:${bare}`);
1715
- if (n.type.includes("langchain")) tags.push("ai");
1716
- return tags;
1717
- })
1718
- ));
1719
- const metadata = {
1720
- description,
1721
- generationMode,
1722
- generationAttempts: designResult.attempts
1723
- };
1724
- if (autoTags.length > 0) metadata.tags = autoTags;
1725
- if (failurePatterns.length > 0) metadata.failurePatterns = failurePatterns;
1726
- if (matches.length > 0) metadata.sourceWorkflowIds = matches.map((m) => m.workflow.id);
1727
- if (topMatch) metadata.topMatchScore = topMatch.score;
1728
- if (designResult.credentialsNeeded.length > 0) metadata.credentialsNeeded = designResult.credentialsNeeded;
1729
- const firstTryPass = designResult.attemptMetadata.length > 0 && designResult.attemptMetadata[0].validationPassed;
1730
- const failedRules = Array.from(new Set(
1731
- designResult.attemptMetadata.filter((m) => !m.validationPassed).flatMap((m) => m.issues.map((i) => i.rule))
1732
- ));
1733
- this.saveQueue = this.saveQueue.then(async () => {
1734
- const savedId = await this.library.save(workflow, metadata);
1735
- for (const match of matches) {
1736
- if (match.mode === "direct" || match.mode === "reference") {
1737
- await this.library.recordOutcome(match.workflow.id, {
1738
- attempts: designResult.attempts,
1739
- firstTryPass,
1740
- failedRules,
1741
- mode: match.mode
1742
- });
1743
- }
1744
- }
1745
- return savedId;
1746
- }).catch((err) => {
1747
- this.logger.warn("Failed to save workflow to library (non-fatal)", { err: String(err) });
1748
- return null;
1749
- });
1750
- }
1751
- async get(id) {
1752
- return this.requireProvider().get(id);
1753
- }
1754
- async list() {
1755
- return this.requireProvider().list();
1756
- }
1757
- async activate(id) {
1758
- await this.requireProvider().activate(id);
1759
- }
1760
- async deactivate(id) {
1761
- await this.requireProvider().deactivate(id);
1762
- }
1763
- async delete(id, options) {
1764
- await this.requireProvider().delete(id, options);
1765
- }
1766
- async executions(workflowId, filter) {
1767
- return this.requireProvider().executions(workflowId, filter);
1768
- }
1769
- async execution(id) {
1770
- return this.requireProvider().execution(id);
1771
- }
1772
- async listTags() {
1773
- return this.requireProvider().listTags();
1774
- }
1775
- async createTag(name) {
1776
- return this.requireProvider().createTag(name);
1777
- }
1778
- async tag(workflowId, tagIds) {
1779
- await this.requireProvider().tag(workflowId, tagIds);
1780
- }
1781
- async untag(workflowId, tagIds) {
1782
- await this.requireProvider().untag(workflowId, tagIds);
1157
+ };
1158
+
1159
+ // src/utils/logger.ts
1160
+ var nullLogger = {
1161
+ debug() {
1162
+ },
1163
+ info() {
1164
+ },
1165
+ warn() {
1166
+ },
1167
+ error() {
1783
1168
  }
1784
1169
  };
1785
1170
 
@@ -1999,9 +1384,16 @@ function rerank(candidates, clusters) {
1999
1384
  }
2000
1385
 
2001
1386
  // src/library/file-library.ts
2002
- import { readFile as readFile2, writeFile, rename, mkdir as mkdir2 } from "fs/promises";
2003
- import { join as join3 } from "path";
2004
- import { homedir as homedir3 } from "os";
1387
+ import { readFile as readFile2, writeFile, rename, mkdir } from "fs/promises";
1388
+ import { join as join2 } from "path";
1389
+ import { homedir as homedir2 } from "os";
1390
+
1391
+ // src/utils/uuid.ts
1392
+ function generateUUID() {
1393
+ return crypto.randomUUID();
1394
+ }
1395
+
1396
+ // src/library/file-library.ts
2005
1397
  function tokenize(text) {
2006
1398
  return text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length > 2);
2007
1399
  }
@@ -2020,7 +1412,7 @@ var FileLibrary = class {
2020
1412
  initPromise = null;
2021
1413
  writeQueue = Promise.resolve();
2022
1414
  constructor(dir) {
2023
- this.dir = dir ?? join3(homedir3(), ".kairos", "library");
1415
+ this.dir = dir ?? join2(homedir2(), ".kairos", "library");
2024
1416
  }
2025
1417
  async initialize() {
2026
1418
  if (!this.initPromise) {
@@ -2029,8 +1421,8 @@ var FileLibrary = class {
2029
1421
  return this.initPromise;
2030
1422
  }
2031
1423
  async doInitialize() {
2032
- await mkdir2(this.dir, { recursive: true });
2033
- const indexPath = join3(this.dir, "index.json");
1424
+ await mkdir(this.dir, { recursive: true });
1425
+ const indexPath = join2(this.dir, "index.json");
2034
1426
  try {
2035
1427
  const raw = await readFile2(indexPath, "utf-8");
2036
1428
  const parsed = JSON.parse(raw);
@@ -2162,7 +1554,7 @@ var FileLibrary = class {
2162
1554
  }
2163
1555
  persist() {
2164
1556
  this.writeQueue = this.writeQueue.then(async () => {
2165
- const indexPath = join3(this.dir, "index.json");
1557
+ const indexPath = join2(this.dir, "index.json");
2166
1558
  const tmpPath = `${indexPath}.tmp`;
2167
1559
  await writeFile(tmpPath, JSON.stringify(this.workflows, null, 2), "utf-8");
2168
1560
  await rename(tmpPath, indexPath);
@@ -2171,206 +1563,25 @@ var FileLibrary = class {
2171
1563
  }
2172
1564
  };
2173
1565
 
2174
- // src/templates/safety.ts
2175
- var BLOCKED_NODE_TYPES = /* @__PURE__ */ new Set([
2176
- "n8n-nodes-base.code",
2177
- "n8n-nodes-base.executeCommand",
2178
- "n8n-nodes-base.ssh"
2179
- ]);
2180
- var REVIEW_NODE_TYPES = /* @__PURE__ */ new Set([
2181
- "n8n-nodes-base.httpRequest"
2182
- ]);
2183
- var SECRET_PATTERNS = [
2184
- /sk-[a-zA-Z0-9]{20,}/,
2185
- /ghp_[a-zA-Z0-9]{36}/,
2186
- /xoxb-[0-9]+-[0-9]+-[a-zA-Z0-9]+/,
2187
- /AIza[a-zA-Z0-9_-]{35}/,
2188
- /AKIA[A-Z0-9]{16}/
2189
- ];
2190
- function assessTemplateSafety(workflow) {
2191
- const reasons = [];
2192
- let worst = "safe";
2193
- const escalate = (level, reason) => {
2194
- reasons.push(reason);
2195
- if (level === "blocked") worst = "blocked";
2196
- else if (level === "review" && worst === "safe") worst = "review";
2197
- };
2198
- for (const node of workflow.nodes) {
2199
- if (BLOCKED_NODE_TYPES.has(node.type)) {
2200
- escalate("blocked", `Contains ${node.type} node "${node.name}"`);
2201
- }
2202
- if (REVIEW_NODE_TYPES.has(node.type)) {
2203
- escalate("review", `Contains ${node.type} node "${node.name}"`);
2204
- }
2205
- const paramStr = JSON.stringify(node.parameters);
2206
- for (const pattern of SECRET_PATTERNS) {
2207
- if (pattern.test(paramStr)) {
2208
- escalate("blocked", `Node "${node.name}" parameters contain a hardcoded secret`);
2209
- break;
2210
- }
2211
- }
2212
- }
2213
- return { trustLevel: worst, reasons };
2214
- }
2215
-
2216
- // src/templates/syncer.ts
2217
- var N8N_TEMPLATE_API = "https://api.n8n.io/api/templates";
2218
- var PAGE_SIZE = 50;
2219
- var DELAY_BETWEEN_FETCHES_MS = 200;
2220
- var DEFAULT_SETTINGS = {
2221
- executionOrder: "v1",
2222
- saveManualExecutions: true,
2223
- timezone: "UTC"
2224
- };
2225
- var TemplateSyncer = class {
2226
- constructor(library, logger) {
2227
- this.library = library;
2228
- this.validator = new N8nValidator();
2229
- this.logger = logger;
2230
- }
2231
- library;
2232
- validator;
2233
- logger;
2234
- async sync(options) {
2235
- const maxTemplates = options?.maxTemplates ?? 500;
2236
- await this.library.initialize();
2237
- const existing = await this.library.list();
2238
- const existingSourceIds = new Set(
2239
- existing.filter((w) => w.sourceKind === "n8n-template" && w.sourceId).map((w) => w.sourceId)
2240
- );
2241
- const progress = {
2242
- total: 0,
2243
- processed: 0,
2244
- saved: 0,
2245
- skippedPaid: 0,
2246
- skippedDuplicate: 0,
2247
- blocked: 0,
2248
- reviewed: 0
2249
- };
2250
- const templateIds = await this.fetchTemplateIds(maxTemplates, progress);
2251
- for (const id of templateIds) {
2252
- if (existingSourceIds.has(String(id))) {
2253
- progress.skippedDuplicate++;
2254
- progress.processed++;
2255
- options?.onProgress?.(progress);
2256
- continue;
2257
- }
2258
- try {
2259
- await this.processTemplate(id, progress);
2260
- } catch (err) {
2261
- this.logger.warn(`Failed to process template ${id}`, { err: String(err) });
2262
- }
2263
- progress.processed++;
2264
- options?.onProgress?.(progress);
2265
- await new Promise((resolve) => setTimeout(resolve, DELAY_BETWEEN_FETCHES_MS));
2266
- }
2267
- return progress;
2268
- }
2269
- async fetchTemplateIds(max, progress) {
2270
- const ids = [];
2271
- let page = 1;
2272
- while (ids.length < max) {
2273
- const url = `${N8N_TEMPLATE_API}/search?page=${page}&rows=${PAGE_SIZE}`;
2274
- const response = await fetch(url);
2275
- if (!response.ok) break;
2276
- const data = await response.json();
2277
- progress.total = Math.min(data.totalWorkflows, max);
2278
- for (const template of data.workflows) {
2279
- if (ids.length >= max) break;
2280
- if (template.price && template.price > 0) {
2281
- progress.skippedPaid++;
2282
- continue;
2283
- }
2284
- ids.push(template.id);
2285
- }
2286
- if (data.workflows.length < PAGE_SIZE) break;
2287
- page++;
2288
- await new Promise((resolve) => setTimeout(resolve, DELAY_BETWEEN_FETCHES_MS));
2289
- }
2290
- return ids;
2291
- }
2292
- async processTemplate(id, progress) {
2293
- const url = `${N8N_TEMPLATE_API}/workflows/${id}`;
2294
- const response = await fetch(url);
2295
- if (!response.ok) return;
2296
- const data = await response.json();
2297
- const templateMeta = data.workflow;
2298
- const rawWorkflow = templateMeta.workflow;
2299
- if (!rawWorkflow?.nodes?.length) return;
2300
- const workflow = {
2301
- name: templateMeta.name,
2302
- nodes: rawWorkflow.nodes.filter((n) => n.type && n.name),
2303
- connections: rawWorkflow.connections,
2304
- settings: rawWorkflow.settings ? { executionOrder: "v1", ...rawWorkflow.settings } : { ...DEFAULT_SETTINGS }
2305
- };
2306
- const validation = this.validator.validate(workflow);
2307
- const validationErrors = validation.issues.filter((i) => i.severity === "error");
2308
- if (validationErrors.length > 0) {
2309
- progress.blocked++;
2310
- this.logger.debug(`Template ${id} blocked: ${validationErrors.length} validation errors`);
2311
- return;
2312
- }
2313
- const safety = assessTemplateSafety(workflow);
2314
- if (safety.trustLevel === "blocked") {
2315
- progress.blocked++;
2316
- this.logger.debug(`Template ${id} blocked: ${safety.reasons.join(", ")}`);
2317
- return;
2318
- }
2319
- if (safety.trustLevel === "review") {
2320
- progress.reviewed++;
2321
- }
2322
- const description = this.cleanDescription(templateMeta.description);
2323
- const autoTags = Array.from(new Set(
2324
- workflow.nodes.flatMap((n) => {
2325
- const bare = n.type.split(".").pop() ?? "";
2326
- const tags = [bare];
2327
- if (n.type.includes("Trigger") || n.type.includes("trigger")) tags.push(`trigger:${bare}`);
2328
- if (n.type.includes("langchain")) tags.push("ai");
2329
- return tags;
2330
- })
2331
- ));
2332
- const metadata = {
2333
- description,
2334
- tags: autoTags,
2335
- sourceKind: "n8n-template",
2336
- sourceId: String(id),
2337
- sourceUrl: `https://n8n.io/workflows/${id}`,
2338
- trustLevel: safety.trustLevel
2339
- };
2340
- await this.library.save(workflow, metadata);
2341
- progress.saved++;
2342
- this.logger.debug(`Template ${id} saved: "${templateMeta.name}" (${safety.trustLevel})`);
2343
- }
2344
- cleanDescription(raw) {
2345
- return raw.replace(/#{1,6}\s*/g, "").replace(/\*{1,2}([^*]+)\*{1,2}/g, "$1").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\n{3,}/g, "\n\n").trim().slice(0, 500);
2346
- }
2347
- };
2348
-
2349
1566
  export {
2350
- NullLibrary,
1567
+ generateUUID,
2351
1568
  KairosError,
2352
1569
  ApiError,
2353
1570
  ProviderError,
2354
1571
  N8nApiClient,
2355
1572
  N8nFieldStripper,
2356
- GuardError,
2357
- N8nProvider,
2358
1573
  DEFAULT_REGISTRY,
2359
1574
  NodeRegistry,
2360
1575
  N8nValidator,
2361
- GenerationError,
2362
- ResponseParseError,
2363
- ValidationError,
2364
- TelemetryCollector,
1576
+ scoreToMode,
1577
+ PromptBuilder,
2365
1578
  TelemetryReader,
2366
1579
  nullLogger,
2367
- Kairos,
2368
1580
  hybridScore,
2369
1581
  clusterWorkflows,
2370
1582
  rerank,
2371
1583
  tokenize,
2372
1584
  buildSearchCorpus,
2373
- FileLibrary,
2374
- TemplateSyncer
1585
+ FileLibrary
2375
1586
  };
2376
- //# sourceMappingURL=chunk-QQJDLS5A.js.map
1587
+ //# sourceMappingURL=chunk-DDV7ZART.js.map