UNPKG

@juspay/neurolink

Version:

The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002

521 lines (520 loc) • 16 kB
/** * Network Topology - Defines and manages network structure * * Provides different topology patterns for agent networks: * - Star: Central coordinator with peripheral agents * - Mesh: All agents can communicate with each other * - Hierarchical: Tree structure with supervisors and workers * - Ring: Agents form a circular communication pattern * - Custom: User-defined topology */ import { randomUUID } from "crypto"; import { logger } from "../../utils/logger.js"; /** * Network Topology - Manages agent network structure */ export class NetworkTopology { nodes = new Map(); edges = new Map(); config; topologyId; constructor(config) { this.config = config; this.topologyId = randomUUID(); logger.debug(`[NetworkTopology] Created with type: ${config.type}`); } /** * Build topology from agents */ buildFromAgents(agents) { // Clear existing topology this.nodes.clear(); this.edges.clear(); // Create nodes for all agents for (const agent of agents) { this.addNode(agent); } // Build edges based on topology type switch (this.config.type) { case "star": this.buildStarTopology(agents); break; case "mesh": this.buildMeshTopology(agents); break; case "hierarchical": this.buildHierarchicalTopology(agents); break; case "ring": this.buildRingTopology(agents); break; case "custom": this.buildCustomTopology(agents); break; } logger.info(`[NetworkTopology] Built ${this.config.type} topology`, { nodes: this.nodes.size, edges: this.edges.size, }); } /** * Add a node to the topology */ addNode(agent, role) { const node = { id: `node-${agent.id}`, agentId: agent.id, agentName: agent.name, role: role ?? "worker", connections: [], childIds: [], }; this.nodes.set(node.id, node); return node; } /** * Remove a node from the topology */ removeNode(nodeId) { const node = this.nodes.get(nodeId); if (!node) { return false; } // Remove all edges connected to this node for (const edgeId of [...this.edges.keys()]) { const edge = this.edges.get(edgeId); if (edge.sourceId === nodeId || edge.targetId === nodeId) { this.edges.delete(edgeId); } } // Remove from other nodes' connections for (const otherNode of this.nodes.values()) { otherNode.connections = otherNode.connections.filter((id) => id !== nodeId); otherNode.childIds = otherNode.childIds.filter((id) => id !== nodeId); if (otherNode.parentId === nodeId) { otherNode.parentId = undefined; } } this.nodes.delete(nodeId); return true; } /** * Add an edge between nodes */ addEdge(sourceId, targetId, type = "bidirectional", weight = 1) { const sourceNode = this.nodes.get(sourceId); const targetNode = this.nodes.get(targetId); if (!sourceNode || !targetNode) { logger.warn(`[NetworkTopology] Cannot add edge: node not found`); return undefined; } const edge = { id: `edge-${sourceId}-${targetId}`, sourceId, targetId, type, weight, }; this.edges.set(edge.id, edge); // Update node connections if (!sourceNode.connections.includes(targetId)) { sourceNode.connections.push(targetId); } if (type === "bidirectional" && !targetNode.connections.includes(sourceId)) { targetNode.connections.push(sourceId); } return edge; } /** * Remove an edge */ removeEdge(edgeId) { const edge = this.edges.get(edgeId); if (!edge) { return false; } // Update node connections const sourceNode = this.nodes.get(edge.sourceId); const targetNode = this.nodes.get(edge.targetId); if (sourceNode) { sourceNode.connections = sourceNode.connections.filter((id) => id !== edge.targetId); } if (targetNode && edge.type === "bidirectional") { targetNode.connections = targetNode.connections.filter((id) => id !== edge.sourceId); } this.edges.delete(edgeId); return true; } /** * Build star topology (hub and spoke) */ buildStarTopology(agents) { if (agents.length === 0) { return; } // Find or create coordinator let coordinatorNode; if (this.config.coordinatorId) { coordinatorNode = Array.from(this.nodes.values()).find((n) => n.agentId === this.config.coordinatorId); } if (!coordinatorNode) { // Use first agent as coordinator coordinatorNode = Array.from(this.nodes.values())[0]; } coordinatorNode.role = "coordinator"; // Connect all other nodes to coordinator for (const node of this.nodes.values()) { if (node.id !== coordinatorNode.id) { node.role = "worker"; this.addEdge(coordinatorNode.id, node.id); } } } /** * Build mesh topology (fully connected) */ buildMeshTopology(_agents) { const nodeArray = Array.from(this.nodes.values()); // Connect every node to every other node for (let i = 0; i < nodeArray.length; i++) { nodeArray[i].role = "peer"; for (let j = i + 1; j < nodeArray.length; j++) { this.addEdge(nodeArray[i].id, nodeArray[j].id); } } } /** * Build hierarchical topology (tree) */ buildHierarchicalTopology(agents) { if (agents.length === 0) { return; } const nodeArray = Array.from(this.nodes.values()); const maxChildren = this.config.maxChildren ?? 3; if (maxChildren < 1) { throw new Error(`[NetworkTopology] maxChildren must be >= 1, got ${maxChildren}. ` + `A value of 0 or less would orphan every non-root node because no ` + `children can ever be assigned to a parent.`); } // Find or set root let rootNode; if (this.config.rootId) { rootNode = nodeArray.find((n) => n.agentId === this.config.rootId); } if (!rootNode) { rootNode = nodeArray[0]; } rootNode.role = "supervisor"; const assigned = new Set([rootNode.id]); const queue = [rootNode]; let nodeIndex = 1; // Start from second node while (queue.length > 0 && nodeIndex < nodeArray.length) { const parent = queue.shift(); let childCount = 0; while (childCount < maxChildren && nodeIndex < nodeArray.length) { const child = nodeArray[nodeIndex]; if (!assigned.has(child.id)) { // Set parent-child relationship child.parentId = parent.id; parent.childIds.push(child.id); child.role = nodeIndex < nodeArray.length - maxChildren ? "supervisor" : "worker"; // Add edge this.addEdge(parent.id, child.id, "unidirectional"); assigned.add(child.id); queue.push(child); childCount++; } nodeIndex++; } } } /** * Build ring topology */ buildRingTopology(_agents) { const nodeArray = Array.from(this.nodes.values()); if (nodeArray.length === 0) { return; } // Connect each node to the next, and last to first for (let i = 0; i < nodeArray.length; i++) { nodeArray[i].role = "peer"; const nextIndex = (i + 1) % nodeArray.length; this.addEdge(nodeArray[i].id, nodeArray[nextIndex].id, "unidirectional"); } } /** * Build custom topology from configuration */ buildCustomTopology(_agents) { if (!this.config.customEdges) { return; } // Create node lookup by agent ID const nodeByAgentId = new Map(); for (const node of this.nodes.values()) { nodeByAgentId.set(node.agentId, node); node.role = "peer"; } // Add custom edges for (const edge of this.config.customEdges) { const sourceNode = nodeByAgentId.get(edge.source); const targetNode = nodeByAgentId.get(edge.target); if (sourceNode && targetNode) { this.addEdge(sourceNode.id, targetNode.id, edge.bidirectional !== false ? "bidirectional" : "unidirectional"); } } } /** * Get node by ID */ getNode(nodeId) { return this.nodes.get(nodeId); } /** * Get node by agent ID */ getNodeByAgentId(agentId) { for (const node of this.nodes.values()) { if (node.agentId === agentId) { return node; } } return undefined; } /** * Get all nodes */ getAllNodes() { return Array.from(this.nodes.values()); } /** * Get all edges */ getAllEdges() { return Array.from(this.edges.values()); } /** * Get connected nodes */ getConnectedNodes(nodeId) { const node = this.nodes.get(nodeId); if (!node) { return []; } return node.connections .map((id) => this.nodes.get(id)) .filter((n) => n !== undefined); } /** * Find shortest path between two nodes (BFS) */ findShortestPath(sourceId, targetId) { if (sourceId === targetId) { return [sourceId]; } const visited = new Set(); const queue = [ { nodeId: sourceId, path: [sourceId] }, ]; while (queue.length > 0) { const { nodeId, path } = queue.shift(); if (visited.has(nodeId)) { continue; } visited.add(nodeId); const node = this.nodes.get(nodeId); if (!node) { continue; } for (const connectedId of node.connections) { if (connectedId === targetId) { return [...path, targetId]; } if (!visited.has(connectedId)) { queue.push({ nodeId: connectedId, path: [...path, connectedId] }); } } } return undefined; // No path found } /** * Check if two nodes are connected (directly or indirectly) */ areConnected(sourceId, targetId) { return this.findShortestPath(sourceId, targetId) !== undefined; } /** * Get nodes by role */ getNodesByRole(role) { return Array.from(this.nodes.values()).filter((n) => n.role === role); } /** * Get coordinator/root node */ getCoordinator() { return (Array.from(this.nodes.values()).find((n) => n.role === "coordinator") || Array.from(this.nodes.values()).find((n) => n.role === "supervisor" && !n.parentId)); } /** * Calculate topology statistics */ getStats() { const nodes = Array.from(this.nodes.values()); const nodeCount = nodes.length; const edgeCount = this.edges.size; if (nodeCount === 0) { return { nodeCount: 0, edgeCount: 0, avgConnections: 0, maxConnections: 0, minConnections: 0, diameter: 0, density: 0, }; } const connectionCounts = nodes.map((n) => n.connections.length); const maxConnections = Math.max(...connectionCounts); const minConnections = Math.min(...connectionCounts); const avgConnections = connectionCounts.reduce((a, b) => a + b, 0) / nodeCount; // Calculate diameter (max shortest path) let diameter = 0; for (const source of nodes) { for (const target of nodes) { if (source.id !== target.id) { const path = this.findShortestPath(source.id, target.id); if (path && path.length - 1 > diameter) { diameter = path.length - 1; } } } } // Calculate density const maxPossibleEdges = (nodeCount * (nodeCount - 1)) / 2; const density = maxPossibleEdges > 0 ? edgeCount / maxPossibleEdges : 0; return { nodeCount, edgeCount, avgConnections, maxConnections, minConnections, diameter, density, }; } /** * Export topology as JSON */ toJSON() { return { id: this.topologyId, type: this.config.type, nodes: Array.from(this.nodes.values()), edges: Array.from(this.edges.values()), }; } /** * Import topology from JSON */ fromJSON(data) { // Restore the original topology id so round-trips preserve identity if (data.id !== undefined) { this.topologyId = data.id; } this.config.type = data.type; this.nodes.clear(); this.edges.clear(); for (const node of data.nodes) { this.nodes.set(node.id, node); } for (const edge of data.edges) { this.edges.set(edge.id, edge); } } /** * Get topology type */ getType() { return this.config.type; } /** * Get topology ID */ getId() { return this.topologyId; } } /** * Topology builder for fluent API */ export class TopologyBuilder { agents = []; config; constructor(type) { this.config = { type }; } /** * Add an agent */ addAgent(agent) { this.agents.push(agent); return this; } /** * Add multiple agents */ addAgents(agents) { this.agents.push(...agents); return this; } /** * Set coordinator (for star topology) */ setCoordinator(agentId) { this.config.coordinatorId = agentId; return this; } /** * Set root (for hierarchical topology) */ setRoot(agentId) { this.config.rootId = agentId; return this; } /** * Set max children (for hierarchical topology) */ setMaxChildren(max) { this.config.maxChildren = max; return this; } /** * Add custom edge */ addCustomEdge(sourceAgentId, targetAgentId, bidirectional = true) { if (!this.config.customEdges) { this.config.customEdges = []; } this.config.customEdges.push({ source: sourceAgentId, target: targetAgentId, bidirectional, }); return this; } /** * Build the topology */ build() { const topology = new NetworkTopology(this.config); topology.buildFromAgents(this.agents); return topology; } }