920 lines
30 KiB
TypeScript
920 lines
30 KiB
TypeScript
|
|
/**
|
||
|
|
* Unified Pipeline
|
||
|
|
*
|
||
|
|
* This module combines the functionality of the primary augmentation pipeline and the streamlined pipeline
|
||
|
|
* into a single, unified pipeline system. It provides both the registry functionality of the primary pipeline
|
||
|
|
* and the simplified execution API of the streamlined pipeline.
|
||
|
|
*/
|
||
|
|
|
||
|
|
import {
|
||
|
|
IAugmentation,
|
||
|
|
AugmentationType,
|
||
|
|
AugmentationResponse,
|
||
|
|
IWebSocketSupport,
|
||
|
|
BrainyAugmentations
|
||
|
|
} from './types/augmentations.js'
|
||
|
|
import { AugmentationRegistry, IPipeline } from './types/pipelineTypes.js'
|
||
|
|
import { isThreadingAvailable } from './utils/environment.js'
|
||
|
|
import { executeInThread } from './utils/workerUtils.js'
|
||
|
|
import { executeAugmentation } from './augmentationFactory.js'
|
||
|
|
import { setDefaultPipeline } from './augmentationRegistry.js'
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Execution mode for the pipeline
|
||
|
|
*/
|
||
|
|
export enum ExecutionMode {
|
||
|
|
SEQUENTIAL = 'sequential',
|
||
|
|
PARALLEL = 'parallel',
|
||
|
|
FIRST_SUCCESS = 'firstSuccess',
|
||
|
|
FIRST_RESULT = 'firstResult',
|
||
|
|
THREADED = 'threaded' // Execute in separate threads when available
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Options for pipeline execution
|
||
|
|
*/
|
||
|
|
export interface PipelineOptions {
|
||
|
|
mode?: ExecutionMode;
|
||
|
|
timeout?: number;
|
||
|
|
stopOnError?: boolean;
|
||
|
|
forceThreading?: boolean; // Force threading even if not in THREADED mode
|
||
|
|
disableThreading?: boolean; // Disable threading even if in THREADED mode
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Default pipeline options
|
||
|
|
*/
|
||
|
|
const DEFAULT_PIPELINE_OPTIONS: PipelineOptions = {
|
||
|
|
mode: ExecutionMode.SEQUENTIAL,
|
||
|
|
timeout: 30000,
|
||
|
|
stopOnError: false,
|
||
|
|
forceThreading: false,
|
||
|
|
disableThreading: false
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Result of a pipeline execution
|
||
|
|
*/
|
||
|
|
export interface PipelineResult<T> {
|
||
|
|
results: AugmentationResponse<T>[];
|
||
|
|
errors: Error[];
|
||
|
|
successful: AugmentationResponse<T>[];
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Pipeline class
|
||
|
|
*
|
||
|
|
* Manages multiple augmentations of each type and provides methods to execute them.
|
||
|
|
* Implements the IPipeline interface to avoid circular dependencies.
|
||
|
|
*/
|
||
|
|
export class Pipeline implements IPipeline {
|
||
|
|
private registry: AugmentationRegistry = {
|
||
|
|
sense: [],
|
||
|
|
conduit: [],
|
||
|
|
cognition: [],
|
||
|
|
memory: [],
|
||
|
|
perception: [],
|
||
|
|
dialog: [],
|
||
|
|
activation: [],
|
||
|
|
webSocket: []
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Register an augmentation with the pipeline
|
||
|
|
*
|
||
|
|
* @param augmentation The augmentation to register
|
||
|
|
* @returns The pipeline instance for chaining
|
||
|
|
*/
|
||
|
|
public register<T extends IAugmentation>(augmentation: T): Pipeline {
|
||
|
|
let registered = false
|
||
|
|
|
||
|
|
// Check for specific augmentation types
|
||
|
|
if (this.isAugmentationType<BrainyAugmentations.ISenseAugmentation>(
|
||
|
|
augmentation,
|
||
|
|
'processRawData',
|
||
|
|
'listenToFeed'
|
||
|
|
)) {
|
||
|
|
this.registry.sense.push(augmentation)
|
||
|
|
registered = true
|
||
|
|
} else if (this.isAugmentationType<BrainyAugmentations.IConduitAugmentation>(
|
||
|
|
augmentation,
|
||
|
|
'establishConnection',
|
||
|
|
'readData',
|
||
|
|
'writeData',
|
||
|
|
'monitorStream'
|
||
|
|
)) {
|
||
|
|
this.registry.conduit.push(augmentation)
|
||
|
|
registered = true
|
||
|
|
} else if (this.isAugmentationType<BrainyAugmentations.ICognitionAugmentation>(
|
||
|
|
augmentation,
|
||
|
|
'reason',
|
||
|
|
'infer',
|
||
|
|
'executeLogic'
|
||
|
|
)) {
|
||
|
|
this.registry.cognition.push(augmentation)
|
||
|
|
registered = true
|
||
|
|
} else if (this.isAugmentationType<BrainyAugmentations.IMemoryAugmentation>(
|
||
|
|
augmentation,
|
||
|
|
'storeData',
|
||
|
|
'retrieveData',
|
||
|
|
'updateData',
|
||
|
|
'deleteData',
|
||
|
|
'listDataKeys'
|
||
|
|
)) {
|
||
|
|
this.registry.memory.push(augmentation)
|
||
|
|
registered = true
|
||
|
|
} else if (this.isAugmentationType<BrainyAugmentations.IPerceptionAugmentation>(
|
||
|
|
augmentation,
|
||
|
|
'interpret',
|
||
|
|
'organize',
|
||
|
|
'generateVisualization'
|
||
|
|
)) {
|
||
|
|
this.registry.perception.push(augmentation)
|
||
|
|
registered = true
|
||
|
|
} else if (this.isAugmentationType<BrainyAugmentations.IDialogAugmentation>(
|
||
|
|
augmentation,
|
||
|
|
'processUserInput',
|
||
|
|
'generateResponse',
|
||
|
|
'manageContext'
|
||
|
|
)) {
|
||
|
|
this.registry.dialog.push(augmentation)
|
||
|
|
registered = true
|
||
|
|
} else if (this.isAugmentationType<BrainyAugmentations.IActivationAugmentation>(
|
||
|
|
augmentation,
|
||
|
|
'triggerAction',
|
||
|
|
'generateOutput',
|
||
|
|
'interactExternal'
|
||
|
|
)) {
|
||
|
|
this.registry.activation.push(augmentation)
|
||
|
|
registered = true
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check if the augmentation supports WebSocket
|
||
|
|
if (this.isAugmentationType<IWebSocketSupport>(
|
||
|
|
augmentation,
|
||
|
|
'connectWebSocket',
|
||
|
|
'sendWebSocketMessage',
|
||
|
|
'onWebSocketMessage',
|
||
|
|
'closeWebSocket'
|
||
|
|
)) {
|
||
|
|
this.registry.webSocket.push(augmentation as IWebSocketSupport)
|
||
|
|
registered = true
|
||
|
|
}
|
||
|
|
|
||
|
|
// If the augmentation wasn't registered as any known type, throw an error
|
||
|
|
if (!registered) {
|
||
|
|
throw new Error(`Unknown augmentation type: ${augmentation.name}`)
|
||
|
|
}
|
||
|
|
|
||
|
|
return this
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Unregister an augmentation from the pipeline
|
||
|
|
*
|
||
|
|
* @param augmentationName The name of the augmentation to unregister
|
||
|
|
* @returns The pipeline instance for chaining
|
||
|
|
*/
|
||
|
|
public unregister(augmentationName: string): Pipeline {
|
||
|
|
let found = false
|
||
|
|
|
||
|
|
// Remove from all registries
|
||
|
|
for (const type in this.registry) {
|
||
|
|
const typedRegistry = this.registry[type as keyof AugmentationRegistry]
|
||
|
|
const index = typedRegistry.findIndex(aug => aug.name === augmentationName)
|
||
|
|
|
||
|
|
if (index !== -1) {
|
||
|
|
typedRegistry.splice(index, 1)
|
||
|
|
found = true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return this
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Initialize all registered augmentations
|
||
|
|
*
|
||
|
|
* @returns A promise that resolves when all augmentations are initialized
|
||
|
|
*/
|
||
|
|
public async initialize(): Promise<void> {
|
||
|
|
const allAugmentations = this.getAllAugmentations()
|
||
|
|
|
||
|
|
await Promise.all(
|
||
|
|
allAugmentations.map(augmentation =>
|
||
|
|
augmentation.initialize().catch(error => {
|
||
|
|
console.error(`Failed to initialize augmentation ${augmentation.name}:`, error)
|
||
|
|
})
|
||
|
|
)
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Shut down all registered augmentations
|
||
|
|
*
|
||
|
|
* @returns A promise that resolves when all augmentations are shut down
|
||
|
|
*/
|
||
|
|
public async shutDown(): Promise<void> {
|
||
|
|
const allAugmentations = this.getAllAugmentations()
|
||
|
|
|
||
|
|
await Promise.all(
|
||
|
|
allAugmentations.map(augmentation =>
|
||
|
|
augmentation.shutDown().catch(error => {
|
||
|
|
console.error(`Failed to shut down augmentation ${augmentation.name}:`, error)
|
||
|
|
})
|
||
|
|
)
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get all registered augmentations
|
||
|
|
*
|
||
|
|
* @returns An array of all registered augmentations
|
||
|
|
*/
|
||
|
|
public getAllAugmentations(): IAugmentation[] {
|
||
|
|
// Create a Set to avoid duplicates (an augmentation might be in multiple registries)
|
||
|
|
const allAugmentations = new Set<IAugmentation>([
|
||
|
|
...this.registry.sense,
|
||
|
|
...this.registry.conduit,
|
||
|
|
...this.registry.cognition,
|
||
|
|
...this.registry.memory,
|
||
|
|
...this.registry.perception,
|
||
|
|
...this.registry.dialog,
|
||
|
|
...this.registry.activation,
|
||
|
|
...this.registry.webSocket
|
||
|
|
])
|
||
|
|
|
||
|
|
// Convert back to array
|
||
|
|
return Array.from(allAugmentations)
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get all augmentations of a specific type
|
||
|
|
*
|
||
|
|
* @param type The type of augmentation to get
|
||
|
|
* @returns An array of all augmentations of the specified type
|
||
|
|
*/
|
||
|
|
public getAugmentationsByType(type: AugmentationType): IAugmentation[] {
|
||
|
|
switch (type) {
|
||
|
|
case AugmentationType.SENSE:
|
||
|
|
return [...this.registry.sense]
|
||
|
|
case AugmentationType.CONDUIT:
|
||
|
|
return [...this.registry.conduit]
|
||
|
|
case AugmentationType.COGNITION:
|
||
|
|
return [...this.registry.cognition]
|
||
|
|
case AugmentationType.MEMORY:
|
||
|
|
return [...this.registry.memory]
|
||
|
|
case AugmentationType.PERCEPTION:
|
||
|
|
return [...this.registry.perception]
|
||
|
|
case AugmentationType.DIALOG:
|
||
|
|
return [...this.registry.dialog]
|
||
|
|
case AugmentationType.ACTIVATION:
|
||
|
|
return [...this.registry.activation]
|
||
|
|
case AugmentationType.WEBSOCKET:
|
||
|
|
return [...this.registry.webSocket]
|
||
|
|
default:
|
||
|
|
return []
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get all available augmentation types
|
||
|
|
*
|
||
|
|
* @returns An array of all augmentation types that have at least one registered augmentation
|
||
|
|
*/
|
||
|
|
public getAvailableAugmentationTypes(): AugmentationType[] {
|
||
|
|
const availableTypes: AugmentationType[] = []
|
||
|
|
|
||
|
|
if (this.registry.sense.length > 0) availableTypes.push(AugmentationType.SENSE)
|
||
|
|
if (this.registry.conduit.length > 0) availableTypes.push(AugmentationType.CONDUIT)
|
||
|
|
if (this.registry.cognition.length > 0) availableTypes.push(AugmentationType.COGNITION)
|
||
|
|
if (this.registry.memory.length > 0) availableTypes.push(AugmentationType.MEMORY)
|
||
|
|
if (this.registry.perception.length > 0) availableTypes.push(AugmentationType.PERCEPTION)
|
||
|
|
if (this.registry.dialog.length > 0) availableTypes.push(AugmentationType.DIALOG)
|
||
|
|
if (this.registry.activation.length > 0) availableTypes.push(AugmentationType.ACTIVATION)
|
||
|
|
if (this.registry.webSocket.length > 0) availableTypes.push(AugmentationType.WEBSOCKET)
|
||
|
|
|
||
|
|
return availableTypes
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get all WebSocket-supporting augmentations
|
||
|
|
*
|
||
|
|
* @returns An array of all augmentations that support WebSocket connections
|
||
|
|
*/
|
||
|
|
public getWebSocketAugmentations(): IWebSocketSupport[] {
|
||
|
|
return [...this.registry.webSocket]
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check if an augmentation is of a specific type
|
||
|
|
*
|
||
|
|
* @param augmentation The augmentation to check
|
||
|
|
* @param methods The methods that should be present on the augmentation
|
||
|
|
* @returns True if the augmentation is of the specified type
|
||
|
|
*/
|
||
|
|
private isAugmentationType<T extends IAugmentation>(
|
||
|
|
augmentation: IAugmentation,
|
||
|
|
...methods: (keyof T)[]
|
||
|
|
): augmentation is T {
|
||
|
|
// First check that the augmentation has all the required base methods
|
||
|
|
const baseMethodsExist = [
|
||
|
|
'initialize',
|
||
|
|
'shutDown',
|
||
|
|
'getStatus'
|
||
|
|
].every(method => typeof (augmentation as any)[method] === 'function')
|
||
|
|
|
||
|
|
if (!baseMethodsExist) {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
// Then check that it has all the specific methods for this type
|
||
|
|
return methods.every(method => typeof (augmentation as any)[method] === 'function')
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Determines if threading should be used based on options and environment
|
||
|
|
*
|
||
|
|
* @param options The pipeline options
|
||
|
|
* @returns True if threading should be used, false otherwise
|
||
|
|
*/
|
||
|
|
private shouldUseThreading(options: PipelineOptions): boolean {
|
||
|
|
// If threading is explicitly disabled, don't use it
|
||
|
|
if (options.disableThreading) {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
// If threading is explicitly forced, use it if available
|
||
|
|
if (options.forceThreading) {
|
||
|
|
return isThreadingAvailable()
|
||
|
|
}
|
||
|
|
|
||
|
|
// If in THREADED mode, use threading if available
|
||
|
|
if (options.mode === ExecutionMode.THREADED) {
|
||
|
|
return isThreadingAvailable()
|
||
|
|
}
|
||
|
|
|
||
|
|
// Otherwise, don't use threading
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Executes a method on multiple augmentations using the specified execution mode
|
||
|
|
*
|
||
|
|
* @param augmentations The augmentations to execute the method on
|
||
|
|
* @param method The method to execute
|
||
|
|
* @param args The arguments to pass to the method
|
||
|
|
* @param options Options for the execution
|
||
|
|
* @returns A promise that resolves with the results
|
||
|
|
*/
|
||
|
|
public async execute<T>(
|
||
|
|
augmentations: IAugmentation[],
|
||
|
|
method: string,
|
||
|
|
args: any[] = [],
|
||
|
|
options: PipelineOptions = {}
|
||
|
|
): Promise<PipelineResult<T>> {
|
||
|
|
const opts = { ...DEFAULT_PIPELINE_OPTIONS, ...options }
|
||
|
|
const enabledAugmentations = augmentations.filter(aug => aug.enabled !== false)
|
||
|
|
|
||
|
|
if (enabledAugmentations.length === 0) {
|
||
|
|
return { results: [], errors: [], successful: [] }
|
||
|
|
}
|
||
|
|
|
||
|
|
const result: PipelineResult<T> = {
|
||
|
|
results: [],
|
||
|
|
errors: [],
|
||
|
|
successful: []
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create a function to execute with timeout
|
||
|
|
const executeWithTimeout = async (
|
||
|
|
augmentation: IAugmentation
|
||
|
|
): Promise<AugmentationResponse<T>> => {
|
||
|
|
try {
|
||
|
|
// Create a timeout promise if a timeout is specified
|
||
|
|
if (opts.timeout) {
|
||
|
|
const timeoutPromise = new Promise<never>((_, reject) => {
|
||
|
|
setTimeout(() => {
|
||
|
|
reject(
|
||
|
|
new Error(`Timeout executing ${method} on ${augmentation.name}`)
|
||
|
|
)
|
||
|
|
}, opts.timeout)
|
||
|
|
})
|
||
|
|
|
||
|
|
// Check if threading should be used
|
||
|
|
const useThreading = this.shouldUseThreading(opts)
|
||
|
|
|
||
|
|
// Execute the method on the augmentation, using threading if appropriate
|
||
|
|
let methodPromise: Promise<AugmentationResponse<T>>
|
||
|
|
|
||
|
|
if (useThreading) {
|
||
|
|
// Execute in a separate thread
|
||
|
|
try {
|
||
|
|
// Create a function that can be serialized and executed in a worker
|
||
|
|
const workerFn = (...workerArgs: any[]) => {
|
||
|
|
// This function will be stringified and executed in the worker
|
||
|
|
// It needs to be self-contained
|
||
|
|
const augFn = augmentation[method as string] as Function
|
||
|
|
return augFn.apply(augmentation, workerArgs)
|
||
|
|
}
|
||
|
|
|
||
|
|
methodPromise = executeInThread<AugmentationResponse<T>>(workerFn.toString(), args)
|
||
|
|
} catch (threadError) {
|
||
|
|
console.warn(`Failed to execute in thread, falling back to main thread: ${threadError}`)
|
||
|
|
// Fall back to executing in the main thread
|
||
|
|
methodPromise = Promise.resolve((augmentation[method] as Function)(...args) as AugmentationResponse<T>)
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
// Execute in the main thread
|
||
|
|
methodPromise = Promise.resolve((augmentation[method] as Function)(...args) as AugmentationResponse<T>)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Race the method promise against the timeout promise
|
||
|
|
return await Promise.race([methodPromise, timeoutPromise])
|
||
|
|
} else {
|
||
|
|
// No timeout, just execute the method
|
||
|
|
return await executeAugmentation<any, T>(augmentation, method, ...args)
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
result.errors.push(
|
||
|
|
error instanceof Error ? error : new Error(String(error))
|
||
|
|
)
|
||
|
|
return {
|
||
|
|
success: false,
|
||
|
|
data: null as any,
|
||
|
|
error: error instanceof Error ? error.message : String(error)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Execute based on the specified mode
|
||
|
|
switch (opts.mode) {
|
||
|
|
case ExecutionMode.PARALLEL:
|
||
|
|
case ExecutionMode.THREADED:
|
||
|
|
// Execute all augmentations in parallel
|
||
|
|
result.results = await Promise.all(
|
||
|
|
enabledAugmentations.map((aug) => executeWithTimeout(aug))
|
||
|
|
)
|
||
|
|
break
|
||
|
|
|
||
|
|
case ExecutionMode.FIRST_SUCCESS:
|
||
|
|
// Execute augmentations sequentially until one succeeds
|
||
|
|
for (const augmentation of enabledAugmentations) {
|
||
|
|
const response = await executeWithTimeout(augmentation)
|
||
|
|
result.results.push(response)
|
||
|
|
|
||
|
|
if (response.success) {
|
||
|
|
break
|
||
|
|
}
|
||
|
|
}
|
||
|
|
break
|
||
|
|
|
||
|
|
case ExecutionMode.FIRST_RESULT:
|
||
|
|
// Execute augmentations sequentially until one returns a non-null result
|
||
|
|
for (const augmentation of enabledAugmentations) {
|
||
|
|
const response = await executeWithTimeout(augmentation)
|
||
|
|
result.results.push(response)
|
||
|
|
|
||
|
|
if (
|
||
|
|
response.success &&
|
||
|
|
response.data !== null &&
|
||
|
|
response.data !== undefined
|
||
|
|
) {
|
||
|
|
break
|
||
|
|
}
|
||
|
|
}
|
||
|
|
break
|
||
|
|
|
||
|
|
case ExecutionMode.SEQUENTIAL:
|
||
|
|
default:
|
||
|
|
// Execute augmentations sequentially
|
||
|
|
for (const augmentation of enabledAugmentations) {
|
||
|
|
const response = await executeWithTimeout(augmentation)
|
||
|
|
result.results.push(response)
|
||
|
|
|
||
|
|
// Check if we need to stop on error
|
||
|
|
if (opts.stopOnError && !response.success) {
|
||
|
|
break
|
||
|
|
}
|
||
|
|
}
|
||
|
|
break
|
||
|
|
}
|
||
|
|
|
||
|
|
// Filter successful results
|
||
|
|
result.successful = result.results.filter((r) => r.success)
|
||
|
|
|
||
|
|
return result
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Executes a method on augmentations of a specific type
|
||
|
|
*
|
||
|
|
* @param type The type of augmentations to execute the method on
|
||
|
|
* @param method The method to execute
|
||
|
|
* @param args The arguments to pass to the method
|
||
|
|
* @param options Options for the execution
|
||
|
|
* @returns A promise that resolves with the results
|
||
|
|
*/
|
||
|
|
public async executeByType<T>(
|
||
|
|
type: AugmentationType,
|
||
|
|
method: string,
|
||
|
|
args: any[] = [],
|
||
|
|
options: PipelineOptions = {}
|
||
|
|
): Promise<PipelineResult<T>> {
|
||
|
|
const augmentations = this.getAugmentationsByType(type)
|
||
|
|
return this.execute<T>(augmentations, method, args, options)
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Executes a method on a single augmentation with automatic error handling
|
||
|
|
*
|
||
|
|
* @param augmentation The augmentation to execute the method on
|
||
|
|
* @param method The method to execute
|
||
|
|
* @param args The arguments to pass to the method
|
||
|
|
* @returns A promise that resolves with the result
|
||
|
|
*/
|
||
|
|
public async executeSingle<T>(
|
||
|
|
augmentation: IAugmentation,
|
||
|
|
method: string,
|
||
|
|
...args: any[]
|
||
|
|
): Promise<AugmentationResponse<T>> {
|
||
|
|
return executeAugmentation<any, T>(augmentation, method, ...args)
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Process static data through a pipeline of augmentations
|
||
|
|
*
|
||
|
|
* @param data The data to process
|
||
|
|
* @param pipeline An array of processing steps, each with an augmentation, method, and optional args transformer
|
||
|
|
* @param options Options for the execution
|
||
|
|
* @returns A promise that resolves with the final result
|
||
|
|
*/
|
||
|
|
public async processStaticData<T, R = any>(
|
||
|
|
data: T,
|
||
|
|
pipeline: Array<{
|
||
|
|
augmentation: IAugmentation
|
||
|
|
method: string
|
||
|
|
transformArgs?: (data: any, prevResult?: any) => any[]
|
||
|
|
}>,
|
||
|
|
options: PipelineOptions = {}
|
||
|
|
): Promise<AugmentationResponse<R>> {
|
||
|
|
let currentData = data
|
||
|
|
let prevResult: any = undefined
|
||
|
|
|
||
|
|
for (const step of pipeline) {
|
||
|
|
// Transform args if a transformer is provided, otherwise use the current data as the only arg
|
||
|
|
const args = step.transformArgs
|
||
|
|
? step.transformArgs(currentData, prevResult)
|
||
|
|
: [currentData]
|
||
|
|
|
||
|
|
// Execute the method
|
||
|
|
const result = await this.executeSingle<any>(
|
||
|
|
step.augmentation,
|
||
|
|
step.method,
|
||
|
|
...args
|
||
|
|
)
|
||
|
|
|
||
|
|
// If the step failed, return the error
|
||
|
|
if (!result.success) {
|
||
|
|
return result as AugmentationResponse<R>
|
||
|
|
}
|
||
|
|
|
||
|
|
// Update the current data for the next step
|
||
|
|
currentData = result.data
|
||
|
|
prevResult = result
|
||
|
|
}
|
||
|
|
|
||
|
|
// Return the final result
|
||
|
|
return prevResult as AugmentationResponse<R>
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Process streaming data through a pipeline of augmentations
|
||
|
|
*
|
||
|
|
* @param source The source augmentation that provides the data stream
|
||
|
|
* @param sourceMethod The method on the source augmentation that provides the data stream
|
||
|
|
* @param sourceArgs The arguments to pass to the source method
|
||
|
|
* @param pipeline An array of processing steps, each with an augmentation, method, and optional args transformer
|
||
|
|
* @param callback Function to call with the results of processing each data item
|
||
|
|
* @param options Options for the execution
|
||
|
|
* @returns A promise that resolves when the pipeline is set up
|
||
|
|
*/
|
||
|
|
public async processStreamingData<T>(
|
||
|
|
source: IAugmentation,
|
||
|
|
sourceMethod: string,
|
||
|
|
sourceArgs: any[],
|
||
|
|
pipeline: Array<{
|
||
|
|
augmentation: IAugmentation
|
||
|
|
method: string
|
||
|
|
transformArgs?: (data: any, prevResult?: any) => any[]
|
||
|
|
}>,
|
||
|
|
callback: (result: AugmentationResponse<T>) => void,
|
||
|
|
options: PipelineOptions = {}
|
||
|
|
): Promise<void> {
|
||
|
|
// Create a chain of processors
|
||
|
|
const processData = async (data: any) => {
|
||
|
|
let currentData = data
|
||
|
|
let prevResult: any = undefined
|
||
|
|
|
||
|
|
for (const step of pipeline) {
|
||
|
|
// Transform args if a transformer is provided, otherwise use the current data as the only arg
|
||
|
|
const args = step.transformArgs
|
||
|
|
? step.transformArgs(currentData, prevResult)
|
||
|
|
: [currentData]
|
||
|
|
|
||
|
|
// Execute the method
|
||
|
|
const result = await this.executeSingle<any>(
|
||
|
|
step.augmentation,
|
||
|
|
step.method,
|
||
|
|
...args
|
||
|
|
)
|
||
|
|
|
||
|
|
// If the step failed, return the error
|
||
|
|
if (!result.success) {
|
||
|
|
callback(result as AugmentationResponse<T>)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// Update the current data for the next step
|
||
|
|
currentData = result.data
|
||
|
|
prevResult = result
|
||
|
|
}
|
||
|
|
|
||
|
|
// Call the callback with the final result
|
||
|
|
callback(prevResult as AugmentationResponse<T>)
|
||
|
|
}
|
||
|
|
|
||
|
|
// The last argument to the source method should be a callback that receives the data
|
||
|
|
const dataCallback = (data: any) => {
|
||
|
|
processData(data).catch((error) => {
|
||
|
|
console.error('Error processing streaming data:', error)
|
||
|
|
callback({
|
||
|
|
success: false,
|
||
|
|
data: null as any,
|
||
|
|
error: error instanceof Error ? error.message : String(error)
|
||
|
|
})
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// Execute the source method with the provided args and the data callback
|
||
|
|
await this.executeSingle(source, sourceMethod, ...sourceArgs, dataCallback)
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create a reusable pipeline for processing data
|
||
|
|
*
|
||
|
|
* @param pipeline An array of processing steps
|
||
|
|
* @param options Options for the execution
|
||
|
|
* @returns A function that processes data through the pipeline
|
||
|
|
*/
|
||
|
|
public createPipeline<T, R>(
|
||
|
|
pipeline: Array<{
|
||
|
|
augmentation: IAugmentation
|
||
|
|
method: string
|
||
|
|
transformArgs?: (data: any, prevResult?: any) => any[]
|
||
|
|
}>,
|
||
|
|
options: PipelineOptions = {}
|
||
|
|
): (data: T) => Promise<AugmentationResponse<R>> {
|
||
|
|
return (data: T) => this.processStaticData<T, R>(data, pipeline, options)
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create a reusable streaming pipeline
|
||
|
|
*
|
||
|
|
* @param source The source augmentation
|
||
|
|
* @param sourceMethod The method on the source augmentation
|
||
|
|
* @param pipeline An array of processing steps
|
||
|
|
* @param options Options for the execution
|
||
|
|
* @returns A function that sets up the streaming pipeline
|
||
|
|
*/
|
||
|
|
public createStreamingPipeline<T, R>(
|
||
|
|
source: IAugmentation,
|
||
|
|
sourceMethod: string,
|
||
|
|
pipeline: Array<{
|
||
|
|
augmentation: IAugmentation
|
||
|
|
method: string
|
||
|
|
transformArgs?: (data: any, prevResult?: any) => any[]
|
||
|
|
}>,
|
||
|
|
options: PipelineOptions = {}
|
||
|
|
): (
|
||
|
|
sourceArgs: any[],
|
||
|
|
callback: (result: AugmentationResponse<R>) => void
|
||
|
|
) => Promise<void> {
|
||
|
|
return (
|
||
|
|
sourceArgs: any[],
|
||
|
|
callback: (result: AugmentationResponse<R>) => void
|
||
|
|
) =>
|
||
|
|
this.processStreamingData<R>(
|
||
|
|
source,
|
||
|
|
sourceMethod,
|
||
|
|
sourceArgs,
|
||
|
|
pipeline,
|
||
|
|
callback,
|
||
|
|
options
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Legacy methods for backward compatibility
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Execute a sense pipeline (legacy method)
|
||
|
|
*/
|
||
|
|
public async executeSensePipeline<
|
||
|
|
M extends keyof BrainyAugmentations.ISenseAugmentation & string,
|
||
|
|
R extends BrainyAugmentations.ISenseAugmentation[M] extends (...args: any[]) => AugmentationResponse<infer U> ? U : never
|
||
|
|
>(
|
||
|
|
method: M & (BrainyAugmentations.ISenseAugmentation[M] extends (...args: any[]) => any ? M : never),
|
||
|
|
args: Parameters<Extract<BrainyAugmentations.ISenseAugmentation[M], (...args: any[]) => any>>,
|
||
|
|
options: PipelineOptions = {}
|
||
|
|
): Promise<Promise<{ success: boolean; data: R; error?: string }>[]> {
|
||
|
|
const result = await this.executeByType<R>(AugmentationType.SENSE, method, args, options)
|
||
|
|
return result.results.map(r => Promise.resolve(r))
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Execute a conduit pipeline (legacy method)
|
||
|
|
*/
|
||
|
|
public async executeConduitPipeline<
|
||
|
|
M extends keyof BrainyAugmentations.IConduitAugmentation & string,
|
||
|
|
R extends BrainyAugmentations.IConduitAugmentation[M] extends (...args: any[]) => AugmentationResponse<infer U> ? U : never
|
||
|
|
>(
|
||
|
|
method: M & (BrainyAugmentations.IConduitAugmentation[M] extends (...args: any[]) => any ? M : never),
|
||
|
|
args: Parameters<Extract<BrainyAugmentations.IConduitAugmentation[M], (...args: any[]) => any>>,
|
||
|
|
options: PipelineOptions = {}
|
||
|
|
): Promise<Promise<{ success: boolean; data: R; error?: string }>[]> {
|
||
|
|
const result = await this.executeByType<R>(AugmentationType.CONDUIT, method, args, options)
|
||
|
|
return result.results.map(r => Promise.resolve(r))
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Execute a cognition pipeline (legacy method)
|
||
|
|
*/
|
||
|
|
public async executeCognitionPipeline<
|
||
|
|
M extends keyof BrainyAugmentations.ICognitionAugmentation & string,
|
||
|
|
R extends BrainyAugmentations.ICognitionAugmentation[M] extends (...args: any[]) => AugmentationResponse<infer U> ? U : never
|
||
|
|
>(
|
||
|
|
method: M & (BrainyAugmentations.ICognitionAugmentation[M] extends (...args: any[]) => any ? M : never),
|
||
|
|
args: Parameters<Extract<BrainyAugmentations.ICognitionAugmentation[M], (...args: any[]) => any>>,
|
||
|
|
options: PipelineOptions = {}
|
||
|
|
): Promise<Promise<{ success: boolean; data: R; error?: string }>[]> {
|
||
|
|
const result = await this.executeByType<R>(AugmentationType.COGNITION, method, args, options)
|
||
|
|
return result.results.map(r => Promise.resolve(r))
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Execute a memory pipeline (legacy method)
|
||
|
|
*/
|
||
|
|
public async executeMemoryPipeline<
|
||
|
|
M extends keyof BrainyAugmentations.IMemoryAugmentation & string,
|
||
|
|
R extends BrainyAugmentations.IMemoryAugmentation[M] extends (...args: any[]) => AugmentationResponse<infer U> ? U : never
|
||
|
|
>(
|
||
|
|
method: M & (BrainyAugmentations.IMemoryAugmentation[M] extends (...args: any[]) => any ? M : never),
|
||
|
|
args: Parameters<Extract<BrainyAugmentations.IMemoryAugmentation[M], (...args: any[]) => any>>,
|
||
|
|
options: PipelineOptions = {}
|
||
|
|
): Promise<Promise<{ success: boolean; data: R; error?: string }>[]> {
|
||
|
|
const result = await this.executeByType<R>(AugmentationType.MEMORY, method, args, options)
|
||
|
|
return result.results.map(r => Promise.resolve(r))
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Execute a perception pipeline (legacy method)
|
||
|
|
*/
|
||
|
|
public async executePerceptionPipeline<
|
||
|
|
M extends keyof BrainyAugmentations.IPerceptionAugmentation & string,
|
||
|
|
R extends BrainyAugmentations.IPerceptionAugmentation[M] extends (...args: any[]) => AugmentationResponse<infer U> ? U : never
|
||
|
|
>(
|
||
|
|
method: M & (BrainyAugmentations.IPerceptionAugmentation[M] extends (...args: any[]) => any ? M : never),
|
||
|
|
args: Parameters<Extract<BrainyAugmentations.IPerceptionAugmentation[M], (...args: any[]) => any>>,
|
||
|
|
options: PipelineOptions = {}
|
||
|
|
): Promise<Promise<{ success: boolean; data: R; error?: string }>[]> {
|
||
|
|
const result = await this.executeByType<R>(AugmentationType.PERCEPTION, method, args, options)
|
||
|
|
return result.results.map(r => Promise.resolve(r))
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Execute a dialog pipeline (legacy method)
|
||
|
|
*/
|
||
|
|
public async executeDialogPipeline<
|
||
|
|
M extends keyof BrainyAugmentations.IDialogAugmentation & string,
|
||
|
|
R extends BrainyAugmentations.IDialogAugmentation[M] extends (...args: any[]) => AugmentationResponse<infer U> ? U : never
|
||
|
|
>(
|
||
|
|
method: M & (BrainyAugmentations.IDialogAugmentation[M] extends (...args: any[]) => any ? M : never),
|
||
|
|
args: Parameters<Extract<BrainyAugmentations.IDialogAugmentation[M], (...args: any[]) => any>>,
|
||
|
|
options: PipelineOptions = {}
|
||
|
|
): Promise<Promise<{ success: boolean; data: R; error?: string }>[]> {
|
||
|
|
const result = await this.executeByType<R>(AugmentationType.DIALOG, method, args, options)
|
||
|
|
return result.results.map(r => Promise.resolve(r))
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Execute an activation pipeline (legacy method)
|
||
|
|
*/
|
||
|
|
public async executeActivationPipeline<
|
||
|
|
M extends keyof BrainyAugmentations.IActivationAugmentation & string,
|
||
|
|
R extends BrainyAugmentations.IActivationAugmentation[M] extends (...args: any[]) => AugmentationResponse<infer U> ? U : never
|
||
|
|
>(
|
||
|
|
method: M & (BrainyAugmentations.IActivationAugmentation[M] extends (...args: any[]) => any ? M : never),
|
||
|
|
args: Parameters<Extract<BrainyAugmentations.IActivationAugmentation[M], (...args: any[]) => any>>,
|
||
|
|
options: PipelineOptions = {}
|
||
|
|
): Promise<Promise<{ success: boolean; data: R; error?: string }>[]> {
|
||
|
|
const result = await this.executeByType<R>(AugmentationType.ACTIVATION, method, args, options)
|
||
|
|
return result.results.map(r => Promise.resolve(r))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create and export a default instance of the pipeline
|
||
|
|
export const pipeline = new Pipeline()
|
||
|
|
|
||
|
|
// Set the default pipeline instance for the augmentation registry
|
||
|
|
// This breaks the circular dependency between pipeline.ts and augmentationRegistry.ts
|
||
|
|
setDefaultPipeline(pipeline)
|
||
|
|
|
||
|
|
// Re-export the legacy pipeline for backward compatibility
|
||
|
|
export const augmentationPipeline = pipeline
|
||
|
|
|
||
|
|
// Re-export the streamlined execution functions for backward compatibility
|
||
|
|
export const executeStreamlined = <T>(
|
||
|
|
augmentations: IAugmentation[],
|
||
|
|
method: string,
|
||
|
|
args: any[] = [],
|
||
|
|
options: PipelineOptions = {}
|
||
|
|
): Promise<PipelineResult<T>> => {
|
||
|
|
return pipeline.execute<T>(augmentations, method, args, options)
|
||
|
|
}
|
||
|
|
|
||
|
|
export const executeByType = <T>(
|
||
|
|
type: AugmentationType,
|
||
|
|
method: string,
|
||
|
|
args: any[] = [],
|
||
|
|
options: PipelineOptions = {}
|
||
|
|
): Promise<PipelineResult<T>> => {
|
||
|
|
return pipeline.executeByType<T>(type, method, args, options)
|
||
|
|
}
|
||
|
|
|
||
|
|
export const executeSingle = <T>(
|
||
|
|
augmentation: IAugmentation,
|
||
|
|
method: string,
|
||
|
|
...args: any[]
|
||
|
|
): Promise<AugmentationResponse<T>> => {
|
||
|
|
return pipeline.executeSingle<T>(augmentation, method, ...args)
|
||
|
|
}
|
||
|
|
|
||
|
|
export const processStaticData = <T, R = any>(
|
||
|
|
data: T,
|
||
|
|
pipelineSteps: Array<{
|
||
|
|
augmentation: IAugmentation
|
||
|
|
method: string
|
||
|
|
transformArgs?: (data: any, prevResult?: any) => any[]
|
||
|
|
}>,
|
||
|
|
options: PipelineOptions = {}
|
||
|
|
): Promise<AugmentationResponse<R>> => {
|
||
|
|
return pipeline.processStaticData<T, R>(data, pipelineSteps, options)
|
||
|
|
}
|
||
|
|
|
||
|
|
export const processStreamingData = <T>(
|
||
|
|
source: IAugmentation,
|
||
|
|
sourceMethod: string,
|
||
|
|
sourceArgs: any[],
|
||
|
|
pipelineSteps: Array<{
|
||
|
|
augmentation: IAugmentation
|
||
|
|
method: string
|
||
|
|
transformArgs?: (data: any, prevResult?: any) => any[]
|
||
|
|
}>,
|
||
|
|
callback: (result: AugmentationResponse<T>) => void,
|
||
|
|
options: PipelineOptions = {}
|
||
|
|
): Promise<void> => {
|
||
|
|
return pipeline.processStreamingData<T>(source, sourceMethod, sourceArgs, pipelineSteps, callback, options)
|
||
|
|
}
|
||
|
|
|
||
|
|
export const createPipeline = <T, R>(
|
||
|
|
pipelineSteps: Array<{
|
||
|
|
augmentation: IAugmentation
|
||
|
|
method: string
|
||
|
|
transformArgs?: (data: any, prevResult?: any) => any[]
|
||
|
|
}>,
|
||
|
|
options: PipelineOptions = {}
|
||
|
|
): (data: T) => Promise<AugmentationResponse<R>> => {
|
||
|
|
return pipeline.createPipeline<T, R>(pipelineSteps, options)
|
||
|
|
}
|
||
|
|
|
||
|
|
export const createStreamingPipeline = <T, R>(
|
||
|
|
source: IAugmentation,
|
||
|
|
sourceMethod: string,
|
||
|
|
pipelineSteps: Array<{
|
||
|
|
augmentation: IAugmentation
|
||
|
|
method: string
|
||
|
|
transformArgs?: (data: any, prevResult?: any) => any[]
|
||
|
|
}>,
|
||
|
|
options: PipelineOptions = {}
|
||
|
|
): (
|
||
|
|
sourceArgs: any[],
|
||
|
|
callback: (result: AugmentationResponse<R>) => void
|
||
|
|
) => Promise<void> => {
|
||
|
|
return pipeline.createStreamingPipeline<T, R>(source, sourceMethod, pipelineSteps, options)
|
||
|
|
}
|
||
|
|
|
||
|
|
// For backward compatibility with StreamlinedExecutionMode
|
||
|
|
export const StreamlinedExecutionMode = ExecutionMode
|
||
|
|
export type StreamlinedPipelineOptions = PipelineOptions
|
||
|
|
export type StreamlinedPipelineResult<T> = PipelineResult<T>
|