@kortyx/runtime 0.3.0 → 0.4.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.
Files changed (49) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/checkpointer.d.ts +2 -2
  3. package/dist/checkpointer.d.ts.map +1 -1
  4. package/dist/checkpointer.js +11 -5
  5. package/dist/framework/adapter.d.ts +21 -0
  6. package/dist/framework/adapter.d.ts.map +1 -0
  7. package/dist/framework/adapter.js +74 -0
  8. package/dist/framework/in-memory-checkpointer.d.ts +9 -0
  9. package/dist/framework/in-memory-checkpointer.d.ts.map +1 -0
  10. package/dist/framework/in-memory-checkpointer.js +134 -0
  11. package/dist/framework/pending-requests.d.ts +33 -0
  12. package/dist/framework/pending-requests.d.ts.map +1 -0
  13. package/dist/framework/pending-requests.js +32 -0
  14. package/dist/framework/redis/pending-request-store.d.ts +8 -0
  15. package/dist/framework/redis/pending-request-store.d.ts.map +1 -0
  16. package/dist/framework/redis/pending-request-store.js +30 -0
  17. package/dist/framework/redis/redis-checkpointer.d.ts +13 -0
  18. package/dist/framework/redis/redis-checkpointer.d.ts.map +1 -0
  19. package/dist/framework/redis/redis-checkpointer.js +159 -0
  20. package/dist/framework/redis/redis-client.d.ts +13 -0
  21. package/dist/framework/redis/redis-client.d.ts.map +1 -0
  22. package/dist/framework/redis/redis-client.js +181 -0
  23. package/dist/framework/redis/redis-store.d.ts +20 -0
  24. package/dist/framework/redis/redis-store.d.ts.map +1 -0
  25. package/dist/framework/redis/redis-store.js +107 -0
  26. package/dist/graph/create-langgraph.d.ts +4 -2
  27. package/dist/graph/create-langgraph.d.ts.map +1 -1
  28. package/dist/graph/create-langgraph.js +56 -34
  29. package/dist/index.d.ts +4 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +4 -0
  32. package/dist/node-loader.d.ts +7 -0
  33. package/dist/node-loader.d.ts.map +1 -0
  34. package/dist/node-loader.js +58 -0
  35. package/dist/node-registry.d.ts +7 -0
  36. package/dist/node-registry.d.ts.map +1 -0
  37. package/dist/node-registry.js +27 -0
  38. package/dist/registry/file-registry.d.ts.map +1 -1
  39. package/dist/registry/file-registry.js +25 -2
  40. package/dist/registry/in-memory-registry.d.ts +6 -0
  41. package/dist/registry/in-memory-registry.d.ts.map +1 -0
  42. package/dist/registry/in-memory-registry.js +25 -0
  43. package/dist/registry/interface.d.ts +2 -2
  44. package/dist/registry/interface.d.ts.map +1 -1
  45. package/dist/state/build-initial-state.d.ts +1 -1
  46. package/dist/state/build-initial-state.d.ts.map +1 -1
  47. package/dist/state/build-initial-state.js +6 -2
  48. package/package.json +17 -16
  49. package/LICENSE +0 -201
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createRedisClient = createRedisClient;
4
+ const node_net_1 = require("node:net");
5
+ const node_tls_1 = require("node:tls");
6
+ const encodeBulk = (s) => {
7
+ const b = Buffer.from(s, "utf8");
8
+ return Buffer.concat([
9
+ Buffer.from(`$${b.length}\r\n`),
10
+ b,
11
+ Buffer.from("\r\n"),
12
+ ]);
13
+ };
14
+ const encodeCommand = (cmd, args) => {
15
+ const parts = [cmd, ...args];
16
+ const bufs = [Buffer.from(`*${parts.length}\r\n`)];
17
+ for (const p of parts)
18
+ bufs.push(encodeBulk(p));
19
+ return Buffer.concat(bufs);
20
+ };
21
+ function readLine(buf, start) {
22
+ const idx = buf.indexOf("\r\n", start);
23
+ if (idx === -1)
24
+ return null;
25
+ return { line: buf.slice(start, idx).toString("utf8"), next: idx + 2 };
26
+ }
27
+ function parseReply(buf, start) {
28
+ if (start >= buf.length)
29
+ return null;
30
+ const byte = buf[start];
31
+ if (byte === undefined)
32
+ return null;
33
+ const prefix = String.fromCharCode(byte);
34
+ if (prefix === "+" || prefix === "-" || prefix === ":") {
35
+ const line = readLine(buf, start + 1);
36
+ if (!line)
37
+ return null;
38
+ if (prefix === "+")
39
+ return { value: line.line, next: line.next };
40
+ if (prefix === ":")
41
+ return { value: Number.parseInt(line.line, 10), next: line.next };
42
+ return { value: { type: "error", message: line.line }, next: line.next };
43
+ }
44
+ if (prefix === "$") {
45
+ const line = readLine(buf, start + 1);
46
+ if (!line)
47
+ return null;
48
+ const len = Number.parseInt(line.line, 10);
49
+ if (len === -1)
50
+ return { value: null, next: line.next };
51
+ const end = line.next + len;
52
+ if (end + 2 > buf.length)
53
+ return null;
54
+ const data = buf.slice(line.next, end).toString("utf8");
55
+ return { value: data, next: end + 2 };
56
+ }
57
+ if (prefix === "*") {
58
+ const line = readLine(buf, start + 1);
59
+ if (!line)
60
+ return null;
61
+ const count = Number.parseInt(line.line, 10);
62
+ if (count === -1)
63
+ return { value: null, next: line.next };
64
+ const items = [];
65
+ let cursor = line.next;
66
+ for (let i = 0; i < count; i++) {
67
+ const parsed = parseReply(buf, cursor);
68
+ if (!parsed)
69
+ return null;
70
+ items.push(parsed.value);
71
+ cursor = parsed.next;
72
+ }
73
+ return { value: items, next: cursor };
74
+ }
75
+ return {
76
+ value: { type: "error", message: `Unknown RESP prefix: ${prefix}` },
77
+ next: buf.length,
78
+ };
79
+ }
80
+ function createRedisClient(options) {
81
+ const resolvedOptions = {
82
+ connectTimeoutMs: options.connectTimeoutMs ?? 5_000,
83
+ url: options.url,
84
+ };
85
+ let socket = null;
86
+ let buffer = Buffer.alloc(0);
87
+ const inflight = [];
88
+ let connecting = null;
89
+ let ready = false;
90
+ const drainReplies = () => {
91
+ while (inflight.length) {
92
+ const parsed = parseReply(buffer, 0);
93
+ if (!parsed)
94
+ return;
95
+ const waiter = inflight.shift();
96
+ buffer = buffer.slice(parsed.next);
97
+ waiter.resolve(parsed.value);
98
+ }
99
+ };
100
+ const send = (cmd, args = []) => {
101
+ if (!socket)
102
+ return Promise.reject(new Error("Redis socket not available"));
103
+ const payload = encodeCommand(cmd, args);
104
+ return new Promise((resolve, reject) => {
105
+ inflight.push({ resolve, reject });
106
+ socket.write(payload);
107
+ });
108
+ };
109
+ const ensureConnected = async () => {
110
+ if (ready)
111
+ return;
112
+ if (connecting)
113
+ return connecting;
114
+ connecting = (async () => {
115
+ const u = new URL(resolvedOptions.url);
116
+ const isTls = u.protocol === "rediss:";
117
+ const host = u.hostname || "127.0.0.1";
118
+ const port = u.port ? Number(u.port) : isTls ? 6380 : 6379;
119
+ const password = u.password ? decodeURIComponent(u.password) : "";
120
+ const db = u.pathname && u.pathname !== "/" ? Number(u.pathname.slice(1)) : 0;
121
+ const s = isTls
122
+ ? (0, node_tls_1.connect)({ host, port, servername: host })
123
+ : (0, node_net_1.connect)({ host, port });
124
+ socket = s;
125
+ s.setNoDelay(true);
126
+ s.setKeepAlive(true);
127
+ s.on("data", (chunk) => {
128
+ buffer = Buffer.concat([buffer, chunk]);
129
+ drainReplies();
130
+ });
131
+ s.on("error", (err) => {
132
+ ready = false;
133
+ while (inflight.length)
134
+ inflight.shift().reject(err);
135
+ });
136
+ s.on("close", () => {
137
+ ready = false;
138
+ while (inflight.length)
139
+ inflight.shift().reject(new Error("Redis socket closed"));
140
+ });
141
+ await new Promise((resolve, reject) => {
142
+ const timer = setTimeout(() => reject(new Error("Redis connect timeout")), resolvedOptions.connectTimeoutMs);
143
+ const onConnect = () => {
144
+ clearTimeout(timer);
145
+ resolve();
146
+ };
147
+ s.once("connect", onConnect);
148
+ s.once?.("secureConnect", onConnect);
149
+ s.once("error", (e) => {
150
+ clearTimeout(timer);
151
+ reject(e);
152
+ });
153
+ });
154
+ if (password) {
155
+ const r = await send("AUTH", [password]);
156
+ if (typeof r === "object" && r && r.type === "error") {
157
+ throw new Error(`Redis AUTH failed: ${r.message}`);
158
+ }
159
+ }
160
+ if (db && Number.isFinite(db) && db > 0) {
161
+ const r = await send("SELECT", [String(db)]);
162
+ if (typeof r === "object" && r && r.type === "error") {
163
+ throw new Error(`Redis SELECT failed: ${r.message}`);
164
+ }
165
+ }
166
+ ready = true;
167
+ })();
168
+ try {
169
+ await connecting;
170
+ }
171
+ finally {
172
+ connecting = null;
173
+ }
174
+ };
175
+ return {
176
+ async command(cmd, args = []) {
177
+ await ensureConnected();
178
+ return send(cmd, args);
179
+ },
180
+ };
181
+ }
@@ -0,0 +1,20 @@
1
+ export type RedisFrameworkStoreOptions = {
2
+ url: string;
3
+ prefix?: string;
4
+ };
5
+ export type FrameworkTtl = {
6
+ ttlMs: number;
7
+ };
8
+ export type RedisFrameworkStore = {
9
+ get: (key: string) => Promise<string | null>;
10
+ set: (key: string, value: string, ttlMs: number) => Promise<void>;
11
+ del: (key: string) => Promise<void>;
12
+ hset: (key: string, field: string, value: string) => Promise<void>;
13
+ hsetnx: (key: string, field: string, value: string) => Promise<number>;
14
+ hgetall: (key: string) => Promise<Record<string, string>>;
15
+ expire: (key: string, ttlMs: number) => Promise<void>;
16
+ scanKeys: (prefix: string) => Promise<string[]>;
17
+ delRaw: (keys: string[]) => Promise<void>;
18
+ };
19
+ export declare function createRedisFrameworkStore(options: RedisFrameworkStoreOptions): RedisFrameworkStore;
20
+ //# sourceMappingURL=redis-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-store.d.ts","sourceRoot":"","sources":["../../../src/framework/redis/redis-store.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,0BAA0B,GAAG;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnE,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACvE,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC1D,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAChD,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3C,CAAC;AAMF,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,0BAA0B,GAClC,mBAAmB,CAwGrB"}
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createRedisFrameworkStore = createRedisFrameworkStore;
4
+ const redis_client_1 = require("./redis-client");
5
+ const isRedisError = (r) => Boolean(r) && typeof r === "object" && r.type === "error";
6
+ function createRedisFrameworkStore(options) {
7
+ const client = (0, redis_client_1.createRedisClient)({ url: options.url });
8
+ const prefix = options.prefix ?? "kortyx:fw:";
9
+ const k = (key) => `${prefix}${key}`;
10
+ return {
11
+ async get(key) {
12
+ const r = await client.command("GET", [k(key)]);
13
+ if (r === null)
14
+ return null;
15
+ if (typeof r === "string")
16
+ return r;
17
+ if (isRedisError(r)) {
18
+ throw new Error(`Redis GET error: ${r.message}`);
19
+ }
20
+ return null;
21
+ },
22
+ async set(key, value, ttlMs) {
23
+ const args = [k(key), value, "PX", String(Math.max(1, ttlMs))];
24
+ const r = await client.command("SET", args);
25
+ if (isRedisError(r)) {
26
+ throw new Error(`Redis SET error: ${r.message}`);
27
+ }
28
+ },
29
+ async del(key) {
30
+ const r = await client.command("DEL", [k(key)]);
31
+ if (isRedisError(r)) {
32
+ throw new Error(`Redis DEL error: ${r.message}`);
33
+ }
34
+ },
35
+ async hset(key, field, value) {
36
+ const r = await client.command("HSET", [k(key), field, value]);
37
+ if (isRedisError(r)) {
38
+ throw new Error(`Redis HSET error: ${r.message}`);
39
+ }
40
+ },
41
+ async hsetnx(key, field, value) {
42
+ const r = await client.command("HSETNX", [k(key), field, value]);
43
+ if (isRedisError(r)) {
44
+ throw new Error(`Redis HSETNX error: ${r.message}`);
45
+ }
46
+ return typeof r === "number" ? r : 0;
47
+ },
48
+ async hgetall(key) {
49
+ const r = await client.command("HGETALL", [k(key)]);
50
+ if (isRedisError(r)) {
51
+ throw new Error(`Redis HGETALL error: ${r.message}`);
52
+ }
53
+ if (!Array.isArray(r))
54
+ return {};
55
+ const out = {};
56
+ for (let i = 0; i < r.length; i += 2) {
57
+ const kk = r[i];
58
+ const v = r[i + 1];
59
+ if (typeof kk === "string" && typeof v === "string")
60
+ out[kk] = v;
61
+ }
62
+ return out;
63
+ },
64
+ async expire(key, ttlMs) {
65
+ const r = await client.command("PEXPIRE", [
66
+ k(key),
67
+ String(Math.max(1, ttlMs)),
68
+ ]);
69
+ if (isRedisError(r)) {
70
+ throw new Error(`Redis PEXPIRE error: ${r.message}`);
71
+ }
72
+ },
73
+ async scanKeys(prefixKey) {
74
+ const fullPrefix = k(prefixKey);
75
+ let cursor = "0";
76
+ const keys = [];
77
+ do {
78
+ const r = await client.command("SCAN", [
79
+ cursor,
80
+ "MATCH",
81
+ `${fullPrefix}*`,
82
+ "COUNT",
83
+ "200",
84
+ ]);
85
+ if (!Array.isArray(r) || r.length < 2)
86
+ break;
87
+ cursor = typeof r[0] === "string" ? r[0] : "0";
88
+ const batch = r[1];
89
+ if (Array.isArray(batch)) {
90
+ for (const key of batch) {
91
+ if (typeof key === "string")
92
+ keys.push(key);
93
+ }
94
+ }
95
+ } while (cursor !== "0");
96
+ return keys;
97
+ },
98
+ async delRaw(keys) {
99
+ if (keys.length === 0)
100
+ return;
101
+ const r = await client.command("DEL", keys);
102
+ if (isRedisError(r)) {
103
+ throw new Error(`Redis DEL error: ${r.message}`);
104
+ }
105
+ },
106
+ };
107
+ }
@@ -1,6 +1,7 @@
1
- import type { Edge, GraphState, NodeMap, WorkflowDefinition } from "@kortyx/core";
1
+ import type { GraphState, WorkflowDefinition } from "@kortyx/core";
2
2
  import type { MemoryAdapter } from "@kortyx/memory";
