@kortyx/runtime 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/checkpointer.d.ts +2 -2
- package/dist/checkpointer.d.ts.map +1 -1
- package/dist/checkpointer.js +11 -5
- package/dist/framework/adapter.d.ts +21 -0
- package/dist/framework/adapter.d.ts.map +1 -0
- package/dist/framework/adapter.js +74 -0
- package/dist/framework/in-memory-checkpointer.d.ts +9 -0
- package/dist/framework/in-memory-checkpointer.d.ts.map +1 -0
- package/dist/framework/in-memory-checkpointer.js +134 -0
- package/dist/framework/pending-requests.d.ts +33 -0
- package/dist/framework/pending-requests.d.ts.map +1 -0
- package/dist/framework/pending-requests.js +32 -0
- package/dist/framework/redis/pending-request-store.d.ts +8 -0
- package/dist/framework/redis/pending-request-store.d.ts.map +1 -0
- package/dist/framework/redis/pending-request-store.js +30 -0
- package/dist/framework/redis/redis-checkpointer.d.ts +13 -0
- package/dist/framework/redis/redis-checkpointer.d.ts.map +1 -0
- package/dist/framework/redis/redis-checkpointer.js +159 -0
- package/dist/framework/redis/redis-client.d.ts +13 -0
- package/dist/framework/redis/redis-client.d.ts.map +1 -0
- package/dist/framework/redis/redis-client.js +181 -0
- package/dist/framework/redis/redis-store.d.ts +20 -0
- package/dist/framework/redis/redis-store.d.ts.map +1 -0
- package/dist/framework/redis/redis-store.js +107 -0
- package/dist/graph/create-langgraph.d.ts +4 -2
- package/dist/graph/create-langgraph.d.ts.map +1 -1
- package/dist/graph/create-langgraph.js +56 -34
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/node-loader.d.ts +7 -0
- package/dist/node-loader.d.ts.map +1 -0
- package/dist/node-loader.js +58 -0
- package/dist/node-registry.d.ts +7 -0
- package/dist/node-registry.d.ts.map +1 -0
- package/dist/node-registry.js +27 -0
- package/dist/registry/file-registry.d.ts.map +1 -1
- package/dist/registry/file-registry.js +25 -2
- package/dist/registry/in-memory-registry.d.ts +6 -0
- package/dist/registry/in-memory-registry.d.ts.map +1 -0
- package/dist/registry/in-memory-registry.js +25 -0
- package/dist/registry/interface.d.ts +2 -2
- package/dist/registry/interface.d.ts.map +1 -1
- package/dist/state/build-initial-state.d.ts +1 -1
- package/dist/state/build-initial-state.d.ts.map +1 -1
- package/dist/state/build-initial-state.js +6 -2
- package/package.json +17 -16
- package/LICENSE +0 -201
- package/dist/config/load-kortyx-config.d.ts +0 -14
- package/dist/config/load-kortyx-config.d.ts.map +0 -1
- package/dist/config/load-kortyx-config.js +0 -33
|
@@ -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 {
|
|
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
|
|
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,
|
|
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.
|
|
35
|
+
const workflowName = workflow.id;
|
|
35
36
|
const runtimeConfig = config;
|
|
36
37
|
if (!runtimeConfig.emit) {
|
|
37
38
|
runtimeConfig.emit = () => { };
|
|
38
39
|
}
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
145
|
-
|
|
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, () =>
|
|
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.
|
|
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,11 @@
|
|
|
1
1
|
export * from "./checkpointer";
|
|
2
|
-
export * from "./
|
|
2
|
+
export * from "./framework/adapter";
|
|
3
|
+
export * from "./framework/pending-requests";
|
|
3
4
|
export * from "./graph/create-langgraph";
|
|
4
5
|
export * from "./interrupt/tokens";
|
|
6
|
+
export * from "./node-registry";
|
|
5
7
|
export * from "./registry/file-registry";
|
|
8
|
+
export * from "./registry/in-memory-registry";
|
|
6
9
|
export * from "./registry/interface";
|
|
7
10
|
export * from "./state/build-initial-state";
|
|
8
11
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,gBAAgB,CAAC;AAC/B,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
|
@@ -15,9 +15,12 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./checkpointer"), exports);
|
|
18
|
-
__exportStar(require("./
|
|
18
|
+
__exportStar(require("./framework/adapter"), exports);
|
|
19
|
+
__exportStar(require("./framework/pending-requests"), exports);
|
|
19
20
|
__exportStar(require("./graph/create-langgraph"), exports);
|
|
20
21
|
__exportStar(require("./interrupt/tokens"), exports);
|
|
22
|
+
__exportStar(require("./node-registry"), exports);
|
|
21
23
|
__exportStar(require("./registry/file-registry"), exports);
|
|
24
|
+
__exportStar(require("./registry/in-memory-registry"), exports);
|
|
22
25
|
__exportStar(require("./registry/interface"), exports);
|
|
23
26
|
__exportStar(require("./state/build-initial-state"), exports);
|
|
@@ -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":"
|
|
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"}
|