lfx-nightly 0.2.0.dev0__py3-none-any.whl → 0.2.0.dev41__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. lfx/_assets/component_index.json +1 -1
  2. lfx/base/agents/agent.py +21 -4
  3. lfx/base/agents/altk_base_agent.py +393 -0
  4. lfx/base/agents/altk_tool_wrappers.py +565 -0
  5. lfx/base/agents/events.py +2 -1
  6. lfx/base/composio/composio_base.py +159 -224
  7. lfx/base/data/base_file.py +97 -20
  8. lfx/base/data/docling_utils.py +61 -10
  9. lfx/base/data/storage_utils.py +301 -0
  10. lfx/base/data/utils.py +178 -14
  11. lfx/base/mcp/util.py +2 -2
  12. lfx/base/models/anthropic_constants.py +21 -12
  13. lfx/base/models/groq_constants.py +74 -58
  14. lfx/base/models/groq_model_discovery.py +265 -0
  15. lfx/base/models/model.py +1 -1
  16. lfx/base/models/model_utils.py +100 -0
  17. lfx/base/models/openai_constants.py +7 -0
  18. lfx/base/models/watsonx_constants.py +32 -8
  19. lfx/base/tools/run_flow.py +601 -129
  20. lfx/cli/commands.py +9 -4
  21. lfx/cli/common.py +2 -2
  22. lfx/cli/run.py +1 -1
  23. lfx/cli/script_loader.py +53 -11
  24. lfx/components/Notion/create_page.py +1 -1
  25. lfx/components/Notion/list_database_properties.py +1 -1
  26. lfx/components/Notion/list_pages.py +1 -1
  27. lfx/components/Notion/list_users.py +1 -1
  28. lfx/components/Notion/page_content_viewer.py +1 -1
  29. lfx/components/Notion/search.py +1 -1
  30. lfx/components/Notion/update_page_property.py +1 -1
  31. lfx/components/__init__.py +19 -5
  32. lfx/components/{agents → altk}/__init__.py +5 -9
  33. lfx/components/altk/altk_agent.py +193 -0
  34. lfx/components/apify/apify_actor.py +1 -1
  35. lfx/components/composio/__init__.py +70 -18
  36. lfx/components/composio/apollo_composio.py +11 -0
  37. lfx/components/composio/bitbucket_composio.py +11 -0
  38. lfx/components/composio/canva_composio.py +11 -0
  39. lfx/components/composio/coda_composio.py +11 -0
  40. lfx/components/composio/composio_api.py +10 -0
  41. lfx/components/composio/discord_composio.py +1 -1
  42. lfx/components/composio/elevenlabs_composio.py +11 -0
  43. lfx/components/composio/exa_composio.py +11 -0
  44. lfx/components/composio/firecrawl_composio.py +11 -0
  45. lfx/components/composio/fireflies_composio.py +11 -0
  46. lfx/components/composio/gmail_composio.py +1 -1
  47. lfx/components/composio/googlebigquery_composio.py +11 -0
  48. lfx/components/composio/googlecalendar_composio.py +1 -1
  49. lfx/components/composio/googledocs_composio.py +1 -1
  50. lfx/components/composio/googlemeet_composio.py +1 -1
  51. lfx/components/composio/googlesheets_composio.py +1 -1
  52. lfx/components/composio/googletasks_composio.py +1 -1
  53. lfx/components/composio/heygen_composio.py +11 -0
  54. lfx/components/composio/mem0_composio.py +11 -0
  55. lfx/components/composio/peopledatalabs_composio.py +11 -0
  56. lfx/components/composio/perplexityai_composio.py +11 -0
  57. lfx/components/composio/serpapi_composio.py +11 -0
  58. lfx/components/composio/slack_composio.py +3 -574
  59. lfx/components/composio/slackbot_composio.py +1 -1
  60. lfx/components/composio/snowflake_composio.py +11 -0
  61. lfx/components/composio/tavily_composio.py +11 -0
  62. lfx/components/composio/youtube_composio.py +2 -2
  63. lfx/components/cuga/__init__.py +34 -0
  64. lfx/components/cuga/cuga_agent.py +730 -0
  65. lfx/components/data/__init__.py +78 -28
  66. lfx/components/data_source/__init__.py +58 -0
  67. lfx/components/{data → data_source}/api_request.py +26 -3
  68. lfx/components/{data → data_source}/csv_to_data.py +15 -10
  69. lfx/components/{data → data_source}/json_to_data.py +15 -8
  70. lfx/components/{data → data_source}/news_search.py +1 -1
  71. lfx/components/{data → data_source}/rss.py +1 -1
  72. lfx/components/{data → data_source}/sql_executor.py +1 -1
  73. lfx/components/{data → data_source}/url.py +1 -1
  74. lfx/components/{data → data_source}/web_search.py +1 -1
  75. lfx/components/datastax/astradb_cql.py +1 -1
  76. lfx/components/datastax/astradb_graph.py +1 -1
  77. lfx/components/datastax/astradb_tool.py +1 -1
  78. lfx/components/datastax/astradb_vectorstore.py +1 -1
  79. lfx/components/datastax/hcd.py +1 -1
  80. lfx/components/deactivated/json_document_builder.py +1 -1
  81. lfx/components/docling/__init__.py +0 -3
  82. lfx/components/docling/chunk_docling_document.py +3 -1
  83. lfx/components/docling/export_docling_document.py +3 -1
  84. lfx/components/elastic/elasticsearch.py +1 -1
  85. lfx/components/files_and_knowledge/__init__.py +47 -0
  86. lfx/components/{data → files_and_knowledge}/directory.py +1 -1
  87. lfx/components/{data → files_and_knowledge}/file.py +304 -24
  88. lfx/components/{knowledge_bases → files_and_knowledge}/retrieval.py +2 -2
  89. lfx/components/{data → files_and_knowledge}/save_file.py +218 -31
  90. lfx/components/flow_controls/__init__.py +58 -0
  91. lfx/components/{logic → flow_controls}/conditional_router.py +1 -1
  92. lfx/components/{logic → flow_controls}/loop.py +43 -9
  93. lfx/components/flow_controls/run_flow.py +108 -0
  94. lfx/components/glean/glean_search_api.py +1 -1
  95. lfx/components/groq/groq.py +35 -28
  96. lfx/components/helpers/__init__.py +102 -0
  97. lfx/components/ibm/watsonx.py +7 -1
  98. lfx/components/input_output/__init__.py +3 -1
  99. lfx/components/input_output/chat.py +4 -3
  100. lfx/components/input_output/chat_output.py +10 -4
  101. lfx/components/input_output/text.py +1 -1
  102. lfx/components/input_output/text_output.py +1 -1
  103. lfx/components/{data → input_output}/webhook.py +1 -1
  104. lfx/components/knowledge_bases/__init__.py +59 -4
  105. lfx/components/langchain_utilities/character.py +1 -1
  106. lfx/components/langchain_utilities/csv_agent.py +84 -16
  107. lfx/components/langchain_utilities/json_agent.py +67 -12
  108. lfx/components/langchain_utilities/language_recursive.py +1 -1
  109. lfx/components/llm_operations/__init__.py +46 -0
  110. lfx/components/{processing → llm_operations}/batch_run.py +17 -8
  111. lfx/components/{processing → llm_operations}/lambda_filter.py +1 -1
  112. lfx/components/{logic → llm_operations}/llm_conditional_router.py +1 -1
  113. lfx/components/{processing/llm_router.py → llm_operations/llm_selector.py} +3 -3
  114. lfx/components/{processing → llm_operations}/structured_output.py +1 -1
  115. lfx/components/logic/__init__.py +126 -0
  116. lfx/components/mem0/mem0_chat_memory.py +11 -0
  117. lfx/components/models/__init__.py +64 -9
  118. lfx/components/models_and_agents/__init__.py +49 -0
  119. lfx/components/{agents → models_and_agents}/agent.py +6 -4
  120. lfx/components/models_and_agents/embedding_model.py +353 -0
  121. lfx/components/models_and_agents/language_model.py +398 -0
  122. lfx/components/{agents → models_and_agents}/mcp_component.py +53 -44
  123. lfx/components/{helpers → models_and_agents}/memory.py +1 -1
  124. lfx/components/nvidia/system_assist.py +1 -1
  125. lfx/components/olivya/olivya.py +1 -1
  126. lfx/components/ollama/ollama.py +24 -5
  127. lfx/components/processing/__init__.py +9 -60
  128. lfx/components/processing/converter.py +1 -1
  129. lfx/components/processing/dataframe_operations.py +1 -1
  130. lfx/components/processing/parse_json_data.py +2 -2
  131. lfx/components/processing/parser.py +1 -1
  132. lfx/components/processing/split_text.py +1 -1
  133. lfx/components/qdrant/qdrant.py +1 -1
  134. lfx/components/redis/redis.py +1 -1
  135. lfx/components/twelvelabs/split_video.py +10 -0
  136. lfx/components/twelvelabs/video_file.py +12 -0
  137. lfx/components/utilities/__init__.py +43 -0
  138. lfx/components/{helpers → utilities}/calculator_core.py +1 -1
  139. lfx/components/{helpers → utilities}/current_date.py +1 -1
  140. lfx/components/{processing → utilities}/python_repl_core.py +1 -1
  141. lfx/components/vectorstores/local_db.py +9 -0
  142. lfx/components/youtube/youtube_transcripts.py +118 -30
  143. lfx/custom/custom_component/component.py +57 -1
  144. lfx/custom/custom_component/custom_component.py +68 -6
  145. lfx/custom/directory_reader/directory_reader.py +5 -2
  146. lfx/graph/edge/base.py +43 -20
  147. lfx/graph/state/model.py +15 -2
  148. lfx/graph/utils.py +6 -0
  149. lfx/graph/vertex/param_handler.py +10 -7
  150. lfx/helpers/__init__.py +12 -0
  151. lfx/helpers/flow.py +117 -0
  152. lfx/inputs/input_mixin.py +24 -1
  153. lfx/inputs/inputs.py +13 -1
  154. lfx/interface/components.py +161 -83
  155. lfx/log/logger.py +5 -3
  156. lfx/schema/image.py +2 -12
  157. lfx/services/database/__init__.py +5 -0
  158. lfx/services/database/service.py +25 -0
  159. lfx/services/deps.py +87 -22
  160. lfx/services/interfaces.py +5 -0
  161. lfx/services/manager.py +24 -10
  162. lfx/services/mcp_composer/service.py +1029 -162
  163. lfx/services/session.py +5 -0
  164. lfx/services/settings/auth.py +18 -11
  165. lfx/services/settings/base.py +56 -30
  166. lfx/services/settings/constants.py +8 -0
  167. lfx/services/storage/local.py +108 -46
  168. lfx/services/storage/service.py +171 -29
  169. lfx/template/field/base.py +3 -0
  170. lfx/utils/image.py +29 -11
  171. lfx/utils/ssrf_protection.py +384 -0
  172. lfx/utils/validate_cloud.py +26 -0
  173. {lfx_nightly-0.2.0.dev0.dist-info → lfx_nightly-0.2.0.dev41.dist-info}/METADATA +38 -22
  174. {lfx_nightly-0.2.0.dev0.dist-info → lfx_nightly-0.2.0.dev41.dist-info}/RECORD +189 -160
  175. {lfx_nightly-0.2.0.dev0.dist-info → lfx_nightly-0.2.0.dev41.dist-info}/WHEEL +1 -1
  176. lfx/components/agents/altk_agent.py +0 -366
  177. lfx/components/agents/cuga_agent.py +0 -1013
  178. lfx/components/docling/docling_remote_vlm.py +0 -284
  179. lfx/components/logic/run_flow.py +0 -71
  180. lfx/components/models/embedding_model.py +0 -195
  181. lfx/components/models/language_model.py +0 -144
  182. lfx/components/processing/dataframe_to_toolset.py +0 -259
  183. /lfx/components/{data → data_source}/mock_data.py +0 -0
  184. /lfx/components/{knowledge_bases → files_and_knowledge}/ingestion.py +0 -0
  185. /lfx/components/{logic → flow_controls}/data_conditional_router.py +0 -0
  186. /lfx/components/{logic → flow_controls}/flow_tool.py +0 -0
  187. /lfx/components/{logic → flow_controls}/listen.py +0 -0
  188. /lfx/components/{logic → flow_controls}/notify.py +0 -0
  189. /lfx/components/{logic → flow_controls}/pass_message.py +0 -0
  190. /lfx/components/{logic → flow_controls}/sub_flow.py +0 -0
  191. /lfx/components/{processing → models_and_agents}/prompt.py +0 -0
  192. /lfx/components/{helpers → processing}/create_list.py +0 -0
  193. /lfx/components/{helpers → processing}/output_parser.py +0 -0
  194. /lfx/components/{helpers → processing}/store_message.py +0 -0
  195. /lfx/components/{helpers → utilities}/id_generator.py +0 -0
  196. {lfx_nightly-0.2.0.dev0.dist-info → lfx_nightly-0.2.0.dev41.dist-info}/entry_points.txt +0 -0
