graphai 2.0.16 → 2.0.17
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/README.md +1 -1
- package/lib/graphai.js +5 -3
- package/lib/node.d.ts +1 -0
- package/lib/node.js +57 -41
- package/lib/task_manager.d.ts +8 -3
- package/lib/task_manager.js +150 -12
- package/lib/type.d.ts +6 -1
- package/lib/utils/utils.d.ts +1 -0
- package/lib/utils/utils.js +12 -1
- package/lib/validators/common.js +1 -0
- package/lib/validators/computed_node_validator.js +3 -0
- package/lib/validators/graph_data_validator.js +41 -6
- package/package.json +8 -4
package/README.md
CHANGED
package/lib/graphai.js
CHANGED
|
@@ -123,9 +123,13 @@ class GraphAI {
|
|
|
123
123
|
this.graphId = `${Date.now().toString(36)}-${Math.random().toString(36).substr(2, 9)}`; // URL.createObjectURL(new Blob()).slice(-36);
|
|
124
124
|
this.agentFunctionInfoDictionary = agentFunctionInfoDictionary;
|
|
125
125
|
this.propFunctions = prop_function_1.propFunctions;
|
|
126
|
+
this.bypassAgentIds = options.bypassAgentIds ?? [];
|
|
127
|
+
// Validate before constructing TaskManager so user-facing ValidationError
|
|
128
|
+
// is reported instead of TaskManager's internal guard message.
|
|
129
|
+
(0, validator_1.validateGraphData)(graphData, [...Object.keys(agentFunctionInfoDictionary), ...this.bypassAgentIds]);
|
|
130
|
+
(0, validator_1.validateAgent)(agentFunctionInfoDictionary);
|
|
126
131
|
this.taskManager = options.taskManager ?? new task_manager_1.TaskManager(graphData.concurrency ?? exports.defaultConcurrency);
|
|
127
132
|
this.agentFilters = options.agentFilters ?? [];
|
|
128
|
-
this.bypassAgentIds = options.bypassAgentIds ?? [];
|
|
129
133
|
this.config = options.config;
|
|
130
134
|
this.graphLoader = options.graphLoader;
|
|
131
135
|
this.forceLoop = options.forceLoop ?? false;
|
|
@@ -135,8 +139,6 @@ class GraphAI {
|
|
|
135
139
|
this.onComplete = (__isAbort) => {
|
|
136
140
|
throw new Error("SOMETHING IS WRONG: onComplete is called without run()");
|
|
137
141
|
};
|
|
138
|
-
(0, validator_1.validateGraphData)(graphData, [...Object.keys(agentFunctionInfoDictionary), ...this.bypassAgentIds]);
|
|
139
|
-
(0, validator_1.validateAgent)(agentFunctionInfoDictionary);
|
|
140
142
|
this.graphData = {
|
|
141
143
|
...graphData,
|
|
142
144
|
nodes: {
|
package/lib/node.d.ts
CHANGED
package/lib/node.js
CHANGED
|
@@ -67,6 +67,7 @@ class ComputedNode extends Node {
|
|
|
67
67
|
agentFunction;
|
|
68
68
|
timeout; // msec
|
|
69
69
|
priority;
|
|
70
|
+
label;
|
|
70
71
|
error;
|
|
71
72
|
transactionId; // To reject callbacks from timed-out transactions
|
|
72
73
|
passThrough;
|
|
@@ -94,6 +95,10 @@ class ComputedNode extends Node {
|
|
|
94
95
|
this.timeout = data.timeout;
|
|
95
96
|
this.isResult = data.isResult ?? false;
|
|
96
97
|
this.priority = data.priority ?? 0;
|
|
98
|
+
// Defensive: graph data may originate from YAML/JSON without strict typing.
|
|
99
|
+
// Keep label only when it is actually a string so TaskManager's label-keyed
|
|
100
|
+
// bookkeeping cannot be silently bypassed by a non-string value.
|
|
101
|
+
this.label = typeof data.label === "string" ? data.label : undefined;
|
|
97
102
|
(0, utils_2.assert)(["function", "string"].includes(typeof data.agent), "agent must be either string or function");
|
|
98
103
|
if (typeof data.agent === "string") {
|
|
99
104
|
this.agentId = data.agent;
|
|
@@ -293,50 +298,61 @@ class ComputedNode extends Node {
|
|
|
293
298
|
const agentFunction = this.agentFunction ?? this.graph.getAgentFunctionInfo(agentId, this.nodeId).agent;
|
|
294
299
|
const localLog = [];
|
|
295
300
|
const context = this.getContext(previousResults, localLog, agentId, config);
|
|
296
|
-
//
|
|
297
|
-
//
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
if ((0, utils_1.isNull)(repeatResult?.data)) {
|
|
334
|
-
this.retry(type_1.NodeState.Failed, Error("Repeat Until"));
|
|
301
|
+
// The `nestingPrepared` flag tracks whether prepareForNesting has actually
|
|
302
|
+
// run. If anything throws between prepareForNesting and the agent call --
|
|
303
|
+
// e.g. resultOf() during forNestedGraph construction -- we still need to
|
|
304
|
+
// restore. Conversely, if prepareForNesting itself throws, we must NOT
|
|
305
|
+
// restore (no bump to undo).
|
|
306
|
+
let nestingPrepared = false;
|
|
307
|
+
try {
|
|
308
|
+
// NOTE: We use the existence of graph object in the agent-specific params to determine
|
|
309
|
+
// if this is a nested agent or not.
|
|
310
|
+
if (hasNestedGraph) {
|
|
311
|
+
this.graph.taskManager.prepareForNesting(this.label, this.graphId);
|
|
312
|
+
nestingPrepared = true;
|
|
313
|
+
context.forNestedGraph = {
|
|
314
|
+
graphData: this.nestedGraph
|
|
315
|
+
? "nodes" in this.nestedGraph
|
|
316
|
+
? this.nestedGraph
|
|
317
|
+
: this.graph.resultOf(this.nestedGraph) // HACK: compiler work-around
|
|
318
|
+
: { version: 0, nodes: {} },
|
|
319
|
+
agents: this.graph.agentFunctionInfoDictionary,
|
|
320
|
+
graphOptions: {
|
|
321
|
+
agentFilters: this.graph.agentFilters,
|
|
322
|
+
taskManager: this.graph.taskManager,
|
|
323
|
+
bypassAgentIds: this.graph.bypassAgentIds,
|
|
324
|
+
config,
|
|
325
|
+
graphLoader: this.graph.graphLoader,
|
|
326
|
+
},
|
|
327
|
+
onLogCallback: this.graph.onLogCallback,
|
|
328
|
+
callbacks: this.graph.callbacks,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
this.beforeConsoleLog(context);
|
|
332
|
+
const result = await this.agentFilterHandler(context, agentFunction, agentId);
|
|
333
|
+
this.afterConsoleLog(result);
|
|
334
|
+
if (!this.isCurrentTransaction(transactionId)) {
|
|
335
|
+
// This condition happens when the agent function returns
|
|
336
|
+
// after the timeout (either retried or not).
|
|
337
|
+
GraphAILogger_1.GraphAILogger.log(`-- transactionId mismatch with ${this.nodeId} (probably timeout)`);
|
|
335
338
|
return;
|
|
336
339
|
}
|
|
340
|
+
if (this.repeatUntil?.exists) {
|
|
341
|
+
const dummyResult = { self: { result: this.getResult(result) } };
|
|
342
|
+
const repeatResult = (0, result_1.resultsOf)({ data: this.repeatUntil?.exists }, dummyResult, [], true);
|
|
343
|
+
if ((0, utils_1.isNull)(repeatResult?.data)) {
|
|
344
|
+
this.retry(type_1.NodeState.Failed, Error("Repeat Until"));
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
// after process
|
|
349
|
+
this.afterExecute(result, localLog);
|
|
350
|
+
}
|
|
351
|
+
finally {
|
|
352
|
+
if (nestingPrepared) {
|
|
353
|
+
this.graph.taskManager.restoreAfterNesting(this.label, this.graphId);
|
|
354
|
+
}
|
|
337
355
|
}
|
|
338
|
-
// after process
|
|
339
|
-
this.afterExecute(result, localLog);
|
|
340
356
|
}
|
|
341
357
|
catch (error) {
|
|
342
358
|
this.errorProcess(error, transactionId, previousResults);
|
package/lib/task_manager.d.ts
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import { ComputedNode } from "./node";
|
|
2
|
+
import { ConcurrencyConfig } from "./type";
|
|
2
3
|
export declare class TaskManager {
|
|
3
4
|
private concurrency;
|
|
5
|
+
private labelLimits;
|
|
6
|
+
private runningByLabel;
|
|
7
|
+
private nestingBypassByLabel;
|
|
4
8
|
private taskQueue;
|
|
5
9
|
private runningNodes;
|
|
6
|
-
constructor(
|
|
10
|
+
constructor(config: number | ConcurrencyConfig);
|
|
11
|
+
private canRun;
|
|
7
12
|
private dequeueTaskIfPossible;
|
|
8
13
|
addTask(node: ComputedNode, graphId: string, callback: (node: ComputedNode) => void): void;
|
|
9
14
|
isRunning(graphId: string): boolean;
|
|
10
15
|
onComplete(node: ComputedNode): void;
|
|
11
|
-
prepareForNesting(): void;
|
|
12
|
-
restoreAfterNesting(): void;
|
|
16
|
+
prepareForNesting(label?: string, parentGraphId?: string): void;
|
|
17
|
+
restoreAfterNesting(label?: string, parentGraphId?: string): void;
|
|
13
18
|
getStatus(verbose?: boolean): {
|
|
14
19
|
runningNodes: string[];
|
|
15
20
|
queuedNodes: string[];
|
package/lib/task_manager.js
CHANGED
|
@@ -2,33 +2,113 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.TaskManager = void 0;
|
|
4
4
|
const utils_1 = require("./utils/utils");
|
|
5
|
+
const assertPositiveInteger = (value, field) => {
|
|
6
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
|
|
7
|
+
throw new Error(`TaskManager: ${field} must be a positive integer (got ${String(value)})`);
|
|
8
|
+
}
|
|
9
|
+
return value;
|
|
10
|
+
};
|
|
11
|
+
// Mirrors the strictness of validateConcurrencyConfig() so that direct
|
|
12
|
+
// `new TaskManager(...)` calls (e.g. tests, advanced consumers that bypass
|
|
13
|
+
// validateGraphData) cannot silently disable label enforcement with a
|
|
14
|
+
// malformed shape such as Map / Date / class instances / arrays.
|
|
15
|
+
const normalizeConcurrencyConfig = (config) => {
|
|
16
|
+
if (typeof config === "number") {
|
|
17
|
+
return { global: assertPositiveInteger(config, "concurrency"), labels: new Map() };
|
|
18
|
+
}
|
|
19
|
+
if (!(0, utils_1.isPlainObject)(config)) {
|
|
20
|
+
throw new Error("TaskManager: concurrency must be a positive integer or a ConcurrencyConfig object");
|
|
21
|
+
}
|
|
22
|
+
const global = assertPositiveInteger(config.global, "concurrency.global");
|
|
23
|
+
const labels = new Map();
|
|
24
|
+
if (config.labels !== undefined) {
|
|
25
|
+
if (!(0, utils_1.isPlainObject)(config.labels)) {
|
|
26
|
+
throw new Error("TaskManager: concurrency.labels must be a plain object");
|
|
27
|
+
}
|
|
28
|
+
for (const [labelKey, labelValue] of Object.entries(config.labels)) {
|
|
29
|
+
labels.set(labelKey, assertPositiveInteger(labelValue, `concurrency.labels.${labelKey}`));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return { global, labels };
|
|
33
|
+
};
|
|
5
34
|
// TaskManage object controls the concurrency of ComputedNode execution.
|
|
6
35
|
//
|
|
7
36
|
// NOTE: A TaskManager instance will be shared between parent graph and its children
|
|
8
37
|
// when nested agents are involved.
|
|
9
38
|
class TaskManager {
|
|
10
39
|
concurrency;
|
|
40
|
+
labelLimits;
|
|
41
|
+
runningByLabel = new Map();
|
|
42
|
+
// Per-label bypass capacity granted to nested children. Keyed by parentGraphId
|
|
43
|
+
// so that only tasks whose graphId differs from the parent (i.e. those running
|
|
44
|
+
// inside the nested graph) can consume the extra slot. Unrelated siblings on
|
|
45
|
+
// the same graphId as the parent are not affected.
|
|
46
|
+
nestingBypassByLabel = new Map();
|
|
11
47
|
taskQueue = [];
|
|
12
48
|
runningNodes = new Set();
|
|
13
|
-
constructor(
|
|
14
|
-
|
|
49
|
+
constructor(config) {
|
|
50
|
+
const normalized = normalizeConcurrencyConfig(config);
|
|
51
|
+
this.concurrency = normalized.global;
|
|
52
|
+
this.labelLimits = normalized.labels;
|
|
15
53
|
}
|
|
16
|
-
//
|
|
17
|
-
// and
|
|
18
|
-
|
|
54
|
+
// Returns true if the task can run right now under both the global limit
|
|
55
|
+
// and (if specified) its label-specific limit.
|
|
56
|
+
canRun(task) {
|
|
57
|
+
if (this.runningNodes.size >= this.concurrency) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
const label = task.node.label;
|
|
61
|
+
if (label === undefined) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
const limit = this.labelLimits.get(label);
|
|
65
|
+
if (limit === undefined) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
const running = this.runningByLabel.get(label) ?? 0;
|
|
69
|
+
if (running < limit) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
// Bypass path: if a labeled parent has prepared for nesting and this task
|
|
73
|
+
// belongs to a different graph (i.e. the nested graph), grant +1 per such
|
|
74
|
+
// outstanding bump. Unrelated siblings on the parent's graphId do NOT get
|
|
75
|
+
// this allowance, so the per-label cap is preserved for them.
|
|
76
|
+
const bypass = this.nestingBypassByLabel.get(label);
|
|
77
|
+
if (!bypass) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
let extra = 0;
|
|
81
|
+
for (const [parentGraphId, count] of bypass) {
|
|
82
|
+
if (parentGraphId !== task.graphId) {
|
|
83
|
+
extra += count;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return running < limit + extra;
|
|
87
|
+
}
|
|
88
|
+
// Walk the queue (already sorted by priority desc) and dispatch the first task
|
|
89
|
+
// whose label still has capacity. This is the "head-of-line skip" policy.
|
|
19
90
|
dequeueTaskIfPossible() {
|
|
20
|
-
if (this.runningNodes.size
|
|
21
|
-
|
|
22
|
-
|
|
91
|
+
if (this.runningNodes.size >= this.concurrency) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
for (let i = 0; i < this.taskQueue.length; i++) {
|
|
95
|
+
const task = this.taskQueue[i];
|
|
96
|
+
if (this.canRun(task)) {
|
|
97
|
+
this.taskQueue.splice(i, 1);
|
|
23
98
|
this.runningNodes.add(task.node);
|
|
99
|
+
const label = task.node.label;
|
|
100
|
+
if (label !== undefined) {
|
|
101
|
+
this.runningByLabel.set(label, (this.runningByLabel.get(label) ?? 0) + 1);
|
|
102
|
+
}
|
|
24
103
|
task.callback(task.node);
|
|
104
|
+
return;
|
|
25
105
|
}
|
|
26
106
|
}
|
|
27
107
|
}
|
|
28
108
|
// Node will call this method to put itself in the execution queue.
|
|
29
109
|
// We call the associated callback function when it is dequeued.
|
|
30
110
|
addTask(node, graphId, callback) {
|
|
31
|
-
//
|
|
111
|
+
// Find tasks in the queue, which has either the same or higher priority.
|
|
32
112
|
const count = this.taskQueue.filter((task) => {
|
|
33
113
|
return task.node.priority >= node.priority;
|
|
34
114
|
}).length;
|
|
@@ -47,16 +127,72 @@ class TaskManager {
|
|
|
47
127
|
onComplete(node) {
|
|
48
128
|
(0, utils_1.assert)(this.runningNodes.has(node), `TaskManager.onComplete node(${node.nodeId}) is not in list`);
|
|
49
129
|
this.runningNodes.delete(node);
|
|
50
|
-
|
|
130
|
+
const label = node.label;
|
|
131
|
+
if (label !== undefined) {
|
|
132
|
+
const running = this.runningByLabel.get(label) ?? 0;
|
|
133
|
+
if (running <= 1) {
|
|
134
|
+
this.runningByLabel.delete(label);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
this.runningByLabel.set(label, running - 1);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// A label slot may have just opened, so try to dispatch as many newly-eligible
|
|
141
|
+
// tasks as possible (the freed label could allow several queued tasks to run if
|
|
142
|
+
// the global limit is not yet reached).
|
|
143
|
+
let progressed = true;
|
|
144
|
+
while (progressed) {
|
|
145
|
+
const before = this.runningNodes.size;
|
|
146
|
+
this.dequeueTaskIfPossible();
|
|
147
|
+
progressed = this.runningNodes.size > before;
|
|
148
|
+
}
|
|
51
149
|
}
|
|
52
150
|
// Node will call this method before it hands the task manager from the graph
|
|
53
151
|
// to a nested agent. We need to make it sure that there is enough room to run
|
|
54
152
|
// computed nodes inside the nested graph to avoid a deadlock.
|
|
55
|
-
|
|
153
|
+
//
|
|
154
|
+
// When the parent carries a label that has a configured per-label limit,
|
|
155
|
+
// a nested child sharing that label would otherwise stay queued forever
|
|
156
|
+
// (parent waits for child, child blocked by parent's label slot). To avoid
|
|
157
|
+
// this without widening the cap for unrelated siblings, we record a per-
|
|
158
|
+
// parent-graphId bypass that canRun() applies only to tasks whose graphId
|
|
159
|
+
// differs from the parent's.
|
|
160
|
+
prepareForNesting(label, parentGraphId) {
|
|
56
161
|
this.concurrency++;
|
|
162
|
+
if (label !== undefined && parentGraphId !== undefined && this.labelLimits.has(label)) {
|
|
163
|
+
let perParent = this.nestingBypassByLabel.get(label);
|
|
164
|
+
if (!perParent) {
|
|
165
|
+
perParent = new Map();
|
|
166
|
+
this.nestingBypassByLabel.set(label, perParent);
|
|
167
|
+
}
|
|
168
|
+
perParent.set(parentGraphId, (perParent.get(parentGraphId) ?? 0) + 1);
|
|
169
|
+
}
|
|
170
|
+
// Both the global slot bump and the optional label bypass can free capacity
|
|
171
|
+
// for already-queued tasks; drain the queue while progress is being made.
|
|
172
|
+
let progressed = true;
|
|
173
|
+
while (progressed) {
|
|
174
|
+
const before = this.runningNodes.size;
|
|
175
|
+
this.dequeueTaskIfPossible();
|
|
176
|
+
progressed = this.runningNodes.size > before;
|
|
177
|
+
}
|
|
57
178
|
}
|
|
58
|
-
restoreAfterNesting() {
|
|
179
|
+
restoreAfterNesting(label, parentGraphId) {
|
|
59
180
|
this.concurrency--;
|
|
181
|
+
if (label !== undefined && parentGraphId !== undefined) {
|
|
182
|
+
const perParent = this.nestingBypassByLabel.get(label);
|
|
183
|
+
if (perParent) {
|
|
184
|
+
const next = (perParent.get(parentGraphId) ?? 0) - 1;
|
|
185
|
+
if (next <= 0) {
|
|
186
|
+
perParent.delete(parentGraphId);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
perParent.set(parentGraphId, next);
|
|
190
|
+
}
|
|
191
|
+
if (perParent.size === 0) {
|
|
192
|
+
this.nestingBypassByLabel.delete(label);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
60
196
|
}
|
|
61
197
|
getStatus(verbose = false) {
|
|
62
198
|
const runningNodes = Array.from(this.runningNodes).map((node) => node.nodeId);
|
|
@@ -72,6 +208,8 @@ class TaskManager {
|
|
|
72
208
|
reset() {
|
|
73
209
|
this.taskQueue.length = 0;
|
|
74
210
|
this.runningNodes.clear();
|
|
211
|
+
this.runningByLabel.clear();
|
|
212
|
+
this.nestingBypassByLabel.clear();
|
|
75
213
|
}
|
|
76
214
|
}
|
|
77
215
|
exports.TaskManager = TaskManager;
|
package/lib/type.d.ts
CHANGED
|
@@ -65,6 +65,7 @@ export type ComputedNodeData = {
|
|
|
65
65
|
graphLoader?: GraphDataLoaderOption;
|
|
66
66
|
isResult?: boolean;
|
|
67
67
|
priority?: number;
|
|
68
|
+
label?: string;
|
|
68
69
|
passThrough?: PassThrough;
|
|
69
70
|
console?: ConsoleElement;
|
|
70
71
|
};
|
|
@@ -73,10 +74,14 @@ export type LoopData = {
|
|
|
73
74
|
count?: number;
|
|
74
75
|
while?: string | boolean;
|
|
75
76
|
};
|
|
77
|
+
export type ConcurrencyConfig = {
|
|
78
|
+
global: number;
|
|
79
|
+
labels?: Record<string, number>;
|
|
80
|
+
};
|
|
76
81
|
export type GraphData = {
|
|
77
82
|
version?: number;
|
|
78
83
|
nodes: Record<string, NodeData>;
|
|
79
|
-
concurrency?: number;
|
|
84
|
+
concurrency?: number | ConcurrencyConfig;
|
|
80
85
|
loop?: LoopData;
|
|
81
86
|
verbose?: boolean;
|
|
82
87
|
retry?: number;
|
package/lib/utils/utils.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export declare const sleep: (milliseconds: number) => Promise<unknown>;
|
|
|
4
4
|
export declare const parseNodeName: (inputNodeId: any, isSelfNode?: boolean, nodes?: GraphNodes) => DataSource;
|
|
5
5
|
export declare function assert(condition: boolean, message: string, isWarn?: boolean, cause?: unknown): asserts condition;
|
|
6
6
|
export declare const isObject: <Values = unknown>(x: unknown) => x is Record<string, Values>;
|
|
7
|
+
export declare const isPlainObject: <Values = unknown>(x: unknown) => x is Record<string, Values>;
|
|
7
8
|
export declare const isNull: (data: unknown) => data is null | undefined;
|
|
8
9
|
export declare const strIntentionalError = "Intentional Error for Debugging";
|
|
9
10
|
export declare const defaultAgentInfo: {
|
package/lib/utils/utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.loopCounterKey = exports.isStaticNodeData = exports.isComputedNodeData = exports.isNamedInputs = exports.defaultTestContext = exports.isLogicallyTrue = exports.debugResultKey = exports.agentInfoWrapper = exports.defaultAgentInfo = exports.strIntentionalError = exports.isNull = exports.isObject = exports.parseNodeName = exports.sleep = void 0;
|
|
3
|
+
exports.loopCounterKey = exports.isStaticNodeData = exports.isComputedNodeData = exports.isNamedInputs = exports.defaultTestContext = exports.isLogicallyTrue = exports.debugResultKey = exports.agentInfoWrapper = exports.defaultAgentInfo = exports.strIntentionalError = exports.isNull = exports.isPlainObject = exports.isObject = exports.parseNodeName = exports.sleep = void 0;
|
|
4
4
|
exports.assert = assert;
|
|
5
5
|
const type_1 = require("../type");
|
|
6
6
|
const GraphAILogger_1 = require("./GraphAILogger");
|
|
@@ -52,6 +52,17 @@ const isObject = (x) => {
|
|
|
52
52
|
return x !== null && typeof x === "object";
|
|
53
53
|
};
|
|
54
54
|
exports.isObject = isObject;
|
|
55
|
+
// Stricter than isObject: rejects arrays, Map, Date, class instances, etc., that
|
|
56
|
+
// would otherwise pass `typeof === "object"` and confuse `Object.entries()`.
|
|
57
|
+
// Realm-sensitive (compares against the current realm's Object.prototype),
|
|
58
|
+
// which is fine for graph data sourced from JSON.parse or in-process construction.
|
|
59
|
+
const isPlainObject = (x) => {
|
|
60
|
+
if (!(0, exports.isObject)(x) || Array.isArray(x))
|
|
61
|
+
return false;
|
|
62
|
+
const proto = Object.getPrototypeOf(x);
|
|
63
|
+
return proto === null || proto === Object.prototype;
|
|
64
|
+
};
|
|
65
|
+
exports.isPlainObject = isPlainObject;
|
|
55
66
|
const isNull = (data) => {
|
|
56
67
|
return data === null || data === undefined;
|
|
57
68
|
};
|
package/lib/validators/common.js
CHANGED
|
@@ -8,6 +8,9 @@ const computedNodeValidator = (nodeData) => {
|
|
|
8
8
|
throw new common_1.ValidationError("Computed node does not allow " + key);
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
|
+
if (nodeData.label !== undefined && typeof nodeData.label !== "string") {
|
|
12
|
+
throw new common_1.ValidationError("Computed node label must be a string");
|
|
13
|
+
}
|
|
11
14
|
return true;
|
|
12
15
|
};
|
|
13
16
|
exports.computedNodeValidator = computedNodeValidator;
|
|
@@ -2,6 +2,46 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.graphDataValidator = exports.graphNodesValidator = void 0;
|
|
4
4
|
const common_1 = require("./common");
|
|
5
|
+
const utils_1 = require("../utils/utils");
|
|
6
|
+
const concurrencyConfigKeys = ["global", "labels"];
|
|
7
|
+
const validateConcurrencyValue = (value, fieldDescription) => {
|
|
8
|
+
if (typeof value !== "number" || !Number.isInteger(value)) {
|
|
9
|
+
throw new common_1.ValidationError(`${fieldDescription} must be an integer`);
|
|
10
|
+
}
|
|
11
|
+
if (value < 1) {
|
|
12
|
+
throw new common_1.ValidationError(`${fieldDescription} must be a positive integer`);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
const validateConcurrencyConfig = (concurrency) => {
|
|
16
|
+
if (typeof concurrency === "number") {
|
|
17
|
+
validateConcurrencyValue(concurrency, "Concurrency");
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (!(0, utils_1.isPlainObject)(concurrency)) {
|
|
21
|
+
throw new common_1.ValidationError("Concurrency must be an integer");
|
|
22
|
+
}
|
|
23
|
+
for (const key of Object.keys(concurrency)) {
|
|
24
|
+
if (!concurrencyConfigKeys.includes(key)) {
|
|
25
|
+
throw new common_1.ValidationError(`Concurrency object does not allow ${key}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (!("global" in concurrency)) {
|
|
29
|
+
throw new common_1.ValidationError("Concurrency object must have a global field");
|
|
30
|
+
}
|
|
31
|
+
validateConcurrencyValue(concurrency.global, "Concurrency.global");
|
|
32
|
+
// The schema declares labels?: Record<string, number>. undefined is the only
|
|
33
|
+
// sentinel for "absent"; null, arrays, Maps, Dates and other non-plain-object
|
|
34
|
+
// shapes are malformed and would silently disable label enforcement (their
|
|
35
|
+
// Object.entries() yields no string keys).
|
|
36
|
+
if (concurrency.labels !== undefined) {
|
|
37
|
+
if (!(0, utils_1.isPlainObject)(concurrency.labels)) {
|
|
38
|
+
throw new common_1.ValidationError("Concurrency.labels must be an object");
|
|
39
|
+
}
|
|
40
|
+
for (const [labelKey, labelValue] of Object.entries(concurrency.labels)) {
|
|
41
|
+
validateConcurrencyValue(labelValue, `Concurrency.labels.${labelKey}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
5
45
|
const graphNodesValidator = (data) => {
|
|
6
46
|
if (data.nodes === undefined) {
|
|
7
47
|
throw new common_1.ValidationError("Invalid Graph Data: no nodes");
|
|
@@ -32,12 +72,7 @@ const graphDataValidator = (data) => {
|
|
|
32
72
|
}
|
|
33
73
|
}
|
|
34
74
|
if (data.concurrency !== undefined) {
|
|
35
|
-
|
|
36
|
-
throw new common_1.ValidationError("Concurrency must be an integer");
|
|
37
|
-
}
|
|
38
|
-
if (data.concurrency < 1) {
|
|
39
|
-
throw new common_1.ValidationError("Concurrency must be a positive integer");
|
|
40
|
-
}
|
|
75
|
+
validateConcurrencyConfig(data.concurrency);
|
|
41
76
|
}
|
|
42
77
|
};
|
|
43
78
|
exports.graphDataValidator = graphDataValidator;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphai",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.17",
|
|
4
4
|
"description": "Asynchronous data flow execution engine for agentic AI apps.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"files": [
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"doc": "echo nothing",
|
|
15
15
|
"format": "prettier --write '{src,tests}/**/*.ts' *.mjs",
|
|
16
16
|
"test": "node --test --require ts-node/register ./tests/**/test_*.ts",
|
|
17
|
-
"b": "yarn run format && yarn run eslint && yarn run build"
|
|
17
|
+
"b": "yarn run format && yarn run eslint && yarn run build",
|
|
18
|
+
"prepack": "yarn build"
|
|
18
19
|
},
|
|
19
20
|
"repository": {
|
|
20
21
|
"type": "git",
|
|
@@ -27,12 +28,15 @@
|
|
|
27
28
|
},
|
|
28
29
|
"homepage": "https://github.com/receptron/graphai#readme",
|
|
29
30
|
"devDependencies": {
|
|
30
|
-
"typedoc": "^0.28.
|
|
31
|
-
"typedoc-plugin-markdown": "^4.
|
|
31
|
+
"typedoc": "^0.28.18",
|
|
32
|
+
"typedoc-plugin-markdown": "^4.11.0"
|
|
32
33
|
},
|
|
33
34
|
"types": "./lib/index.d.ts",
|
|
34
35
|
"directories": {
|
|
35
36
|
"lib": "lib",
|
|
36
37
|
"test": "tests"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
37
41
|
}
|
|
38
42
|
}
|