diff --git a/packages/breadboard/src/board.ts b/packages/breadboard/src/board.ts new file mode 100644 index 00000000000..ca7056f77ec --- /dev/null +++ b/packages/breadboard/src/board.ts @@ -0,0 +1,40 @@ +import type { GraphDescriptor, Edge, NodeDescriptor } from "./types.js"; + +export class Board { + private edges: Edge[] = []; + private nodes: NodeDescriptor[] = []; + + serialize(): GraphDescriptor { + return { + edges: this.edges.map((edge) => { + const serialized: Edge = { + from: edge.from, + to: edge.to, + }; + if (edge.out !== undefined) serialized.out = edge.out; + if (edge.in !== undefined) serialized.in = edge.in; + if (edge.required !== undefined) serialized.required = edge.required; + return serialized; + }), + nodes: this.nodes, + }; + } + + static deserialize(descriptor: GraphDescriptor): Board { + const board = new Board(); + board.nodes = descriptor.nodes || []; + + for (const edge of descriptor.edges || []) { + const newEdge: Edge = { + from: edge.from, + to: edge.to, + }; + if (edge.out !== undefined) newEdge.out = edge.out; + if (edge.in !== undefined) newEdge.in = edge.in; + if (edge.required !== undefined) newEdge.required = edge.required; + board.edges.push(newEdge); + } + + return board; + } +} \ No newline at end of file diff --git a/packages/breadboard/src/node.ts b/packages/breadboard/src/node.ts new file mode 100644 index 00000000000..21ed9098c3f --- /dev/null +++ b/packages/breadboard/src/node.ts @@ -0,0 +1,35 @@ +export interface WireOptions { + required?: boolean; +} + +export class Wire { + source: Node; + target: Node; + required: boolean; + + constructor(source: Node, target: Node, required: boolean = false) { + this.source = source; + this.target = target; + this.required = required; + } +} + +export class Node { + id: string; + outgoing: Wire[] = []; + + constructor(id: string) { + this.id = id; + this.outgoing = []; + } + + to(target: Node, options?: WireOptions): Wire { + const wire = new Wire(this, target, options?.required); + this.outgoing.push(wire); + return wire; + } + + requiredTo(target: Node): Wire { + return this.to(target, { required: true }); + } +} \ No newline at end of file diff --git a/packages/breadboard/src/runtime/traversal.ts b/packages/breadboard/src/runtime/traversal.ts new file mode 100644 index 00000000000..8f78b269154 --- /dev/null +++ b/packages/breadboard/src/runtime/traversal.ts @@ -0,0 +1,74 @@ +/** + * @license + * Copyright 2024 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { Edge, NodeIdentifier, InputValues } from "../types.js"; + +interface TraversalEdge { + from: NodeIdentifier; + to: NodeIdentifier; + out: string; + in: string; + required?: boolean; +} + +interface NodeState { + inputs: InputValues; + receivedEdges: Set; +} + +export class Traversal { + private incomingEdges: Map = new Map(); + private nodeStates: Map = new Map(); + + constructor(edges: Edge[]) { + for (const edge of edges) { + const edgesForNode = this.incomingEdges.get(edge.to) || []; + edgesForNode.push(edge as TraversalEdge); + this.incomingEdges.set(edge.to, edgesForNode); + } + } + + /** + * Check if a node is ready to execute. + * A node is ready only when all required incoming edges have provided data. + */ + isNodeReady(nodeId: NodeIdentifier): boolean { + const edges = this.incomingEdges.get(nodeId) || []; + const state = this.nodeStates.get(nodeId); + + for (const edge of edges) { + if (edge.required) { + const edgeKey = `${edge.from}:${edge.out}`; + if (!state?.receivedEdges.has(edgeKey)) { + return false; + } + } + } + + return true; + } + + /** + * Receive data from an edge. + */ + receiveEdgeData(nodeId: NodeIdentifier, from: NodeIdentifier, out: string, data: unknown): void { + let state = this.nodeStates.get(nodeId); + if (!state) { + state = { inputs: {}, receivedEdges: new Set() }; + this.nodeStates.set(nodeId, state); + } + + state.receivedEdges.add(`${from}:${out}`); + + if (data !== undefined && typeof data === "object") { + Object.assign(state.inputs, data); + } + } + + getNodeInputs(nodeId: NodeIdentifier): InputValues { + return this.nodeStates.get(nodeId)?.inputs || {}; + } +} \ No newline at end of file diff --git a/packages/breadboard/src/types.ts b/packages/breadboard/src/types.ts new file mode 100644 index 00000000000..39d7528f80a --- /dev/null +++ b/packages/breadboard/src/types.ts @@ -0,0 +1,26 @@ +/** + * @license + * Copyright 2024 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +export type NodeIdentifier = string; + +/** + * Represents an edge (wire) connecting two nodes in the graph. + */ +export interface Edge { + from: NodeIdentifier; + to: NodeIdentifier; + out?: string; + in?: string; + required?: boolean; +} + +/** + * Proxy interface for building node connections in a fluent API style. + */ +export interface NodeProxy { + to(target: NodeIdentifier): void; + requiredTo(target: NodeIdentifier): void; +} \ No newline at end of file