@breadboard-ai/build
Advanced tools
Comparing version
# Changelog | ||
## 0.9.1 | ||
### Patch Changes | ||
- bbcdd2d: Ensure descriptions survive kitification | ||
- 9ed58cf: Add starInputs function to get a handle on the "\*" input port of a board | ||
- 7f2ef33: Improve generated typings of legacy api bridge function | ||
- bac2e35: Adds a legacy() function to the result of calling kit(). It's a function that asynchronously returns a kit that is automatically built with and type for the old API, allowing new API kits to be used directly in old boards. | ||
- ec2fedd: Improvements to the describe() behavior of boards built with the Build API that are polymorphic. | ||
- Updated dependencies [7d46a63] | ||
- @google-labs/breadboard@0.26.0 | ||
## 0.9.0 | ||
@@ -4,0 +16,0 @@ |
@@ -14,2 +14,3 @@ /** | ||
export { serialize } from "./internal/board/serialize.js"; | ||
export { starInputs } from "./internal/board/star-inputs.js"; | ||
export { unsafeCast } from "./internal/board/unsafe-cast.js"; | ||
@@ -16,0 +17,0 @@ export { type SerializableBoard, } from "./internal/common/serializable.js"; |
@@ -14,2 +14,3 @@ /** | ||
export { serialize } from "./internal/board/serialize.js"; | ||
export { starInputs } from "./internal/board/star-inputs.js"; | ||
export { unsafeCast } from "./internal/board/unsafe-cast.js"; | ||
@@ -16,0 +17,0 @@ export {} from "./internal/common/serializable.js"; |
@@ -9,3 +9,3 @@ /** | ||
import type { JSONSchema4 } from "json-schema"; | ||
import type { Value } from "../../index.js"; | ||
import { type Value } from "../../index.js"; | ||
import { InputPort, OutputPort, type OutputPortReference, type ValuesOrOutputPorts } from "../common/port.js"; | ||
@@ -19,2 +19,3 @@ import type { SerializableBoard, SerializableInputPort, SerializableOutputPort, SerializableOutputPortReference } from "../common/serializable.js"; | ||
import type { AutoOptional, Expand, FlattenUnion, RemoveReadonly } from "../common/type-util.js"; | ||
import type { StarInputs } from "./star-inputs.js"; | ||
/** | ||
@@ -117,3 +118,3 @@ * Define a new Breadboard board. | ||
}; | ||
export declare function describeOutput(output: SerializableOutputPortReference | Output<JsonSerializable> | Input<JsonSerializable> | InputWithDefault<JsonSerializable>): { | ||
export declare function describeOutput(output: SerializableOutputPortReference | Output<JsonSerializable | undefined> | Input<JsonSerializable | undefined> | InputWithDefault<JsonSerializable | undefined>): { | ||
schema: JSONSchema4; | ||
@@ -133,3 +134,3 @@ required: boolean; | ||
} | ||
type AnonymousInputNodeShorthand = Record<string, GenericSpecialInput>; | ||
type AnonymousInputNodeShorthand = Record<string, GenericSpecialInput | StarInputs>; | ||
type AnonymousOutputNodeShorthand = Record<string, Value | Output | undefined>; | ||
@@ -166,4 +167,4 @@ export type BoardDefinition<I extends Record<string, JsonSerializable | undefined> = any, O extends Record<string, JsonSerializable | undefined> = any> = BoardInstantiateFunction<I, O> & SerializableBoard & { | ||
*/ | ||
type ExtractInputTypes<T extends Record<string, GenericSpecialInput>> = { | ||
[K in keyof T]: T[K] extends Input<infer X> ? X : T[K] extends InputWithDefault<infer X> ? X | undefined : never; | ||
type ExtractInputTypes<T extends Record<string, GenericSpecialInput | StarInputs>> = { | ||
[K in keyof T]: T[K] extends Input<infer X> ? X : T[K] extends InputWithDefault<infer X> ? X | undefined : T[K] extends StarInputs<infer X> ? X : never; | ||
}; | ||
@@ -178,5 +179,5 @@ /** | ||
}; | ||
export declare function inputNode<T extends Record<string, GenericSpecialInput>>(inputs: T, metadata?: NodeMetadata & { | ||
export declare function inputNode<T extends Record<string, GenericSpecialInput | StarInputs>>(inputs: T, metadata?: NodeMetadata & { | ||
id?: string; | ||
}): InputNode<ExtractInputTypes<T>>; | ||
}): InputNode<Expand<ExtractInputTypes<T>>>; | ||
export interface InputNode<T extends Record<string, JsonSerializable | undefined> = Record<string, JsonSerializable | undefined>> { | ||
@@ -183,0 +184,0 @@ isInputNode: true; |
@@ -6,4 +6,5 @@ /** | ||
*/ | ||
import { anyOf, unsafeType } from "../../index.js"; | ||
import { InputPort, isOutputPortReference, OutputPort, OutputPortGetter, } from "../common/port.js"; | ||
import { toJSONSchema } from "../type-system/type.js"; | ||
import { toJSONSchema, } from "../type-system/type.js"; | ||
import { isSpecialInput, } from "./input.js"; | ||
@@ -40,3 +41,5 @@ import { isOptional } from "./optional.js"; | ||
const flatOutputs = flattenOutputs(outputs); | ||
const defImpl = new BoardDefinitionImpl(flatInputs, flatOutputs); | ||
const newInputs = normalizeBoardInputs(inputs); | ||
const newOutputs = normalizeBoardOutputs(outputs); | ||
const defImpl = new BoardDefinitionImpl(flatInputs, flatOutputs, newInputs, newOutputs); | ||
const definition = Object.assign(defImpl.instantiate.bind(defImpl), { | ||
@@ -61,2 +64,36 @@ id, | ||
} | ||
/** | ||
* Normalize the 3 allowed forms for board `inputs` to just 1. | ||
*/ | ||
function normalizeBoardInputs(inputs) { | ||
if (Array.isArray(inputs)) { | ||
return inputs; | ||
} | ||
if (isInputNode(inputs)) { | ||
return [inputs]; | ||
} | ||
return [inputNode(inputs)]; | ||
} | ||
/** | ||
* Normalize the 3 allowed forms for board `outputs` to just 1. | ||
*/ | ||
function normalizeBoardOutputs(outputs) { | ||
if (Array.isArray(outputs)) { | ||
return outputs; | ||
} | ||
if (isOutputNode(outputs)) { | ||
return [outputs]; | ||
} | ||
return [outputNode(outputs)]; | ||
} | ||
function isInputNode(value) { | ||
return (typeof value === "object" && | ||
value !== null && | ||
value["isInputNode"] === true); | ||
} | ||
function isOutputNode(value) { | ||
return (typeof value === "object" && | ||
value !== null && | ||
value["isOutputNode"] === true); | ||
} | ||
function flattenInputs(inputs) { | ||
@@ -94,6 +131,10 @@ if (!Array.isArray(inputs)) { | ||
#outputs; | ||
#newInputs; | ||
#newOutputs; | ||
definition; | ||
constructor(inputs, outputs) { | ||
constructor(inputs, outputs, newInputs, newOutputs) { | ||
this.#inputs = inputs; | ||
this.#outputs = outputs; | ||
this.#newInputs = newInputs; | ||
this.#newOutputs = newOutputs; | ||
} | ||
@@ -104,31 +145,101 @@ instantiate(values) { | ||
async describe() { | ||
const requiredInputs = []; | ||
const requiredOutputs = []; | ||
return { | ||
inputSchema: { | ||
type: "object", | ||
required: requiredInputs, | ||
additionalProperties: false, | ||
properties: Object.fromEntries(Object.entries(this.#inputs).map(([name, input]) => { | ||
const { schema, required } = describeInput(input); | ||
if (required) { | ||
requiredInputs.push(name); | ||
} | ||
return [name, schema]; | ||
})), | ||
}, | ||
outputSchema: { | ||
type: "object", | ||
required: requiredOutputs, | ||
additionalProperties: false, | ||
properties: Object.fromEntries(Object.entries(this.#outputs).map(([name, output]) => { | ||
const { schema, required } = describeOutput(output); | ||
if (required) { | ||
requiredOutputs.push(name); | ||
} | ||
return [name, schema]; | ||
})), | ||
}, | ||
inputSchema: this.#describeInputs(), | ||
outputSchema: this.#describeOutputs(), | ||
}; | ||
} | ||
#describeInputs() { | ||
const groups = {}; | ||
for (const inputNode of this.#newInputs) { | ||
for (const [name, value] of Object.entries( | ||
// TODO(aomarks) Should not need this cast. | ||
inputNode)) { | ||
if (name === "$id" || name === "$metadata") { | ||
continue; | ||
} | ||
let group = groups[name]; | ||
if (group === undefined) { | ||
group = []; | ||
groups[name] = group; | ||
} | ||
group.push(value); | ||
} | ||
} | ||
const properties = {}; | ||
const required = []; | ||
const numInputs = this.#newInputs.length; | ||
for (const [name, values] of Object.entries(groups)) { | ||
const schemasAndRequireds = values.map((value) => describeInput(value)); | ||
const schemas = schemasAndRequireds.map(({ schema }) => schema); | ||
const requireds = schemasAndRequireds | ||
.map(({ required }) => required) | ||
.filter((value) => value); | ||
if (requireds.length === numInputs) { | ||
required.push(name); | ||
} | ||
const uniqueSchemas = new Set( | ||
// TODO(aomarks) This is not an ideal way to compare schemas. | ||
schemas.map((schema) => JSON.stringify(schema))); | ||
if (uniqueSchemas.size === 1) { | ||
properties[name] = schemas[0]; | ||
} | ||
else { | ||
properties[name] = toJSONSchema(anyOf(...values.map((value) => value.type))); | ||
} | ||
} | ||
return { | ||
type: "object", | ||
properties, | ||
required, | ||
additionalProperties: false, | ||
}; | ||
} | ||
#describeOutputs() { | ||
const groups = {}; | ||
for (const outputNode of this.#newOutputs) { | ||
for (const [name, value] of Object.entries( | ||
// TODO(aomarks) Should not need this cast. | ||
outputNode)) { | ||
if (name === "$id" || name === "$metadata") { | ||
continue; | ||
} | ||
let group = groups[name]; | ||
if (group === undefined) { | ||
group = []; | ||
groups[name] = group; | ||
} | ||
group.push(value); | ||
} | ||
} | ||
const properties = {}; | ||
const required = []; | ||
const numOutputs = this.#newOutputs.length; | ||
for (const [name, values] of Object.entries(groups)) { | ||
const schemasAndRequireds = values.map((value) => describeOutput( | ||
// TODO(aomarks) Should not need this cast. | ||
value)); | ||
const schemas = schemasAndRequireds.map(({ schema }) => schema); | ||
const requireds = schemasAndRequireds | ||
.map(({ required }) => required) | ||
.filter((value) => value); | ||
if (requireds.length === numOutputs) { | ||
required.push(name); | ||
} | ||
const uniqueSchemas = new Set( | ||
// TODO(aomarks) This is not an ideal way to compare schemas. | ||
schemas.map((schema) => JSON.stringify(schema))); | ||
if (uniqueSchemas.size === 1) { | ||
properties[name] = schemas[0]; | ||
} | ||
else { | ||
properties[name] = toJSONSchema(anyOf(...schemas.map((schema) => unsafeType(schema)))); | ||
} | ||
} | ||
return { | ||
type: "object", | ||
properties, | ||
required, | ||
additionalProperties: false, | ||
}; | ||
} | ||
} | ||
@@ -135,0 +246,0 @@ export class OldBoardInstance { |
@@ -15,2 +15,3 @@ /** | ||
import { isSpecialOutput } from "./output.js"; | ||
import { isStarInputs } from "./star-inputs.js"; | ||
/** | ||
@@ -92,3 +93,3 @@ * Serialize a Breadboard board to Breadboard Graph Language (BGL) so that it | ||
} | ||
inputNode.configuration.schema.properties[mainInputName] = sortKeys(schema, [ | ||
const normalizedSchema = sortKeys(schema, [ | ||
"type", | ||
@@ -106,5 +107,12 @@ "behavior", | ||
]); | ||
if (required) { | ||
inputNode.configuration.schema.required.push(mainInputName); | ||
if (mainInputName === "*") { | ||
inputNode.configuration.schema.additionalProperties = normalizedSchema; | ||
} | ||
else { | ||
inputNode.configuration.schema.properties[mainInputName] = | ||
normalizedSchema; | ||
if (required) { | ||
inputNode.configuration.schema.required.push(mainInputName); | ||
} | ||
} | ||
if (isSpecialInput(input)) { | ||
@@ -336,2 +344,13 @@ magicInputResolutions.set(input, { | ||
} | ||
else if (isStarInputs(value)) { | ||
unconnectedInputs.delete(value); | ||
const inputNodeInfo = inputObjectsToInputNodeInfo.get(value); | ||
if (inputNodeInfo !== undefined) { | ||
addEdge(inputNodeInfo.nodeId, inputNodeInfo.portName, thisNodeId, portName, wasConstant, wasOptional); | ||
} | ||
else { | ||
errors.push(`${thisNodeId}:${portName} was wired to an input, ` + | ||
`but that input was not provided to the board inputs.`); | ||
} | ||
} | ||
else if (isSerializableOutputPortReference(value)) { | ||
@@ -338,0 +357,0 @@ const wiredOutputPort = value[OutputPortGetter]; |
@@ -9,2 +9,3 @@ /** | ||
import { type Loopback } from "../board/loopback.js"; | ||
import { type StarInputs } from "../board/star-inputs.js"; | ||
import { type BreadboardType, type JsonSerializable } from "../type-system/type.js"; | ||
@@ -27,2 +28,2 @@ import { OutputPort, type OutputPortReference } from "./port.js"; | ||
*/ | ||
export declare function extractTypeFromValue(value: Value<JsonSerializable>): BreadboardType; | ||
export declare function extractTypeFromValue(value: Value<JsonSerializable> | StarInputs): BreadboardType; |
@@ -9,2 +9,3 @@ /** | ||
import { isLoopback } from "../board/loopback.js"; | ||
import { isStarInputs } from "../board/star-inputs.js"; | ||
import { anyOf } from "../type-system/any-of.js"; | ||
@@ -41,4 +42,7 @@ import {} from "../type-system/type.js"; | ||
} | ||
if (isStarInputs(value)) { | ||
return value.type; | ||
} | ||
return "unknown"; | ||
} | ||
//# sourceMappingURL=value.js.map |
@@ -19,2 +19,3 @@ /** | ||
import type { SerializableBoard } from "../common/serializable.js"; | ||
import type { StarInputs } from "../board/star-inputs.js"; | ||
export interface Definition<SI extends { | ||
@@ -65,2 +66,3 @@ [K: string]: JsonSerializable; | ||
}; | ||
"*"?: StarInputs<DI>; | ||
} & { | ||
@@ -71,3 +73,3 @@ [K in keyof Omit<SI, OI | "$id" | "$metadata">]: IM[K extends keyof IM ? K : never]["board"] extends true ? InstantiateArg<SI[K]> | SerializableBoard : InstantiateArg<SI[K]>; | ||
} & { | ||
[K in keyof Omit<A, keyof SI | "$id" | "$metadata">]: DI extends JsonSerializable ? InstantiateArg<DI> : never; | ||
[K in keyof Omit<A, keyof SI | "$id" | "$metadata" | "*">]: DI extends JsonSerializable ? InstantiateArg<DI> : never; | ||
}; | ||
@@ -74,0 +76,0 @@ type InstanceInputs<SI extends { |
@@ -6,8 +6,9 @@ /** | ||
*/ | ||
import type { Kit, KitConstructor } from "@google-labs/breadboard"; | ||
import { type Kit, type KitConstructor, type NewNodeFactory, type NewNodeValue } from "@google-labs/breadboard"; | ||
import type { KitTag } from "@google-labs/breadboard-schema/graph.js"; | ||
import type { BoardDefinition } from "./board/board.js"; | ||
import { type GenericDiscreteComponent } from "./define/definition.js"; | ||
import type { KitTag } from "@google-labs/breadboard-schema/graph.js"; | ||
import { type Definition, type GenericDiscreteComponent } from "./define/definition.js"; | ||
import type { Expand } from "./common/type-util.js"; | ||
type ComponentManifest = Record<string, GenericDiscreteComponent | BoardDefinition>; | ||
export interface KitOptions<T extends ComponentManifest> { | ||
export interface KitOptions<T extends ComponentManifest = ComponentManifest> { | ||
title: string; | ||
@@ -20,3 +21,13 @@ description: string; | ||
} | ||
export declare function kit<T extends ComponentManifest>(options: KitOptions<T>): KitConstructor<Kit> & T; | ||
export declare function kit<T extends ComponentManifest>(options: KitOptions<T>): KitConstructor<Kit> & T & { | ||
legacy(): Promise<Expand<LegacyKit<T>>>; | ||
}; | ||
type LegacyKit<T extends ComponentManifest> = { | ||
[K in keyof T]: LegacyNodeSignature<T[K]>; | ||
}; | ||
type LegacyNodeSignature<T extends GenericDiscreteComponent | BoardDefinition> = T extends BoardDefinition<infer I, infer O> | Definition<infer I, infer O, any, any, any, any, any, any, any> ? NewNodeFactory<Expand<Required<{ | ||
[K in keyof I]: NewNodeValue; | ||
}>>, Expand<Required<{ | ||
[K in keyof O]: NewNodeValue; | ||
}>>> : never; | ||
export {}; |
@@ -6,2 +6,4 @@ /** | ||
*/ | ||
import { addKit, Board, } from "@google-labs/breadboard"; | ||
import { GraphToKitAdapter, KitBuilder } from "@google-labs/breadboard/kits"; | ||
import { serialize } from "./board/serialize.js"; | ||
@@ -12,3 +14,3 @@ import { isDiscreteComponent, } from "./define/definition.js"; | ||
id, | ||
{ ...component, id }, | ||
bindComponentToKit(component, id), | ||
])); | ||
@@ -20,6 +22,2 @@ const handlers = Object.fromEntries(Object.values(componentsWithIds).map((component) => { | ||
else { | ||
if (!component.id) { | ||
// TODO(aomarks) Make id required. | ||
throw new Error("To be added to a kit, boards must have an id."); | ||
} | ||
return [ | ||
@@ -45,3 +43,6 @@ component.id, | ||
}; | ||
return Object.assign(result, componentsWithIds); | ||
return Object.assign(result, { | ||
...componentsWithIds, | ||
legacy: () => makeLegacyKit(options), | ||
}); | ||
} | ||
@@ -51,3 +52,7 @@ function makeBoardComponentHandler(board) { | ||
return { | ||
metadata: board.metadata, | ||
metadata: { | ||
...board.metadata, | ||
title: board.title, | ||
description: board.description, | ||
}, | ||
describe: board.describe.bind(board), | ||
@@ -77,2 +82,52 @@ async invoke(inputs, context) { | ||
} | ||
/** | ||
* Components don't have ids until they are added to a kit. This function | ||
* returns a proxy of the component that adds an "id" property. | ||
*/ | ||
function bindComponentToKit(component, id) { | ||
return new Proxy(component, { | ||
get(target, prop) { | ||
return prop === "id" | ||
? id | ||
: target[prop]; | ||
}, | ||
}); | ||
} | ||
/** | ||
* We also expose a "legacy" property, an async function which returns a version | ||
* of this Kit that can be used directly in the old API. | ||
*/ | ||
async function makeLegacyKit({ title, description, version, url, components, }) { | ||
const kitBoard = new Board({ title, description, version }); | ||
const { Core } = await import( | ||
// Cast to prevent TypeScript from trying to import these types (we don't | ||
// want to depend on them in the type system because it's a circular | ||
// dependency). | ||
"@google-labs/core-kit"); | ||
const core = kitBoard.addKit(Core); | ||
const handlers = {}; | ||
const adapter = await GraphToKitAdapter.create(kitBoard, url, []); | ||
const graphs = {}; | ||
for (const [id, component] of Object.entries(components)) { | ||
if (isDiscreteComponent(component)) { | ||
handlers[id] = component; | ||
} | ||
else { | ||
core.invoke({ | ||
$id: id, | ||
$board: `#${id}`, | ||
$metadata: { | ||
...component.metadata, | ||
title: component.title, | ||
description: component.description, | ||
}, | ||
}); | ||
graphs[id] = serialize(component); | ||
handlers[id] = adapter.handlerForNode(id); | ||
} | ||
} | ||
kitBoard.graphs = graphs; | ||
const builder = new KitBuilder(adapter.populateDescriptor({ url })); | ||
return addKit(builder.build(handlers)); | ||
} | ||
//# sourceMappingURL=kit.js.map |
{ | ||
"name": "@breadboard-ai/build", | ||
"version": "0.9.0", | ||
"version": "0.9.1", | ||
"description": "JavaScript library for building boards and defining new node types for the Breadboard AI prototyping library", | ||
@@ -78,3 +78,4 @@ "license": "Apache-2.0", | ||
"dependencies": [ | ||
"build:tsc" | ||
"build:tsc", | ||
"../core-kit:build:tsc" | ||
], | ||
@@ -87,3 +88,4 @@ "files": [], | ||
"dependencies": [ | ||
"build:tsc" | ||
"build:tsc", | ||
"../core-kit:build:tsc" | ||
], | ||
@@ -105,4 +107,6 @@ "files": [], | ||
"command": "eslint src/ --ext .ts", | ||
"dependencies": [ | ||
"build:tsc" | ||
], | ||
"files": [ | ||
"src/**/*.ts", | ||
".eslintrc", | ||
@@ -121,3 +125,3 @@ "../../.eslintrc.json" | ||
"dependencies": { | ||
"@google-labs/breadboard": "^0.25.0", | ||
"@google-labs/breadboard": "^0.26.0", | ||
"@types/json-schema": "^7.0.15" | ||
@@ -124,0 +128,0 @@ }, |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
393146
7.23%111
2.78%3899
7.09%+ Added
- Removed