* Recursively resolve the actual node type behind a source, tracing through * Group pass-through nodes.
( sourceId: string, sourceHandle: string | null, allNodes: NodeInstance[], allEdges: Edge[], visited: Set<string>, )
| 156 | * Group pass-through nodes. |
| 157 | */ |
| 158 | function resolveSourceNodeType( |
| 159 | sourceId: string, |
| 160 | sourceHandle: string | null, |
| 161 | allNodes: NodeInstance[], |
| 162 | allEdges: Edge[], |
| 163 | visited: Set<string>, |
| 164 | ): string | null { |
| 165 | if (visited.has(sourceId)) return null; |
| 166 | visited.add(sourceId); |
| 167 | |
| 168 | const sourceNode = allNodes.find(n => n.id === sourceId); |
| 169 | if (!sourceNode) return null; |
| 170 | |
| 171 | if (sourceNode.nodeType === 'Group') { |
| 172 | // The edge comes from a Group node. The sourceHandle may have an __inner |
| 173 | // suffix (raw parser edges) or may already be stripped (editor snapshot). |
| 174 | // Strip __inner to get the canonical port name, then find the edge that |
| 175 | // feeds this port. It could be: |
| 176 | // - an external edge with targetHandle === portName (input forwarding inward) |
| 177 | // - an internal edge with targetHandle === portName__inner (output receiving from inside) |
| 178 | const portName = sourceHandle?.endsWith('__inner') ? sourceHandle.slice(0, -7) : sourceHandle; |
| 179 | const upstreamEdge = allEdges.find( |
| 180 | e => e.target === sourceId |
| 181 | && (e.targetHandle === portName || e.targetHandle === `${portName}__inner`) |
| 182 | ); |
| 183 | if (!upstreamEdge) return null; |
| 184 | return resolveSourceNodeType(upstreamEdge.source, upstreamEdge.sourceHandle, allNodes, allEdges, visited); |
| 185 | } |
| 186 | |
| 187 | return sourceNode.nodeType; |
| 188 | } |
| 189 | |
| 190 | /** |
| 191 | * Helper to check if an api_key field is ready to use. |
no test coverage detected