diff --git a/package-lock.json b/package-lock.json index 730d9736..5c1bb863 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9469,6 +9469,9 @@ "name": "@google-labs/breadboard-core-kit", "version": "0.0.1", "license": "Apache-2.0", + "dependencies": { + "@google-labs/breadboard": "*" + }, "devDependencies": { "@ava/typescript": "^4.0.0", "@google-labs/tsconfig": "*", diff --git a/seeds/core-kit/package.json b/seeds/core-kit/package.json index 10b612f8..322b1a93 100644 --- a/seeds/core-kit/package.json +++ b/seeds/core-kit/package.json @@ -1,5 +1,5 @@ { - "name": "@google-labs/breadboard-core-kit", + "name": "@google-labs/core-kit", "private": true, "version": "0.0.1", "description": "A Breadboard Kit containing nodes that enable composition and reuse of boards.", @@ -46,5 +46,8 @@ "ava": "^5.2.0", "typescript": "^5.0.4", "@google-labs/tsconfig": "*" + }, + "dependencies": { + "@google-labs/breadboard": "*" } } diff --git a/seeds/core-kit/src/nodes/import.ts b/seeds/core-kit/src/nodes/import.ts new file mode 100644 index 00000000..09ffde35 --- /dev/null +++ b/seeds/core-kit/src/nodes/import.ts @@ -0,0 +1,72 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Board } from "@google-labs/breadboard"; +import { SchemaBuilder } from "@google-labs/breadboard/kits"; +import type { + InputValues, + BreadboardCapability, + NodeHandlerContext, + GraphDescriptor, +} from "@google-labs/breadboard"; +import { LambdaNodeOutputs } from "./lambda.js"; + +export type ImportNodeInputs = InputValues & { + path?: string; + graph?: GraphDescriptor; + args: InputValues; +}; + +export default { + describe: async (inputs?: InputValues) => { + return { + inputSchema: new SchemaBuilder() + .addInputs(inputs) + .addProperties({ + path: { + title: "path", + description: "The path to the board to import.", + type: "string", + }, + graph: { + title: "graph", + description: "The graph descriptor of the board to import.", + type: "object", + }, + }) + .setAdditionalProperties(true) + .build(), + outputSchema: new SchemaBuilder().addProperties({ + board: { + title: "board", + description: "The imported board.", + type: "object", + }, + }), + }; + }, + invoke: async ( + inputs: InputValues, + context: NodeHandlerContext + ): Promise => { + const { path, graph, ...args } = inputs as ImportNodeInputs; + + const board = graph + ? (graph as Board).runOnce // TODO: Hack! Use JSON schema or so instead. + ? ({ ...graph } as Board) + : await Board.fromGraphDescriptor(graph) + : path + ? await Board.load(path, { + base: context.board.url, + outerGraph: context.parent, + }) + : undefined; + if (!board) throw Error("No board provided"); + board.args = args; + + return { board: { kind: "board", board } as BreadboardCapability }; + }, +}; diff --git a/seeds/core-kit/src/nodes/include.ts b/seeds/core-kit/src/nodes/include.ts new file mode 100644 index 00000000..6e3f7400 --- /dev/null +++ b/seeds/core-kit/src/nodes/include.ts @@ -0,0 +1,88 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { + InputValues, + OutputValues, + BreadboardSlotSpec, + NodeHandlerContext, + BreadboardCapability, + GraphDescriptor, +} from "@google-labs/breadboard"; +import { Board } from "@google-labs/breadboard"; +import { SchemaBuilder } from "@google-labs/breadboard/kits"; + +export type IncludeNodeInputs = InputValues & { + path?: string; + $ref?: string; + board?: BreadboardCapability; + graph?: GraphDescriptor; + slotted?: BreadboardSlotSpec; + args: InputValues; +}; + +export default { + describe: async (inputs?: InputValues) => ({ + inputSchema: new SchemaBuilder() + .setAdditionalProperties(true) + .addInputs(inputs) + .addProperties({ + path: { + title: "path", + description: "The path to the board to include.", + type: "string", + }, + $ref: { + title: "$ref", + description: "The $ref to the board to include.", + type: "string", + }, + graph: { + title: "graph", + description: "The graph descriptor of the board to include.", + type: "object", + }, + slotted: { + title: "slotted", + description: "The slotted graphs to include.", + type: "object", + }, + }) + .build(), + outputSchema: new SchemaBuilder().setAdditionalProperties(true).build(), + }), + invoke: async ( + inputs: InputValues, + context: NodeHandlerContext + ): Promise => { + const { path, $ref, board, graph, slotted, ...args } = + inputs as IncludeNodeInputs; + + // Add the current graph's URL as the url of the slotted graph, + // if there isn't an URL already. + const slottedWithUrls: BreadboardSlotSpec = {}; + if (slotted) { + for (const key in slotted) { + slottedWithUrls[key] = { url: context.board.url, ...slotted[key] }; + } + } + + // TODO: Please fix the $ref/path mess. + const source = path || $ref || ""; + + const runnableBoard = board + ? await Board.fromBreadboardCapability(board) + : graph + ? await Board.fromGraphDescriptor(graph) + : await Board.load(source, { + slotted: slottedWithUrls, + base: context.board.url, + outerGraph: context.parent, + }); + + return await runnableBoard.runOnce(args, context); + }, +}; diff --git a/seeds/core-kit/src/nodes/invoke.ts b/seeds/core-kit/src/nodes/invoke.ts new file mode 100644 index 00000000..b0825757 --- /dev/null +++ b/seeds/core-kit/src/nodes/invoke.ts @@ -0,0 +1,69 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { + InputValues, + OutputValues, + NodeHandlerContext, + BreadboardCapability, + GraphDescriptor, +} from "@google-labs/breadboard"; +import { Board } from "@google-labs/breadboard"; +import { SchemaBuilder } from "@google-labs/breadboard/kits"; + +export type InvokeNodeInputs = InputValues & { + path?: string; + board?: BreadboardCapability; + graph?: GraphDescriptor; +}; + +export default { + describe: async (inputs?: InputValues) => ({ + inputSchema: new SchemaBuilder() + .setAdditionalProperties(true) + .addInputs(inputs) + .addProperties({ + path: { + title: "path", + description: "The path to the board to invoke.", + type: "string", + }, + $ref: { + title: "board", + description: "The board to invoke, created by `lambda` or `import`", + type: "BoardCapability", + }, + graph: { + title: "graph", + description: "The graph descriptor of the board to invoke.", + type: "object", + }, + }) + .build(), + outputSchema: new SchemaBuilder().setAdditionalProperties(true).build(), + }), + invoke: async ( + inputs: InputValues, + context: NodeHandlerContext + ): Promise => { + const { path, board, graph, ...args } = inputs as InvokeNodeInputs; + + const runnableBoard = board + ? await Board.fromBreadboardCapability(board) + : graph + ? await Board.fromGraphDescriptor(graph) + : path + ? await Board.load(path, { + base: context.board.url, + outerGraph: context.parent, + }) + : undefined; + + if (!runnableBoard) throw new Error("No board provided"); + + return await runnableBoard.runOnce(args, context); + }, +}; diff --git a/seeds/core-kit/src/nodes/lambda.ts b/seeds/core-kit/src/nodes/lambda.ts new file mode 100644 index 00000000..94ad545f --- /dev/null +++ b/seeds/core-kit/src/nodes/lambda.ts @@ -0,0 +1,73 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + BreadboardCapability, + GraphDescriptor, + InputValues, + OutputValues, +} from "@google-labs/breadboard"; +import { Board } from "@google-labs/breadboard"; +import { SchemaBuilder } from "@google-labs/breadboard/kits"; + +export type LambdaNodeInputs = InputValues & { + /** + * The (lambda) board this node represents. The purpose of the this node is to + * allow wiring data into the lambda board, outside of where it's called. + * This is useful when passing a lambda to a map node or as a slot. + * + * Note that (for now) each board can only be represented by one node. + */ + board: BreadboardCapability; + + /** + * All other inputs will be bound to the board. + */ + args: InputValues; +}; + +export type LambdaNodeOutputs = OutputValues & { + /** + * The lambda board that can be run. + */ + board: BreadboardCapability; +}; + +export default { + describe: async (inputs?: InputValues) => ({ + inputSchema: new SchemaBuilder() + .setAdditionalProperties(true) + .addInputs(inputs) + .addProperty("board", { + title: "board", + description: "The board to run.", + type: "object", + }) + .build(), + outputSchema: new SchemaBuilder() + .addProperty("board", { + title: "board", + description: "The now-runnable board.", + type: "object", + }) + .build(), + }), + invoke: async (inputs: InputValues): Promise => { + const { board, ...args } = inputs as LambdaNodeInputs; + if (!board || board.kind !== "board" || !board.board) + throw new Error( + `Lambda node requires a BoardCapability as "board" input` + ); + const runnableBoard = { + ...(await Board.fromBreadboardCapability(board)), + args, + }; + + return { + board: { ...board, board: runnableBoard as GraphDescriptor }, + }; + }, +}; diff --git a/seeds/core-kit/src/nodes/passthrough.ts b/seeds/core-kit/src/nodes/passthrough.ts new file mode 100644 index 00000000..6431bc21 --- /dev/null +++ b/seeds/core-kit/src/nodes/passthrough.ts @@ -0,0 +1,29 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { InputValues, OutputValues } from "@google-labs/breadboard"; +import { SchemaBuilder } from "@google-labs/breadboard/kits"; + +export default { + desribe: async (inputs?: InputValues) => { + if (!inputs) { + return { + inputSchema: SchemaBuilder.empty(true), + outputSchema: SchemaBuilder.empty(true), + }; + } + return { + inputSchema: new SchemaBuilder() + .addInputs(inputs) + .setAdditionalProperties(true) + .build(), + outputSchema: new SchemaBuilder().addInputs(inputs).build(), + }; + }, + invoke: async (inputs: InputValues): Promise => { + return inputs; + }, +}; diff --git a/seeds/core-kit/src/nodes/reflect.ts b/seeds/core-kit/src/nodes/reflect.ts new file mode 100644 index 00000000..008029ea --- /dev/null +++ b/seeds/core-kit/src/nodes/reflect.ts @@ -0,0 +1,43 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { + NodeHandlerContext, + GraphDescriptor, + InputValues, + OutputValues, +} from "@google-labs/breadboard"; +import { SchemaBuilder } from "@google-labs/breadboard/kits"; + +const deepCopy = (graph: GraphDescriptor): GraphDescriptor => { + return JSON.parse(JSON.stringify(graph)); +}; + +export default { + desribe: async () => { + return { + inputSchema: SchemaBuilder.empty(), + outputSchema: new SchemaBuilder() + .addProperties({ + graph: { + title: "graph", + description: "The graph descriptor of the current board.", + type: "object", + }, + }) + .setAdditionalProperties(false) + .addRequired("graph") + .build(), + }; + }, + invoke: async ( + _inputs: InputValues, + context: NodeHandlerContext + ): Promise => { + const graph = deepCopy(context.board); + return { graph }; + }, +}; diff --git a/seeds/core-kit/src/nodes/slot.ts b/seeds/core-kit/src/nodes/slot.ts new file mode 100644 index 00000000..b9e588e8 --- /dev/null +++ b/seeds/core-kit/src/nodes/slot.ts @@ -0,0 +1,45 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { + InputValues, + OutputValues, + NodeHandlerContext, + NodeDescriptor, +} from "@google-labs/breadboard"; +import { BoardRunner } from "@google-labs/breadboard"; +import { SchemaBuilder } from "@google-labs/breadboard/kits"; + +export type SlotNodeInputs = { + slot: string; + parent: NodeDescriptor; +}; + +export default { + describe: async (inputs?: InputValues) => ({ + inputSchema: new SchemaBuilder() + .setAdditionalProperties(true) + .addInputs(inputs) + .addProperty("slot", { + title: "slot", + description: "The slot to run.", + type: "string", + }) + .build(), + outputSchema: new SchemaBuilder().setAdditionalProperties(true).build(), + }), + invoke: async ( + inputs: InputValues, + context: NodeHandlerContext + ): Promise => { + const { slot, ...args } = inputs as SlotNodeInputs; + if (!slot) throw new Error("To use a slot, we need to specify its name"); + const graph = context.slots[slot]; + if (!graph) throw new Error(`No graph found for slot "${slot}"`); + const slottedBreadboard = await BoardRunner.fromGraphDescriptor(graph); + return await slottedBreadboard.runOnce(args, context); + }, +};