ai-sdk-graph 0.1.2 → 0.1.4
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/LICENSE +21 -0
- package/dist/graph.d.ts +3 -10
- package/dist/index.js +87 -67
- package/dist/types.d.ts +10 -0
- package/package.json +3 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Can Temizyurek
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/graph.d.ts
CHANGED
|
@@ -13,10 +13,8 @@ export declare class Graph<State extends Record<string, unknown>, NodeKeys exten
|
|
|
13
13
|
private readonly emitter;
|
|
14
14
|
private readonly stateManager;
|
|
15
15
|
private readonly onFinish;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
onFinish?: (state: State) => Promise<void> | void;
|
|
19
|
-
});
|
|
16
|
+
private readonly onStart;
|
|
17
|
+
constructor(options?: GraphSDK.GraphOptions<State, NodeKeys>);
|
|
20
18
|
node<NewKey extends string>(id: NewKey, execute: ({ state, writer, suspense, update }: {
|
|
21
19
|
state: () => Readonly<State>;
|
|
22
20
|
writer: Writer;
|
|
@@ -34,8 +32,6 @@ export declare class Graph<State extends Record<string, unknown>, NodeKeys exten
|
|
|
34
32
|
toMermaid(options?: {
|
|
35
33
|
direction?: 'TB' | 'LR';
|
|
36
34
|
}): string;
|
|
37
|
-
private generateMermaid;
|
|
38
|
-
private extractPossibleTargets;
|
|
39
35
|
execute(runId: string, initialState: State | ((state: State | undefined) => State)): ReadableStream<import("ai").InferUIMessageChunk<import("ai").UIMessage<unknown, import("ai").UIDataTypes, import("ai").UITools>>>;
|
|
40
36
|
executeInternal(runId: string, initialState: State, writer: Writer): Promise<State>;
|
|
41
37
|
private registerBuiltInNodes;
|
|
@@ -69,8 +65,5 @@ export declare class Graph<State extends Record<string, unknown>, NodeKeys exten
|
|
|
69
65
|
private deduplicateNodes;
|
|
70
66
|
private excludeTerminalNodes;
|
|
71
67
|
}
|
|
72
|
-
export declare function graph<State extends Record<string, unknown>, NodeKeys extends string = 'START' | 'END'>(options?:
|
|
73
|
-
storage?: GraphSDK.GraphStorage<State, NodeKeys>;
|
|
74
|
-
onFinish?: (state: State) => Promise<void> | void;
|
|
75
|
-
}): Graph<State, NodeKeys>;
|
|
68
|
+
export declare function graph<State extends Record<string, unknown>, NodeKeys extends string = 'START' | 'END'>(options?: GraphSDK.GraphOptions<State, NodeKeys>): Graph<State, NodeKeys>;
|
|
76
69
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -55,10 +55,12 @@ class Graph {
|
|
|
55
55
|
emitter = new NodeEventEmitter;
|
|
56
56
|
stateManager = new StateManager;
|
|
57
57
|
onFinish;
|
|
58
|
+
onStart;
|
|
58
59
|
constructor(options = {}) {
|
|
59
60
|
this.storage = options.storage ?? new InMemoryStorage;
|
|
60
61
|
this.registerBuiltInNodes();
|
|
61
|
-
this.onFinish = options.onFinish ?? ((
|
|
62
|
+
this.onFinish = options.onFinish ?? (() => {});
|
|
63
|
+
this.onStart = options.onStart ?? (() => {});
|
|
62
64
|
}
|
|
63
65
|
node(id, execute) {
|
|
64
66
|
const node = { id, execute };
|
|
@@ -89,78 +91,30 @@ class Graph {
|
|
|
89
91
|
return this.subgraphRegistry;
|
|
90
92
|
}
|
|
91
93
|
toMermaid(options) {
|
|
92
|
-
const
|
|
93
|
-
return
|
|
94
|
-
}
|
|
95
|
-
generateMermaid(direction, prefix) {
|
|
96
|
-
const lines = [];
|
|
97
|
-
const indent = prefix ? " " : " ";
|
|
98
|
-
if (!prefix) {
|
|
99
|
-
lines.push(`flowchart ${direction}`);
|
|
100
|
-
}
|
|
101
|
-
for (const [nodeId] of this.nodeRegistry) {
|
|
102
|
-
const prefixedId = prefix ? `${prefix}_${nodeId}` : nodeId;
|
|
103
|
-
const subgraphEntry = this.subgraphRegistry.get(nodeId);
|
|
104
|
-
if (subgraphEntry) {
|
|
105
|
-
lines.push(`${indent}subgraph ${prefixedId}[${nodeId}]`);
|
|
106
|
-
lines.push(`${indent} direction ${direction}`);
|
|
107
|
-
const subgraphContent = subgraphEntry.subgraph.generateMermaid(direction, prefixedId);
|
|
108
|
-
const subgraphLines = subgraphContent.split(`
|
|
109
|
-
`);
|
|
110
|
-
lines.push(...subgraphLines.map((line) => `${indent}${line}`));
|
|
111
|
-
lines.push(`${indent}end`);
|
|
112
|
-
} else if (nodeId === "START" || nodeId === "END") {
|
|
113
|
-
lines.push(`${indent}${prefixedId}([${nodeId}])`);
|
|
114
|
-
} else {
|
|
115
|
-
lines.push(`${indent}${prefixedId}[${nodeId}]`);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
for (const [fromId, edges] of this.edgeRegistry) {
|
|
119
|
-
for (const edge of edges) {
|
|
120
|
-
const prefixedFrom = prefix ? `${prefix}_${fromId}` : fromId;
|
|
121
|
-
if (typeof edge.to === "function") {
|
|
122
|
-
const possibleTargets = this.extractPossibleTargets(edge.to);
|
|
123
|
-
for (const targetId of possibleTargets) {
|
|
124
|
-
const prefixedTo = prefix ? `${prefix}_${targetId}` : targetId;
|
|
125
|
-
lines.push(`${indent}${prefixedFrom} -.-> ${prefixedTo}`);
|
|
126
|
-
}
|
|
127
|
-
} else {
|
|
128
|
-
const prefixedTo = prefix ? `${prefix}_${edge.to}` : edge.to;
|
|
129
|
-
lines.push(`${indent}${prefixedFrom} --> ${prefixedTo}`);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return lines.join(`
|
|
134
|
-
`);
|
|
135
|
-
}
|
|
136
|
-
extractPossibleTargets(edgeFn) {
|
|
137
|
-
const fnString = edgeFn.toString();
|
|
138
|
-
const nodeIds = Array.from(this.nodeRegistry.keys());
|
|
139
|
-
return nodeIds.filter((nodeId) => {
|
|
140
|
-
const patterns = [
|
|
141
|
-
`'${nodeId}'`,
|
|
142
|
-
`"${nodeId}"`,
|
|
143
|
-
`\`${nodeId}\``
|
|
144
|
-
];
|
|
145
|
-
return patterns.some((pattern) => fnString.includes(pattern));
|
|
146
|
-
});
|
|
94
|
+
const generator = new MermaidGenerator(this.nodeRegistry, this.edgeRegistry, this.subgraphRegistry);
|
|
95
|
+
return generator.generate(options);
|
|
147
96
|
}
|
|
148
97
|
execute(runId, initialState) {
|
|
149
98
|
let context;
|
|
150
99
|
return createUIMessageStream({
|
|
151
100
|
execute: async ({ writer }) => {
|
|
152
|
-
|
|
101
|
+
const result = await this.createExecutionContext(runId, initialState, writer);
|
|
102
|
+
context = result.context;
|
|
103
|
+
const firstTime = result.firstTime;
|
|
104
|
+
if (firstTime) {
|
|
105
|
+
await this.onStart({ state: context.state, writer });
|
|
106
|
+
}
|
|
153
107
|
await this.runExecutionLoop(context);
|
|
154
108
|
},
|
|
155
109
|
onFinish: async () => {
|
|
156
110
|
if (context) {
|
|
157
|
-
await this.onFinish(context.state);
|
|
111
|
+
await this.onFinish({ state: context.state });
|
|
158
112
|
}
|
|
159
113
|
}
|
|
160
114
|
});
|
|
161
115
|
}
|
|
162
116
|
async executeInternal(runId, initialState, writer) {
|
|
163
|
-
const context = await this.createExecutionContext(runId, initialState, writer);
|
|
117
|
+
const { context } = await this.createExecutionContext(runId, initialState, writer);
|
|
164
118
|
await this.runExecutionLoopInternal(context);
|
|
165
119
|
return context.state;
|
|
166
120
|
}
|
|
@@ -174,8 +128,8 @@ class Graph {
|
|
|
174
128
|
this.edgeRegistry.set(edge.from, existingEdges);
|
|
175
129
|
}
|
|
176
130
|
async createExecutionContext(runId, initialState, writer) {
|
|
177
|
-
const
|
|
178
|
-
return { runId, writer,
|
|
131
|
+
const { context, firstTime } = await this.restoreCheckpoint(runId, initialState);
|
|
132
|
+
return { context: { ...context, runId, writer }, firstTime };
|
|
179
133
|
}
|
|
180
134
|
async runExecutionLoop(context) {
|
|
181
135
|
await this.executeWithStrategy(context, "return");
|
|
@@ -227,9 +181,9 @@ class Graph {
|
|
|
227
181
|
async restoreCheckpoint(runId, initialState) {
|
|
228
182
|
const checkpoint = await this.storage.load(runId);
|
|
229
183
|
if (this.isValidCheckpoint(checkpoint)) {
|
|
230
|
-
return this.restoreFromCheckpoint(checkpoint, initialState);
|
|
184
|
+
return { context: this.restoreFromCheckpoint(checkpoint, initialState), firstTime: false };
|
|
231
185
|
}
|
|
232
|
-
return this.createFreshExecution(initialState);
|
|
186
|
+
return { context: this.createFreshExecution(initialState), firstTime: true };
|
|
233
187
|
}
|
|
234
188
|
isValidCheckpoint(checkpoint) {
|
|
235
189
|
return this.hasNodeIds(checkpoint) && this.hasAtLeastOneNode(checkpoint);
|
|
@@ -346,10 +300,7 @@ class Graph {
|
|
|
346
300
|
}
|
|
347
301
|
}
|
|
348
302
|
function graph(options = {}) {
|
|
349
|
-
return new Graph(
|
|
350
|
-
storage: options.storage,
|
|
351
|
-
onFinish: options.onFinish
|
|
352
|
-
});
|
|
303
|
+
return new Graph(options);
|
|
353
304
|
}
|
|
354
305
|
|
|
355
306
|
class NodeEventEmitter {
|
|
@@ -381,6 +332,75 @@ class StateManager {
|
|
|
381
332
|
return typeof initialState === "function";
|
|
382
333
|
}
|
|
383
334
|
}
|
|
335
|
+
|
|
336
|
+
class MermaidGenerator {
|
|
337
|
+
nodeRegistry;
|
|
338
|
+
edgeRegistry;
|
|
339
|
+
subgraphRegistry;
|
|
340
|
+
constructor(nodeRegistry, edgeRegistry, subgraphRegistry) {
|
|
341
|
+
this.nodeRegistry = nodeRegistry;
|
|
342
|
+
this.edgeRegistry = edgeRegistry;
|
|
343
|
+
this.subgraphRegistry = subgraphRegistry;
|
|
344
|
+
}
|
|
345
|
+
generate(options) {
|
|
346
|
+
const direction = options?.direction ?? "TB";
|
|
347
|
+
return this.generateMermaid(direction, "");
|
|
348
|
+
}
|
|
349
|
+
generateMermaid(direction, prefix) {
|
|
350
|
+
const lines = [];
|
|
351
|
+
const indent = prefix ? " " : " ";
|
|
352
|
+
if (!prefix) {
|
|
353
|
+
lines.push(`flowchart ${direction}`);
|
|
354
|
+
}
|
|
355
|
+
for (const [nodeId] of this.nodeRegistry) {
|
|
356
|
+
const prefixedId = prefix ? `${prefix}_${nodeId}` : nodeId;
|
|
357
|
+
const subgraphEntry = this.subgraphRegistry.get(nodeId);
|
|
358
|
+
if (subgraphEntry) {
|
|
359
|
+
lines.push(`${indent}subgraph ${prefixedId}[${nodeId}]`);
|
|
360
|
+
lines.push(`${indent} direction ${direction}`);
|
|
361
|
+
const subgraphGenerator = new MermaidGenerator(subgraphEntry.subgraph.nodes, subgraphEntry.subgraph.edges, subgraphEntry.subgraph.subgraphs);
|
|
362
|
+
const subgraphContent = subgraphGenerator.generateMermaid(direction, prefixedId);
|
|
363
|
+
const subgraphLines = subgraphContent.split(`
|
|
364
|
+
`);
|
|
365
|
+
lines.push(...subgraphLines.map((line) => `${indent}${line}`));
|
|
366
|
+
lines.push(`${indent}end`);
|
|
367
|
+
} else if (nodeId === "START" || nodeId === "END") {
|
|
368
|
+
lines.push(`${indent}${prefixedId}([${nodeId}])`);
|
|
369
|
+
} else {
|
|
370
|
+
lines.push(`${indent}${prefixedId}[${nodeId}]`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
for (const [fromId, edges] of this.edgeRegistry) {
|
|
374
|
+
for (const edge of edges) {
|
|
375
|
+
const prefixedFrom = prefix ? `${prefix}_${fromId}` : fromId;
|
|
376
|
+
if (typeof edge.to === "function") {
|
|
377
|
+
const possibleTargets = this.extractPossibleTargets(edge.to);
|
|
378
|
+
for (const targetId of possibleTargets) {
|
|
379
|
+
const prefixedTo = prefix ? `${prefix}_${targetId}` : targetId;
|
|
380
|
+
lines.push(`${indent}${prefixedFrom} -.-> ${prefixedTo}`);
|
|
381
|
+
}
|
|
382
|
+
} else {
|
|
383
|
+
const prefixedTo = prefix ? `${prefix}_${edge.to}` : edge.to;
|
|
384
|
+
lines.push(`${indent}${prefixedFrom} --> ${prefixedTo}`);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return lines.join(`
|
|
389
|
+
`);
|
|
390
|
+
}
|
|
391
|
+
extractPossibleTargets(edgeFn) {
|
|
392
|
+
const fnString = edgeFn.toString();
|
|
393
|
+
const nodeIds = Array.from(this.nodeRegistry.keys());
|
|
394
|
+
return nodeIds.filter((nodeId) => {
|
|
395
|
+
const patterns = [
|
|
396
|
+
`'${nodeId}'`,
|
|
397
|
+
`"${nodeId}"`,
|
|
398
|
+
`\`${nodeId}\``
|
|
399
|
+
];
|
|
400
|
+
return patterns.some((pattern) => fnString.includes(pattern));
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
|
384
404
|
export {
|
|
385
405
|
graph,
|
|
386
406
|
SuspenseError,
|
package/dist/types.d.ts
CHANGED
|
@@ -39,4 +39,14 @@ export declare namespace GraphSDK {
|
|
|
39
39
|
suspendedNodes: Node<State, NodeKeys>[];
|
|
40
40
|
writer: UIMessageStreamWriter;
|
|
41
41
|
}
|
|
42
|
+
interface GraphOptions<State extends Record<string, unknown>, NodeKeys extends string> {
|
|
43
|
+
storage?: GraphStorage<State, NodeKeys>;
|
|
44
|
+
onFinish?: (args: {
|
|
45
|
+
state: State;
|
|
46
|
+
}) => Promise<void> | void;
|
|
47
|
+
onStart?: (args: {
|
|
48
|
+
state: State;
|
|
49
|
+
writer: UIMessageStreamWriter;
|
|
50
|
+
}) => Promise<void> | void;
|
|
51
|
+
}
|
|
42
52
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-sdk-graph",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Graph-based workflows for the AI SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@changesets/cli": "^2.29.8",
|
|
28
|
-
"@types/bun": "latest"
|
|
28
|
+
"@types/bun": "latest",
|
|
29
|
+
"prettier": "^3.8.0"
|
|
29
30
|
},
|
|
30
31
|
"peerDependencies": {
|
|
31
32
|
"typescript": "^5"
|