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 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
- constructor(options?: {
17
- storage?: GraphSDK.GraphStorage<State, NodeKeys>;
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 ?? ((state) => {});
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 direction = options?.direction ?? "TB";
93
- return this.generateMermaid(direction, "");
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
- context = await this.createExecutionContext(runId, initialState, writer);
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 restored = await this.restoreCheckpoint(runId, initialState);
178
- return { runId, writer, ...restored };
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.2",
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"