3
3
  import type { GetProviderFn } from "@kortyx/providers";
4
+ import type { BaseCheckpointSaver } from "@langchain/langgraph-checkpoint";
4
5
  export interface GraphRuntimeConfig {
5
6
  emit?: (event: string, payload: unknown) => void;
6
7
  onCheckpoint?: (args: {
@@ -8,8 +9,9 @@ export interface GraphRuntimeConfig {
8
9
  state: GraphState;
9
10
  }) => void;
10
11
  memoryAdapter?: MemoryAdapter;
12
+ checkpointer?: BaseCheckpointSaver;
11
13
  getProvider?: GetProviderFn;
12
14
  [key: string]: unknown;
13
15
  }
14
- export declare function createLangGraph<const N extends NodeMap, const E extends readonly Edge<(keyof N & string) | "__start__", (keyof N & string) | "__end__">[]>(workflow: WorkflowDefinition<N, E>, config: GraphRuntimeConfig): Promise<any>;
16
+ export declare function createLangGraph(workflow: WorkflowDefinition, config: GraphRuntimeConfig): Promise<any>;
15
17
  //# sourceMappingURL=create-langgraph.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-langgraph.d.ts","sourceRoot":"","sources":["../../src/graph/create-langgraph.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,IAAI,EACJ,UAAU,EAGV,OAAO,EAEP,kBAAkB,EACnB,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAUvD,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACjD,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,UAAU,CAAA;KAAE,KAAK,IAAI,CAAC;IACrE,aAAa,CAAC,EAAE,aAAa,CAAC;IAK9B,WAAW,CAAC,EAAE,aAAa,CAAC;IAC5B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,wBAAsB,eAAe,CACnC,KAAK,CAAC,CAAC,SAAS,OAAO,EACvB,KAAK,CAAC,CAAC,SAAS,SAAS,IAAI,CAC3B,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,WAAW,EAChC,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,SAAS,CAC/B,EAAE,EACH,QAAQ,EAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,kBAAkB,gBA4Y/D"}
1
+ {"version":3,"file":"create-langgraph.d.ts","sourceRoot":"","sources":["../../src/graph/create-langgraph.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EAIV,kBAAkB,EACnB,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAQvD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAI3E,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACjD,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,UAAU,CAAA;KAAE,KAAK,IAAI,CAAC;IACrE,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,YAAY,CAAC,EAAE,mBAAmB,CAAC;IAKnC,WAAW,CAAC,EAAE,aAAa,CAAC;IAC5B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,wBAAsB,eAAe,CACnC,QAAQ,EAAE,kBAAkB,EAC5B,MAAM,EAAE,kBAAkB,gBA2b3B"}
@@ -6,6 +6,7 @@ const utils_1 = require("@kortyx/utils");
6
6
  const messages_1 = require("@langchain/core/messages");
7
7
  const langgraph_1 = require("@langchain/langgraph");
8
8
  const checkpointer_1 = require("../checkpointer");
9
+ const node_loader_1 = require("../node-loader");
9
10
  async function createLangGraph(workflow, config) {
10
11
  const StateAnnotation = langgraph_1.Annotation.Root({
11
12
  input: (langgraph_1.Annotation),
@@ -31,14 +32,44 @@ async function createLangGraph(workflow, config) {
31
32
  });
32
33
  const builder = new langgraph_1.StateGraph(StateAnnotation);
33
34
  const edgeApi = builder;
34
- const workflowName = workflow.name;
35
+ const workflowName = workflow.id;
35
36
  const runtimeConfig = config;
36
37
  if (!runtimeConfig.emit) {
37
38
  runtimeConfig.emit = () => { };
38
39
  }
39
- const nodesMap = workflow.nodes;
40
- for (const [nodeId, node] of Object.entries(nodesMap)) {
41
- const nodeConfig = node.config ?? {};
40
+ const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
41
+ const toRecordInput = (value) => {
42
+ if (isRecord(value))
43
+ return value;
44
+ if (value === undefined)
45
+ return {};
46
+ return { rawInput: value };
47
+ };
48
+ const buildNodeConfig = (params, nodeBehavior) => {
49
+ const model = isRecord(params?.model) ? params.model : undefined;
50
+ const tool = isRecord(params?.tool) ? params.tool : undefined;
51
+ const behaviorFromParams = isRecord(params?.behavior)
52
+ ? params.behavior
53
+ : undefined;
54
+ const behavior = {
55
+ ...(behaviorFromParams ?? {}),
56
+ ...(nodeBehavior ?? {}),
57
+ };
58
+ const options = params ?? undefined;
59
+ return {
60
+ ...(model ? { model } : {}),
61
+ ...(tool ? { tool } : {}),
62
+ ...(Object.keys(behavior).length > 0 ? { behavior } : {}),
63
+ ...(options ? { options } : {}),
64
+ };
65
+ };
66
+ for (const [nodeId, nodeDef] of Object.entries(workflow.nodes ?? {})) {
67
+ const resolvedRun = await (0, node_loader_1.resolveNodeHandler)({
68
+ run: nodeDef.run,
69
+ workflow,
70
+ });
71
+ const nodeParams = (nodeDef.params ?? undefined);
72
+ const nodeConfig = buildNodeConfig(nodeParams, (nodeDef.behavior ?? undefined));
42
73
  const behavior = nodeConfig.behavior ?? {};
43
74
  builder.addNode(nodeId, async (state) => {
44
75
  const ctx = {
@@ -108,23 +139,10 @@ async function createLangGraph(workflow, config) {
108
139
  ...(args.system ? [new messages_1.SystemMessage(args.system)] : []),
109
140
  new messages_1.HumanMessage(args.user ?? ""),
110
141
  ];
111
- const minChars = args.stream?.minChars ?? 24;
112
- const flushMs = args.stream?.flushMs ?? 100;
113
- const segmentChars = args.stream?.segmentChars ?? 60;
114
142
  let final = "";
115
- let buffer = "";
116
- let timer = null;
117
143
  const t0 = Date.now();
118
144
  let seenFirst = false;
119
145
  const isSilent = false;
120
- const flush = () => {
121
- if (!buffer)
122
- return;
123
- if (!isSilent)
124
- ctx.emit("text-delta", { node: nodeId, delta: buffer });
125
- buffer = "";
126
- timer = null;
127
- };
128
146
  if (!isSilent)
129
147
  ctx.emit("text-start", { node: nodeId });
130
148
  const stream = await model.stream(messages);
@@ -141,20 +159,9 @@ async function createLangGraph(workflow, config) {
141
159
  });
142
160
  }
143
161
  final += part;
144
- for (let i = 0; i < part.length; i += segmentChars) {
145
- const seg = part.slice(i, i + segmentChars);
146
- buffer += seg;
147
- if (buffer.length >= minChars) {
148
- flush();
149
- }
150
- else if (!timer) {
151
- timer = setTimeout(flush, flushMs);
152
- }
153
- }
162
+ if (!isSilent)
163
+ ctx.emit("text-delta", { node: nodeId, delta: part });
154
164
  }
155
- if (timer)
156
- clearTimeout(timer);
157
- flush();
158
165
  if (!isSilent)
159
166
  ctx.emit("text-end", { node: nodeId });
160
167
  const t1 = Date.now();
@@ -173,12 +180,19 @@ async function createLangGraph(workflow, config) {
173
180
  let attempt = 0;
174
181
  let nodeResult;
175
182
  let hookMemoryUpdates = null;
183
+ let retryHookMemory = null;
176
184
  while (attempt < maxAttempts) {
177
185
  try {
178
186
  attempt++;
187
+ const attemptState = retryHookMemory && typeof retryHookMemory === "object"
188
+ ? {
189
+ ...state,
190
+ memory: (0, utils_1.deepMergeWithArrayOverwrite)((state.memory ?? {}), retryHookMemory),
191
+ }
192
+ : state;
179
193
  const hookContext = {
180
194
  node: ctx,
181
- state,
195
+ state: attemptState,
182
196
  ...(runtimeConfig.getProvider
183
197
  ? { getProvider: runtimeConfig.getProvider }
184
198
  : {}),
@@ -186,12 +200,19 @@ async function createLangGraph(workflow, config) {
186
200
  ? { memoryAdapter: runtimeConfig.memoryAdapter }
187
201
  : {}),
188
202
  };
189
- const hookRun = await (0, hooks_1.runWithHookContext)(hookContext, () => node.run(state, ctx));
203
+ const hookRun = await (0, hooks_1.runWithHookContext)(hookContext, async () => resolvedRun({
204
+ input: state.input,
205
+ params: (nodeParams ?? {}),
206
+ }));
190
207
  nodeResult = hookRun.result;
191
208
  hookMemoryUpdates = hookRun.memoryUpdates;
192
209
  break;
193
210
  }
194
211
  catch (err) {
212
+ const patch = err?.__kortyxMemoryUpdates;
213
+ if (patch && typeof patch === "object") {
214
+ retryHookMemory = (0, utils_1.deepMergeWithArrayOverwrite)((retryHookMemory ?? {}), patch);
215
+ }
195
216
  const hasMore = attempt < maxAttempts;
196
217
  const delayMs = behavior.retry?.delayMs ?? 0;
197
218
  if (hasMore && delayMs > 0) {
@@ -226,6 +247,7 @@ async function createLangGraph(workflow, config) {
226
247
  if (typeof res.intent === "string")
227
248
  updates.lastIntent = res.intent;
228
249
  if (res.data && typeof res.data === "object") {
250
+ updates.input = (0, utils_1.deepMergeWithArrayOverwrite)(toRecordInput(state.input), res.data);
229
251
  updates.data = (0, utils_1.deepMergeWithArrayOverwrite)((state.data ?? {}), res.data);
230
252
  }
231
253
  if (res.infra?.memory && typeof res.infra.memory === "object") {
@@ -295,10 +317,10 @@ async function createLangGraph(workflow, config) {
295
317
  }, mapping);
296
318
  }
297
319
  const sessionId = config?.session?.id ?? "__default__";
298
- const checkpointer = (0, checkpointer_1.getCheckpointer)(sessionId);
320
+ const checkpointer = config.checkpointer ?? (0, checkpointer_1.getCheckpointer)(sessionId);
299
321
  const graph = builder.compile({ checkpointer });
300
322
  const runtimeGraph = graph;
301
- runtimeGraph.name = workflow.name;
323
+ runtimeGraph.name = workflow.id;
302
324
  runtimeGraph.config = config;
303
325
  runtimeGraph.resume = async (state, input) => {
304
326
  const resumed = { ...state, input, awaitingHumanInput: false };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,12 @@
1
1
  export * from "./checkpointer";
2
2
  export * from "./config/load-kortyx-config";
3
+ export * from "./framework/adapter";
4
+ export * from "./framework/pending-requests";
3
5
  export * from "./graph/create-langgraph";
4
6
  export * from "./interrupt/tokens";
7
+ export * from "./node-registry";
5
8
  export * from "./registry/file-registry";
9
+ export * from "./registry/in-memory-registry";
6
10
  export * from "./registry/interface";
7
11
  export * from "./state/build-initial-state";
8
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,0BAA0B,CAAC;AACzC,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,6BAA6B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,0BAA0B,CAAC;AACzC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,0BAA0B,CAAC;AACzC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,sBAAsB,CAAC;AACrC,cAAc,6BAA6B,CAAC"}
package/dist/index.js CHANGED
@@ -16,8 +16,12 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./checkpointer"), exports);
18
18
  __exportStar(require("./config/load-kortyx-config"), exports);
19
+ __exportStar(require("./framework/adapter"), exports);
20
+ __exportStar(require("./framework/pending-requests"), exports);
19
21
  __exportStar(require("./graph/create-langgraph"), exports);
20
22
  __exportStar(require("./interrupt/tokens"), exports);
23
+ __exportStar(require("./node-registry"), exports);
21
24
  __exportStar(require("./registry/file-registry"), exports);
25
+ __exportStar(require("./registry/in-memory-registry"), exports);
22
26
  __exportStar(require("./registry/interface"), exports);
23
27
  __exportStar(require("./state/build-initial-state"), exports);
@@ -0,0 +1,7 @@
1
+ import type { NodeFn, WorkflowDefinition } from "@kortyx/core";
2
+ export declare function resolveNodeHandler(args: {
3
+ run: string | NodeFn;
4
+ workflow?: WorkflowDefinition;
5
+ cwd?: string;
6
+ }): Promise<NodeFn>;
7
+ //# sourceMappingURL=node-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-loader.d.ts","sourceRoot":"","sources":["../src/node-loader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAsD/D,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC7C,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC,MAAM,CAAC,CA0BlB"}
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveNodeHandler = resolveNodeHandler;
4
+ const node_path_1 = require("node:path");
5
+ const node_url_1 = require("node:url");
6
+ const node_registry_1 = require("./node-registry");
7
+ const dynamicImport = new Function("specifier", "return import(specifier)");
8
+ function looksLikePath(specifier) {
9
+ if (specifier.startsWith("file:"))
10
+ return true;
11
+ if (specifier.startsWith("./") || specifier.startsWith("../"))
12
+ return true;
13
+ if (specifier.startsWith("/"))
14
+ return true;
15
+ return specifier.includes("/");
16
+ }
17
+ function getWorkflowBaseDir(workflow, cwd) {
18
+ const meta = workflow?.metadata;
19
+ const filePath = meta?.__filePath;
20
+ if (typeof filePath === "string" && filePath.length > 0) {
21
+ return (0, node_path_1.dirname)(filePath);
22
+ }
23
+ return cwd;
24
+ }
25
+ function pickNodeExport(mod, exportName) {
26
+ if (exportName) {
27
+ const candidate = mod[exportName];
28
+ if (typeof candidate === "function")
29
+ return candidate;
30
+ throw new Error(`Node export "${exportName}" not found or not a function.`);
31
+ }
32
+ const defaultExport = mod.default;
33
+ if (typeof defaultExport === "function")
34
+ return defaultExport;
35
+ const fnExports = Object.entries(mod).filter(([, v]) => typeof v === "function");
36
+ if (fnExports.length === 1)
37
+ return fnExports[0][1];
38
+ const names = fnExports.map(([k]) => k).sort();
39
+ throw new Error(`Node module must export a default function or exactly one named function (found: ${names.join(", ") || "none"}).`);
40
+ }
41
+ async function resolveNodeHandler(args) {
42
+ const { run, workflow } = args;
43
+ const cwd = args.cwd ?? process.cwd();
44
+ if (typeof run === "function")
45
+ return run;
46
+ if (looksLikePath(run)) {
47
+ const [moduleRef, exportName] = run.split("#", 2);
48
+ const baseDir = getWorkflowBaseDir(workflow, cwd);
49
+ const specifier = moduleRef.startsWith("file:")
50
+ ? moduleRef
51
+ : (0, node_path_1.isAbsolute)(moduleRef)
52
+ ? (0, node_url_1.pathToFileURL)(moduleRef).href
53
+ : (0, node_url_1.pathToFileURL)((0, node_path_1.resolve)(baseDir, moduleRef)).href;
54
+ const mod = await dynamicImport(specifier);
55
+ return pickNodeExport(mod, exportName);
56
+ }
57
+ return (0, node_registry_1.resolveNode)(run);
58
+ }
@@ -0,0 +1,7 @@
1
+ import type { NodeFn } from "@kortyx/core";
2
+ export declare function registerNode(id: string, fn: NodeFn): void;
3
+ export declare function getRegisteredNode(id: string): NodeFn | null;
4
+ export declare function listRegisteredNodes(): string[];
5
+ export declare function clearRegisteredNodes(): void;
6
+ export declare function resolveNode(id: string): NodeFn;
7
+ //# sourceMappingURL=node-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-registry.d.ts","sourceRoot":"","sources":["../src/node-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAI3C,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAEzD;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE3D;AAED,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAE9C;AAED,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAQ9C"}
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerNode = registerNode;
4
+ exports.getRegisteredNode = getRegisteredNode;
5
+ exports.listRegisteredNodes = listRegisteredNodes;
6
+ exports.clearRegisteredNodes = clearRegisteredNodes;
7
+ exports.resolveNode = resolveNode;
8
+ const registry = new Map();
9
+ function registerNode(id, fn) {
10
+ registry.set(id, fn);
11
+ }
12
+ function getRegisteredNode(id) {
13
+ return registry.get(id) ?? null;
14
+ }
15
+ function listRegisteredNodes() {
16
+ return [...registry.keys()].sort();
17
+ }
18
+ function clearRegisteredNodes() {
19
+ registry.clear();
20
+ }
21
+ function resolveNode(id) {
22
+ const fn = registry.get(id);
23
+ if (!fn) {
24
+ throw new Error(`Node '${id}' is not registered. Call registerNode('${id}', fn) before running workflows that reference it.`);
25
+ }
26
+ return fn;
27
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"file-registry.d.ts","sourceRoot":"","sources":["../../src/registry/file-registry.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,MAAM,WAAW,2BAA2B;IAC1C,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAeD,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,2BAA2B,GACnC,gBAAgB,CAgGlB"}
1
+ {"version":3,"file":"file-registry.d.ts","sourceRoot":"","sources":["../../src/registry/file-registry.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,MAAM,WAAW,2BAA2B;IAC1C,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AA0BD,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,2BAA2B,GACnC,gBAAgB,CAgHlB"}