{"version":3,"file":"graph.cjs","names":["isUuid","isRunnableInterface","toJsonSchema","uuidv4","drawMermaid","drawMermaidImage"],"sources":["../../src/runnables/graph.ts"],"sourcesContent":["import { v4 as uuidv4, validate as isUuid } from \"../utils/uuid/index.js\";\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":";;;;;;;;AAaA,SAAS,YACP,IACA,MACQ;CACR,IAAI,OAAO,KAAA,KAAa,CAACA,yBAAAA,SAAO,EAAE,GAChC,OAAO;MACF,IAAIC,cAAAA,oBAAoB,IAAI,GACjC,IAAI;EACF,IAAI,UAAU,KAAK,QAAQ;EAC3B,UAAU,QAAQ,WAAW,UAAU,IACnC,QAAQ,MAAM,CAAiB,IAC/B;EACJ,OAAO;CACT,QAAQ;EACN,OAAO,KAAK,QAAQ;CACtB;MAEA,OAAO,KAAK,QAAQ;AAExB;AAEA,SAAS,aAAa,MAAY;CAEhC,IAAIA,cAAAA,oBAAoB,KAAK,IAAI,GAC/B,OAAO;EACL,MAAM;EACN,MAAM;GACJ,IAAI,KAAK,KAAK;GACd,MAAM,KAAK,KAAK,QAAQ;EAC1B;CACF;MAEA,OAAO;EACL,MAAM;EACN,MAAM;GAAE,GAAGC,0BAAAA,aAAa,KAAK,KAAK,MAAM;GAAG,OAAO,KAAK,KAAK;EAAK;CACnE;AAEJ;AAEA,IAAa,QAAb,MAAa,MAAM;CACjB,QAA8B,CAAC;CAE/B,QAAgB,CAAC;CAEjB,YAAY,QAAyD;EACnE,KAAK,QAAQ,QAAQ,SAAS,KAAK;EACnC,KAAK,QAAQ,QAAQ,SAAS,KAAK;CACrC;CAIA,SAA8B;EAC5B,MAAM,gBAAiD,CAAC;EACxD,OAAO,OAAO,KAAK,KAAK,CAAC,CAAC,SAAS,MAAM,MAAM;GAC7C,cAAc,KAAK,MAAMF,yBAAAA,SAAO,KAAK,EAAE,IAAI,IAAI,KAAK;EACtD,CAAC;EAED,OAAO;GACL,OAAO,OAAO,OAAO,KAAK,KAAK,CAAC,CAAC,KAAK,UAAU;IAC9C,IAAI,cAAc,KAAK;IACvB,GAAG,aAAa,IAAI;GACtB,EAAE;GACF,OAAO,KAAK,MAAM,KAAK,SAAS;IAC9B,MAAM,OAAgC;KACpC,QAAQ,cAAc,KAAK;KAC3B,QAAQ,cAAc,KAAK;IAC7B;IAEA,IAAI,OAAO,KAAK,SAAS,aACvB,KAAK,OAAO,KAAK;IAGnB,IAAI,OAAO,KAAK,gBAAgB,aAC9B,KAAK,cAAc,KAAK;IAE1B,OAAO;GACT,CAAC;EACH;CACF;CAEA,QACE,MACA,IAEA,UACM;EACN,IAAI,OAAO,KAAA,KAAa,KAAK,MAAM,QAAQ,KAAA,GACzC,MAAM,IAAI,MAAM,gBAAgB,GAAG,gBAAgB;EAErD,MAAM,SAAS,MAAMG,yBAAAA,GAAO;EAC5B,MAAM,OAAa;GACjB,IAAI;GACJ;GACA,MAAM,YAAY,IAAI,IAAI;GAC1B;EACF;EACA,KAAK,MAAM,UAAU;EACrB,OAAO;CACT;CAEA,WAAW,MAAkB;EAE3B,OAAO,KAAK,MAAM,KAAK;EAGvB,KAAK,QAAQ,KAAK,MAAM,QACrB,SAAS,KAAK,WAAW,KAAK,MAAM,KAAK,WAAW,KAAK,EAC5D;CACF;CAEA,QACE,QACA,QACA,MACA,aACM;EACN,IAAI,KAAK,MAAM,OAAO,QAAQ,KAAA,GAC5B,MAAM,IAAI,MAAM,eAAe,OAAO,GAAG,cAAc;EAEzD,IAAI,KAAK,MAAM,OAAO,QAAQ,KAAA,GAC5B,MAAM,IAAI,MAAM,eAAe,OAAO,GAAG,cAAc;EAEzD,MAAM,OAAa;GACjB,QAAQ,OAAO;GACf,QAAQ,OAAO;GACf;GACA;EACF;EACA,KAAK,MAAM,KAAK,IAAI;EACpB,OAAO;CACT;CAEA,YAA8B;EAC5B,OAAO,WAAW,IAAI;CACxB;CAEA,WAA6B;EAC3B,OAAO,UAAU,IAAI;CACvB;;;;;CAMA,OAAO,OAAc,SAAS,IAAI;EAChC,IAAI,cAAc;EAElB,IADgB,OAAO,OAAO,MAAM,KAAK,CAAC,CAAC,KAAK,SAAS,KAAK,EACpD,CAAC,CAAC,MAAMH,yBAAAA,QAAM,GACtB,cAAc;EAGhB,MAAM,YAAY,OAAe;GAC/B,OAAO,cAAc,GAAG,YAAY,GAAG,OAAO;EAChD;EAEA,OAAO,QAAQ,MAAM,KAAK,CAAC,CAAC,SAAS,CAAC,KAAK,WAAW;GACpD,KAAK,MAAM,SAAS,GAAG,KAAK;IAAE,GAAG;IAAO,IAAI,SAAS,GAAG;GAAE;EAC5D,CAAC;EAED,MAAM,WAAW,MAAM,MAAM,KAAK,SAAS;GACzC,OAAO;IACL,GAAG;IACH,QAAQ,SAAS,KAAK,MAAM;IAC5B,QAAQ,SAAS,KAAK,MAAM;GAC9B;EACF,CAAC;EAED,KAAK,QAAQ,CAAC,GAAG,KAAK,OAAO,GAAG,QAAQ;EACxC,MAAM,QAAQ,MAAM,UAAU;EAC9B,MAAM,OAAO,MAAM,SAAS;EAC5B,OAAO,CACL,QAAQ;GAAE,IAAI,SAAS,MAAM,EAAE;GAAG,MAAM,MAAM;EAAK,IAAI,KAAA,GACvD,OAAO;GAAE,IAAI,SAAS,KAAK,EAAE;GAAG,MAAM,KAAK;EAAK,IAAI,KAAA,CACtD;CACF;CAEA,gBAAsB;EACpB,MAAM,YAAY,KAAK,UAAU;EACjC,IAAI,aAAa,WAAW,MAAM,CAAC,UAAU,EAAE,CAAC,GAC9C,KAAK,WAAW,SAAS;CAE7B;CAEA,eAAqB;EACnB,MAAM,WAAW,KAAK,SAAS;EAC/B,IAAI,YAAY,UAAU,MAAM,CAAC,SAAS,EAAE,CAAC,GAC3C,KAAK,WAAW,QAAQ;CAE5B;;;;;CAMA,OAAc;EACZ,MAAM,aAAqC,OAAO,YAChD,OAAO,OAAO,KAAK,KAAK,CAAC,CAAC,KAAK,SAAS,CAAC,KAAK,IAAI,KAAK,IAAI,CAAC,CAC9D;EACA,MAAM,kCAAkB,IAAI,IAAoB;EAChD,OAAO,OAAO,UAAU,CAAC,CAAC,SAAS,UAAU;GAC3C,gBAAgB,IAAI,QAAQ,gBAAgB,IAAI,KAAK,KAAK,KAAK,CAAC;EAClE,CAAC;EAED,MAAM,aAAa,WAA2B;GAC5C,MAAM,QAAQ,WAAW;GACzB,IAAIA,yBAAAA,SAAO,MAAM,KAAK,gBAAgB,IAAI,KAAK,MAAM,GACnD,OAAO;QAEP,OAAO;EAEX;EAEA,OAAO,IAAI,MAAM;GACf,OAAO,OAAO,YACZ,OAAO,QAAQ,KAAK,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,UAAU,CAC7C,UAAU,EAAE,GACZ;IAAE,GAAG;IAAM,IAAI,UAAU,EAAE;GAAE,CAC/B,CAAC,CACH;GACA,OAAO,KAAK,MAAM,KAAK,UAAU;IAC/B,GAAG;IACH,QAAQ,UAAU,KAAK,MAAM;IAC7B,QAAQ,UAAU,KAAK,MAAM;GAC/B,EAAE;EACJ,CAAC;CACH;CAEA,YAAY,QAKD;EACT,MAAM,EACJ,YACA,YACA,aAAa;GACX,SAAS;GACT,OAAO;GACP,MAAM;EACR,GACA,oBACE,UAAU,CAAC;EACf,MAAM,QAAQ,KAAK,KAAK;EACxB,MAAM,YAAY,MAAM,UAAU;EAElC,MAAM,WAAW,MAAM,SAAS;EAEhC,OAAOI,sBAAAA,YAAY,MAAM,OAAO,MAAM,OAAO;GAC3C,WAAW,WAAW;GACtB,UAAU,UAAU;GACpB;GACA;GACA;GACA;EACF,CAAC;CACH;CAEA,MAAM,eAAe,QAMH;EAEhB,OAAOC,sBAAAA,iBADe,KAAK,YAAY,MACH,GAAG,EACrC,iBAAiB,QAAQ,gBAC3B,CAAC;CACH;AACF;;;;;;;AAOA,SAAS,WAAW,OAAc,UAAoB,CAAC,GAAqB;CAC1E,MAAM,UAAU,IAAI,IAClB,MAAM,MACH,QAAQ,SAAS,CAAC,QAAQ,SAAS,KAAK,MAAM,CAAC,CAAC,CAChD,KAAK,SAAS,KAAK,MAAM,CAC9B;CAEA,MAAM,QAAgB,CAAC;CACvB,KAAK,MAAM,QAAQ,OAAO,OAAO,MAAM,KAAK,GAC1C,IAAI,CAAC,QAAQ,SAAS,KAAK,EAAE,KAAK,CAAC,QAAQ,IAAI,KAAK,EAAE,GACpD,MAAM,KAAK,IAAI;CAGnB,OAAO,MAAM,WAAW,IAAI,MAAM,KAAK,KAAA;AACzC;;;;;;;AAQA,SAAS,UAAU,OAAc,UAAoB,CAAC,GAAqB;CACzE,MAAM,UAAU,IAAI,IAClB,MAAM,MACH,QAAQ,SAAS,CAAC,QAAQ,SAAS,KAAK,MAAM,CAAC,CAAC,CAChD,KAAK,SAAS,KAAK,MAAM,CAC9B;CAEA,MAAM,QAAgB,CAAC;CACvB,KAAK,MAAM,QAAQ,OAAO,OAAO,MAAM,KAAK,GAC1C,IAAI,CAAC,QAAQ,SAAS,KAAK,EAAE,KAAK,CAAC,QAAQ,IAAI,KAAK,EAAE,GACpD,MAAM,KAAK,IAAI;CAGnB,OAAO,MAAM,WAAW,IAAI,MAAM,KAAK,KAAA;AACzC"}