@@ -45,6 +45,7 @@ A2uiStreamParserImpl::A2uiStreamParserImpl(A2uiCatalog catalog)
4545 }
4646 }
4747 }
48+ discover_reference_fields ();
4849}
4950
5051std::vector<ResponsePart> A2uiStreamParserImpl::process_chunk (const std::string& chunk) {
@@ -751,4 +752,208 @@ void A2uiStreamParserImpl::yield_messages(const std::vector<nlohmann::json>& mes
751752 }
752753}
753754
755+ std::set<std::string> A2uiStreamParserImpl::get_reachable_components (
756+ const std::string& root_id,
757+ const std::map<std::string, nlohmann::json>& seen_components
758+ ) {
759+ std::map<std::string, std::vector<std::string>> adj_list;
760+ for (const auto & pair : seen_components) {
761+ const auto & id = pair.first ;
762+ const auto & comp = pair.second ;
763+ adj_list[id] = {};
764+
765+ // Extract refs using schema analysis
766+ std::string comp_type;
767+ if (version_ == VERSION_0_9 ) {
768+ comp_type = comp.value (" component" , " " );
769+ } else {
770+ if (comp.contains (" component" ) && comp[" component" ].is_object () && !comp[" component" ].empty ()) {
771+ comp_type = comp[" component" ].begin ().key ();
772+ }
773+ }
774+
775+ if (comp_type.empty ()) {
776+ throw std::runtime_error (" Failed to determine component type during streaming" );
777+ }
778+
779+ auto it = component_ref_fields_.find (comp_type);
780+ if (it == component_ref_fields_.end ()) {
781+ throw std::runtime_error (" Schema analysis failed or component type unknown: " + comp_type);
782+ }
783+
784+ for (const auto & field_name : it->second ) {
785+ nlohmann::json props;
786+ if (version_ == VERSION_0_9 ) {
787+ props = comp;
788+ } else {
789+ if (comp.contains (" component" ) && comp[" component" ].is_object ()) {
790+ props = comp[" component" ].begin ().value ();
791+ }
792+ }
793+
794+ if (props.contains (field_name)) {
795+ const auto & val = props[field_name];
796+ if (val.is_string ()) {
797+ adj_list[id].push_back (val.get <std::string>());
798+ } else if (val.is_array ()) {
799+ for (const auto & item : val) {
800+ if (item.is_string ()) adj_list[id].push_back (item.get <std::string>());
801+ }
802+ } else if (val.is_object ()) {
803+ if (val.contains (" componentId" ) && val[" componentId" ].is_string ()) {
804+ adj_list[id].push_back (val[" componentId" ].get <std::string>());
805+ }
806+ if (val.contains (" explicitList" ) && val[" explicitList" ].is_array ()) {
807+ for (const auto & item : val[" explicitList" ]) {
808+ if (item.is_string ()) adj_list[id].push_back (item.get <std::string>());
809+ }
810+ }
811+ if (val.contains (" template" ) && val[" template" ].is_object ()) {
812+ const auto & temp = val[" template" ];
813+ if (temp.contains (" componentId" ) && temp[" componentId" ].is_string ()) {
814+ adj_list[id].push_back (temp[" componentId" ].get <std::string>());
815+ }
816+ }
817+ }
818+
819+ }
820+ }
821+
822+
823+ }
824+
825+ std::set<std::string> visited;
826+ std::set<std::string> recursion_stack;
827+
828+ std::function<void (const std::string&)> dfs = [&](const std::string& node_id) {
829+ visited.insert (node_id);
830+ recursion_stack.insert (node_id);
831+
832+ auto it = adj_list.find (node_id);
833+ if (it != adj_list.end ()) {
834+ for (const auto & neighbor : it->second ) {
835+ if (neighbor == node_id) {
836+ throw std::runtime_error (" Self-reference detected" );
837+ }
838+ if (visited.find (neighbor) == visited.end ()) {
839+ dfs (neighbor);
840+ } else if (recursion_stack.find (neighbor) != recursion_stack.end ()) {
841+ throw std::runtime_error (" Circular reference detected" );
842+ }
843+ }
844+ }
845+
846+ recursion_stack.erase (node_id);
847+ };
848+
849+ if (seen_components.find (root_id) != seen_components.end ()) {
850+ dfs (root_id);
851+ }
852+ return visited;
853+ }
854+
855+ void A2uiStreamParserImpl::extract_properties (const nlohmann::json& schema, std::vector<std::string>& ref_fields) const {
856+ if (!schema.is_object ()) return ;
857+
858+ if (schema.contains (" properties" ) && schema[" properties" ].is_object ()) {
859+ auto props = schema[" properties" ];
860+ for (auto it = props.begin (); it != props.end (); ++it) {
861+ std::string prop_name = it.key ();
862+ if (is_component_id_ref (it.value ()) || is_child_list_ref (it.value ()) ||
863+ prop_name == " child" || prop_name == " contentChild" || prop_name == " entryPointChild" || prop_name == " children" ) {
864+ ref_fields.push_back (prop_name);
865+ }
866+ }
867+ }
868+
869+ for (const auto & key : {" allOf" , " oneOf" , " anyOf" }) {
870+ if (schema.contains (key) && schema[key].is_array ()) {
871+ for (const auto & sub : schema[key]) {
872+ extract_properties (sub, ref_fields);
873+ }
874+ }
875+ }
876+ }
877+
878+
879+ void A2uiStreamParserImpl::discover_reference_fields () {
880+ auto catalog_schema = catalog_.catalog_schema ();
881+ if (!catalog_schema.contains (" components" )) return ;
882+
883+ auto comps = catalog_schema[" components" ];
884+ for (auto it = comps.begin (); it != comps.end (); ++it) {
885+ std::string comp_name = it.key ();
886+ auto comp_def = it.value ();
887+
888+ // Initialize with empty vector so we know it's a valid component type
889+ component_ref_fields_[comp_name] = {};
890+
891+ extract_properties (comp_def, component_ref_fields_[comp_name]);
892+ }
893+ }
894+
895+
896+ bool A2uiStreamParserImpl::is_component_id_ref (const nlohmann::json& schema) const {
897+ if (!schema.is_object ()) return false ;
898+
899+ if (schema.contains (" $ref" )) {
900+ std::string ref = schema[" $ref" ].get <std::string>();
901+ if (ref.rfind (" ComponentId" ) != std::string::npos ||
902+ ref.rfind (" child" ) != std::string::npos ||
903+ ref.find (" /child" ) != std::string::npos) {
904+ return true ;
905+ }
906+ }
907+
908+ if (schema.value (" type" , " " ) == " string" && schema.value (" title" , " " ) == " ComponentId" ) {
909+ return true ;
910+ }
911+
912+ for (const auto & key : {" oneOf" , " anyOf" , " allOf" }) {
913+ if (schema.contains (key) && schema[key].is_array ()) {
914+ for (const auto & item : schema[key]) {
915+ if (is_component_id_ref (item)) return true ;
916+ }
917+ }
918+ }
919+ return false ;
920+ }
921+
922+ bool A2uiStreamParserImpl::is_child_list_ref (const nlohmann::json& schema) const {
923+ if (!schema.is_object ()) return false ;
924+
925+ if (schema.contains (" $ref" )) {
926+ std::string ref = schema[" $ref" ].get <std::string>();
927+ if (ref.rfind (" ChildList" ) != std::string::npos ||
928+ ref.rfind (" children" ) != std::string::npos ||
929+ ref.find (" /children" ) != std::string::npos) {
930+ return true ;
931+ }
932+ }
933+
934+ if (schema.value (" type" , " " ) == " object" ) {
935+ if (schema.contains (" properties" ) && schema[" properties" ].is_object ()) {
936+ auto props = schema[" properties" ];
937+ if (props.contains (" explicitList" ) || props.contains (" template" ) || props.contains (" componentId" )) {
938+ return true ;
939+ }
940+ }
941+ }
942+
943+ if (schema.value (" type" , " " ) == " array" ) {
944+ if (schema.contains (" items" )) {
945+ return is_component_id_ref (schema[" items" ]);
946+ }
947+ }
948+
949+ for (const auto & key : {" oneOf" , " anyOf" , " allOf" }) {
950+ if (schema.contains (key) && schema[key].is_array ()) {
951+ for (const auto & item : schema[key]) {
952+ if (is_child_list_ref (item)) return true ;
953+ }
954+ }
955+ }
956+ return false ;
957+ }
958+
754959} // namespace a2ui
0 commit comments