agentflow-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +406 -0
- package/dist/index.d.cts +370 -0
- package/dist/index.d.ts +370 -0
- package/dist/index.js +368 -0
- package/package.json +31 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
// src/graph-builder.ts
|
|
2
|
+
function deepFreeze(obj) {
|
|
3
|
+
if (obj === null || typeof obj !== "object") return obj;
|
|
4
|
+
if (obj instanceof Map) {
|
|
5
|
+
Object.freeze(obj);
|
|
6
|
+
for (const value of obj.values()) {
|
|
7
|
+
deepFreeze(value);
|
|
8
|
+
}
|
|
9
|
+
return obj;
|
|
10
|
+
}
|
|
11
|
+
Object.freeze(obj);
|
|
12
|
+
const record = obj;
|
|
13
|
+
for (const key of Object.keys(record)) {
|
|
14
|
+
const value = record[key];
|
|
15
|
+
if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
|
|
16
|
+
deepFreeze(value);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return obj;
|
|
20
|
+
}
|
|
21
|
+
function createCounterIdGenerator() {
|
|
22
|
+
let counter = 0;
|
|
23
|
+
return () => {
|
|
24
|
+
counter++;
|
|
25
|
+
return `node_${String(counter).padStart(3, "0")}`;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function createGraphBuilder(config) {
|
|
29
|
+
const generateId = config?.idGenerator ?? createCounterIdGenerator();
|
|
30
|
+
const agentId = config?.agentId ?? "unknown";
|
|
31
|
+
const trigger = config?.trigger ?? "manual";
|
|
32
|
+
const graphId = generateId();
|
|
33
|
+
const startTime = Date.now();
|
|
34
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
35
|
+
const edges = [];
|
|
36
|
+
const events = [];
|
|
37
|
+
const parentStack = [];
|
|
38
|
+
let rootNodeId = null;
|
|
39
|
+
let built = false;
|
|
40
|
+
function assertNotBuilt() {
|
|
41
|
+
if (built) {
|
|
42
|
+
throw new Error("GraphBuilder: cannot mutate after build() has been called");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function getNode2(nodeId) {
|
|
46
|
+
const node = nodes.get(nodeId);
|
|
47
|
+
if (!node) {
|
|
48
|
+
throw new Error(`GraphBuilder: node "${nodeId}" does not exist`);
|
|
49
|
+
}
|
|
50
|
+
return node;
|
|
51
|
+
}
|
|
52
|
+
function recordEvent(nodeId, eventType, data = {}) {
|
|
53
|
+
events.push({
|
|
54
|
+
timestamp: Date.now(),
|
|
55
|
+
eventType,
|
|
56
|
+
nodeId,
|
|
57
|
+
data
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function buildGraph() {
|
|
61
|
+
if (rootNodeId === null) {
|
|
62
|
+
throw new Error("GraphBuilder: cannot build a graph with no nodes");
|
|
63
|
+
}
|
|
64
|
+
let graphStatus = "completed";
|
|
65
|
+
for (const node of nodes.values()) {
|
|
66
|
+
if (node.status === "failed" || node.status === "timeout" || node.status === "hung") {
|
|
67
|
+
graphStatus = "failed";
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
if (node.status === "running") {
|
|
71
|
+
graphStatus = "running";
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const endTime = graphStatus === "running" ? null : Date.now();
|
|
75
|
+
const frozenNodes = new Map(
|
|
76
|
+
[...nodes.entries()].map(([id, mNode]) => [
|
|
77
|
+
id,
|
|
78
|
+
{
|
|
79
|
+
id: mNode.id,
|
|
80
|
+
type: mNode.type,
|
|
81
|
+
name: mNode.name,
|
|
82
|
+
startTime: mNode.startTime,
|
|
83
|
+
endTime: mNode.endTime,
|
|
84
|
+
status: mNode.status,
|
|
85
|
+
parentId: mNode.parentId,
|
|
86
|
+
children: [...mNode.children],
|
|
87
|
+
metadata: { ...mNode.metadata },
|
|
88
|
+
state: { ...mNode.state }
|
|
89
|
+
}
|
|
90
|
+
])
|
|
91
|
+
);
|
|
92
|
+
const graph = {
|
|
93
|
+
id: graphId,
|
|
94
|
+
rootNodeId,
|
|
95
|
+
nodes: frozenNodes,
|
|
96
|
+
edges: [...edges],
|
|
97
|
+
startTime,
|
|
98
|
+
endTime,
|
|
99
|
+
status: graphStatus,
|
|
100
|
+
trigger,
|
|
101
|
+
agentId,
|
|
102
|
+
events: [...events]
|
|
103
|
+
};
|
|
104
|
+
return deepFreeze(graph);
|
|
105
|
+
}
|
|
106
|
+
const builder = {
|
|
107
|
+
get graphId() {
|
|
108
|
+
return graphId;
|
|
109
|
+
},
|
|
110
|
+
startNode(opts) {
|
|
111
|
+
assertNotBuilt();
|
|
112
|
+
const id = generateId();
|
|
113
|
+
const parentId = opts.parentId ?? parentStack[parentStack.length - 1] ?? null;
|
|
114
|
+
if (parentId !== null && !nodes.has(parentId)) {
|
|
115
|
+
throw new Error(`GraphBuilder: parent node "${parentId}" does not exist`);
|
|
116
|
+
}
|
|
117
|
+
const node = {
|
|
118
|
+
id,
|
|
119
|
+
type: opts.type,
|
|
120
|
+
name: opts.name,
|
|
121
|
+
startTime: Date.now(),
|
|
122
|
+
endTime: null,
|
|
123
|
+
status: "running",
|
|
124
|
+
parentId,
|
|
125
|
+
children: [],
|
|
126
|
+
metadata: opts.metadata ? { ...opts.metadata } : {},
|
|
127
|
+
state: {}
|
|
128
|
+
};
|
|
129
|
+
nodes.set(id, node);
|
|
130
|
+
if (parentId !== null) {
|
|
131
|
+
const parent = nodes.get(parentId);
|
|
132
|
+
if (parent) {
|
|
133
|
+
parent.children.push(id);
|
|
134
|
+
}
|
|
135
|
+
edges.push({ from: parentId, to: id, type: "spawned" });
|
|
136
|
+
}
|
|
137
|
+
if (rootNodeId === null) {
|
|
138
|
+
rootNodeId = id;
|
|
139
|
+
}
|
|
140
|
+
recordEvent(id, "agent_start", { type: opts.type, name: opts.name });
|
|
141
|
+
return id;
|
|
142
|
+
},
|
|
143
|
+
endNode(nodeId, status = "completed") {
|
|
144
|
+
assertNotBuilt();
|
|
145
|
+
const node = getNode2(nodeId);
|
|
146
|
+
if (node.endTime !== null) {
|
|
147
|
+
throw new Error(
|
|
148
|
+
`GraphBuilder: node "${nodeId}" has already ended (status: ${node.status})`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
node.endTime = Date.now();
|
|
152
|
+
node.status = status;
|
|
153
|
+
recordEvent(nodeId, "agent_end", { status });
|
|
154
|
+
},
|
|
155
|
+
failNode(nodeId, error) {
|
|
156
|
+
assertNotBuilt();
|
|
157
|
+
const node = getNode2(nodeId);
|
|
158
|
+
if (node.endTime !== null) {
|
|
159
|
+
throw new Error(
|
|
160
|
+
`GraphBuilder: node "${nodeId}" has already ended (status: ${node.status})`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
const errorMessage = error instanceof Error ? error.message : error;
|
|
164
|
+
const errorStack = error instanceof Error ? error.stack : void 0;
|
|
165
|
+
node.endTime = Date.now();
|
|
166
|
+
node.status = "failed";
|
|
167
|
+
node.metadata.error = errorMessage;
|
|
168
|
+
if (errorStack) {
|
|
169
|
+
node.metadata.errorStack = errorStack;
|
|
170
|
+
}
|
|
171
|
+
recordEvent(nodeId, "tool_error", { error: errorMessage });
|
|
172
|
+
},
|
|
173
|
+
addEdge(from, to, type) {
|
|
174
|
+
assertNotBuilt();
|
|
175
|
+
getNode2(from);
|
|
176
|
+
getNode2(to);
|
|
177
|
+
edges.push({ from, to, type });
|
|
178
|
+
recordEvent(from, "custom", { to, type, action: "edge_add" });
|
|
179
|
+
},
|
|
180
|
+
pushEvent(event) {
|
|
181
|
+
assertNotBuilt();
|
|
182
|
+
getNode2(event.nodeId);
|
|
183
|
+
events.push({
|
|
184
|
+
...event,
|
|
185
|
+
timestamp: Date.now()
|
|
186
|
+
});
|
|
187
|
+
},
|
|
188
|
+
updateState(nodeId, state) {
|
|
189
|
+
assertNotBuilt();
|
|
190
|
+
const node = getNode2(nodeId);
|
|
191
|
+
Object.assign(node.state, state);
|
|
192
|
+
recordEvent(nodeId, "custom", { action: "state_update", ...state });
|
|
193
|
+
},
|
|
194
|
+
withParent(parentId, fn) {
|
|
195
|
+
assertNotBuilt();
|
|
196
|
+
getNode2(parentId);
|
|
197
|
+
parentStack.push(parentId);
|
|
198
|
+
try {
|
|
199
|
+
return fn();
|
|
200
|
+
} finally {
|
|
201
|
+
parentStack.pop();
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
getSnapshot() {
|
|
205
|
+
return buildGraph();
|
|
206
|
+
},
|
|
207
|
+
build() {
|
|
208
|
+
assertNotBuilt();
|
|
209
|
+
const graph = buildGraph();
|
|
210
|
+
built = true;
|
|
211
|
+
return graph;
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
return builder;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// src/graph-query.ts
|
|
218
|
+
function getNode(graph, nodeId) {
|
|
219
|
+
return graph.nodes.get(nodeId);
|
|
220
|
+
}
|
|
221
|
+
function getChildren(graph, nodeId) {
|
|
222
|
+
const node = graph.nodes.get(nodeId);
|
|
223
|
+
if (!node) return [];
|
|
224
|
+
const result = [];
|
|
225
|
+
for (const childId of node.children) {
|
|
226
|
+
const child = graph.nodes.get(childId);
|
|
227
|
+
if (child) result.push(child);
|
|
228
|
+
}
|
|
229
|
+
return result;
|
|
230
|
+
}
|
|
231
|
+
function getParent(graph, nodeId) {
|
|
232
|
+
const node = graph.nodes.get(nodeId);
|
|
233
|
+
if (!node || node.parentId === null) return void 0;
|
|
234
|
+
return graph.nodes.get(node.parentId);
|
|
235
|
+
}
|
|
236
|
+
function getFailures(graph) {
|
|
237
|
+
const failureStatuses = /* @__PURE__ */ new Set(["failed", "hung", "timeout"]);
|
|
238
|
+
return [...graph.nodes.values()].filter((node) => failureStatuses.has(node.status));
|
|
239
|
+
}
|
|
240
|
+
function getHungNodes(graph) {
|
|
241
|
+
return [...graph.nodes.values()].filter(
|
|
242
|
+
(node) => node.status === "running" && node.endTime === null
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
function getCriticalPath(graph) {
|
|
246
|
+
const root = graph.nodes.get(graph.rootNodeId);
|
|
247
|
+
if (!root) return [];
|
|
248
|
+
function nodeDuration(node) {
|
|
249
|
+
const end = node.endTime ?? Date.now();
|
|
250
|
+
return end - node.startTime;
|
|
251
|
+
}
|
|
252
|
+
function dfs(node) {
|
|
253
|
+
if (node.children.length === 0) {
|
|
254
|
+
return { duration: nodeDuration(node), path: [node] };
|
|
255
|
+
}
|
|
256
|
+
let bestChild = { duration: -1, path: [] };
|
|
257
|
+
for (const childId of node.children) {
|
|
258
|
+
const child = graph.nodes.get(childId);
|
|
259
|
+
if (!child) continue;
|
|
260
|
+
const result = dfs(child);
|
|
261
|
+
if (result.duration > bestChild.duration) {
|
|
262
|
+
bestChild = result;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
duration: nodeDuration(node) + bestChild.duration,
|
|
267
|
+
path: [node, ...bestChild.path]
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
return dfs(root).path;
|
|
271
|
+
}
|
|
272
|
+
function findWaitingOn(graph, nodeId) {
|
|
273
|
+
const results = [];
|
|
274
|
+
for (const edge of graph.edges) {
|
|
275
|
+
if (edge.from === nodeId && edge.type === "waited_on") {
|
|
276
|
+
const node = graph.nodes.get(edge.to);
|
|
277
|
+
if (node) results.push(node);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return results;
|
|
281
|
+
}
|
|
282
|
+
function getSubtree(graph, nodeId) {
|
|
283
|
+
const startNode = graph.nodes.get(nodeId);
|
|
284
|
+
if (!startNode) return [];
|
|
285
|
+
const result = [];
|
|
286
|
+
const queue = [...startNode.children];
|
|
287
|
+
while (queue.length > 0) {
|
|
288
|
+
const currentId = queue.shift();
|
|
289
|
+
if (currentId === void 0) break;
|
|
290
|
+
const current = graph.nodes.get(currentId);
|
|
291
|
+
if (!current) continue;
|
|
292
|
+
result.push(current);
|
|
293
|
+
queue.push(...current.children);
|
|
294
|
+
}
|
|
295
|
+
return result;
|
|
296
|
+
}
|
|
297
|
+
function getDuration(graph) {
|
|
298
|
+
const end = graph.endTime ?? Date.now();
|
|
299
|
+
return end - graph.startTime;
|
|
300
|
+
}
|
|
301
|
+
function getDepth(graph) {
|
|
302
|
+
const root = graph.nodes.get(graph.rootNodeId);
|
|
303
|
+
if (!root) return -1;
|
|
304
|
+
function dfs(node, depth) {
|
|
305
|
+
if (node.children.length === 0) return depth;
|
|
306
|
+
let maxDepth = depth;
|
|
307
|
+
for (const childId of node.children) {
|
|
308
|
+
const child = graph.nodes.get(childId);
|
|
309
|
+
if (!child) continue;
|
|
310
|
+
const childDepth = dfs(child, depth + 1);
|
|
311
|
+
if (childDepth > maxDepth) maxDepth = childDepth;
|
|
312
|
+
}
|
|
313
|
+
return maxDepth;
|
|
314
|
+
}
|
|
315
|
+
return dfs(root, 0);
|
|
316
|
+
}
|
|
317
|
+
function getStats(graph) {
|
|
318
|
+
const byStatus = {
|
|
319
|
+
running: 0,
|
|
320
|
+
completed: 0,
|
|
321
|
+
failed: 0,
|
|
322
|
+
hung: 0,
|
|
323
|
+
timeout: 0
|
|
324
|
+
};
|
|
325
|
+
const byType = {
|
|
326
|
+
agent: 0,
|
|
327
|
+
tool: 0,
|
|
328
|
+
subagent: 0,
|
|
329
|
+
wait: 0,
|
|
330
|
+
decision: 0,
|
|
331
|
+
custom: 0
|
|
332
|
+
};
|
|
333
|
+
let failureCount = 0;
|
|
334
|
+
let hungCount = 0;
|
|
335
|
+
for (const node of graph.nodes.values()) {
|
|
336
|
+
byStatus[node.status]++;
|
|
337
|
+
byType[node.type]++;
|
|
338
|
+
if (node.status === "failed" || node.status === "timeout" || node.status === "hung") {
|
|
339
|
+
failureCount++;
|
|
340
|
+
}
|
|
341
|
+
if (node.status === "running" && node.endTime === null) {
|
|
342
|
+
hungCount++;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return {
|
|
346
|
+
totalNodes: graph.nodes.size,
|
|
347
|
+
byStatus,
|
|
348
|
+
byType,
|
|
349
|
+
depth: getDepth(graph),
|
|
350
|
+
duration: getDuration(graph),
|
|
351
|
+
failureCount,
|
|
352
|
+
hungCount
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
export {
|
|
356
|
+
createGraphBuilder,
|
|
357
|
+
findWaitingOn,
|
|
358
|
+
getChildren,
|
|
359
|
+
getCriticalPath,
|
|
360
|
+
getDepth,
|
|
361
|
+
getDuration,
|
|
362
|
+
getFailures,
|
|
363
|
+
getHungNodes,
|
|
364
|
+
getNode,
|
|
365
|
+
getParent,
|
|
366
|
+
getStats,
|
|
367
|
+
getSubtree
|
|
368
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agentflow-core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Universal execution tracing for AI agent systems",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --clean --tsconfig tsconfig.build.json"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"ai",
|
|
25
|
+
"agent",
|
|
26
|
+
"tracing",
|
|
27
|
+
"execution-graph",
|
|
28
|
+
"observability"
|
|
29
|
+
],
|
|
30
|
+
"license": "MIT"
|
|
31
|
+
}
|