lfx-nightly 0.1.13.dev0__py3-none-any.whl → 0.2.0.dev26__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.
- lfx/_assets/component_index.json +1 -1
- lfx/base/agents/agent.py +121 -29
- lfx/base/agents/altk_base_agent.py +380 -0
- lfx/base/agents/altk_tool_wrappers.py +565 -0
- lfx/base/agents/events.py +103 -35
- lfx/base/agents/utils.py +15 -2
- lfx/base/composio/composio_base.py +183 -233
- lfx/base/data/base_file.py +88 -21
- lfx/base/data/storage_utils.py +192 -0
- lfx/base/data/utils.py +178 -14
- lfx/base/datastax/__init__.py +5 -0
- lfx/{components/vectorstores/astradb.py → base/datastax/astradb_base.py} +84 -473
- lfx/base/embeddings/embeddings_class.py +113 -0
- lfx/base/io/chat.py +5 -4
- lfx/base/mcp/util.py +101 -15
- lfx/base/models/groq_constants.py +74 -58
- lfx/base/models/groq_model_discovery.py +265 -0
- lfx/base/models/model.py +1 -1
- lfx/base/models/model_input_constants.py +74 -7
- lfx/base/models/model_utils.py +100 -0
- lfx/base/models/ollama_constants.py +3 -0
- lfx/base/models/openai_constants.py +7 -0
- lfx/base/models/watsonx_constants.py +36 -0
- lfx/base/tools/run_flow.py +601 -129
- lfx/cli/commands.py +7 -4
- lfx/cli/common.py +2 -2
- lfx/cli/run.py +1 -1
- lfx/cli/script_loader.py +53 -11
- lfx/components/Notion/create_page.py +1 -1
- lfx/components/Notion/list_database_properties.py +1 -1
- lfx/components/Notion/list_pages.py +1 -1
- lfx/components/Notion/list_users.py +1 -1
- lfx/components/Notion/page_content_viewer.py +1 -1
- lfx/components/Notion/search.py +1 -1
- lfx/components/Notion/update_page_property.py +1 -1
- lfx/components/__init__.py +19 -5
- lfx/components/altk/__init__.py +34 -0
- lfx/components/altk/altk_agent.py +193 -0
- lfx/components/amazon/amazon_bedrock_converse.py +1 -1
- lfx/components/apify/apify_actor.py +4 -4
- lfx/components/composio/__init__.py +70 -18
- lfx/components/composio/apollo_composio.py +11 -0
- lfx/components/composio/bitbucket_composio.py +11 -0
- lfx/components/composio/canva_composio.py +11 -0
- lfx/components/composio/coda_composio.py +11 -0
- lfx/components/composio/composio_api.py +10 -0
- lfx/components/composio/discord_composio.py +1 -1
- lfx/components/composio/elevenlabs_composio.py +11 -0
- lfx/components/composio/exa_composio.py +11 -0
- lfx/components/composio/firecrawl_composio.py +11 -0
- lfx/components/composio/fireflies_composio.py +11 -0
- lfx/components/composio/gmail_composio.py +1 -1
- lfx/components/composio/googlebigquery_composio.py +11 -0
- lfx/components/composio/googlecalendar_composio.py +1 -1
- lfx/components/composio/googledocs_composio.py +1 -1
- lfx/components/composio/googlemeet_composio.py +1 -1
- lfx/components/composio/googlesheets_composio.py +1 -1
- lfx/components/composio/googletasks_composio.py +1 -1
- lfx/components/composio/heygen_composio.py +11 -0
- lfx/components/composio/mem0_composio.py +11 -0
- lfx/components/composio/peopledatalabs_composio.py +11 -0
- lfx/components/composio/perplexityai_composio.py +11 -0
- lfx/components/composio/serpapi_composio.py +11 -0
- lfx/components/composio/slack_composio.py +3 -574
- lfx/components/composio/slackbot_composio.py +1 -1
- lfx/components/composio/snowflake_composio.py +11 -0
- lfx/components/composio/tavily_composio.py +11 -0
- lfx/components/composio/youtube_composio.py +2 -2
- lfx/components/{agents → cuga}/__init__.py +5 -7
- lfx/components/cuga/cuga_agent.py +730 -0
- lfx/components/data/__init__.py +78 -28
- lfx/components/data_source/__init__.py +58 -0
- lfx/components/{data → data_source}/api_request.py +26 -3
- lfx/components/{data → data_source}/csv_to_data.py +15 -10
- lfx/components/{data → data_source}/json_to_data.py +15 -8
- lfx/components/{data → data_source}/news_search.py +1 -1
- lfx/components/{data → data_source}/rss.py +1 -1
- lfx/components/{data → data_source}/sql_executor.py +1 -1
- lfx/components/{data → data_source}/url.py +1 -1
- lfx/components/{data → data_source}/web_search.py +1 -1
- lfx/components/datastax/__init__.py +12 -6
- lfx/components/datastax/{astra_assistant_manager.py → astradb_assistant_manager.py} +1 -0
- lfx/components/datastax/astradb_chatmemory.py +40 -0
- lfx/components/datastax/astradb_cql.py +6 -32
- lfx/components/datastax/astradb_graph.py +10 -124
- lfx/components/datastax/astradb_tool.py +13 -53
- lfx/components/datastax/astradb_vectorstore.py +134 -977
- lfx/components/datastax/create_assistant.py +1 -0
- lfx/components/datastax/create_thread.py +1 -0
- lfx/components/datastax/dotenv.py +1 -0
- lfx/components/datastax/get_assistant.py +1 -0
- lfx/components/datastax/getenvvar.py +1 -0
- lfx/components/datastax/graph_rag.py +1 -1
- lfx/components/datastax/hcd.py +1 -1
- lfx/components/datastax/list_assistants.py +1 -0
- lfx/components/datastax/run.py +1 -0
- lfx/components/deactivated/json_document_builder.py +1 -1
- lfx/components/elastic/elasticsearch.py +1 -1
- lfx/components/elastic/opensearch_multimodal.py +1575 -0
- lfx/components/files_and_knowledge/__init__.py +47 -0
- lfx/components/{data → files_and_knowledge}/directory.py +1 -1
- lfx/components/{data → files_and_knowledge}/file.py +246 -18
- lfx/components/{knowledge_bases → files_and_knowledge}/ingestion.py +17 -9
- lfx/components/{knowledge_bases → files_and_knowledge}/retrieval.py +18 -10
- lfx/components/{data → files_and_knowledge}/save_file.py +142 -22
- lfx/components/flow_controls/__init__.py +58 -0
- lfx/components/{logic → flow_controls}/conditional_router.py +1 -1
- lfx/components/{logic → flow_controls}/loop.py +47 -9
- lfx/components/flow_controls/run_flow.py +108 -0
- lfx/components/glean/glean_search_api.py +1 -1
- lfx/components/groq/groq.py +35 -28
- lfx/components/helpers/__init__.py +102 -0
- lfx/components/ibm/watsonx.py +25 -21
- lfx/components/input_output/__init__.py +3 -1
- lfx/components/input_output/chat.py +12 -3
- lfx/components/input_output/chat_output.py +12 -4
- lfx/components/input_output/text.py +1 -1
- lfx/components/input_output/text_output.py +1 -1
- lfx/components/{data → input_output}/webhook.py +1 -1
- lfx/components/knowledge_bases/__init__.py +59 -4
- lfx/components/langchain_utilities/character.py +1 -1
- lfx/components/langchain_utilities/csv_agent.py +84 -16
- lfx/components/langchain_utilities/json_agent.py +67 -12
- lfx/components/langchain_utilities/language_recursive.py +1 -1
- lfx/components/llm_operations/__init__.py +46 -0
- lfx/components/{processing → llm_operations}/batch_run.py +1 -1
- lfx/components/{processing → llm_operations}/lambda_filter.py +1 -1
- lfx/components/{logic → llm_operations}/llm_conditional_router.py +1 -1
- lfx/components/{processing/llm_router.py → llm_operations/llm_selector.py} +3 -3
- lfx/components/{processing → llm_operations}/structured_output.py +56 -18
- lfx/components/logic/__init__.py +126 -0
- lfx/components/mem0/mem0_chat_memory.py +11 -0
- lfx/components/mistral/mistral_embeddings.py +1 -1
- lfx/components/models/__init__.py +64 -9
- lfx/components/models_and_agents/__init__.py +49 -0
- lfx/components/{agents → models_and_agents}/agent.py +49 -6
- lfx/components/models_and_agents/embedding_model.py +423 -0
- lfx/components/models_and_agents/language_model.py +398 -0
- lfx/components/{agents → models_and_agents}/mcp_component.py +84 -45
- lfx/components/{helpers → models_and_agents}/memory.py +1 -1
- lfx/components/nvidia/system_assist.py +1 -1
- lfx/components/olivya/olivya.py +1 -1
- lfx/components/ollama/ollama.py +235 -14
- lfx/components/openrouter/openrouter.py +49 -147
- lfx/components/processing/__init__.py +9 -57
- lfx/components/processing/converter.py +1 -1
- lfx/components/processing/dataframe_operations.py +1 -1
- lfx/components/processing/parse_json_data.py +2 -2
- lfx/components/processing/parser.py +7 -2
- lfx/components/processing/split_text.py +1 -1
- lfx/components/qdrant/qdrant.py +1 -1
- lfx/components/redis/redis.py +1 -1
- lfx/components/twelvelabs/split_video.py +10 -0
- lfx/components/twelvelabs/video_file.py +12 -0
- lfx/components/utilities/__init__.py +43 -0
- lfx/components/{helpers → utilities}/calculator_core.py +1 -1
- lfx/components/{helpers → utilities}/current_date.py +1 -1
- lfx/components/{processing → utilities}/python_repl_core.py +1 -1
- lfx/components/vectorstores/__init__.py +0 -6
- lfx/components/vectorstores/local_db.py +9 -0
- lfx/components/youtube/youtube_transcripts.py +118 -30
- lfx/custom/custom_component/component.py +60 -3
- lfx/custom/custom_component/custom_component.py +68 -6
- lfx/field_typing/constants.py +1 -0
- lfx/graph/edge/base.py +45 -22
- lfx/graph/graph/base.py +5 -2
- lfx/graph/graph/schema.py +3 -2
- lfx/graph/state/model.py +15 -2
- lfx/graph/utils.py +6 -0
- lfx/graph/vertex/base.py +4 -1
- lfx/graph/vertex/param_handler.py +10 -7
- lfx/graph/vertex/vertex_types.py +1 -1
- lfx/helpers/__init__.py +12 -0
- lfx/helpers/flow.py +117 -0
- lfx/inputs/input_mixin.py +24 -1
- lfx/inputs/inputs.py +13 -1
- lfx/interface/components.py +161 -83
- lfx/io/schema.py +6 -0
- lfx/log/logger.py +5 -3
- lfx/schema/schema.py +5 -0
- lfx/services/database/__init__.py +5 -0
- lfx/services/database/service.py +25 -0
- lfx/services/deps.py +87 -22
- lfx/services/manager.py +19 -6
- lfx/services/mcp_composer/service.py +998 -157
- lfx/services/session.py +5 -0
- lfx/services/settings/base.py +51 -7
- lfx/services/settings/constants.py +8 -0
- lfx/services/storage/local.py +76 -46
- lfx/services/storage/service.py +152 -29
- lfx/template/field/base.py +3 -0
- lfx/utils/ssrf_protection.py +384 -0
- lfx/utils/validate_cloud.py +26 -0
- {lfx_nightly-0.1.13.dev0.dist-info → lfx_nightly-0.2.0.dev26.dist-info}/METADATA +38 -22
- {lfx_nightly-0.1.13.dev0.dist-info → lfx_nightly-0.2.0.dev26.dist-info}/RECORD +210 -196
- {lfx_nightly-0.1.13.dev0.dist-info → lfx_nightly-0.2.0.dev26.dist-info}/WHEEL +1 -1
- lfx/components/agents/cuga_agent.py +0 -1013
- lfx/components/datastax/astra_db.py +0 -77
- lfx/components/datastax/cassandra.py +0 -92
- lfx/components/logic/run_flow.py +0 -71
- lfx/components/models/embedding_model.py +0 -114
- lfx/components/models/language_model.py +0 -144
- lfx/components/vectorstores/astradb_graph.py +0 -326
- lfx/components/vectorstores/cassandra.py +0 -264
- lfx/components/vectorstores/cassandra_graph.py +0 -238
- lfx/components/vectorstores/chroma.py +0 -167
- lfx/components/vectorstores/clickhouse.py +0 -135
- lfx/components/vectorstores/couchbase.py +0 -102
- lfx/components/vectorstores/elasticsearch.py +0 -267
- lfx/components/vectorstores/faiss.py +0 -111
- lfx/components/vectorstores/graph_rag.py +0 -141
- lfx/components/vectorstores/hcd.py +0 -314
- lfx/components/vectorstores/milvus.py +0 -115
- lfx/components/vectorstores/mongodb_atlas.py +0 -213
- lfx/components/vectorstores/opensearch.py +0 -243
- lfx/components/vectorstores/pgvector.py +0 -72
- lfx/components/vectorstores/pinecone.py +0 -134
- lfx/components/vectorstores/qdrant.py +0 -109
- lfx/components/vectorstores/supabase.py +0 -76
- lfx/components/vectorstores/upstash.py +0 -124
- lfx/components/vectorstores/vectara.py +0 -97
- lfx/components/vectorstores/vectara_rag.py +0 -164
- lfx/components/vectorstores/weaviate.py +0 -89
- /lfx/components/{data → data_source}/mock_data.py +0 -0
- /lfx/components/datastax/{astra_vectorize.py → astradb_vectorize.py} +0 -0
- /lfx/components/{logic → flow_controls}/data_conditional_router.py +0 -0
- /lfx/components/{logic → flow_controls}/flow_tool.py +0 -0
- /lfx/components/{logic → flow_controls}/listen.py +0 -0
- /lfx/components/{logic → flow_controls}/notify.py +0 -0
- /lfx/components/{logic → flow_controls}/pass_message.py +0 -0
- /lfx/components/{logic → flow_controls}/sub_flow.py +0 -0
- /lfx/components/{processing → models_and_agents}/prompt.py +0 -0
- /lfx/components/{helpers → processing}/create_list.py +0 -0
- /lfx/components/{helpers → processing}/output_parser.py +0 -0
- /lfx/components/{helpers → processing}/store_message.py +0 -0
- /lfx/components/{helpers → utilities}/id_generator.py +0 -0
- {lfx_nightly-0.1.13.dev0.dist-info → lfx_nightly-0.2.0.dev26.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,
|
|
@@ -284,6 +290,21 @@ class ComposioBaseComponent(Component):
|
|
|
284
290
|
# Track all auth field names discovered across all toolkits
|
|
285
291
|
_all_auth_field_names: set[str] = set()
|
|
286
292
|
|
|
293
|
+
@classmethod
|
|
294
|
+
def get_actions_cache(cls) -> dict[str, dict[str, Any]]:
|
|
295
|
+
"""Get the class-level actions cache."""
|
|
296
|
+
return cls._actions_cache
|
|
297
|
+
|
|
298
|
+
@classmethod
|
|
299
|
+
def get_action_schema_cache(cls) -> dict[str, dict[str, Any]]:
|
|
300
|
+
"""Get the class-level action schema cache."""
|
|
301
|
+
return cls._action_schema_cache
|
|
302
|
+
|
|
303
|
+
@classmethod
|
|
304
|
+
def get_all_auth_field_names(cls) -> set[str]:
|
|
305
|
+
"""Get all auth field names discovered across toolkits."""
|
|
306
|
+
return cls._all_auth_field_names
|
|
307
|
+
|
|
287
308
|
outputs = [
|
|
288
309
|
Output(name="dataFrame", display_name="DataFrame", method="as_dataframe"),
|
|
289
310
|
]
|
|
@@ -313,6 +334,8 @@ class ComposioBaseComponent(Component):
|
|
|
313
334
|
return Message(text=str(result))
|
|
314
335
|
|
|
315
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)
|
|
316
339
|
result = self.execute_action()
|
|
317
340
|
|
|
318
341
|
if isinstance(result, dict):
|
|
@@ -356,6 +379,8 @@ class ComposioBaseComponent(Component):
|
|
|
356
379
|
|
|
357
380
|
def _build_wrapper(self) -> Composio:
|
|
358
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)
|
|
359
384
|
try:
|
|
360
385
|
if not self.api_key:
|
|
361
386
|
msg = "Composio API Key is required"
|
|
@@ -403,11 +428,11 @@ class ComposioBaseComponent(Component):
|
|
|
403
428
|
|
|
404
429
|
# Try to load from the class-level cache
|
|
405
430
|
toolkit_slug = self.app_name.lower()
|
|
406
|
-
if toolkit_slug in self.__class__.
|
|
431
|
+
if toolkit_slug in self.__class__.get_actions_cache():
|
|
407
432
|
# Deep-copy so that any mutation on this instance does not affect the
|
|
408
433
|
# cached master copy.
|
|
409
|
-
self._actions_data = copy.deepcopy(self.__class__.
|
|
410
|
-
self._action_schemas = copy.deepcopy(self.__class__.
|
|
434
|
+
self._actions_data = copy.deepcopy(self.__class__.get_actions_cache()[toolkit_slug])
|
|
435
|
+
self._action_schemas = copy.deepcopy(self.__class__.get_action_schema_cache().get(toolkit_slug, {}))
|
|
411
436
|
logger.debug(f"Loaded actions for {toolkit_slug} from in-process cache")
|
|
412
437
|
return
|
|
413
438
|
|
|
@@ -455,11 +480,17 @@ class ComposioBaseComponent(Component):
|
|
|
455
480
|
if parameters_schema is None:
|
|
456
481
|
logger.warning(f"Parameters schema is None for action key: {action_key}")
|
|
457
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
|
+
|
|
458
487
|
self._action_schemas[action_key] = tool_dict
|
|
459
488
|
self._actions_data[action_key] = {
|
|
460
489
|
"display_name": display_name,
|
|
461
490
|
"action_fields": [],
|
|
462
491
|
"file_upload_fields": set(),
|
|
492
|
+
"version": version,
|
|
493
|
+
"available_versions": available_versions,
|
|
463
494
|
}
|
|
464
495
|
continue
|
|
465
496
|
|
|
@@ -473,11 +504,17 @@ class ComposioBaseComponent(Component):
|
|
|
473
504
|
parameters_schema = parameters_schema.__dict__
|
|
474
505
|
else:
|
|
475
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
|
+
|
|
476
511
|
self._action_schemas[action_key] = tool_dict
|
|
477
512
|
self._actions_data[action_key] = {
|
|
478
513
|
"display_name": display_name,
|
|
479
514
|
"action_fields": [],
|
|
480
515
|
"file_upload_fields": set(),
|
|
516
|
+
"version": version,
|
|
517
|
+
"available_versions": available_versions,
|
|
481
518
|
}
|
|
482
519
|
continue
|
|
483
520
|
|
|
@@ -516,22 +553,34 @@ class ComposioBaseComponent(Component):
|
|
|
516
553
|
elif field_name in original_descriptions:
|
|
517
554
|
field_schema["description"] = original_descriptions[field_name]
|
|
518
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
|
+
|
|
519
560
|
self._action_schemas[action_key] = tool_dict
|
|
520
561
|
self._actions_data[action_key] = {
|
|
521
562
|
"display_name": display_name,
|
|
522
563
|
"action_fields": [],
|
|
523
564
|
"file_upload_fields": set(),
|
|
565
|
+
"version": version,
|
|
566
|
+
"available_versions": available_versions,
|
|
524
567
|
}
|
|
525
568
|
continue
|
|
526
569
|
|
|
527
570
|
if flat_schema is None:
|
|
528
571
|
logger.warning(f"Flat schema is None for action key: {action_key}")
|
|
529
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
|
+
|
|
530
577
|
self._action_schemas[action_key] = tool_dict
|
|
531
578
|
self._actions_data[action_key] = {
|
|
532
579
|
"display_name": display_name,
|
|
533
580
|
"action_fields": [],
|
|
534
581
|
"file_upload_fields": set(),
|
|
582
|
+
"version": version,
|
|
583
|
+
"available_versions": available_versions,
|
|
535
584
|
}
|
|
536
585
|
continue
|
|
537
586
|
|
|
@@ -604,20 +653,32 @@ class ComposioBaseComponent(Component):
|
|
|
604
653
|
clean_field_name = p_name.replace("[0]", "")
|
|
605
654
|
self._bool_variables.add(clean_field_name)
|
|
606
655
|
|
|
656
|
+
# Extract version information from the tool
|
|
657
|
+
version = tool_dict.get("version")
|
|
658
|
+
available_versions = tool_dict.get("available_versions", [])
|
|
659
|
+
|
|
607
660
|
self._action_schemas[action_key] = tool_dict
|
|
608
661
|
self._actions_data[action_key] = {
|
|
609
662
|
"display_name": display_name,
|
|
610
663
|
"action_fields": action_fields,
|
|
611
664
|
"file_upload_fields": file_upload_fields,
|
|
665
|
+
"version": version,
|
|
666
|
+
"available_versions": available_versions,
|
|
612
667
|
}
|
|
613
668
|
|
|
614
669
|
except (KeyError, TypeError, ValueError) as flatten_error:
|
|
615
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
|
+
|
|
616
675
|
self._action_schemas[action_key] = tool_dict
|
|
617
676
|
self._actions_data[action_key] = {
|
|
618
677
|
"display_name": display_name,
|
|
619
678
|
"action_fields": [],
|
|
620
679
|
"file_upload_fields": set(),
|
|
680
|
+
"version": version,
|
|
681
|
+
"available_versions": available_versions,
|
|
621
682
|
}
|
|
622
683
|
continue
|
|
623
684
|
|
|
@@ -630,8 +691,8 @@ class ComposioBaseComponent(Component):
|
|
|
630
691
|
|
|
631
692
|
# Cache actions for this toolkit so subsequent component instances
|
|
632
693
|
# can reuse them without hitting the Composio API again.
|
|
633
|
-
self.__class__.
|
|
634
|
-
self.__class__.
|
|
694
|
+
self.__class__.get_actions_cache()[toolkit_slug] = copy.deepcopy(self._actions_data)
|
|
695
|
+
self.__class__.get_action_schema_cache()[toolkit_slug] = copy.deepcopy(self._action_schemas)
|
|
635
696
|
|
|
636
697
|
except ValueError as e:
|
|
637
698
|
logger.debug(f"Could not populate Composio actions for {self.app_name}: {e}")
|
|
@@ -672,6 +733,9 @@ class ComposioBaseComponent(Component):
|
|
|
672
733
|
parameters_schema = parameters_schema.copy() # Don't modify the original
|
|
673
734
|
parameters_schema["required"] = []
|
|
674
735
|
|
|
736
|
+
# Also get top-level required fields from original schema
|
|
737
|
+
original_required = set(parameters_schema.get("required", []))
|
|
738
|
+
|
|
675
739
|
try:
|
|
676
740
|
# Preserve original descriptions before flattening to restore if lost
|
|
677
741
|
original_descriptions = {}
|
|
@@ -895,6 +959,8 @@ class ComposioBaseComponent(Component):
|
|
|
895
959
|
if any(getattr(i, "name", None) == top_name for i in processed_inputs):
|
|
896
960
|
continue
|
|
897
961
|
top_schema = props_dict.get(top_name, {})
|
|
962
|
+
# For MultilineInput fields (complex JSON objects/arrays)
|
|
963
|
+
is_required = top_name in original_required
|
|
898
964
|
processed_inputs.append(
|
|
899
965
|
MultilineInput(
|
|
900
966
|
name=top_name,
|
|
@@ -902,7 +968,7 @@ class ComposioBaseComponent(Component):
|
|
|
902
968
|
info=(
|
|
903
969
|
top_schema.get("description") or "Provide JSON for this parameter (object or array)."
|
|
904
970
|
),
|
|
905
|
-
required=
|
|
971
|
+
required=is_required, # Setting original schema
|
|
906
972
|
)
|
|
907
973
|
)
|
|
908
974
|
|
|
@@ -992,24 +1058,14 @@ class ComposioBaseComponent(Component):
|
|
|
992
1058
|
return auth_config.id
|
|
993
1059
|
|
|
994
1060
|
def _initiate_connection(self, app_name: str) -> tuple[str, str]:
|
|
995
|
-
"""Initiate
|
|
1061
|
+
"""Initiate connection using link method and return (redirect_url, connection_id)."""
|
|
996
1062
|
try:
|
|
997
1063
|
composio = self._build_wrapper()
|
|
998
1064
|
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
auth_config_id = self.create_new_auth_config(app_name)
|
|
1002
|
-
else:
|
|
1003
|
-
auth_config_id = None
|
|
1004
|
-
for auth_config in auth_configs.items:
|
|
1005
|
-
if auth_config.auth_scheme == "OAUTH2":
|
|
1006
|
-
auth_config_id = auth_config.id
|
|
1007
|
-
|
|
1008
|
-
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)
|
|
1009
1067
|
|
|
1010
|
-
connection_request = composio.connected_accounts.
|
|
1011
|
-
user_id=self.entity_id, auth_config_id=auth_config_id
|
|
1012
|
-
)
|
|
1068
|
+
connection_request = composio.connected_accounts.link(user_id=self.entity_id, auth_config_id=auth_config_id)
|
|
1013
1069
|
|
|
1014
1070
|
redirect_url = getattr(connection_request, "redirect_url", None)
|
|
1015
1071
|
connection_id = getattr(connection_request, "id", None)
|
|
@@ -1022,12 +1078,12 @@ class ComposioBaseComponent(Component):
|
|
|
1022
1078
|
msg = "No connection ID received from Composio"
|
|
1023
1079
|
raise ValueError(msg)
|
|
1024
1080
|
|
|
1025
|
-
logger.info(f"
|
|
1081
|
+
logger.info(f"Connection initiated for {app_name}: {redirect_url} (ID: {connection_id})")
|
|
1026
1082
|
return redirect_url, connection_id # noqa: TRY300
|
|
1027
1083
|
|
|
1028
1084
|
except (ValueError, ConnectionError, TypeError, AttributeError) as e:
|
|
1029
1085
|
logger.error(f"Error initiating connection for {app_name}: {e}")
|
|
1030
|
-
msg = f"Failed to initiate
|
|
1086
|
+
msg = f"Failed to initiate connection: {e}"
|
|
1031
1087
|
raise ValueError(msg) from e
|
|
1032
1088
|
|
|
1033
1089
|
def _check_connection_status_by_id(self, connection_id: str) -> str | None:
|
|
@@ -1313,7 +1369,7 @@ class ComposioBaseComponent(Component):
|
|
|
1313
1369
|
|
|
1314
1370
|
self._auth_dynamic_fields.add(name)
|
|
1315
1371
|
# Also add to class-level cache for better tracking
|
|
1316
|
-
self.__class__.
|
|
1372
|
+
self.__class__.get_all_auth_field_names().add(name)
|
|
1317
1373
|
|
|
1318
1374
|
def _render_custom_auth_fields(self, build_config: dict, schema: dict[str, Any], mode: str) -> None:
|
|
1319
1375
|
"""Render fields for custom auth based on schema auth_config_details sections."""
|
|
@@ -1344,20 +1400,14 @@ class ComposioBaseComponent(Component):
|
|
|
1344
1400
|
desc = field.get("description")
|
|
1345
1401
|
self._add_text_field(build_config, name, disp, desc, required=required, default_value=default_val)
|
|
1346
1402
|
|
|
1347
|
-
#
|
|
1403
|
+
# Only process AuthConfigCreation fields (for custom OAuth2, etc.)
|
|
1404
|
+
# Connection initiation fields are now handled on Composio page via link method
|
|
1348
1405
|
creation = fields.get("auth_config_creation") or fields.get("authConfigCreation") or {}
|
|
1349
1406
|
# Process required fields
|
|
1350
1407
|
process_fields(creation.get("required", []), required=True)
|
|
1351
1408
|
# Process optional fields (excluding those with defaults and bearer_token)
|
|
1352
1409
|
process_fields(creation.get("optional", []), required=False)
|
|
1353
1410
|
|
|
1354
|
-
# b) ConnectedAccountInitiation fields (for API_KEY, etc.)
|
|
1355
|
-
initiation = fields.get("connected_account_initiation") or fields.get("connectedAccountInitiation") or {}
|
|
1356
|
-
# Process required fields
|
|
1357
|
-
process_fields(initiation.get("required", []), required=True)
|
|
1358
|
-
# Process optional fields (excluding those with defaults)
|
|
1359
|
-
process_fields(initiation.get("optional", []), required=False)
|
|
1360
|
-
|
|
1361
1411
|
def _collect_all_auth_field_names(self, schema: dict[str, Any] | None) -> set[str]:
|
|
1362
1412
|
names: set[str] = set()
|
|
1363
1413
|
if not schema:
|
|
@@ -1378,7 +1428,7 @@ class ComposioBaseComponent(Component):
|
|
|
1378
1428
|
if name:
|
|
1379
1429
|
names.add(name)
|
|
1380
1430
|
# Add to class-level cache for tracking all discovered auth fields
|
|
1381
|
-
self.__class__.
|
|
1431
|
+
self.__class__.get_all_auth_field_names().add(name)
|
|
1382
1432
|
# Only use names discovered from the toolkit schema; do not add aliases
|
|
1383
1433
|
return names
|
|
1384
1434
|
|
|
@@ -1443,7 +1493,7 @@ class ComposioBaseComponent(Component):
|
|
|
1443
1493
|
# Check if we need to populate actions - but also check cache availability
|
|
1444
1494
|
actions_available = bool(self._actions_data)
|
|
1445
1495
|
toolkit_slug = getattr(self, "app_name", "").lower()
|
|
1446
|
-
cached_actions_available = toolkit_slug in self.__class__.
|
|
1496
|
+
cached_actions_available = toolkit_slug in self.__class__.get_actions_cache()
|
|
1447
1497
|
|
|
1448
1498
|
should_populate = False
|
|
1449
1499
|
|
|
@@ -1467,10 +1517,15 @@ class ComposioBaseComponent(Component):
|
|
|
1467
1517
|
selected_mode = (build_config.get("auth_mode") or {}).get("value")
|
|
1468
1518
|
managed = (schema or {}).get("composio_managed_auth_schemes") or []
|
|
1469
1519
|
# Don't render custom fields if "Composio_Managed" is selected
|
|
1470
|
-
|
|
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]:
|
|
1471
1523
|
self._clear_auth_dynamic_fields(build_config)
|
|
1472
1524
|
self._render_custom_auth_fields(build_config, schema or {}, selected_mode)
|
|
1473
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)
|
|
1474
1529
|
except (TypeError, ValueError, AttributeError):
|
|
1475
1530
|
pass
|
|
1476
1531
|
|
|
@@ -1617,6 +1672,9 @@ class ComposioBaseComponent(Component):
|
|
|
1617
1672
|
if mode == "Composio_Managed":
|
|
1618
1673
|
# Composio_Managed → no extra fields needed
|
|
1619
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
|
|
1620
1678
|
elif isinstance(managed, list) and mode in managed:
|
|
1621
1679
|
# This is a specific managed auth scheme (e.g., OAUTH2) but user can still choose custom
|
|
1622
1680
|
# So we should render custom fields for this mode
|
|
@@ -1681,6 +1739,13 @@ class ComposioBaseComponent(Component):
|
|
|
1681
1739
|
|
|
1682
1740
|
# Create new connection ONLY if we truly have no usable connection yet
|
|
1683
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
|
+
|
|
1684
1749
|
try:
|
|
1685
1750
|
# Determine auth mode
|
|
1686
1751
|
schema = self._get_toolkit_schema()
|
|
@@ -1703,7 +1768,7 @@ class ComposioBaseComponent(Component):
|
|
|
1703
1768
|
build_config["auth_link"]["auth_tooltip"] = "Select Auth Mode"
|
|
1704
1769
|
return self.update_input_types(build_config)
|
|
1705
1770
|
# Custom modes: create auth config and/or initiate with config
|
|
1706
|
-
#
|
|
1771
|
+
# Only validate auth_config_creation fields for OAUTH2
|
|
1707
1772
|
required_missing = []
|
|
1708
1773
|
if mode == "OAUTH2":
|
|
1709
1774
|
req_names_pre = self._get_schema_field_names(
|
|
@@ -1717,30 +1782,6 @@ class ComposioBaseComponent(Component):
|
|
|
1717
1782
|
val = build_config[fname].get("value")
|
|
1718
1783
|
if val in (None, ""):
|
|
1719
1784
|
required_missing.append(fname)
|
|
1720
|
-
elif mode == "API_KEY":
|
|
1721
|
-
req_names_pre = self._get_schema_field_names(
|
|
1722
|
-
schema,
|
|
1723
|
-
"API_KEY",
|
|
1724
|
-
"connected_account_initiation",
|
|
1725
|
-
"required",
|
|
1726
|
-
)
|
|
1727
|
-
for fname in req_names_pre:
|
|
1728
|
-
if fname in build_config:
|
|
1729
|
-
val = build_config[fname].get("value")
|
|
1730
|
-
if val in (None, ""):
|
|
1731
|
-
required_missing.append(fname)
|
|
1732
|
-
else:
|
|
1733
|
-
req_names_pre = self._get_schema_field_names(
|
|
1734
|
-
schema,
|
|
1735
|
-
mode,
|
|
1736
|
-
"connected_account_initiation",
|
|
1737
|
-
"required",
|
|
1738
|
-
)
|
|
1739
|
-
for fname in req_names_pre:
|
|
1740
|
-
if fname in build_config:
|
|
1741
|
-
val = build_config[fname].get("value")
|
|
1742
|
-
if val in (None, ""):
|
|
1743
|
-
required_missing.append(fname)
|
|
1744
1785
|
if required_missing:
|
|
1745
1786
|
# Surface errors on each missing field
|
|
1746
1787
|
for fname in required_missing:
|
|
@@ -1764,24 +1805,18 @@ class ComposioBaseComponent(Component):
|
|
|
1764
1805
|
# If an auth_config was already created via the button, use it and include initiation fields
|
|
1765
1806
|
stored_ac_id = (build_config.get("auth_link") or {}).get("auth_config_id")
|
|
1766
1807
|
if stored_ac_id:
|
|
1767
|
-
#
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
if fname in build_config:
|
|
1778
|
-
v = build_config[fname].get("value")
|
|
1779
|
-
if v not in (None, ""):
|
|
1780
|
-
val_payload[fname] = v
|
|
1781
|
-
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(
|
|
1782
1818
|
user_id=self.entity_id,
|
|
1783
1819
|
auth_config_id=stored_ac_id,
|
|
1784
|
-
config={"auth_scheme": "OAUTH2", "val": val_payload} if val_payload else None,
|
|
1785
1820
|
)
|
|
1786
1821
|
redirect_url = getattr(redirect, "redirect_url", None)
|
|
1787
1822
|
connection_id = getattr(redirect, "id", None)
|
|
@@ -1792,6 +1827,9 @@ class ComposioBaseComponent(Component):
|
|
|
1792
1827
|
# Clear action blocker text on successful initiation
|
|
1793
1828
|
build_config["action_button"]["helper_text"] = ""
|
|
1794
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)
|
|
1795
1833
|
return self.update_input_types(build_config)
|
|
1796
1834
|
# Otherwise, create custom OAuth2 auth config using schema-declared required fields
|
|
1797
1835
|
credentials = {}
|
|
@@ -1812,6 +1850,14 @@ class ComposioBaseComponent(Component):
|
|
|
1812
1850
|
else:
|
|
1813
1851
|
missing.append(fname)
|
|
1814
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
|
+
|
|
1815
1861
|
ac = composio.auth_configs.create(
|
|
1816
1862
|
toolkit=toolkit_slug,
|
|
1817
1863
|
options={
|
|
@@ -1821,30 +1867,8 @@ class ComposioBaseComponent(Component):
|
|
|
1821
1867
|
},
|
|
1822
1868
|
)
|
|
1823
1869
|
auth_config_id = getattr(ac, "id", None)
|
|
1824
|
-
#
|
|
1825
|
-
|
|
1826
|
-
schema,
|
|
1827
|
-
"OAUTH2",
|
|
1828
|
-
"connected_account_initiation",
|
|
1829
|
-
"required",
|
|
1830
|
-
)
|
|
1831
|
-
if init_req:
|
|
1832
|
-
self._clear_auth_dynamic_fields(build_config)
|
|
1833
|
-
for name in init_req:
|
|
1834
|
-
self._add_text_field(
|
|
1835
|
-
build_config,
|
|
1836
|
-
name=name,
|
|
1837
|
-
display_name=name.replace("_", " ").title(),
|
|
1838
|
-
info="Provide connection parameter",
|
|
1839
|
-
required=True,
|
|
1840
|
-
)
|
|
1841
|
-
build_config.setdefault("auth_link", {})
|
|
1842
|
-
build_config["auth_link"]["auth_config_id"] = auth_config_id
|
|
1843
|
-
build_config["auth_link"]["value"] = "connect"
|
|
1844
|
-
build_config["auth_link"]["auth_tooltip"] = "Connect"
|
|
1845
|
-
return self.update_input_types(build_config)
|
|
1846
|
-
# Otherwise initiate immediately
|
|
1847
|
-
redirect = composio.connected_accounts.initiate(
|
|
1870
|
+
# Use link method directly - no need to check for connection initiation fields
|
|
1871
|
+
redirect = composio.connected_accounts.link(
|
|
1848
1872
|
user_id=self.entity_id,
|
|
1849
1873
|
auth_config_id=auth_config_id,
|
|
1850
1874
|
)
|
|
@@ -1861,141 +1885,64 @@ class ComposioBaseComponent(Component):
|
|
|
1861
1885
|
build_config["action_button"]["helper_text_metadata"] = {}
|
|
1862
1886
|
return self.update_input_types(build_config)
|
|
1863
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
|
+
|
|
1864
1896
|
ac = composio.auth_configs.create(
|
|
1865
1897
|
toolkit=toolkit_slug,
|
|
1866
1898
|
options={"type": "use_custom_auth", "auth_scheme": "API_KEY", "credentials": {}},
|
|
1867
1899
|
)
|
|
1868
1900
|
auth_config_id = getattr(ac, "id", None)
|
|
1869
|
-
#
|
|
1870
|
-
|
|
1871
|
-
missing = []
|
|
1872
|
-
# Collect required names from schema
|
|
1873
|
-
req_names = self._get_schema_field_names(
|
|
1874
|
-
schema,
|
|
1875
|
-
"API_KEY",
|
|
1876
|
-
"connected_account_initiation",
|
|
1877
|
-
"required",
|
|
1878
|
-
)
|
|
1879
|
-
# Merge rendered dynamic fields and schema-required names
|
|
1880
|
-
candidate_names = set(self._auth_dynamic_fields) | req_names
|
|
1881
|
-
for fname in candidate_names:
|
|
1882
|
-
if fname in build_config:
|
|
1883
|
-
val = build_config[fname].get("value")
|
|
1884
|
-
if val not in (None, ""):
|
|
1885
|
-
val_payload[fname] = val
|
|
1886
|
-
else:
|
|
1887
|
-
missing.append(fname)
|
|
1888
|
-
initiation = composio.connected_accounts.initiate(
|
|
1901
|
+
# Use link method - user will enter API key on Composio page
|
|
1902
|
+
initiation = composio.connected_accounts.link(
|
|
1889
1903
|
user_id=self.entity_id,
|
|
1890
1904
|
auth_config_id=auth_config_id,
|
|
1891
|
-
config={"auth_scheme": "API_KEY", "val": val_payload},
|
|
1892
1905
|
)
|
|
1893
1906
|
connection_id = getattr(initiation, "id", None)
|
|
1894
1907
|
redirect_url = getattr(initiation, "redirect_url", None)
|
|
1895
|
-
#
|
|
1908
|
+
# API_KEY now also returns redirect URL with new link method
|
|
1896
1909
|
if redirect_url:
|
|
1897
1910
|
build_config["auth_link"]["value"] = redirect_url
|
|
1898
1911
|
build_config["auth_link"]["auth_tooltip"] = "Disconnect"
|
|
1899
|
-
|
|
1900
|
-
# No redirect for API_KEY; mark as connected
|
|
1901
|
-
build_config["auth_link"]["value"] = "validated"
|
|
1902
|
-
build_config["auth_link"]["auth_tooltip"] = "Disconnect"
|
|
1903
|
-
# In both cases, hide auth fields immediately after successful initiation
|
|
1912
|
+
# Hide auth fields immediately after successful initiation
|
|
1904
1913
|
schema = self._get_toolkit_schema()
|
|
1905
1914
|
self._clear_auth_fields_from_schema(build_config, schema)
|
|
1906
1915
|
build_config["action_button"]["helper_text"] = ""
|
|
1907
1916
|
build_config["action_button"]["helper_text_metadata"] = {}
|
|
1908
1917
|
|
|
1909
|
-
# Convert auth_mode to pill for connected state
|
|
1910
|
-
if not redirect_url and mode: # API_KEY or similar direct connection
|
|
1911
|
-
build_config["auth_link"]["connection_id"] = connection_id
|
|
1912
|
-
build_config.setdefault("auth_mode", {})
|
|
1913
|
-
build_config["auth_mode"]["value"] = mode
|
|
1914
|
-
build_config["auth_mode"]["options"] = [mode]
|
|
1915
|
-
build_config["auth_mode"]["show"] = False
|
|
1916
|
-
try:
|
|
1917
|
-
pill = TabInput(
|
|
1918
|
-
name="auth_mode",
|
|
1919
|
-
display_name="Auth Mode",
|
|
1920
|
-
options=[mode],
|
|
1921
|
-
value=mode,
|
|
1922
|
-
).to_dict()
|
|
1923
|
-
pill["show"] = True
|
|
1924
|
-
build_config["auth_mode"] = pill
|
|
1925
|
-
except (TypeError, ValueError, AttributeError):
|
|
1926
|
-
build_config["auth_mode"] = {
|
|
1927
|
-
"name": "auth_mode",
|
|
1928
|
-
"display_name": "Auth Mode",
|
|
1929
|
-
"type": "tab",
|
|
1930
|
-
"options": [mode],
|
|
1931
|
-
"value": mode,
|
|
1932
|
-
"show": True,
|
|
1933
|
-
}
|
|
1934
|
-
|
|
1935
1918
|
return self.update_input_types(build_config)
|
|
1936
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
|
+
|
|
1937
1926
|
ac = composio.auth_configs.create(
|
|
1938
1927
|
toolkit=toolkit_slug,
|
|
1939
1928
|
options={"type": "use_custom_auth", "auth_scheme": mode, "credentials": {}},
|
|
1940
1929
|
)
|
|
1941
1930
|
auth_config_id = getattr(ac, "id", None)
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
schema,
|
|
1945
|
-
mode,
|
|
1946
|
-
"connected_account_initiation",
|
|
1947
|
-
"required",
|
|
1948
|
-
)
|
|
1949
|
-
candidate_names = set(self._auth_dynamic_fields) | req_names
|
|
1950
|
-
for fname in candidate_names:
|
|
1951
|
-
if fname in build_config:
|
|
1952
|
-
val = build_config[fname].get("value")
|
|
1953
|
-
if val not in (None, ""):
|
|
1954
|
-
val_payload[fname] = val
|
|
1955
|
-
initiation = composio.connected_accounts.initiate(
|
|
1931
|
+
# Use link method - user will enter required fields on Composio page
|
|
1932
|
+
initiation = composio.connected_accounts.link(
|
|
1956
1933
|
user_id=self.entity_id,
|
|
1957
1934
|
auth_config_id=auth_config_id,
|
|
1958
|
-
config={"auth_scheme": mode, "val": val_payload},
|
|
1959
1935
|
)
|
|
1960
1936
|
connection_id = getattr(initiation, "id", None)
|
|
1961
1937
|
redirect_url = getattr(initiation, "redirect_url", None)
|
|
1962
|
-
# Do not store connection_id on initiation; only when ACTIVE
|
|
1963
1938
|
if redirect_url:
|
|
1964
1939
|
build_config["auth_link"]["value"] = redirect_url
|
|
1965
1940
|
build_config["auth_link"]["auth_tooltip"] = "Disconnect"
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
# Clear auth fields when connected
|
|
1972
|
-
schema = self._get_toolkit_schema()
|
|
1973
|
-
self._clear_auth_fields_from_schema(build_config, schema)
|
|
1974
|
-
|
|
1975
|
-
# Convert auth_mode to pill for connected state
|
|
1976
|
-
if mode:
|
|
1977
|
-
build_config.setdefault("auth_mode", {})
|
|
1978
|
-
build_config["auth_mode"]["value"] = mode
|
|
1979
|
-
build_config["auth_mode"]["options"] = [mode]
|
|
1980
|
-
build_config["auth_mode"]["show"] = False
|
|
1981
|
-
try:
|
|
1982
|
-
pill = TabInput(
|
|
1983
|
-
name="auth_mode",
|
|
1984
|
-
display_name="Auth Mode",
|
|
1985
|
-
options=[mode],
|
|
1986
|
-
value=mode,
|
|
1987
|
-
).to_dict()
|
|
1988
|
-
pill["show"] = True
|
|
1989
|
-
build_config["auth_mode"] = pill
|
|
1990
|
-
except (TypeError, ValueError, AttributeError):
|
|
1991
|
-
build_config["auth_mode"] = {
|
|
1992
|
-
"name": "auth_mode",
|
|
1993
|
-
"display_name": "Auth Mode",
|
|
1994
|
-
"type": "tab",
|
|
1995
|
-
"options": [mode],
|
|
1996
|
-
"value": mode,
|
|
1997
|
-
"show": True,
|
|
1998
|
-
}
|
|
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"] = {}
|
|
1999
1946
|
return self.update_input_types(build_config)
|
|
2000
1947
|
except (ValueError, ConnectionError, TypeError) as e:
|
|
2001
1948
|
logger.error(f"Error creating connection: {e}")
|
|
@@ -2129,7 +2076,12 @@ class ComposioBaseComponent(Component):
|
|
|
2129
2076
|
schema = self._get_toolkit_schema()
|
|
2130
2077
|
mode = (build_config.get("auth_mode") or {}).get("value")
|
|
2131
2078
|
managed = (schema or {}).get("composio_managed_auth_schemes") or []
|
|
2132
|
-
|
|
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
|
+
):
|
|
2133
2085
|
self._render_custom_auth_fields(build_config, schema or {}, mode)
|
|
2134
2086
|
# Already reordered in _render_custom_auth_fields
|
|
2135
2087
|
except (TypeError, ValueError, AttributeError):
|
|
@@ -2174,6 +2126,12 @@ class ComposioBaseComponent(Component):
|
|
|
2174
2126
|
# Handle auth config button click
|
|
2175
2127
|
if field_name == "create_auth_config" and field_value == "create":
|
|
2176
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
|
+
|
|
2177
2135
|
composio = self._build_wrapper()
|
|
2178
2136
|
toolkit_slug = self.app_name.lower()
|
|
2179
2137
|
schema = self._get_toolkit_schema() or {}
|
|
@@ -2194,29 +2152,8 @@ class ComposioBaseComponent(Component):
|
|
|
2194
2152
|
auth_config_id = getattr(ac, "id", None)
|
|
2195
2153
|
build_config.setdefault("auth_link", {})
|
|
2196
2154
|
if auth_config_id:
|
|
2197
|
-
#
|
|
2198
|
-
|
|
2199
|
-
schema, "OAUTH2", "connected_account_initiation", "required"
|
|
2200
|
-
)
|
|
2201
|
-
if initiation_required:
|
|
2202
|
-
# Populate those fields dynamically for the user to fill
|
|
2203
|
-
self._clear_auth_dynamic_fields(build_config)
|
|
2204
|
-
for name in initiation_required:
|
|
2205
|
-
# Render as text inputs to collect connection fields
|
|
2206
|
-
self._add_text_field(
|
|
2207
|
-
build_config,
|
|
2208
|
-
name=name,
|
|
2209
|
-
display_name=name.replace("_", " ").title(),
|
|
2210
|
-
info="Provide connection parameter",
|
|
2211
|
-
required=True,
|
|
2212
|
-
)
|
|
2213
|
-
# Store the new auth_config_id so pressing Connect will use it
|
|
2214
|
-
build_config["auth_link"]["auth_config_id"] = auth_config_id
|
|
2215
|
-
build_config["auth_link"]["value"] = "connect"
|
|
2216
|
-
build_config["auth_link"]["auth_tooltip"] = "Connect"
|
|
2217
|
-
return self.update_input_types(build_config)
|
|
2218
|
-
# If no initiation fields required, initiate immediately
|
|
2219
|
-
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(
|
|
2220
2157
|
user_id=self.entity_id, auth_config_id=auth_config_id
|
|
2221
2158
|
)
|
|
2222
2159
|
redirect_url = getattr(connection_request, "redirect_url", None)
|
|
@@ -2423,6 +2360,8 @@ class ComposioBaseComponent(Component):
|
|
|
2423
2360
|
|
|
2424
2361
|
def execute_action(self):
|
|
2425
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)
|
|
2426
2365
|
composio = self._build_wrapper()
|
|
2427
2366
|
self._populate_actions_data()
|
|
2428
2367
|
self._build_action_maps()
|
|
@@ -2492,12 +2431,23 @@ class ComposioBaseComponent(Component):
|
|
|
2492
2431
|
|
|
2493
2432
|
arguments[final_field_name] = value
|
|
2494
2433
|
|
|
2495
|
-
#
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
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)
|
|
2501
2451
|
|
|
2502
2452
|
if isinstance(result, dict) and "successful" in result:
|
|
2503
2453
|
if result["successful"]:
|
|
@@ -2623,7 +2573,7 @@ class ComposioBaseComponent(Component):
|
|
|
2623
2573
|
# Add all dynamic auth fields to protected set
|
|
2624
2574
|
protected.update(self._auth_dynamic_fields)
|
|
2625
2575
|
# Also protect any auth fields discovered across all instances
|
|
2626
|
-
protected.update(self.__class__.
|
|
2576
|
+
protected.update(self.__class__.get_all_auth_field_names())
|
|
2627
2577
|
|
|
2628
2578
|
for key, cfg in list(build_config.items()):
|
|
2629
2579
|
if key in protected:
|