@kortyx/runtime 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/dist/checkpointer.d.ts +3 -0
- package/dist/checkpointer.d.ts.map +1 -0
- package/dist/checkpointer.js +14 -0
- package/dist/config/load-kortyx-config.d.ts +14 -0
- package/dist/config/load-kortyx-config.d.ts.map +1 -0
- package/dist/config/load-kortyx-config.js +33 -0
- package/dist/graph/create-langgraph.d.ts +15 -0
- package/dist/graph/create-langgraph.d.ts.map +1 -0
- package/dist/graph/create-langgraph.js +315 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/interrupt/tokens.d.ts +3 -0
- package/dist/interrupt/tokens.d.ts.map +1 -0
- package/dist/interrupt/tokens.js +11 -0
- package/dist/registry/file-registry.d.ts +9 -0
- package/dist/registry/file-registry.d.ts.map +1 -0
- package/dist/registry/file-registry.js +82 -0
- package/dist/registry/interface.d.ts +12 -0
- package/dist/registry/interface.d.ts.map +1 -0
- package/dist/registry/interface.js +2 -0
- package/dist/state/build-initial-state.d.ts +9 -0
- package/dist/state/build-initial-state.d.ts.map +1 -0
- package/dist/state/build-initial-state.js +14 -0
- package/package.json +36 -0
- package/src/checkpointer.ts +13 -0
- package/src/config/load-kortyx-config.ts +46 -0
- package/src/graph/create-langgraph.ts +437 -0
- package/src/index.ts +7 -0
- package/src/interrupt/tokens.ts +9 -0
- package/src/registry/file-registry.ts +124 -0
- package/src/registry/interface.ts +13 -0
- package/src/state/build-initial-state.ts +30 -0
- package/tsconfig.build.json +25 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.json +29 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkpointer.d.ts","sourceRoot":"","sources":["../src/checkpointer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAInD,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAQxD"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getCheckpointer = getCheckpointer;
|
|
4
|
+
const langgraph_1 = require("@langchain/langgraph");
|
|
5
|
+
const map = new Map();
|
|
6
|
+
function getCheckpointer(key) {
|
|
7
|
+
const k = key || "__default__";
|
|
8
|
+
let saver = map.get(k);
|
|
9
|
+
if (!saver) {
|
|
10
|
+
saver = new langgraph_1.MemorySaver();
|
|
11
|
+
map.set(k, saver);
|
|
12
|
+
}
|
|
13
|
+
return saver;
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface KortyxConfig {
|
|
2
|
+
workflowsDir?: string;
|
|
3
|
+
fallbackWorkflowId?: string;
|
|
4
|
+
registry?: {
|
|
5
|
+
cache?: boolean;
|
|
6
|
+
extensions?: string[];
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export interface LoadKortyxConfigOptions {
|
|
10
|
+
cwd?: string;
|
|
11
|
+
configPath?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function loadKortyxConfig(options?: LoadKortyxConfigOptions): Promise<KortyxConfig | null>;
|
|
14
|
+
//# sourceMappingURL=load-kortyx-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-kortyx-config.d.ts","sourceRoot":"","sources":["../../src/config/load-kortyx-config.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE;QACT,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;KACvB,CAAC;CACH;AAED,MAAM,WAAW,uBAAuB;IACtC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAWD,wBAAsB,gBAAgB,CACpC,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAgB9B"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadKortyxConfig = loadKortyxConfig;
|
|
4
|
+
const promises_1 = require("fs/promises");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const url_1 = require("url");
|
|
7
|
+
async function exists(filePath) {
|
|
8
|
+
try {
|
|
9
|
+
const info = await (0, promises_1.stat)(filePath);
|
|
10
|
+
return info.isFile();
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async function loadKortyxConfig(options = {}) {
|
|
17
|
+
const cwd = options.cwd ?? process.cwd();
|
|
18
|
+
const configPath = options.configPath ?? "kortyx.config.mjs";
|
|
19
|
+
const resolvedPath = (0, path_1.resolve)(cwd, configPath);
|
|
20
|
+
if (!(await exists(resolvedPath)))
|
|
21
|
+
return null;
|
|
22
|
+
try {
|
|
23
|
+
const mod = await import((0, url_1.pathToFileURL)(resolvedPath).href);
|
|
24
|
+
const config = (mod?.default ?? mod?.kortyx ?? mod);
|
|
25
|
+
if (!config || typeof config !== "object")
|
|
26
|
+
return null;
|
|
27
|
+
return config;
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
31
|
+
throw new Error(`Failed to load ${configPath}: ${message}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Edge, GraphState, NodeMap, WorkflowDefinition } from "@kortyx/core";
|
|
2
|
+
import type { MemoryAdapter } from "@kortyx/memory";
|
|
3
|
+
import type { GetProviderFn } from "@kortyx/providers";
|
|
4
|
+
export interface GraphRuntimeConfig {
|
|
5
|
+
emit?: (event: string, payload: unknown) => void;
|
|
6
|
+
onCheckpoint?: (args: {
|
|
7
|
+
nodeId: string;
|
|
8
|
+
state: GraphState;
|
|
9
|
+
}) => void;
|
|
10
|
+
memoryAdapter?: MemoryAdapter;
|
|
11
|
+
getProvider?: GetProviderFn;
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
}
|
|
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>;
|
|
15
|
+
//# sourceMappingURL=create-langgraph.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createLangGraph = createLangGraph;
|
|
4
|
+
const hooks_1 = require("@kortyx/hooks");
|
|
5
|
+
const utils_1 = require("@kortyx/utils");
|
|
6
|
+
const messages_1 = require("@langchain/core/messages");
|
|
7
|
+
const langgraph_1 = require("@langchain/langgraph");
|
|
8
|
+
const checkpointer_1 = require("../checkpointer");
|
|
9
|
+
async function createLangGraph(workflow, config) {
|
|
10
|
+
const StateAnnotation = langgraph_1.Annotation.Root({
|
|
11
|
+
input: (langgraph_1.Annotation),
|
|
12
|
+
output: (langgraph_1.Annotation),
|
|
13
|
+
lastNode: (langgraph_1.Annotation),
|
|
14
|
+
lastCondition: (langgraph_1.Annotation),
|
|
15
|
+
lastIntent: (langgraph_1.Annotation),
|
|
16
|
+
memory: (0, langgraph_1.Annotation)({
|
|
17
|
+
reducer: (l, r) => (0, utils_1.deepMergeWithArrayOverwrite)(l ?? {}, r ?? {}),
|
|
18
|
+
default: () => ({}),
|
|
19
|
+
}),
|
|
20
|
+
config: (langgraph_1.Annotation),
|
|
21
|
+
transitionTo: (langgraph_1.Annotation),
|
|
22
|
+
data: (0, langgraph_1.Annotation)({
|
|
23
|
+
reducer: (l, r) => (0, utils_1.deepMergeWithArrayOverwrite)(l ?? {}, r ?? {}),
|
|
24
|
+
default: () => ({}),
|
|
25
|
+
}),
|
|
26
|
+
conversationHistory: (0, langgraph_1.Annotation)({
|
|
27
|
+
reducer: (l, r) => [...(l ?? []), ...(r ?? [])],
|
|
28
|
+
default: () => [],
|
|
29
|
+
}),
|
|
30
|
+
awaitingHumanInput: (langgraph_1.Annotation),
|
|
31
|
+
});
|
|
32
|
+
const builder = new langgraph_1.StateGraph(StateAnnotation);
|
|
33
|
+
const edgeApi = builder;
|
|
34
|
+
const workflowName = workflow.name;
|
|
35
|
+
const runtimeConfig = config;
|
|
36
|
+
if (!runtimeConfig.emit) {
|
|
37
|
+
runtimeConfig.emit = () => { };
|
|
38
|
+
}
|
|
39
|
+
const nodesMap = workflow.nodes;
|
|
40
|
+
for (const [nodeId, node] of Object.entries(nodesMap)) {
|
|
41
|
+
const nodeConfig = node.config ?? {};
|
|
42
|
+
const behavior = nodeConfig.behavior ?? {};
|
|
43
|
+
builder.addNode(nodeId, async (state) => {
|
|
44
|
+
const ctx = {
|
|
45
|
+
graph: { name: workflowName, node: nodeId },
|
|
46
|
+
config: nodeConfig,
|
|
47
|
+
emit: (event, payload) => {
|
|
48
|
+
runtimeConfig.emit(event, payload);
|
|
49
|
+
},
|
|
50
|
+
error: (message) => {
|
|
51
|
+
runtimeConfig.emit("error", {
|
|
52
|
+
node: nodeId,
|
|
53
|
+
message,
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
awaitInterrupt: (interruptConfig) => {
|
|
57
|
+
const { kind, question } = interruptConfig;
|
|
58
|
+
const isMulti = kind === "multi-choice" ||
|
|
59
|
+
(interruptConfig.kind !== "text" &&
|
|
60
|
+
interruptConfig.multiple === true);
|
|
61
|
+
const payload = {
|
|
62
|
+
kind,
|
|
63
|
+
multiple: isMulti,
|
|
64
|
+
question,
|
|
65
|
+
...(interruptConfig.kind !== "text"
|
|
66
|
+
? { options: interruptConfig.options }
|
|
67
|
+
: {}),
|
|
68
|
+
};
|
|
69
|
+
runtimeConfig.emit("interrupt", {
|
|
70
|
+
node: nodeId,
|
|
71
|
+
workflow: workflowName,
|
|
72
|
+
input: payload,
|
|
73
|
+
});
|
|
74
|
+
const resumed = (0, langgraph_1.interrupt)(payload);
|
|
75
|
+
if (isMulti) {
|
|
76
|
+
if (Array.isArray(resumed)) {
|
|
77
|
+
return resumed
|
|
78
|
+
.map((v) => typeof v === "string"
|
|
79
|
+
? v
|
|
80
|
+
: v && typeof v.id === "string"
|
|
81
|
+
? v.id
|
|
82
|
+
: "")
|
|
83
|
+
.filter(Boolean);
|
|
84
|
+
}
|
|
85
|
+
if (typeof resumed === "string")
|
|
86
|
+
return [resumed];
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
if (typeof resumed === "string")
|
|
90
|
+
return resumed;
|
|
91
|
+
if (resumed && typeof resumed.id === "string")
|
|
92
|
+
return String(resumed.id);
|
|
93
|
+
return "";
|
|
94
|
+
},
|
|
95
|
+
speak: async (args) => {
|
|
96
|
+
const providerId = args.model?.provider ?? nodeConfig.model?.provider ?? "google";
|
|
97
|
+
const modelName = args.model?.name ?? nodeConfig.model?.name ?? "gemini-2.5-flash";
|
|
98
|
+
const temperature = args.model?.temperature ?? nodeConfig.model?.temperature ?? 0.3;
|
|
99
|
+
const getProvider = runtimeConfig.getProvider;
|
|
100
|
+
if (!getProvider) {
|
|
101
|
+
throw new Error("Runtime config is missing getProvider; wire a provider factory into @kortyx/runtime.");
|
|
102
|
+
}
|
|
103
|
+
const model = getProvider(providerId, modelName, {
|
|
104
|
+
temperature,
|
|
105
|
+
streaming: true,
|
|
106
|
+
});
|
|
107
|
+
const messages = [
|
|
108
|
+
...(args.system ? [new messages_1.SystemMessage(args.system)] : []),
|
|
109
|
+
new messages_1.HumanMessage(args.user ?? ""),
|
|
110
|
+
];
|
|
111
|
+
const minChars = args.stream?.minChars ?? 24;
|
|
112
|
+
const flushMs = args.stream?.flushMs ?? 100;
|
|
113
|
+
const segmentChars = args.stream?.segmentChars ?? 60;
|
|
114
|
+
let final = "";
|
|
115
|
+
let buffer = "";
|
|
116
|
+
let timer = null;
|
|
117
|
+
const t0 = Date.now();
|
|
118
|
+
let seenFirst = false;
|
|
119
|
+
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
|
+
if (!isSilent)
|
|
129
|
+
ctx.emit("text-start", { node: nodeId });
|
|
130
|
+
const stream = await model.stream(messages);
|
|
131
|
+
for await (const chunk of stream) {
|
|
132
|
+
const part = (0, utils_1.contentToText)(chunk.content);
|
|
133
|
+
if (!part)
|
|
134
|
+
continue;
|
|
135
|
+
if (!seenFirst) {
|
|
136
|
+
seenFirst = true;
|
|
137
|
+
const ttft = Date.now() - t0;
|
|
138
|
+
ctx.emit("status", {
|
|
139
|
+
node: nodeId,
|
|
140
|
+
message: `⏱️ TTFT ${ttft}ms`,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
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
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (timer)
|
|
156
|
+
clearTimeout(timer);
|
|
157
|
+
flush();
|
|
158
|
+
if (!isSilent)
|
|
159
|
+
ctx.emit("text-end", { node: nodeId });
|
|
160
|
+
const t1 = Date.now();
|
|
161
|
+
if (seenFirst)
|
|
162
|
+
ctx.emit("status", {
|
|
163
|
+
node: nodeId,
|
|
164
|
+
message: `✅ Stream done in ${t1 - t0}ms`,
|
|
165
|
+
});
|
|
166
|
+
return final;
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
if (behavior.checkpoint && config?.onCheckpoint) {
|
|
170
|
+
config.onCheckpoint({ nodeId, state });
|
|
171
|
+
}
|
|
172
|
+
const maxAttempts = behavior.retry?.maxAttempts ?? 1;
|
|
173
|
+
let attempt = 0;
|
|
174
|
+
let nodeResult;
|
|
175
|
+
let hookMemoryUpdates = null;
|
|
176
|
+
while (attempt < maxAttempts) {
|
|
177
|
+
try {
|
|
178
|
+
attempt++;
|
|
179
|
+
const hookContext = {
|
|
180
|
+
node: ctx,
|
|
181
|
+
state,
|
|
182
|
+
...(runtimeConfig.getProvider
|
|
183
|
+
? { getProvider: runtimeConfig.getProvider }
|
|
184
|
+
: {}),
|
|
185
|
+
...(runtimeConfig.memoryAdapter
|
|
186
|
+
? { memoryAdapter: runtimeConfig.memoryAdapter }
|
|
187
|
+
: {}),
|
|
188
|
+
};
|
|
189
|
+
const hookRun = await (0, hooks_1.runWithHookContext)(hookContext, () => node.run(state, ctx));
|
|
190
|
+
nodeResult = hookRun.result;
|
|
191
|
+
hookMemoryUpdates = hookRun.memoryUpdates;
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
const hasMore = attempt < maxAttempts;
|
|
196
|
+
const delayMs = behavior.retry?.delayMs ?? 0;
|
|
197
|
+
if (hasMore && delayMs > 0) {
|
|
198
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (!hasMore)
|
|
202
|
+
throw err;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const res = (nodeResult ?? {});
|
|
206
|
+
if (hookMemoryUpdates) {
|
|
207
|
+
res.infra = {
|
|
208
|
+
...(res.infra ?? {}),
|
|
209
|
+
memory: (0, utils_1.deepMergeWithArrayOverwrite)((res.infra?.memory ?? {}) || {}, hookMemoryUpdates),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
const uiMessage = res.ui?.message;
|
|
213
|
+
const shouldRespond = typeof uiMessage === "string" && uiMessage.trim().length > 0;
|
|
214
|
+
if (shouldRespond) {
|
|
215
|
+
ctx.emit("message", { node: nodeId, content: uiMessage });
|
|
216
|
+
}
|
|
217
|
+
if (res.transitionTo) {
|
|
218
|
+
ctx.emit("transition", {
|
|
219
|
+
transitionTo: res.transitionTo,
|
|
220
|
+
payload: res.data ?? {},
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
const updates = {};
|
|
224
|
+
if (typeof res.condition === "string")
|
|
225
|
+
updates.lastCondition = res.condition;
|
|
226
|
+
if (typeof res.intent === "string")
|
|
227
|
+
updates.lastIntent = res.intent;
|
|
228
|
+
if (res.data && typeof res.data === "object") {
|
|
229
|
+
updates.data = (0, utils_1.deepMergeWithArrayOverwrite)((state.data ?? {}), res.data);
|
|
230
|
+
}
|
|
231
|
+
if (res.infra?.memory && typeof res.infra.memory === "object") {
|
|
232
|
+
updates.memory = (0, utils_1.deepMergeWithArrayOverwrite)((state.memory ?? {}), res.infra.memory);
|
|
233
|
+
}
|
|
234
|
+
if (res.ui) {
|
|
235
|
+
updates.ui = {
|
|
236
|
+
message: typeof res.ui.message === "string"
|
|
237
|
+
? res.ui.message
|
|
238
|
+
: (state.ui?.message ?? ""),
|
|
239
|
+
structured: (0, utils_1.deepMergeWithArrayOverwrite)((state.ui?.structured ?? {}), (res.ui.structured ?? {})),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
if (shouldRespond && uiMessage) {
|
|
243
|
+
updates.conversationHistory = [
|
|
244
|
+
...(state.conversationHistory ?? []),
|
|
245
|
+
{
|
|
246
|
+
node: nodeId,
|
|
247
|
+
message: uiMessage,
|
|
248
|
+
timestamp: new Date().toISOString(),
|
|
249
|
+
},
|
|
250
|
+
];
|
|
251
|
+
}
|
|
252
|
+
if (res.ui?.structured) {
|
|
253
|
+
const payload = res.ui.structured;
|
|
254
|
+
const inferredType = payload && typeof payload === "object" && "jobs" in payload
|
|
255
|
+
? "jobs"
|
|
256
|
+
: typeof payload.summary === "string" ||
|
|
257
|
+
payload.topJobs
|
|
258
|
+
? "summary"
|
|
259
|
+
: "generic";
|
|
260
|
+
ctx.emit("structured_data", {
|
|
261
|
+
node: nodeId,
|
|
262
|
+
dataType: inferredType,
|
|
263
|
+
data: payload,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
return updates;
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
const condGroups = {};
|
|
270
|
+
const plainEdges = [];
|
|
271
|
+
for (const edge of workflow.edges) {
|
|
272
|
+
const [from, to, condition] = edge;
|
|
273
|
+
if (condition && typeof condition.when === "string" && condition.when) {
|
|
274
|
+
if (!condGroups[from])
|
|
275
|
+
condGroups[from] = [];
|
|
276
|
+
condGroups[from].push({ when: condition.when, to });
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
plainEdges.push([from, to]);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
for (const [from, to] of plainEdges) {
|
|
283
|
+
edgeApi.addEdge(from, to);
|
|
284
|
+
}
|
|
285
|
+
for (const [from, rules] of Object.entries(condGroups)) {
|
|
286
|
+
const mapping = Object.fromEntries(rules.map((r) => [r.when, r.to]));
|
|
287
|
+
const fallbackKey = `__default__:${from}`;
|
|
288
|
+
mapping[fallbackKey] = "__end__";
|
|
289
|
+
edgeApi.addConditionalEdges(from, (s) => {
|
|
290
|
+
const trigger = s.lastCondition ?? s.lastIntent;
|
|
291
|
+
if (trigger && Object.hasOwn(mapping, trigger)) {
|
|
292
|
+
return trigger;
|
|
293
|
+
}
|
|
294
|
+
return fallbackKey;
|
|
295
|
+
}, mapping);
|
|
296
|
+
}
|
|
297
|
+
const sessionId = config?.session?.id ?? "__default__";
|
|
298
|
+
const checkpointer = (0, checkpointer_1.getCheckpointer)(sessionId);
|
|
299
|
+
const graph = builder.compile({ checkpointer });
|
|
300
|
+
const runtimeGraph = graph;
|
|
301
|
+
runtimeGraph.name = workflow.name;
|
|
302
|
+
runtimeGraph.config = config;
|
|
303
|
+
runtimeGraph.resume = async (state, input) => {
|
|
304
|
+
const resumed = { ...state, input, awaitingHumanInput: false };
|
|
305
|
+
return graph.invoke(resumed);
|
|
306
|
+
};
|
|
307
|
+
runtimeGraph.timeTravelTo = async (state, checkpointId) => {
|
|
308
|
+
const checkpoints = state.memory?.checkpoints;
|
|
309
|
+
const snapshot = checkpoints?.[checkpointId];
|
|
310
|
+
if (!snapshot)
|
|
311
|
+
throw new Error(`Checkpoint '${checkpointId}' not found`);
|
|
312
|
+
return graph.invoke(snapshot.snapshot);
|
|
313
|
+
};
|
|
314
|
+
return runtimeGraph;
|
|
315
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from "./checkpointer";
|
|
2
|
+
export * from "./config/load-kortyx-config";
|
|
3
|
+
export * from "./graph/create-langgraph";
|
|
4
|
+
export * from "./interrupt/tokens";
|
|
5
|
+
export * from "./registry/file-registry";
|
|
6
|
+
export * from "./registry/interface";
|
|
7
|
+
export * from "./state/build-initial-state";
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./checkpointer"), exports);
|
|
18
|
+
__exportStar(require("./config/load-kortyx-config"), exports);
|
|
19
|
+
__exportStar(require("./graph/create-langgraph"), exports);
|
|
20
|
+
__exportStar(require("./interrupt/tokens"), exports);
|
|
21
|
+
__exportStar(require("./registry/file-registry"), exports);
|
|
22
|
+
__exportStar(require("./registry/interface"), exports);
|
|
23
|
+
__exportStar(require("./state/build-initial-state"), exports);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/interrupt/tokens.ts"],"names":[],"mappings":"AAEA,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,wBAAgB,aAAa,CAAC,MAAM,SAAQ,GAAG,MAAM,CAEpD"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.makeResumeToken = makeResumeToken;
|
|
4
|
+
exports.makeRequestId = makeRequestId;
|
|
5
|
+
const uuid_1 = require("uuid");
|
|
6
|
+
function makeResumeToken() {
|
|
7
|
+
return (0, uuid_1.v7)();
|
|
8
|
+
}
|
|
9
|
+
function makeRequestId(prefix = "req") {
|
|
10
|
+
return `${prefix}-${(0, uuid_1.v7)()}`;
|
|
11
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { WorkflowRegistry } from "./interface";
|
|
2
|
+
export interface FileWorkflowRegistryOptions {
|
|
3
|
+
workflowsDir: string;
|
|
4
|
+
fallbackId?: string;
|
|
5
|
+
cache?: boolean;
|
|
6
|
+
extensions?: string[];
|
|
7
|
+
}
|
|
8
|
+
export declare function createFileWorkflowRegistry(options: FileWorkflowRegistryOptions): WorkflowRegistry;
|
|
9
|
+
//# sourceMappingURL=file-registry.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createFileWorkflowRegistry = createFileWorkflowRegistry;
|
|
4
|
+
const promises_1 = require("fs/promises");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const DEFAULT_EXTENSIONS = [
|
|
7
|
+
".workflow.ts",
|
|
8
|
+
".workflow.mts",
|
|
9
|
+
".workflow.js",
|
|
10
|
+
".workflow.mjs",
|
|
11
|
+
];
|
|
12
|
+
function isWorkflowFile(name, extensions) {
|
|
13
|
+
return extensions.some((ext) => name.endsWith(ext));
|
|
14
|
+
}
|
|
15
|
+
function createFileWorkflowRegistry(options) {
|
|
16
|
+
const workflowsDir = options.workflowsDir;
|
|
17
|
+
const fallbackId = options.fallbackId ?? "general-chat";
|
|
18
|
+
const extensions = options.extensions ?? DEFAULT_EXTENSIONS;
|
|
19
|
+
const shouldCache = options.cache ?? true;
|
|
20
|
+
let workflowCache = null;
|
|
21
|
+
async function loadWorkflows() {
|
|
22
|
+
if (shouldCache && workflowCache)
|
|
23
|
+
return workflowCache;
|
|
24
|
+
const files = await (0, promises_1.readdir)(workflowsDir, { withFileTypes: true });
|
|
25
|
+
const workflowFiles = files
|
|
26
|
+
.filter((entry) => entry.isFile())
|
|
27
|
+
.map((entry) => entry.name)
|
|
28
|
+
.filter((file) => isWorkflowFile(file, extensions));
|
|
29
|
+
const workflows = {};
|
|
30
|
+
for (const file of workflowFiles) {
|
|
31
|
+
try {
|
|
32
|
+
const modulePath = (0, path_1.join)(workflowsDir, file);
|
|
33
|
+
const module = await import(modulePath);
|
|
34
|
+
const workflowExport = Object.values(module).find((exp) => {
|
|
35
|
+
const obj = exp;
|
|
36
|
+
return (exp !== null &&
|
|
37
|
+
typeof exp === "object" &&
|
|
38
|
+
typeof obj.name === "string" &&
|
|
39
|
+
typeof obj.nodes === "object" &&
|
|
40
|
+
obj.nodes !== null &&
|
|
41
|
+
Array.isArray(obj.edges));
|
|
42
|
+
});
|
|
43
|
+
if (workflowExport) {
|
|
44
|
+
workflows[workflowExport.name] = workflowExport;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
console.warn(`Failed to load workflow from ${file}:`, error);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (shouldCache)
|
|
52
|
+
workflowCache = workflows;
|
|
53
|
+
return workflows;
|
|
54
|
+
}
|
|
55
|
+
async function list() {
|
|
56
|
+
const workflows = await loadWorkflows();
|
|
57
|
+
return Object.values(workflows);
|
|
58
|
+
}
|
|
59
|
+
async function get(id) {
|
|
60
|
+
const workflows = await loadWorkflows();
|
|
61
|
+
return workflows[id] ?? null;
|
|
62
|
+
}
|
|
63
|
+
async function select(id, selectOptions) {
|
|
64
|
+
const workflows = await loadWorkflows();
|
|
65
|
+
const fallback = selectOptions?.fallbackId ?? fallbackId;
|
|
66
|
+
const selected = workflows[id] ?? (fallback ? workflows[fallback] : null);
|
|
67
|
+
if (!selected) {
|
|
68
|
+
throw new Error(`No workflow found for id "${id}" and no fallback "${fallback}" workflow available`);
|
|
69
|
+
}
|
|
70
|
+
return selected;
|
|
71
|
+
}
|
|
72
|
+
async function refresh() {
|
|
73
|
+
workflowCache = null;
|
|
74
|
+
await loadWorkflows();
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
list,
|
|
78
|
+
get,
|
|
79
|
+
select,
|
|
80
|
+
refresh,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Edge, NodeMap, WorkflowDefinition } from "@kortyx/core";
|
|
2
|
+
type AnyWorkflow = WorkflowDefinition<NodeMap, readonly Edge<string, string>[]>;
|
|
3
|
+
export interface WorkflowRegistry {
|
|
4
|
+
list: () => Promise<AnyWorkflow[]>;
|
|
5
|
+
get: (id: string) => Promise<AnyWorkflow | null>;
|
|
6
|
+
select: (id: string, options?: {
|
|
7
|
+
fallbackId?: string;
|
|
8
|
+
}) => Promise<AnyWorkflow>;
|
|
9
|
+
refresh?: () => Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../src/registry/interface.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEtE,KAAK,WAAW,GAAG,kBAAkB,CAAC,OAAO,EAAE,SAAS,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;AAEhF,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IACnC,GAAG,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IACjD,MAAM,EAAE,CACN,EAAE,EAAE,MAAM,EACV,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,KAC9B,OAAO,CAAC,WAAW,CAAC,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { GraphState, MemoryEnvelope } from "@kortyx/core";
|
|
2
|
+
export interface InitialStateArgs<Config = unknown> {
|
|
3
|
+
input: string;
|
|
4
|
+
memory: MemoryEnvelope;
|
|
5
|
+
config: Config;
|
|
6
|
+
defaultWorkflowId?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function buildInitialGraphState<Config>({ input, memory, config, defaultWorkflowId, }: InitialStateArgs<Config>): Promise<GraphState>;
|
|
9
|
+
//# sourceMappingURL=build-initial-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-initial-state.d.ts","sourceRoot":"","sources":["../../src/state/build-initial-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAM/D,MAAM,WAAW,gBAAgB,CAAC,MAAM,GAAG,OAAO;IAChD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,EACnD,KAAK,EACL,MAAM,EACN,MAAM,EACN,iBAA+B,GAChC,EAAE,gBAAgB,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,CAWhD"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildInitialGraphState = buildInitialGraphState;
|
|
4
|
+
async function buildInitialGraphState({ input, memory, config, defaultWorkflowId = "frontdesk", }) {
|
|
5
|
+
return {
|
|
6
|
+
input,
|
|
7
|
+
lastNode: "__start__",
|
|
8
|
+
memory,
|
|
9
|
+
config: config,
|
|
10
|
+
conversationHistory: [],
|
|
11
|
+
currentWorkflow: memory.currentWorkflow || defaultWorkflowId,
|
|
12
|
+
awaitingHumanInput: false,
|
|
13
|
+
};
|
|
14
|
+
}
|