ai-sdk-graph 0.1.1 → 0.1.3

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
@@ -12,7 +12,9 @@ export declare class Graph<State extends Record<string, unknown>, NodeKeys exten
12
12
  private readonly storage;
13
13
  private readonly emitter;
14
14
  private readonly stateManager;
15
- constructor(storage?: GraphSDK.GraphStorage<State, NodeKeys>);
15
+ private readonly onFinish;
16
+ private readonly onStart;
17
+ constructor(options?: GraphSDK.GraphOptions<State, NodeKeys>);
16
18
  node<NewKey extends string>(id: NewKey, execute: ({ state, writer, suspense, update }: {
17
19
  state: () => Readonly<State>;
18
20
  writer: Writer;
@@ -30,8 +32,6 @@ export declare class Graph<State extends Record<string, unknown>, NodeKeys exten
30
32
  toMermaid(options?: {
31
33
  direction?: 'TB' | 'LR';
32
34
  }): string;
33
- private generateMermaid;
34
- private extractPossibleTargets;
35
35
  execute(runId: string, initialState: State | ((state: State | undefined) => State)): ReadableStream<import("ai").InferUIMessageChunk<import("ai").UIMessage<unknown, import("ai").UIDataTypes, import("ai").UITools>>>;
36
36
  executeInternal(runId: string, initialState: State, writer: Writer): Promise<State>;
37
37
  private registerBuiltInNodes;
@@ -65,5 +65,5 @@ export declare class Graph<State extends Record<string, unknown>, NodeKeys exten
65
65
  private deduplicateNodes;
66
66
  private excludeTerminalNodes;
67
67
  }
68
- export declare function graph<State extends Record<string, unknown>, NodeKeys extends string = 'START' | 'END'>(storage?: GraphSDK.GraphStorage<State, NodeKeys>): 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>;
69
69
  export {};
package/dist/index.js CHANGED
@@ -54,9 +54,13 @@ class Graph {
54
54
  storage;
55
55
  emitter = new NodeEventEmitter;
56
56
  stateManager = new StateManager;
57
- constructor(storage = new InMemoryStorage) {
58
- this.storage = storage;
57
+ onFinish;
58
+ onStart;
59
+ constructor(options = {}) {
60
+ this.storage = options.storage ?? new InMemoryStorage;
59
61
  this.registerBuiltInNodes();
62
+ this.onFinish = options.onFinish ?? (() => {});
63
+ this.onStart = options.onStart ?? (() => {});
60
64
  }
61
65
  node(id, execute) {
62
66
  const node = { id, execute };
@@ -87,67 +91,21 @@ class Graph {
87
91
  return this.subgraphRegistry;
88
92
  }
89
93
  toMermaid(options) {
90
- const direction = options?.direction ?? "TB";
91
- return this.generateMermaid(direction, "");
92
- }
93
- generateMermaid(direction, prefix) {
94
- const lines = [];
95
- const indent = prefix ? " " : " ";
96
- if (!prefix) {
97
- lines.push(`flowchart ${direction}`);
98
- }
99
- for (const [nodeId] of this.nodeRegistry) {
100
- const prefixedId = prefix ? `${prefix}_${nodeId}` : nodeId;
101
- const subgraphEntry = this.subgraphRegistry.get(nodeId);
102
- if (subgraphEntry) {
103
- lines.push(`${indent}subgraph ${prefixedId}[${nodeId}]`);
104
- lines.push(`${indent} direction ${direction}`);
105
- const subgraphContent = subgraphEntry.subgraph.generateMermaid(direction, prefixedId);
106
- const subgraphLines = subgraphContent.split(`
107
- `);
108
- lines.push(...subgraphLines.map((line) => `${indent}${line}`));
109
- lines.push(`${indent}end`);
110
- } else if (nodeId === "START" || nodeId === "END") {
111
- lines.push(`${indent}${prefixedId}([${nodeId}])`);
112
- } else {
113
- lines.push(`${indent}${prefixedId}[${nodeId}]`);
114
- }
115
- }
116
- for (const [fromId, edges] of this.edgeRegistry) {
117
- for (const edge of edges) {
118
- const prefixedFrom = prefix ? `${prefix}_${fromId}` : fromId;
119
- if (typeof edge.to === "function") {
120
- const possibleTargets = this.extractPossibleTargets(edge.to);
121
- for (const targetId of possibleTargets) {
122
- const prefixedTo = prefix ? `${prefix}_${targetId}` : targetId;
123
- lines.push(`${indent}${prefixedFrom} -.-> ${prefixedTo}`);
124
- }
125
- } else {
126
- const prefixedTo = prefix ? `${prefix}_${edge.to}` : edge.to;
127
- lines.push(`${indent}${prefixedFrom} --> ${prefixedTo}`);
128
- }
129
- }
130
- }
131
- return lines.join(`
132
- `);
133
- }
134
- extractPossibleTargets(edgeFn) {
135
- const fnString = edgeFn.toString();
136
- const nodeIds = Array.from(this.nodeRegistry.keys());
137
- return nodeIds.filter((nodeId) => {
138
- const patterns = [
139
- `'${nodeId}'`,
140
- `"${nodeId}"`,
141
- `\`${nodeId}\``
142
- ];
143
- return patterns.some((pattern) => fnString.includes(pattern));
144
- });
94
+ const generator = new MermaidGenerator(this.nodeRegistry, this.edgeRegistry, this.subgraphRegistry);
95
+ return generator.generate(options);
145
96
  }
146
97
  execute(runId, initialState) {
98
+ let context;
147
99
  return createUIMessageStream({
148
100
  execute: async ({ writer }) => {
149
- const context = await this.createExecutionContext(runId, initialState, writer);
101
+ context = await this.createExecutionContext(runId, initialState, writer);
102
+ await this.onStart({ state: context.state, writer });
150
103
  await this.runExecutionLoop(context);
104
+ },
105
+ onFinish: async () => {
106
+ if (context) {
107
+ await this.onFinish({ state: context.state });
108
+ }
151
109
  }
152
110
  });
153
111
  }
@@ -337,8 +295,8 @@ class Graph {
337
295
  return nodes.filter((node) => node.id !== BUILT_IN_NODES.END);
338
296
  }
339
297
  }
340
- function graph(storage) {
341
- return new Graph(storage);
298
+ function graph(options = {}) {
299
+ return new Graph(options);
342
300
  }
343
301
 
344
302
  class NodeEventEmitter {
@@ -370,6 +328,75 @@ class StateManager {
370
328
  return typeof initialState === "function";
371
329
  }
372
330
  }
331
+
332
+ class MermaidGenerator {
333
+ nodeRegistry;
334
+ edgeRegistry;
335
+ subgraphRegistry;
336
+ constructor(nodeRegistry, edgeRegistry, subgraphRegistry) {
337
+ this.nodeRegistry = nodeRegistry;
338
+ this.edgeRegistry = edgeRegistry;
339
+ this.subgraphRegistry = subgraphRegistry;
340
+ }
341
+ generate(options) {
342
+ const direction = options?.direction ?? "TB";
343
+ return this.generateMermaid(direction, "");
344
+ }
345
+ generateMermaid(direction, prefix) {
346
+ const lines = [];
347
+ const indent = prefix ? " " : " ";
348
+ if (!prefix) {
349
+ lines.push(`flowchart ${direction}`);
350
+ }
351
+ for (const [nodeId] of this.nodeRegistry) {
352
+ const prefixedId = prefix ? `${prefix}_${nodeId}` : nodeId;
353
+ const subgraphEntry = this.subgraphRegistry.get(nodeId);
354
+ if (subgraphEntry) {
355
+ lines.push(`${indent}subgraph ${prefixedId}[${nodeId}]`);
356
+ lines.push(`${indent} direction ${direction}`);
357
+ const subgraphGenerator = new MermaidGenerator(subgraphEntry.subgraph.nodes, subgraphEntry.subgraph.edges, subgraphEntry.subgraph.subgraphs);
358
+ const subgraphContent = subgraphGenerator.generateMermaid(direction, prefixedId);
359
+ const subgraphLines = subgraphContent.split(`
360
+ `);
361
+ lines.push(...subgraphLines.map((line) => `${indent}${line}`));
362
+ lines.push(`${indent}end`);
363
+ } else if (nodeId === "START" || nodeId === "END") {
364
+ lines.push(`${indent}${prefixedId}([${nodeId}])`);
365
+ } else {
366
+ lines.push(`${indent}${prefixedId}[${nodeId}]`);
367
+ }
368
+ }
369
+ for (const [fromId, edges] of this.edgeRegistry) {
370
+ for (const edge of edges) {
371
+ const prefixedFrom = prefix ? `${prefix}_${fromId}` : fromId;
372
+ if (typeof edge.to === "function") {
373
+ const possibleTargets = this.extractPossibleTargets(edge.to);
374
+ for (const targetId of possibleTargets) {
375
+ const prefixedTo = prefix ? `${prefix}_${targetId}` : targetId;
376
+ lines.push(`${indent}${prefixedFrom} -.-> ${prefixedTo}`);
377
+ }
378
+ } else {
379
+ const prefixedTo = prefix ? `${prefix}_${edge.to}` : edge.to;
380
+ lines.push(`${indent}${prefixedFrom} --> ${prefixedTo}`);
381
+ }
382
+ }
383
+ }
384
+ return lines.join(`
385
+ `);
386
+ }
387
+ extractPossibleTargets(edgeFn) {
388
+ const fnString = edgeFn.toString();
389
+ const nodeIds = Array.from(this.nodeRegistry.keys());
390
+ return nodeIds.filter((nodeId) => {
391
+ const patterns = [
392
+ `'${nodeId}'`,
393
+ `"${nodeId}"`,
394
+ `\`${nodeId}\``
395
+ ];
396
+ return patterns.some((pattern) => fnString.includes(pattern));
397
+ });
398
+ }
399
+ }
373
400
  export {
374
401
  graph,
375
402
  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.1",
3
+ "version": "0.1.3",
4
4
  "description": "Graph-based workflows for the AI SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -18,10 +18,15 @@
18
18
  ],
19
19
  "scripts": {
20
20
  "test": "bun test",
21
- "build": "bun build src/index.ts --outdir dist --target node --packages=external && tsc -p tsconfig.build.json"
21
+ "build": "bun build src/index.ts --outdir dist --target node --packages=external && tsc -p tsconfig.build.json",
22
+ "changeset": "changeset",
23
+ "version": "changeset version",
24
+ "release": "bun run build && changeset publish"
22
25
  },
23
26
  "devDependencies": {
24
- "@types/bun": "latest"
27
+ "@changesets/cli": "^2.29.8",
28
+ "@types/bun": "latest",
29
+ "prettier": "^3.8.0"
25
30
  },
26
31
  "peerDependencies": {
27
32
  "typescript": "^5"