@@ -28,6 +28,12 @@ from lfx.log.logger import logger
28
28
  from lfx.schema.data import Data
29
29
  from lfx.schema.dataframe import DataFrame
30
30
  from lfx.schema.message import Message
31
+ from lfx.utils.validate_cloud import raise_error_if_astra_cloud_disable_component
32
+
33
+ disable_component_in_astra_cloud_msg = (
34
+ "Composio tools are not supported in Astra cloud environment. "
35
+ "Please use local storage mode or cloud-based versions of the tools."
36
+ )
31
37
 
32
38
 
33
39
  class ComposioBaseComponent(Component):
@@ -135,7 +141,7 @@ class ComposioBaseComponent(Component):
135
141
  SecretStrInput(
136
142
  name="generic_api_key",
137
143
  display_name="API Key",
138
- info="",
144
+ info="Enter API key on Composio page",
139
145
  show=False,
140
146
  value="",
141
147
  required=False,
@@ -328,6 +334,8 @@ class ComposioBaseComponent(Component):
328
334
  return Message(text=str(result))
329
335
 
330
336
  def as_dataframe(self) -> DataFrame:
337
+ # Check if we're in Astra cloud environment and raise an error if we are.
338
+ raise_error_if_astra_cloud_disable_component(disable_component_in_astra_cloud_msg)
331
339
  result = self.execute_action()
332
340
 
333
341
  if isinstance(result, dict):
@@ -371,6 +379,8 @@ class ComposioBaseComponent(Component):
371
379
 
372
380
  def _build_wrapper(self) -> Composio:
373
381
  """Build the Composio wrapper."""
382
+ # Check if we're in Astra cloud environment and raise an error if we are.
383
+ raise_error_if_astra_cloud_disable_component(disable_component_in_astra_cloud_msg)
374
384
  try:
375
385
  if not self.api_key:
376
386
  msg = "Composio API Key is required"
@@ -470,11 +480,17 @@ class ComposioBaseComponent(Component):
470
480
  if parameters_schema is None:
471
481
  logger.warning(f"Parameters schema is None for action key: {action_key}")
472
482
  # Still add the action but with empty fields
483
+ # Extract version information from the tool
484
+ version = tool_dict.get("version")
485
+ available_versions = tool_dict.get("available_versions", [])
486
+
473
487
  self._action_schemas[action_key] = tool_dict
474
488
  self._actions_data[action_key] = {
475
489
  "display_name": display_name,
476
490
  "action_fields": [],
477
491
  "file_upload_fields": set(),
492
+ "version": version,
493
+ "available_versions": available_versions,
478
494
  }
479
495
  continue
480
496
 
@@ -488,11 +504,17 @@ class ComposioBaseComponent(Component):
488
504
  parameters_schema = parameters_schema.__dict__
489
505
  else:
490
506
  logger.warning(f"Cannot process parameters schema for {action_key}, skipping")
507
+ # Extract version information from the tool
508
+ version = tool_dict.get("version")
509
+ available_versions = tool_dict.get("available_versions", [])
510
+
491
511
  self._action_schemas[action_key] = tool_dict
492
512
  self._actions_data[action_key] = {
493
513
  "display_name": display_name,
494
514
  "action_fields": [],
495
515
  "file_upload_fields": set(),
516
+ "version": version,
517
+ "available_versions": available_versions,
496
518
  }
497
519
  continue
498
520
 
@@ -531,22 +553,34 @@ class ComposioBaseComponent(Component):
531
553
  elif field_name in original_descriptions:
532
554
  field_schema["description"] = original_descriptions[field_name]
533
555
  except (KeyError, TypeError, ValueError):
556
+ # Extract version information from the tool
557
+ version = tool_dict.get("version")
558
+ available_versions = tool_dict.get("available_versions", [])
559
+
534
560
  self._action_schemas[action_key] = tool_dict
535
561
  self._actions_data[action_key] = {
536
562
  "display_name": display_name,
537
563
  "action_fields": [],
538
564
  "file_upload_fields": set(),
565
+ "version": version,
566
+ "available_versions": available_versions,
539
567
  }
540
568
  continue
541
569
 
542
570
  if flat_schema is None:
543
571
  logger.warning(f"Flat schema is None for action key: {action_key}")
544
572
  # Still add the action but with empty fields so the UI doesn't break
573
+ # Extract version information from the tool
574
+ version = tool_dict.get("version")
575
+ available_versions = tool_dict.get("available_versions", [])
576
+
545
577
  self._action_schemas[action_key] = tool_dict
546
578
  self._actions_data[action_key] = {
547
579
  "display_name": display_name,
548
580
  "action_fields": [],
549
581
  "file_upload_fields": set(),
582
+ "version": version,
583
+ "available_versions": available_versions,
550
584
  }
551
585
  continue
552
586
 
@@ -619,20 +653,32 @@ class ComposioBaseComponent(Component):
619
653
  clean_field_name = p_name.replace("[0]", "")
620
654
  self._bool_variables.add(clean_field_name)
621
655
 
656
+ # Extract version information from the tool
657
+ version = tool_dict.get("version")
658
+ available_versions = tool_dict.get("available_versions", [])
659
+
622
660
  self._action_schemas[action_key] = tool_dict
623
661
  self._actions_data[action_key] = {
624
662
  "display_name": display_name,
625
663
  "action_fields": action_fields,
626
664
  "file_upload_fields": file_upload_fields,
665
+ "version": version,
666
+ "available_versions": available_versions,
627
667
  }
628
668
 
629
669
  except (KeyError, TypeError, ValueError) as flatten_error:
630
670
  logger.error(f"flatten_schema failed for {action_key}: {flatten_error}")
671
+ # Extract version information from the tool
672
+ version = tool_dict.get("version")
673
+ available_versions = tool_dict.get("available_versions", [])
674
+
631
675
  self._action_schemas[action_key] = tool_dict
632
676
  self._actions_data[action_key] = {
633
677
  "display_name": display_name,
634
678
  "action_fields": [],
635
679
  "file_upload_fields": set(),
680
+ "version": version,
681
+ "available_versions": available_versions,
636
682
  }
637
683
  continue
638
684
 
@@ -687,6 +733,9 @@ class ComposioBaseComponent(Component):
687
733
  parameters_schema = parameters_schema.copy() # Don't modify the original
688
734
  parameters_schema["required"] = []
689
735
 
736
+ # Also get top-level required fields from original schema
737
+ original_required = set(parameters_schema.get("required", []))
738
+
690
739
  try:
691
740
  # Preserve original descriptions before flattening to restore if lost
692
741
  original_descriptions = {}
@@ -910,6 +959,8 @@ class ComposioBaseComponent(Component):
910
959
  if any(getattr(i, "name", None) == top_name for i in processed_inputs):
911
960
  continue
912
961
  top_schema = props_dict.get(top_name, {})
962
+ # For MultilineInput fields (complex JSON objects/arrays)
963
+ is_required = top_name in original_required
913
964
  processed_inputs.append(
914
965
  MultilineInput(
915
966
  name=top_name,
@@ -917,7 +968,7 @@ class ComposioBaseComponent(Component):
917
968
  info=(
918
969
  top_schema.get("description") or "Provide JSON for this parameter (object or array)."
919
970
  ),
920
- required=top_name in required_fields_set,
971
+ required=is_required, # Setting original schema
921
972
  )
922
973
  )
923
974
 
@@ -1007,24 +1058,14 @@ class ComposioBaseComponent(Component):
1007
1058
  return auth_config.id
1008
1059
 
1009
1060
  def _initiate_connection(self, app_name: str) -> tuple[str, str]:
1010
- """Initiate OAuth connection and return (redirect_url, connection_id)."""
1061
+ """Initiate connection using link method and return (redirect_url, connection_id)."""
1011
1062
  try:
1012
1063
  composio = self._build_wrapper()
1013
1064
 
1014
- auth_configs = composio.auth_configs.list(toolkit_slug=app_name)
1015
- if len(auth_configs.items) == 0:
1016
- auth_config_id = self.create_new_auth_config(app_name)
1017
- else:
1018
- auth_config_id = None
1019
- for auth_config in auth_configs.items:
1020
- if auth_config.auth_scheme == "OAUTH2":
1021
- auth_config_id = auth_config.id
1022
-
1023
- auth_config_id = auth_configs.items[0].id
1065
+ # Always create a new auth config (previous behavior)
1066
+ auth_config_id = self.create_new_auth_config(app_name)
1024
1067
 
1025
- connection_request = composio.connected_accounts.initiate(
1026
- user_id=self.entity_id, auth_config_id=auth_config_id
1027
- )
1068
+ connection_request = composio.connected_accounts.link(user_id=self.entity_id, auth_config_id=auth_config_id)
1028
1069
 
1029
1070
  redirect_url = getattr(connection_request, "redirect_url", None)
1030
1071
  connection_id = getattr(connection_request, "id", None)
@@ -1037,12 +1078,12 @@ class ComposioBaseComponent(Component):
1037
1078
  msg = "No connection ID received from Composio"
1038
1079
  raise ValueError(msg)
1039
1080
 
1040
- logger.info(f"OAuth connection initiated for {app_name}: {redirect_url} (ID: {connection_id})")
1081
+ logger.info(f"Connection initiated for {app_name}: {redirect_url} (ID: {connection_id})")
1041
1082
  return redirect_url, connection_id # noqa: TRY300
1042
1083
 
1043
1084
  except (ValueError, ConnectionError, TypeError, AttributeError) as e:
1044
1085
  logger.error(f"Error initiating connection for {app_name}: {e}")
1045
- msg = f"Failed to initiate OAuth connection: {e}"
1086
+ msg = f"Failed to initiate connection: {e}"
1046
1087
  raise ValueError(msg) from e
1047
1088
 
1048
1089
  def _check_connection_status_by_id(self, connection_id: str) -> str | None:
@@ -1359,20 +1400,14 @@ class ComposioBaseComponent(Component):
1359
1400
  desc = field.get("description")
1360
1401
  self._add_text_field(build_config, name, disp, desc, required=required, default_value=default_val)
1361
1402
 
1362
- # a) AuthConfigCreation fields (for custom OAuth2, etc.)
1403
+ # Only process AuthConfigCreation fields (for custom OAuth2, etc.)
1404
+ # Connection initiation fields are now handled on Composio page via link method
1363
1405
  creation = fields.get("auth_config_creation") or fields.get("authConfigCreation") or {}
1364
1406
  # Process required fields
1365
1407
  process_fields(creation.get("required", []), required=True)
1366
1408
  # Process optional fields (excluding those with defaults and bearer_token)
1367
1409
  process_fields(creation.get("optional", []), required=False)
1368
1410
 
1369
- # b) ConnectedAccountInitiation fields (for API_KEY, etc.)
1370
- initiation = fields.get("connected_account_initiation") or fields.get("connectedAccountInitiation") or {}
1371
- # Process required fields
1372
- process_fields(initiation.get("required", []), required=True)
1373
- # Process optional fields (excluding those with defaults)
1374
- process_fields(initiation.get("optional", []), required=False)
1375
-
1376
1411
  def _collect_all_auth_field_names(self, schema: dict[str, Any] | None) -> set[str]:
1377
1412
  names: set[str] = set()
1378
1413
  if not schema:
@@ -1482,10 +1517,15 @@ class ComposioBaseComponent(Component):
1482
1517
  selected_mode = (build_config.get("auth_mode") or {}).get("value")
1483
1518
  managed = (schema or {}).get("composio_managed_auth_schemes") or []
1484
1519
  # Don't render custom fields if "Composio_Managed" is selected
1485
- if selected_mode and selected_mode != "Composio_Managed":
1520
+ # For API_KEY and other token modes, no fields are needed as they use link method
1521
+ token_modes = ["API_KEY", "BEARER_TOKEN", "BASIC"]
1522
+ if selected_mode and selected_mode not in ["Composio_Managed", *token_modes]:
1486
1523
  self._clear_auth_dynamic_fields(build_config)
1487
1524
  self._render_custom_auth_fields(build_config, schema or {}, selected_mode)
1488
1525
  # Already reordered in _render_custom_auth_fields
1526
+ elif selected_mode in token_modes:
1527
+ # Clear any existing auth fields for token-based modes
1528
+ self._clear_auth_dynamic_fields(build_config)
1489
1529
  except (TypeError, ValueError, AttributeError):
1490
1530
  pass
1491
1531
 
@@ -1632,6 +1672,9 @@ class ComposioBaseComponent(Component):
1632
1672
  if mode == "Composio_Managed":
1633
1673
  # Composio_Managed → no extra fields needed
1634
1674
  pass
1675
+ elif mode in ["API_KEY", "BEARER_TOKEN", "BASIC"]:
1676
+ # Token-based modes → no fields needed, user enters on Composio page via link
1677
+ pass
1635
1678
  elif isinstance(managed, list) and mode in managed:
1636
1679
  # This is a specific managed auth scheme (e.g., OAUTH2) but user can still choose custom
1637
1680
  # So we should render custom fields for this mode
@@ -1696,6 +1739,13 @@ class ComposioBaseComponent(Component):
1696
1739
 
1697
1740
  # Create new connection ONLY if we truly have no usable connection yet
1698
1741
  if existing_active is None:
1742
+ # Check if we already have a redirect URL in progress
1743
+ current_auth_link_value = build_config.get("auth_link", {}).get("value", "")
1744
+ if current_auth_link_value and current_auth_link_value.startswith(("http://", "https://")):
1745
+ # We already have a redirect URL, don't create a new one
1746
+ logger.info(f"Redirect URL already exists for {toolkit_slug}, skipping new creation")
1747
+ return self.update_input_types(build_config)
1748
+
1699
1749
  try:
1700
1750
  # Determine auth mode
1701
1751
  schema = self._get_toolkit_schema()
@@ -1718,7 +1768,7 @@ class ComposioBaseComponent(Component):
1718
1768
  build_config["auth_link"]["auth_tooltip"] = "Select Auth Mode"
1719
1769
  return self.update_input_types(build_config)
1720
1770
  # Custom modes: create auth config and/or initiate with config
1721
- # Validate required fields before creating any auth config
1771
+ # Only validate auth_config_creation fields for OAUTH2
1722
1772
  required_missing = []
1723
1773
  if mode == "OAUTH2":
1724
1774
  req_names_pre = self._get_schema_field_names(
@@ -1732,30 +1782,6 @@ class ComposioBaseComponent(Component):
1732
1782
  val = build_config[fname].get("value")
1733
1783
  if val in (None, ""):
1734
1784
  required_missing.append(fname)
1735
- elif mode == "API_KEY":
1736
- req_names_pre = self._get_schema_field_names(
1737
- schema,
1738
- "API_KEY",
1739
- "connected_account_initiation",
1740
- "required",
1741
- )
1742
- for fname in req_names_pre:
1743
- if fname in build_config:
1744
- val = build_config[fname].get("value")
1745
- if val in (None, ""):
1746
- required_missing.append(fname)
1747
- else:
1748
- req_names_pre = self._get_schema_field_names(
1749
- schema,
1750
- mode,
1751
- "connected_account_initiation",
1752
- "required",
1753
- )
1754
- for fname in req_names_pre:
1755
- if fname in build_config:
1756
- val = build_config[fname].get("value")
1757
- if val in (None, ""):
1758
- required_missing.append(fname)
1759
1785
  if required_missing:
1760
1786
  # Surface errors on each missing field
1761
1787
  for fname in required_missing:
@@ -1779,24 +1805,18 @@ class ComposioBaseComponent(Component):
1779
1805
  # If an auth_config was already created via the button, use it and include initiation fields
1780
1806
  stored_ac_id = (build_config.get("auth_link") or {}).get("auth_config_id")
1781
1807
  if stored_ac_id:
1782
- # Build val from schema-declared connected_account_initiation required + rendered fields
1783
- val_payload = {}
1784
- init_req = self._get_schema_field_names(
1785
- schema,
1786
- "OAUTH2",
1787
- "connected_account_initiation",
1788
- "required",
1789
- )
1790
- candidate_names = set(self._auth_dynamic_fields) | init_req
1791
- for fname in candidate_names:
1792
- if fname in build_config:
1793
- v = build_config[fname].get("value")
1794
- if v not in (None, ""):
1795
- val_payload[fname] = v
1796
- redirect = composio.connected_accounts.initiate(
1808
+ # Check if we already have a redirect URL to prevent duplicates
1809
+ current_link_value = build_config.get("auth_link", {}).get("value", "")
1810
+ if current_link_value and current_link_value.startswith(("http://", "https://")):
1811
+ logger.info(
1812
+ f"Redirect URL already exists for {toolkit_slug} OAUTH2, skipping new creation"
1813
+ )
1814
+ return self.update_input_types(build_config)
1815
+
1816
+ # Use link method - no need to collect connection initiation fields
1817
+ redirect = composio.connected_accounts.link(
1797
1818
  user_id=self.entity_id,
1798
1819
  auth_config_id=stored_ac_id,
1799
- config={"auth_scheme": "OAUTH2", "val": val_payload} if val_payload else None,
1800
1820
  )
1801
1821
  redirect_url = getattr(redirect, "redirect_url", None)
1802
1822
  connection_id = getattr(redirect, "id", None)
@@ -1807,6 +1827,9 @@ class ComposioBaseComponent(Component):
1807
1827
  # Clear action blocker text on successful initiation
1808
1828
  build_config["action_button"]["helper_text"] = ""
1809
1829
  build_config["action_button"]["helper_text_metadata"] = {}
1830
+ # Clear any auth fields
1831
+ schema = self._get_toolkit_schema()
1832
+ self._clear_auth_fields_from_schema(build_config, schema)
1810
1833
  return self.update_input_types(build_config)
1811
1834
  # Otherwise, create custom OAuth2 auth config using schema-declared required fields
1812
1835
  credentials = {}
@@ -1827,6 +1850,14 @@ class ComposioBaseComponent(Component):
1827
1850
  else:
1828
1851
  missing.append(fname)
1829
1852
  # proceed even if missing optional; backend will validate
1853
+ # Check if we already have a redirect URL to prevent duplicates
1854
+ current_link_value = build_config.get("auth_link", {}).get("value", "")
1855
+ if current_link_value and current_link_value.startswith(("http://", "https://")):
1856
+ logger.info(
1857
+ f"Redirect URL already exists for {toolkit_slug} OAUTH2, skipping new creation"
1858
+ )
1859
+ return self.update_input_types(build_config)
1860
+
1830
1861
  ac = composio.auth_configs.create(
1831
1862
  toolkit=toolkit_slug,
1832
1863
  options={
@@ -1836,30 +1867,8 @@ class ComposioBaseComponent(Component):
1836
1867
  },
1837
1868
  )
1838
1869
  auth_config_id = getattr(ac, "id", None)
1839
- # If the schema declares initiation required fields, render them and defer initiation
1840
- init_req = self._get_schema_field_names(
1841
- schema,
1842
- "OAUTH2",
1843
- "connected_account_initiation",
1844
- "required",
1845
- )
1846
- if init_req:
1847
- self._clear_auth_dynamic_fields(build_config)
1848
- for name in init_req:
1849
- self._add_text_field(
1850
- build_config,
1851
- name=name,
1852
- display_name=name.replace("_", " ").title(),
1853
- info="Provide connection parameter",
1854
- required=True,
1855
- )
1856
- build_config.setdefault("auth_link", {})
1857
- build_config["auth_link"]["auth_config_id"] = auth_config_id
1858
- build_config["auth_link"]["value"] = "connect"
1859
- build_config["auth_link"]["auth_tooltip"] = "Connect"
1860
- return self.update_input_types(build_config)
1861
- # Otherwise initiate immediately
1862
- redirect = composio.connected_accounts.initiate(
1870
+ # Use link method directly - no need to check for connection initiation fields
1871
+ redirect = composio.connected_accounts.link(
1863
1872
  user_id=self.entity_id,
1864
1873
  auth_config_id=auth_config_id,
1865
1874
  )
@@ -1876,141 +1885,64 @@ class ComposioBaseComponent(Component):
1876
1885
  build_config["action_button"]["helper_text_metadata"] = {}
1877
1886
  return self.update_input_types(build_config)
1878
1887
  if mode == "API_KEY":
1888
+ # Check if we already have a redirect URL to prevent duplicates
1889
+ current_link_value = build_config.get("auth_link", {}).get("value", "")
1890
+ if current_link_value and current_link_value.startswith(("http://", "https://")):
1891
+ logger.info(
1892
+ f"Redirect URL already exists for {toolkit_slug} API_KEY, skipping new creation"
1893
+ )
1894
+ return self.update_input_types(build_config)
1895
+
1879
1896
  ac = composio.auth_configs.create(
1880
1897
  toolkit=toolkit_slug,
1881
1898
  options={"type": "use_custom_auth", "auth_scheme": "API_KEY", "credentials": {}},
1882
1899
  )
1883
1900
  auth_config_id = getattr(ac, "id", None)
1884
- # Build initiation config.val from schema-declared required names and dynamic fields
1885
- val_payload = {}
1886
- missing = []
1887
- # Collect required names from schema
1888
- req_names = self._get_schema_field_names(
1889
- schema,
1890
- "API_KEY",
1891
- "connected_account_initiation",
1892
- "required",
1893
- )
1894
- # Merge rendered dynamic fields and schema-required names
1895
- candidate_names = set(self._auth_dynamic_fields) | req_names
1896
- for fname in candidate_names:
1897
- if fname in build_config:
1898
- val = build_config[fname].get("value")
1899
- if val not in (None, ""):
1900
- val_payload[fname] = val
1901
- else:
1902
- missing.append(fname)
1903
- initiation = composio.connected_accounts.initiate(
1901
+ # Use link method - user will enter API key on Composio page
1902
+ initiation = composio.connected_accounts.link(
1904
1903
  user_id=self.entity_id,
1905
1904
  auth_config_id=auth_config_id,
1906
- config={"auth_scheme": "API_KEY", "val": val_payload},
1907
1905
  )
1908
1906
  connection_id = getattr(initiation, "id", None)
1909
1907
  redirect_url = getattr(initiation, "redirect_url", None)
1910
- # Do not store connection_id on initiation; only when ACTIVE
1908
+ # API_KEY now also returns redirect URL with new link method
1911
1909
  if redirect_url:
1912
1910
  build_config["auth_link"]["value"] = redirect_url
1913
1911
  build_config["auth_link"]["auth_tooltip"] = "Disconnect"
1914
- else:
1915
- # No redirect for API_KEY; mark as connected
1916
- build_config["auth_link"]["value"] = "validated"
1917
- build_config["auth_link"]["auth_tooltip"] = "Disconnect"
1918
- # In both cases, hide auth fields immediately after successful initiation
1912
+ # Hide auth fields immediately after successful initiation
1919
1913
  schema = self._get_toolkit_schema()
1920
1914
  self._clear_auth_fields_from_schema(build_config, schema)
1921
1915
  build_config["action_button"]["helper_text"] = ""
1922
1916
  build_config["action_button"]["helper_text_metadata"] = {}
1923
1917
 
1924
- # Convert auth_mode to pill for connected state
1925
- if not redirect_url and mode: # API_KEY or similar direct connection
1926
- build_config["auth_link"]["connection_id"] = connection_id
1927
- build_config.setdefault("auth_mode", {})
1928
- build_config["auth_mode"]["value"] = mode
1929
- build_config["auth_mode"]["options"] = [mode]
1930
- build_config["auth_mode"]["show"] = False
1931
- try:
1932
- pill = TabInput(
1933
- name="auth_mode",
1934
- display_name="Auth Mode",
1935
- options=[mode],
1936
- value=mode,
1937
- ).to_dict()
1938
- pill["show"] = True
1939
- build_config["auth_mode"] = pill
1940
- except (TypeError, ValueError, AttributeError):
1941
- build_config["auth_mode"] = {
1942
- "name": "auth_mode",
1943
- "display_name": "Auth Mode",
1944
- "type": "tab",
1945
- "options": [mode],
1946
- "value": mode,
1947
- "show": True,
1948
- }
1949
-
1950
1918
  return self.update_input_types(build_config)
1951
1919
  # Generic custom auth flow for any other mode (treat like API_KEY)
1920
+ # Check if we already have a redirect URL to prevent duplicates
1921
+ current_link_value = build_config.get("auth_link", {}).get("value", "")
1922
+ if current_link_value and current_link_value.startswith(("http://", "https://")):
1923
+ logger.info(f"Redirect URL already exists for {toolkit_slug} {mode}, skipping new creation")
1924
+ return self.update_input_types(build_config)
1925
+
1952
1926
  ac = composio.auth_configs.create(
1953
1927
  toolkit=toolkit_slug,
1954
1928
  options={"type": "use_custom_auth", "auth_scheme": mode, "credentials": {}},
1955
1929
  )
1956
1930
  auth_config_id = getattr(ac, "id", None)
1957
- val_payload = {}
1958
- req_names = self._get_schema_field_names(
1959
- schema,
1960
- mode,
1961
- "connected_account_initiation",
1962
- "required",
1963
- )
1964
- candidate_names = set(self._auth_dynamic_fields) | req_names
1965
- for fname in candidate_names:
1966
- if fname in build_config:
1967
- val = build_config[fname].get("value")
1968
- if val not in (None, ""):
1969
- val_payload[fname] = val
1970
- initiation = composio.connected_accounts.initiate(
1931
+ # Use link method - user will enter required fields on Composio page
1932
+ initiation = composio.connected_accounts.link(
1971
1933
  user_id=self.entity_id,
1972
1934
  auth_config_id=auth_config_id,
1973
- config={"auth_scheme": mode, "val": val_payload},
1974
1935
  )
1975
1936
  connection_id = getattr(initiation, "id", None)
1976
1937
  redirect_url = getattr(initiation, "redirect_url", None)
1977
- # Do not store connection_id on initiation; only when ACTIVE
1978
1938
  if redirect_url:
1979
1939
  build_config["auth_link"]["value"] = redirect_url
1980
1940
  build_config["auth_link"]["auth_tooltip"] = "Disconnect"
1981
- else:
1982
- build_config["auth_link"]["value"] = "validated"
1983
- build_config["auth_link"]["auth_tooltip"] = "Disconnect"
1984
- build_config["auth_link"]["connection_id"] = connection_id
1985
-
1986
- # Clear auth fields when connected
1987
- schema = self._get_toolkit_schema()
1988
- self._clear_auth_fields_from_schema(build_config, schema)
1989
-
1990
- # Convert auth_mode to pill for connected state
1991
- if mode:
1992
- build_config.setdefault("auth_mode", {})
1993
- build_config["auth_mode"]["value"] = mode
1994
- build_config["auth_mode"]["options"] = [mode]
1995
- build_config["auth_mode"]["show"] = False
1996
- try:
1997
- pill = TabInput(
1998
- name="auth_mode",
1999
- display_name="Auth Mode",
2000
- options=[mode],
2001
- value=mode,
2002
- ).to_dict()
2003
- pill["show"] = True
2004
- build_config["auth_mode"] = pill
2005
- except (TypeError, ValueError, AttributeError):
2006
- build_config["auth_mode"] = {
2007
- "name": "auth_mode",
2008
- "display_name": "Auth Mode",
2009
- "type": "tab",
2010
- "options": [mode],
2011
- "value": mode,
2012
- "show": True,
2013
- }
1941
+ # Clear auth fields
1942
+ schema = self._get_toolkit_schema()
1943
+ self._clear_auth_fields_from_schema(build_config, schema)
1944
+ build_config["action_button"]["helper_text"] = ""
1945
+ build_config["action_button"]["helper_text_metadata"] = {}
2014
1946
  return self.update_input_types(build_config)
2015
1947
  except (ValueError, ConnectionError, TypeError) as e:
2016
1948
  logger.error(f"Error creating connection: {e}")
@@ -2144,7 +2076,12 @@ class ComposioBaseComponent(Component):
2144
2076
  schema = self._get_toolkit_schema()
2145
2077
  mode = (build_config.get("auth_mode") or {}).get("value")
2146
2078
  managed = (schema or {}).get("composio_managed_auth_schemes") or []
2147
- if mode and mode != "Composio_Managed" and not getattr(self, "_auth_dynamic_fields", set()):
2079
+ token_modes = ["API_KEY", "BEARER_TOKEN", "BASIC"]
2080
+ if (
2081
+ mode
2082
+ and mode not in ["Composio_Managed", *token_modes]
2083
+ and not getattr(self, "_auth_dynamic_fields", set())
2084
+ ):
2148
2085
  self._render_custom_auth_fields(build_config, schema or {}, mode)
2149
2086
  # Already reordered in _render_custom_auth_fields
2150
2087
  except (TypeError, ValueError, AttributeError):
@@ -2189,6 +2126,12 @@ class ComposioBaseComponent(Component):
2189
2126
  # Handle auth config button click
2190
2127
  if field_name == "create_auth_config" and field_value == "create":
2191
2128
  try:
2129
+ # Check if we already have a redirect URL to prevent duplicates
2130
+ current_link_value = build_config.get("auth_link", {}).get("value", "")
2131
+ if current_link_value and current_link_value.startswith(("http://", "https://")):
2132
+ logger.info("Redirect URL already exists, skipping new auth config creation")
2133
+ return self.update_input_types(build_config)
2134
+
2192
2135
  composio = self._build_wrapper()
2193
2136
  toolkit_slug = self.app_name.lower()
2194
2137
  schema = self._get_toolkit_schema() or {}
@@ -2209,29 +2152,8 @@ class ComposioBaseComponent(Component):
2209
2152
  auth_config_id = getattr(ac, "id", None)
2210
2153
  build_config.setdefault("auth_link", {})
2211
2154
  if auth_config_id:
2212
- # Check if there are connection initiation required fields
2213
- initiation_required = self._get_schema_field_names(
2214
- schema, "OAUTH2", "connected_account_initiation", "required"
2215
- )
2216
- if initiation_required:
2217
- # Populate those fields dynamically for the user to fill
2218
- self._clear_auth_dynamic_fields(build_config)
2219
- for name in initiation_required:
2220
- # Render as text inputs to collect connection fields
2221
- self._add_text_field(
2222
- build_config,
2223
- name=name,
2224
- display_name=name.replace("_", " ").title(),
2225
- info="Provide connection parameter",
2226
- required=True,
2227
- )
2228
- # Store the new auth_config_id so pressing Connect will use it
2229
- build_config["auth_link"]["auth_config_id"] = auth_config_id
2230
- build_config["auth_link"]["value"] = "connect"
2231
- build_config["auth_link"]["auth_tooltip"] = "Connect"
2232
- return self.update_input_types(build_config)
2233
- # If no initiation fields required, initiate immediately
2234
- connection_request = composio.connected_accounts.initiate(
2155
+ # Use link method directly - no need to check for connection initiation fields
2156
+ connection_request = composio.connected_accounts.link(
2235
2157
  user_id=self.entity_id, auth_config_id=auth_config_id
2236
2158
  )
2237
2159
  redirect_url = getattr(connection_request, "redirect_url", None)
@@ -2438,6 +2360,8 @@ class ComposioBaseComponent(Component):
2438
2360
 
2439
2361
  def execute_action(self):
2440
2362
  """Execute the selected Composio tool."""
2363
+ # Check if we're in Astra cloud environment and raise an error if we are.
2364
+ raise_error_if_astra_cloud_disable_component(disable_component_in_astra_cloud_msg)
2441
2365
  composio = self._build_wrapper()
2442
2366
  self._populate_actions_data()
2443
2367
  self._build_action_maps()
@@ -2507,12 +2431,23 @@ class ComposioBaseComponent(Component):
2507
2431
 
2508
2432
  arguments[final_field_name] = value
2509
2433
 
2510
- # Execute using new SDK
2511
- result = composio.tools.execute(
2512
- slug=action_key,
2513
- arguments=arguments,
2514
- user_id=self.entity_id,
2515
- )
2434
+ # Get the version from the action data
2435
+ version = self._actions_data.get(action_key, {}).get("version")
2436
+ if version:
2437
+ logger.info(f"Executing {action_key} with version: {version}")
2438
+
2439
+ # Execute using new SDK with version parameter
2440
+ execute_params = {
2441
+ "slug": action_key,
2442
+ "arguments": arguments,
2443
+ "user_id": self.entity_id,
2444
+ }
2445
+
2446
+ # Only add version if it's available
2447
+ if version:
2448
+ execute_params["version"] = version
2449
+
2450
+ result = composio.tools.execute(**execute_params)
2516
2451
 
2517
2452
  if isinstance(result, dict) and "successful" in result:
2518
2453
  if result["successful"]: