{"version":3,"file":"graph.cjs","names":["isRunnableInterface","toJsonSchema","isUuid","drawMermaid","drawMermaidImage"],"sources":["../../src/runnables/graph.ts"],"sourcesContent":["import { v4 as uuidv4, validate as isUuid } from \"uuid\";\nimport type {\n  RunnableInterface,\n  RunnableIOSchema,\n  Node,\n  Edge,\n} from \"./types.js\";\nimport { isRunnableInterface } from \"./utils.js\";\nimport { drawMermaid, drawMermaidImage } from \"./graph_mermaid.js\";\nimport { toJsonSchema } from \"../utils/json_schema.js\";\n\nexport { Node, Edge };\n\nfunction nodeDataStr(\n  id: string | undefined,\n  data: RunnableInterface | RunnableIOSchema\n): string {\n  if (id !== undefined && !isUuid(id)) {\n    return id;\n  } else if (isRunnableInterface(data)) {\n    try {\n      let dataStr = data.getName();\n      dataStr = dataStr.startsWith(\"Runnable\")\n        ? dataStr.slice(\"Runnable\".length)\n        : dataStr;\n      return dataStr;\n    } catch {\n      return data.getName();\n    }\n  } else {\n    return data.name ?? \"UnknownSchema\";\n  }\n}\n\nfunction nodeDataJson(node: Node) {\n  // if node.data implements Runnable\n  if (isRunnableInterface(node.data)) {\n    return {\n      type: \"runnable\",\n      data: {\n        id: node.data.lc_id,\n        name: node.data.getName(),\n      },\n    };\n  } else {\n    return {\n      type: \"schema\",\n      data: { ...toJsonSchema(node.data.schema), title: node.data.name },\n    };\n  }\n}\n\nexport class Graph {\n  nodes: Record<string, Node> = {};\n\n  edges: Edge[] = [];\n\n  constructor(params?: { nodes: Record<string, Node>; edges: Edge[] }) {\n    this.nodes = params?.nodes ?? this.nodes;\n    this.edges = params?.edges ?? this.edges;\n  }\n\n  // Convert the graph to a JSON-serializable format.\n  // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n  toJSON(): Record<string, any> {\n    const stableNodeIds: Record<string, string | number> = {};\n    Object.values(this.nodes).forEach((node, i) => {\n      stableNodeIds[node.id] = isUuid(node.id) ? i : node.id;\n    });\n\n    return {\n      nodes: Object.values(this.nodes).map((node) => ({\n        id: stableNodeIds[node.id],\n        ...nodeDataJson(node),\n      })),\n      edges: this.edges.map((edge) => {\n        const item: Record<string, unknown> = {\n          source: stableNodeIds[edge.source],\n          target: stableNodeIds[edge.target],\n        };\n\n        if (typeof edge.data !== \"undefined\") {\n          item.data = edge.data;\n        }\n\n        if (typeof edge.conditional !== \"undefined\") {\n          item.conditional = edge.conditional;\n        }\n        return item;\n      }),\n    };\n  }\n\n  addNode(\n    data: RunnableInterface | RunnableIOSchema,\n    id?: string,\n    // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n    metadata?: Record<string, any>\n  ): Node {\n    if (id !== undefined && this.nodes[id] !== undefined) {\n      throw new Error(`Node with id ${id} already exists`);\n    }\n    const nodeId = id ?? uuidv4();\n    const node: Node = {\n      id: nodeId,\n      data,\n      name: nodeDataStr(id, data),\n      metadata,\n    };\n    this.nodes[nodeId] = node;\n    return node;\n  }\n\n  removeNode(node: Node): void {\n    // Remove the node from the nodes map\n    delete this.nodes[node.id];\n\n    // Filter out edges connected to the node\n    this.edges = this.edges.filter(\n      (edge) => edge.source !== node.id && edge.target !== node.id\n    );\n  }\n\n  addEdge(\n    source: Node,\n    target: Node,\n    data?: string,\n    conditional?: boolean\n  ): Edge {\n    if (this.nodes[source.id] === undefined) {\n      throw new Error(`Source node ${source.id} not in graph`);\n    }\n    if (this.nodes[target.id] === undefined) {\n      throw new Error(`Target node ${target.id} not in graph`);\n    }\n    const edge: Edge = {\n      source: source.id,\n      target: target.id,\n      data,\n      conditional,\n    };\n    this.edges.push(edge);\n    return edge;\n  }\n\n  firstNode(): Node | undefined {\n    return _firstNode(this);\n  }\n\n  lastNode(): Node | undefined {\n    return _lastNode(this);\n  }\n\n  /**\n   * Add all nodes and edges from another graph.\n   * Note this doesn't check for duplicates, nor does it connect the graphs.\n   */\n  extend(graph: Graph, prefix = \"\") {\n    let finalPrefix = prefix;\n    const nodeIds = Object.values(graph.nodes).map((node) => node.id);\n    if (nodeIds.every(isUuid)) {\n      finalPrefix = \"\";\n    }\n\n    const prefixed = (id: string) => {\n      return finalPrefix ? `${finalPrefix}:${id}` : id;\n    };\n\n    Object.entries(graph.nodes).forEach(([key, value]) => {\n      this.nodes[prefixed(key)] = { ...value, id: prefixed(key) };\n    });\n\n    const newEdges = graph.edges.map((edge) => {\n      return {\n        ...edge,\n        source: prefixed(edge.source),\n        target: prefixed(edge.target),\n      };\n    });\n    // Add all edges from the other graph\n    this.edges = [...this.edges, ...newEdges];\n    const first = graph.firstNode();\n    const last = graph.lastNode();\n    return [\n      first ? { id: prefixed(first.id), data: first.data } : undefined,\n      last ? { id: prefixed(last.id), data: last.data } : undefined,\n    ];\n  }\n\n  trimFirstNode(): void {\n    const firstNode = this.firstNode();\n    if (firstNode && _firstNode(this, [firstNode.id])) {\n      this.removeNode(firstNode);\n    }\n  }\n\n  trimLastNode(): void {\n    const lastNode = this.lastNode();\n    if (lastNode && _lastNode(this, [lastNode.id])) {\n      this.removeNode(lastNode);\n    }\n  }\n\n  /**\n   * Return a new graph with all nodes re-identified,\n   * using their unique, readable names where possible.\n   */\n  reid(): Graph {\n    const nodeLabels: Record<string, string> = Object.fromEntries(\n      Object.values(this.nodes).map((node) => [node.id, node.name])\n    );\n    const nodeLabelCounts = new Map<string, number>();\n    Object.values(nodeLabels).forEach((label) => {\n      nodeLabelCounts.set(label, (nodeLabelCounts.get(label) || 0) + 1);\n    });\n\n    const getNodeId = (nodeId: string): string => {\n      const label = nodeLabels[nodeId];\n      if (isUuid(nodeId) && nodeLabelCounts.get(label) === 1) {\n        return label;\n      } else {\n        return nodeId;\n      }\n    };\n\n    return new Graph({\n      nodes: Object.fromEntries(\n        Object.entries(this.nodes).map(([id, node]) => [\n          getNodeId(id),\n          { ...node, id: getNodeId(id) },\n        ])\n      ),\n      edges: this.edges.map((edge) => ({\n        ...edge,\n        source: getNodeId(edge.source),\n        target: getNodeId(edge.target),\n      })),\n    });\n  }\n\n  drawMermaid(params?: {\n    withStyles?: boolean;\n    curveStyle?: string;\n    nodeColors?: Record<string, string>;\n    wrapLabelNWords?: number;\n  }): string {\n    const {\n      withStyles,\n      curveStyle,\n      nodeColors = {\n        default: \"fill:#f2f0ff,line-height:1.2\",\n        first: \"fill-opacity:0\",\n        last: \"fill:#bfb6fc\",\n      },\n      wrapLabelNWords,\n    } = params ?? {};\n    const graph = this.reid();\n    const firstNode = graph.firstNode();\n\n    const lastNode = graph.lastNode();\n\n    return drawMermaid(graph.nodes, graph.edges, {\n      firstNode: firstNode?.id,\n      lastNode: lastNode?.id,\n      withStyles,\n      curveStyle,\n      nodeColors,\n      wrapLabelNWords,\n    });\n  }\n\n  async drawMermaidPng(params?: {\n    withStyles?: boolean;\n    curveStyle?: string;\n    nodeColors?: Record<string, string>;\n    wrapLabelNWords?: number;\n    backgroundColor?: string;\n  }): Promise<Blob> {\n    const mermaidSyntax = this.drawMermaid(params);\n    return drawMermaidImage(mermaidSyntax, {\n      backgroundColor: params?.backgroundColor,\n    });\n  }\n}\n/**\n * Find the single node that is not a target of any edge.\n * Exclude nodes/sources with ids in the exclude list.\n * If there is no such node, or there are multiple, return undefined.\n * When drawing the graph, this node would be the origin.\n */\nfunction _firstNode(graph: Graph, exclude: string[] = []): Node | undefined {\n  const targets = new Set(\n    graph.edges\n      .filter((edge) => !exclude.includes(edge.source))\n      .map((edge) => edge.target)\n  );\n\n  const found: Node[] = [];\n  for (const node of Object.values(graph.nodes)) {\n    if (!exclude.includes(node.id) && !targets.has(node.id)) {\n      found.push(node);\n    }\n  }\n  return found.length === 1 ? found[0] : undefined;\n}\n\n/**\n * Find the single node that is not a source of any edge.\n * Exclude nodes/targets with ids in the exclude list.\n * If there is no such node, or there are multiple, return undefined.\n * When drawing the graph, this node would be the destination.\n */\nfunction _lastNode(graph: Graph, exclude: string[] = []): Node | undefined {\n  const sources = new Set(\n    graph.edges\n      .filter((edge) => !exclude.includes(edge.target))\n      .map((edge) => edge.source)\n  );\n\n  const found: Node[] = [];\n  for (const node of Object.values(graph.nodes)) {\n    if (!exclude.includes(node.id) && !sources.has(node.id)) {\n      found.push(node);\n    }\n  }\n  return found.length === 1 ? found[0] : undefined;\n}\n"],"mappings":"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"}