lfx-nightly 0.1.11.dev0__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 (699) hide show
  1. lfx/__init__.py +0 -0
  2. lfx/__main__.py +25 -0
  3. lfx/base/__init__.py +0 -0
  4. lfx/base/agents/__init__.py +0 -0
  5. lfx/base/agents/agent.py +268 -0
  6. lfx/base/agents/callback.py +130 -0
  7. lfx/base/agents/context.py +109 -0
  8. lfx/base/agents/crewai/__init__.py +0 -0
  9. lfx/base/agents/crewai/crew.py +231 -0
  10. lfx/base/agents/crewai/tasks.py +12 -0
  11. lfx/base/agents/default_prompts.py +23 -0
  12. lfx/base/agents/errors.py +15 -0
  13. lfx/base/agents/events.py +346 -0
  14. lfx/base/agents/utils.py +205 -0
  15. lfx/base/astra_assistants/__init__.py +0 -0
  16. lfx/base/astra_assistants/util.py +171 -0
  17. lfx/base/chains/__init__.py +0 -0
  18. lfx/base/chains/model.py +19 -0
  19. lfx/base/composio/__init__.py +0 -0
  20. lfx/base/composio/composio_base.py +1291 -0
  21. lfx/base/compressors/__init__.py +0 -0
  22. lfx/base/compressors/model.py +60 -0
  23. lfx/base/constants.py +46 -0
  24. lfx/base/curl/__init__.py +0 -0
  25. lfx/base/curl/parse.py +188 -0
  26. lfx/base/data/__init__.py +5 -0
  27. lfx/base/data/base_file.py +685 -0
  28. lfx/base/data/docling_utils.py +245 -0
  29. lfx/base/data/utils.py +198 -0
  30. lfx/base/document_transformers/__init__.py +0 -0
  31. lfx/base/document_transformers/model.py +43 -0
  32. lfx/base/embeddings/__init__.py +0 -0
  33. lfx/base/embeddings/aiml_embeddings.py +62 -0
  34. lfx/base/embeddings/model.py +26 -0
  35. lfx/base/flow_processing/__init__.py +0 -0
  36. lfx/base/flow_processing/utils.py +86 -0
  37. lfx/base/huggingface/__init__.py +0 -0
  38. lfx/base/huggingface/model_bridge.py +133 -0
  39. lfx/base/io/__init__.py +0 -0
  40. lfx/base/io/chat.py +20 -0
  41. lfx/base/io/text.py +22 -0
  42. lfx/base/langchain_utilities/__init__.py +0 -0
  43. lfx/base/langchain_utilities/model.py +35 -0
  44. lfx/base/langchain_utilities/spider_constants.py +1 -0
  45. lfx/base/langwatch/__init__.py +0 -0
  46. lfx/base/langwatch/utils.py +18 -0
  47. lfx/base/mcp/__init__.py +0 -0
  48. lfx/base/mcp/constants.py +2 -0
  49. lfx/base/mcp/util.py +1398 -0
  50. lfx/base/memory/__init__.py +0 -0
  51. lfx/base/memory/memory.py +49 -0
  52. lfx/base/memory/model.py +38 -0
  53. lfx/base/models/__init__.py +3 -0
  54. lfx/base/models/aiml_constants.py +51 -0
  55. lfx/base/models/anthropic_constants.py +47 -0
  56. lfx/base/models/aws_constants.py +151 -0
  57. lfx/base/models/chat_result.py +76 -0
  58. lfx/base/models/google_generative_ai_constants.py +70 -0
  59. lfx/base/models/groq_constants.py +134 -0
  60. lfx/base/models/model.py +375 -0
  61. lfx/base/models/model_input_constants.py +307 -0
  62. lfx/base/models/model_metadata.py +41 -0
  63. lfx/base/models/model_utils.py +8 -0
  64. lfx/base/models/novita_constants.py +35 -0
  65. lfx/base/models/ollama_constants.py +49 -0
  66. lfx/base/models/openai_constants.py +122 -0
  67. lfx/base/models/sambanova_constants.py +18 -0
  68. lfx/base/processing/__init__.py +0 -0
  69. lfx/base/prompts/__init__.py +0 -0
  70. lfx/base/prompts/api_utils.py +224 -0
  71. lfx/base/prompts/utils.py +61 -0
  72. lfx/base/textsplitters/__init__.py +0 -0
  73. lfx/base/textsplitters/model.py +28 -0
  74. lfx/base/tools/__init__.py +0 -0
  75. lfx/base/tools/base.py +26 -0
  76. lfx/base/tools/component_tool.py +325 -0
  77. lfx/base/tools/constants.py +49 -0
  78. lfx/base/tools/flow_tool.py +132 -0
  79. lfx/base/tools/run_flow.py +224 -0
  80. lfx/base/vectorstores/__init__.py +0 -0
  81. lfx/base/vectorstores/model.py +193 -0
  82. lfx/base/vectorstores/utils.py +22 -0
  83. lfx/base/vectorstores/vector_store_connection_decorator.py +52 -0
  84. lfx/cli/__init__.py +5 -0
  85. lfx/cli/commands.py +319 -0
  86. lfx/cli/common.py +650 -0
  87. lfx/cli/run.py +441 -0
  88. lfx/cli/script_loader.py +247 -0
  89. lfx/cli/serve_app.py +546 -0
  90. lfx/cli/validation.py +69 -0
  91. lfx/components/FAISS/__init__.py +34 -0
  92. lfx/components/FAISS/faiss.py +111 -0
  93. lfx/components/Notion/__init__.py +19 -0
  94. lfx/components/Notion/add_content_to_page.py +269 -0
  95. lfx/components/Notion/create_page.py +94 -0
  96. lfx/components/Notion/list_database_properties.py +68 -0
  97. lfx/components/Notion/list_pages.py +122 -0
  98. lfx/components/Notion/list_users.py +77 -0
  99. lfx/components/Notion/page_content_viewer.py +93 -0
  100. lfx/components/Notion/search.py +111 -0
  101. lfx/components/Notion/update_page_property.py +114 -0
  102. lfx/components/__init__.py +411 -0
  103. lfx/components/_importing.py +42 -0
  104. lfx/components/agentql/__init__.py +3 -0
  105. lfx/components/agentql/agentql_api.py +151 -0
  106. lfx/components/agents/__init__.py +34 -0
  107. lfx/components/agents/agent.py +558 -0
  108. lfx/components/agents/mcp_component.py +501 -0
  109. lfx/components/aiml/__init__.py +37 -0
  110. lfx/components/aiml/aiml.py +112 -0
  111. lfx/components/aiml/aiml_embeddings.py +37 -0
  112. lfx/components/amazon/__init__.py +36 -0
  113. lfx/components/amazon/amazon_bedrock_embedding.py +109 -0
  114. lfx/components/amazon/amazon_bedrock_model.py +124 -0
  115. lfx/components/amazon/s3_bucket_uploader.py +211 -0
  116. lfx/components/anthropic/__init__.py +34 -0
  117. lfx/components/anthropic/anthropic.py +187 -0
  118. lfx/components/apify/__init__.py +5 -0
  119. lfx/components/apify/apify_actor.py +325 -0
  120. lfx/components/arxiv/__init__.py +3 -0
  121. lfx/components/arxiv/arxiv.py +163 -0
  122. lfx/components/assemblyai/__init__.py +46 -0
  123. lfx/components/assemblyai/assemblyai_get_subtitles.py +83 -0
  124. lfx/components/assemblyai/assemblyai_lemur.py +183 -0
  125. lfx/components/assemblyai/assemblyai_list_transcripts.py +95 -0
  126. lfx/components/assemblyai/assemblyai_poll_transcript.py +72 -0
  127. lfx/components/assemblyai/assemblyai_start_transcript.py +188 -0
  128. lfx/components/azure/__init__.py +37 -0
  129. lfx/components/azure/azure_openai.py +95 -0
  130. lfx/components/azure/azure_openai_embeddings.py +83 -0
  131. lfx/components/baidu/__init__.py +32 -0
  132. lfx/components/baidu/baidu_qianfan_chat.py +113 -0
  133. lfx/components/bing/__init__.py +3 -0
  134. lfx/components/bing/bing_search_api.py +61 -0
  135. lfx/components/cassandra/__init__.py +40 -0
  136. lfx/components/cassandra/cassandra.py +264 -0
  137. lfx/components/cassandra/cassandra_chat.py +92 -0
  138. lfx/components/cassandra/cassandra_graph.py +238 -0
  139. lfx/components/chains/__init__.py +3 -0
  140. lfx/components/chroma/__init__.py +34 -0
  141. lfx/components/chroma/chroma.py +167 -0
  142. lfx/components/cleanlab/__init__.py +40 -0
  143. lfx/components/cleanlab/cleanlab_evaluator.py +155 -0
  144. lfx/components/cleanlab/cleanlab_rag_evaluator.py +254 -0
  145. lfx/components/cleanlab/cleanlab_remediator.py +131 -0
  146. lfx/components/clickhouse/__init__.py +34 -0
  147. lfx/components/clickhouse/clickhouse.py +135 -0
  148. lfx/components/cloudflare/__init__.py +32 -0
  149. lfx/components/cloudflare/cloudflare.py +81 -0
  150. lfx/components/cohere/__init__.py +40 -0
  151. lfx/components/cohere/cohere_embeddings.py +81 -0
  152. lfx/components/cohere/cohere_models.py +46 -0
  153. lfx/components/cohere/cohere_rerank.py +51 -0
  154. lfx/components/composio/__init__.py +74 -0
  155. lfx/components/composio/composio_api.py +268 -0
  156. lfx/components/composio/dropbox_compnent.py +11 -0
  157. lfx/components/composio/github_composio.py +11 -0
  158. lfx/components/composio/gmail_composio.py +38 -0
  159. lfx/components/composio/googlecalendar_composio.py +11 -0
  160. lfx/components/composio/googlemeet_composio.py +11 -0
  161. lfx/components/composio/googletasks_composio.py +8 -0
  162. lfx/components/composio/linear_composio.py +11 -0
  163. lfx/components/composio/outlook_composio.py +11 -0
  164. lfx/components/composio/reddit_composio.py +11 -0
  165. lfx/components/composio/slack_composio.py +582 -0
  166. lfx/components/composio/slackbot_composio.py +11 -0
  167. lfx/components/composio/supabase_composio.py +11 -0
  168. lfx/components/composio/todoist_composio.py +11 -0
  169. lfx/components/composio/youtube_composio.py +11 -0
  170. lfx/components/confluence/__init__.py +3 -0
  171. lfx/components/confluence/confluence.py +84 -0
  172. lfx/components/couchbase/__init__.py +34 -0
  173. lfx/components/couchbase/couchbase.py +102 -0
  174. lfx/components/crewai/__init__.py +49 -0
  175. lfx/components/crewai/crewai.py +107 -0
  176. lfx/components/crewai/hierarchical_crew.py +46 -0
  177. lfx/components/crewai/hierarchical_task.py +44 -0
  178. lfx/components/crewai/sequential_crew.py +52 -0
  179. lfx/components/crewai/sequential_task.py +73 -0
  180. lfx/components/crewai/sequential_task_agent.py +143 -0
  181. lfx/components/custom_component/__init__.py +34 -0
  182. lfx/components/custom_component/custom_component.py +31 -0
  183. lfx/components/data/__init__.py +64 -0
  184. lfx/components/data/api_request.py +544 -0
  185. lfx/components/data/csv_to_data.py +95 -0
  186. lfx/components/data/directory.py +113 -0
  187. lfx/components/data/file.py +577 -0
  188. lfx/components/data/json_to_data.py +98 -0
  189. lfx/components/data/news_search.py +164 -0
  190. lfx/components/data/rss.py +69 -0
  191. lfx/components/data/sql_executor.py +101 -0
  192. lfx/components/data/url.py +311 -0
  193. lfx/components/data/web_search.py +112 -0
  194. lfx/components/data/webhook.py +56 -0
  195. lfx/components/datastax/__init__.py +70 -0
  196. lfx/components/datastax/astra_assistant_manager.py +306 -0
  197. lfx/components/datastax/astra_db.py +75 -0
  198. lfx/components/datastax/astra_vectorize.py +124 -0
  199. lfx/components/datastax/astradb.py +1285 -0
  200. lfx/components/datastax/astradb_cql.py +314 -0
  201. lfx/components/datastax/astradb_graph.py +330 -0
  202. lfx/components/datastax/astradb_tool.py +414 -0
  203. lfx/components/datastax/astradb_vectorstore.py +1285 -0
  204. lfx/components/datastax/cassandra.py +92 -0
  205. lfx/components/datastax/create_assistant.py +58 -0
  206. lfx/components/datastax/create_thread.py +32 -0
  207. lfx/components/datastax/dotenv.py +35 -0
  208. lfx/components/datastax/get_assistant.py +37 -0
  209. lfx/components/datastax/getenvvar.py +30 -0
  210. lfx/components/datastax/graph_rag.py +141 -0
  211. lfx/components/datastax/hcd.py +314 -0
  212. lfx/components/datastax/list_assistants.py +25 -0
  213. lfx/components/datastax/run.py +89 -0
  214. lfx/components/deactivated/__init__.py +15 -0
  215. lfx/components/deactivated/amazon_kendra.py +66 -0
  216. lfx/components/deactivated/chat_litellm_model.py +158 -0
  217. lfx/components/deactivated/code_block_extractor.py +26 -0
  218. lfx/components/deactivated/documents_to_data.py +22 -0
  219. lfx/components/deactivated/embed.py +16 -0
  220. lfx/components/deactivated/extract_key_from_data.py +46 -0
  221. lfx/components/deactivated/json_document_builder.py +57 -0
  222. lfx/components/deactivated/list_flows.py +20 -0
  223. lfx/components/deactivated/mcp_sse.py +61 -0
  224. lfx/components/deactivated/mcp_stdio.py +62 -0
  225. lfx/components/deactivated/merge_data.py +93 -0
  226. lfx/components/deactivated/message.py +37 -0
  227. lfx/components/deactivated/metal.py +54 -0
  228. lfx/components/deactivated/multi_query.py +59 -0
  229. lfx/components/deactivated/retriever.py +43 -0
  230. lfx/components/deactivated/selective_passthrough.py +77 -0
  231. lfx/components/deactivated/should_run_next.py +40 -0
  232. lfx/components/deactivated/split_text.py +63 -0
  233. lfx/components/deactivated/store_message.py +24 -0
  234. lfx/components/deactivated/sub_flow.py +124 -0
  235. lfx/components/deactivated/vectara_self_query.py +76 -0
  236. lfx/components/deactivated/vector_store.py +24 -0
  237. lfx/components/deepseek/__init__.py +34 -0
  238. lfx/components/deepseek/deepseek.py +136 -0
  239. lfx/components/docling/__init__.py +43 -0
  240. lfx/components/docling/chunk_docling_document.py +186 -0
  241. lfx/components/docling/docling_inline.py +231 -0
  242. lfx/components/docling/docling_remote.py +193 -0
  243. lfx/components/docling/export_docling_document.py +117 -0
  244. lfx/components/documentloaders/__init__.py +3 -0
  245. lfx/components/duckduckgo/__init__.py +3 -0
  246. lfx/components/duckduckgo/duck_duck_go_search_run.py +92 -0
  247. lfx/components/elastic/__init__.py +37 -0
  248. lfx/components/elastic/elasticsearch.py +267 -0
  249. lfx/components/elastic/opensearch.py +243 -0
  250. lfx/components/embeddings/__init__.py +37 -0
  251. lfx/components/embeddings/similarity.py +76 -0
  252. lfx/components/embeddings/text_embedder.py +64 -0
  253. lfx/components/exa/__init__.py +3 -0
  254. lfx/components/exa/exa_search.py +68 -0
  255. lfx/components/firecrawl/__init__.py +43 -0
  256. lfx/components/firecrawl/firecrawl_crawl_api.py +88 -0
  257. lfx/components/firecrawl/firecrawl_extract_api.py +136 -0
  258. lfx/components/firecrawl/firecrawl_map_api.py +89 -0
  259. lfx/components/firecrawl/firecrawl_scrape_api.py +73 -0
  260. lfx/components/git/__init__.py +4 -0
  261. lfx/components/git/git.py +262 -0
  262. lfx/components/git/gitextractor.py +196 -0
  263. lfx/components/glean/__init__.py +3 -0
  264. lfx/components/glean/glean_search_api.py +173 -0
  265. lfx/components/google/__init__.py +17 -0
  266. lfx/components/google/gmail.py +192 -0
  267. lfx/components/google/google_bq_sql_executor.py +157 -0
  268. lfx/components/google/google_drive.py +92 -0
  269. lfx/components/google/google_drive_search.py +152 -0
  270. lfx/components/google/google_generative_ai.py +147 -0
  271. lfx/components/google/google_generative_ai_embeddings.py +141 -0
  272. lfx/components/google/google_oauth_token.py +89 -0
  273. lfx/components/google/google_search_api_core.py +68 -0
  274. lfx/components/google/google_serper_api_core.py +74 -0
  275. lfx/components/groq/__init__.py +34 -0
  276. lfx/components/groq/groq.py +136 -0
  277. lfx/components/helpers/__init__.py +52 -0
  278. lfx/components/helpers/calculator_core.py +89 -0
  279. lfx/components/helpers/create_list.py +40 -0
  280. lfx/components/helpers/current_date.py +42 -0
  281. lfx/components/helpers/id_generator.py +42 -0
  282. lfx/components/helpers/memory.py +251 -0
  283. lfx/components/helpers/output_parser.py +45 -0
  284. lfx/components/helpers/store_message.py +90 -0
  285. lfx/components/homeassistant/__init__.py +7 -0
  286. lfx/components/homeassistant/home_assistant_control.py +152 -0
  287. lfx/components/homeassistant/list_home_assistant_states.py +137 -0
  288. lfx/components/huggingface/__init__.py +37 -0
  289. lfx/components/huggingface/huggingface.py +197 -0
  290. lfx/components/huggingface/huggingface_inference_api.py +106 -0
  291. lfx/components/ibm/__init__.py +34 -0
  292. lfx/components/ibm/watsonx.py +203 -0
  293. lfx/components/ibm/watsonx_embeddings.py +135 -0
  294. lfx/components/icosacomputing/__init__.py +5 -0
  295. lfx/components/icosacomputing/combinatorial_reasoner.py +84 -0
  296. lfx/components/input_output/__init__.py +38 -0
  297. lfx/components/input_output/chat.py +120 -0
  298. lfx/components/input_output/chat_output.py +200 -0
  299. lfx/components/input_output/text.py +27 -0
  300. lfx/components/input_output/text_output.py +29 -0
  301. lfx/components/jigsawstack/__init__.py +23 -0
  302. lfx/components/jigsawstack/ai_scrape.py +126 -0
  303. lfx/components/jigsawstack/ai_web_search.py +136 -0
  304. lfx/components/jigsawstack/file_read.py +115 -0
  305. lfx/components/jigsawstack/file_upload.py +94 -0
  306. lfx/components/jigsawstack/image_generation.py +205 -0
  307. lfx/components/jigsawstack/nsfw.py +60 -0
  308. lfx/components/jigsawstack/object_detection.py +124 -0
  309. lfx/components/jigsawstack/sentiment.py +112 -0
  310. lfx/components/jigsawstack/text_to_sql.py +90 -0
  311. lfx/components/jigsawstack/text_translate.py +77 -0
  312. lfx/components/jigsawstack/vocr.py +107 -0
  313. lfx/components/langchain_utilities/__init__.py +109 -0
  314. lfx/components/langchain_utilities/character.py +53 -0
  315. lfx/components/langchain_utilities/conversation.py +59 -0
  316. lfx/components/langchain_utilities/csv_agent.py +107 -0
  317. lfx/components/langchain_utilities/fake_embeddings.py +26 -0
  318. lfx/components/langchain_utilities/html_link_extractor.py +35 -0
  319. lfx/components/langchain_utilities/json_agent.py +45 -0
  320. lfx/components/langchain_utilities/langchain_hub.py +126 -0
  321. lfx/components/langchain_utilities/language_recursive.py +49 -0
  322. lfx/components/langchain_utilities/language_semantic.py +138 -0
  323. lfx/components/langchain_utilities/llm_checker.py +39 -0
  324. lfx/components/langchain_utilities/llm_math.py +42 -0
  325. lfx/components/langchain_utilities/natural_language.py +61 -0
  326. lfx/components/langchain_utilities/openai_tools.py +53 -0
  327. lfx/components/langchain_utilities/openapi.py +48 -0
  328. lfx/components/langchain_utilities/recursive_character.py +60 -0
  329. lfx/components/langchain_utilities/retrieval_qa.py +83 -0
  330. lfx/components/langchain_utilities/runnable_executor.py +137 -0
  331. lfx/components/langchain_utilities/self_query.py +80 -0
  332. lfx/components/langchain_utilities/spider.py +142 -0
  333. lfx/components/langchain_utilities/sql.py +40 -0
  334. lfx/components/langchain_utilities/sql_database.py +35 -0
  335. lfx/components/langchain_utilities/sql_generator.py +78 -0
  336. lfx/components/langchain_utilities/tool_calling.py +59 -0
  337. lfx/components/langchain_utilities/vector_store_info.py +49 -0
  338. lfx/components/langchain_utilities/vector_store_router.py +33 -0
  339. lfx/components/langchain_utilities/xml_agent.py +71 -0
  340. lfx/components/langwatch/__init__.py +3 -0
  341. lfx/components/langwatch/langwatch.py +278 -0
  342. lfx/components/link_extractors/__init__.py +3 -0
  343. lfx/components/lmstudio/__init__.py +34 -0
  344. lfx/components/lmstudio/lmstudioembeddings.py +89 -0
  345. lfx/components/lmstudio/lmstudiomodel.py +129 -0
  346. lfx/components/logic/__init__.py +52 -0
  347. lfx/components/logic/conditional_router.py +171 -0
  348. lfx/components/logic/data_conditional_router.py +125 -0
  349. lfx/components/logic/flow_tool.py +110 -0
  350. lfx/components/logic/listen.py +29 -0
  351. lfx/components/logic/loop.py +125 -0
  352. lfx/components/logic/notify.py +88 -0
  353. lfx/components/logic/pass_message.py +35 -0
  354. lfx/components/logic/run_flow.py +71 -0
  355. lfx/components/logic/sub_flow.py +114 -0
  356. lfx/components/maritalk/__init__.py +32 -0
  357. lfx/components/maritalk/maritalk.py +52 -0
  358. lfx/components/mem0/__init__.py +3 -0
  359. lfx/components/mem0/mem0_chat_memory.py +136 -0
  360. lfx/components/milvus/__init__.py +34 -0
  361. lfx/components/milvus/milvus.py +115 -0
  362. lfx/components/mistral/__init__.py +37 -0
  363. lfx/components/mistral/mistral.py +114 -0
  364. lfx/components/mistral/mistral_embeddings.py +58 -0
  365. lfx/components/models/__init__.py +34 -0
  366. lfx/components/models/embedding_model.py +114 -0
  367. lfx/components/models/language_model.py +144 -0
  368. lfx/components/mongodb/__init__.py +34 -0
  369. lfx/components/mongodb/mongodb_atlas.py +213 -0
  370. lfx/components/needle/__init__.py +3 -0
  371. lfx/components/needle/needle.py +104 -0
  372. lfx/components/notdiamond/__init__.py +34 -0
  373. lfx/components/notdiamond/notdiamond.py +228 -0
  374. lfx/components/novita/__init__.py +32 -0
  375. lfx/components/novita/novita.py +130 -0
  376. lfx/components/nvidia/__init__.py +57 -0
  377. lfx/components/nvidia/nvidia.py +157 -0
  378. lfx/components/nvidia/nvidia_embedding.py +77 -0
  379. lfx/components/nvidia/nvidia_ingest.py +317 -0
  380. lfx/components/nvidia/nvidia_rerank.py +63 -0
  381. lfx/components/nvidia/system_assist.py +65 -0
  382. lfx/components/olivya/__init__.py +3 -0
  383. lfx/components/olivya/olivya.py +116 -0
  384. lfx/components/ollama/__init__.py +37 -0
  385. lfx/components/ollama/ollama.py +330 -0
  386. lfx/components/ollama/ollama_embeddings.py +106 -0
  387. lfx/components/openai/__init__.py +37 -0
  388. lfx/components/openai/openai.py +100 -0
  389. lfx/components/openai/openai_chat_model.py +176 -0
  390. lfx/components/openrouter/__init__.py +32 -0
  391. lfx/components/openrouter/openrouter.py +202 -0
  392. lfx/components/output_parsers/__init__.py +3 -0
  393. lfx/components/perplexity/__init__.py +34 -0
  394. lfx/components/perplexity/perplexity.py +75 -0
  395. lfx/components/pgvector/__init__.py +34 -0
  396. lfx/components/pgvector/pgvector.py +72 -0
  397. lfx/components/pinecone/__init__.py +34 -0
  398. lfx/components/pinecone/pinecone.py +134 -0
  399. lfx/components/processing/__init__.py +117 -0
  400. lfx/components/processing/alter_metadata.py +108 -0
  401. lfx/components/processing/batch_run.py +205 -0
  402. lfx/components/processing/combine_text.py +39 -0
  403. lfx/components/processing/converter.py +159 -0
  404. lfx/components/processing/create_data.py +110 -0
  405. lfx/components/processing/data_operations.py +438 -0
  406. lfx/components/processing/data_to_dataframe.py +70 -0
  407. lfx/components/processing/dataframe_operations.py +313 -0
  408. lfx/components/processing/extract_key.py +53 -0
  409. lfx/components/processing/filter_data.py +42 -0
  410. lfx/components/processing/filter_data_values.py +88 -0
  411. lfx/components/processing/json_cleaner.py +103 -0
  412. lfx/components/processing/lambda_filter.py +154 -0
  413. lfx/components/processing/llm_router.py +499 -0
  414. lfx/components/processing/merge_data.py +90 -0
  415. lfx/components/processing/message_to_data.py +36 -0
  416. lfx/components/processing/parse_data.py +70 -0
  417. lfx/components/processing/parse_dataframe.py +68 -0
  418. lfx/components/processing/parse_json_data.py +90 -0
  419. lfx/components/processing/parser.py +143 -0
  420. lfx/components/processing/prompt.py +67 -0
  421. lfx/components/processing/python_repl_core.py +98 -0
  422. lfx/components/processing/regex.py +82 -0
  423. lfx/components/processing/save_file.py +225 -0
  424. lfx/components/processing/select_data.py +48 -0
  425. lfx/components/processing/split_text.py +141 -0
  426. lfx/components/processing/structured_output.py +202 -0
  427. lfx/components/processing/update_data.py +160 -0
  428. lfx/components/prototypes/__init__.py +34 -0
  429. lfx/components/prototypes/python_function.py +73 -0
  430. lfx/components/qdrant/__init__.py +34 -0
  431. lfx/components/qdrant/qdrant.py +109 -0
  432. lfx/components/redis/__init__.py +37 -0
  433. lfx/components/redis/redis.py +89 -0
  434. lfx/components/redis/redis_chat.py +43 -0
  435. lfx/components/sambanova/__init__.py +32 -0
  436. lfx/components/sambanova/sambanova.py +84 -0
  437. lfx/components/scrapegraph/__init__.py +40 -0
  438. lfx/components/scrapegraph/scrapegraph_markdownify_api.py +64 -0
  439. lfx/components/scrapegraph/scrapegraph_search_api.py +64 -0
  440. lfx/components/scrapegraph/scrapegraph_smart_scraper_api.py +71 -0
  441. lfx/components/searchapi/__init__.py +34 -0
  442. lfx/components/searchapi/search.py +79 -0
  443. lfx/components/serpapi/__init__.py +3 -0
  444. lfx/components/serpapi/serp.py +115 -0
  445. lfx/components/supabase/__init__.py +34 -0
  446. lfx/components/supabase/supabase.py +76 -0
  447. lfx/components/tavily/__init__.py +4 -0
  448. lfx/components/tavily/tavily_extract.py +117 -0
  449. lfx/components/tavily/tavily_search.py +212 -0
  450. lfx/components/textsplitters/__init__.py +3 -0
  451. lfx/components/toolkits/__init__.py +3 -0
  452. lfx/components/tools/__init__.py +72 -0
  453. lfx/components/tools/calculator.py +108 -0
  454. lfx/components/tools/google_search_api.py +45 -0
  455. lfx/components/tools/google_serper_api.py +115 -0
  456. lfx/components/tools/python_code_structured_tool.py +327 -0
  457. lfx/components/tools/python_repl.py +97 -0
  458. lfx/components/tools/search_api.py +87 -0
  459. lfx/components/tools/searxng.py +145 -0
  460. lfx/components/tools/serp_api.py +119 -0
  461. lfx/components/tools/tavily_search_tool.py +344 -0
  462. lfx/components/tools/wikidata_api.py +102 -0
  463. lfx/components/tools/wikipedia_api.py +49 -0
  464. lfx/components/tools/yahoo_finance.py +129 -0
  465. lfx/components/twelvelabs/__init__.py +52 -0
  466. lfx/components/twelvelabs/convert_astra_results.py +84 -0
  467. lfx/components/twelvelabs/pegasus_index.py +311 -0
  468. lfx/components/twelvelabs/split_video.py +291 -0
  469. lfx/components/twelvelabs/text_embeddings.py +57 -0
  470. lfx/components/twelvelabs/twelvelabs_pegasus.py +408 -0
  471. lfx/components/twelvelabs/video_embeddings.py +100 -0
  472. lfx/components/twelvelabs/video_file.py +179 -0
  473. lfx/components/unstructured/__init__.py +3 -0
  474. lfx/components/unstructured/unstructured.py +121 -0
  475. lfx/components/upstash/__init__.py +34 -0
  476. lfx/components/upstash/upstash.py +124 -0
  477. lfx/components/vectara/__init__.py +37 -0
  478. lfx/components/vectara/vectara.py +97 -0
  479. lfx/components/vectara/vectara_rag.py +164 -0
  480. lfx/components/vectorstores/__init__.py +40 -0
  481. lfx/components/vectorstores/astradb.py +1285 -0
  482. lfx/components/vectorstores/astradb_graph.py +319 -0
  483. lfx/components/vectorstores/cassandra.py +264 -0
  484. lfx/components/vectorstores/cassandra_graph.py +238 -0
  485. lfx/components/vectorstores/chroma.py +167 -0
  486. lfx/components/vectorstores/clickhouse.py +135 -0
  487. lfx/components/vectorstores/couchbase.py +102 -0
  488. lfx/components/vectorstores/elasticsearch.py +267 -0
  489. lfx/components/vectorstores/faiss.py +111 -0
  490. lfx/components/vectorstores/graph_rag.py +141 -0
  491. lfx/components/vectorstores/hcd.py +314 -0
  492. lfx/components/vectorstores/local_db.py +261 -0
  493. lfx/components/vectorstores/milvus.py +115 -0
  494. lfx/components/vectorstores/mongodb_atlas.py +213 -0
  495. lfx/components/vectorstores/opensearch.py +243 -0
  496. lfx/components/vectorstores/pgvector.py +72 -0
  497. lfx/components/vectorstores/pinecone.py +134 -0
  498. lfx/components/vectorstores/qdrant.py +109 -0
  499. lfx/components/vectorstores/supabase.py +76 -0
  500. lfx/components/vectorstores/upstash.py +124 -0
  501. lfx/components/vectorstores/vectara.py +97 -0
  502. lfx/components/vectorstores/vectara_rag.py +164 -0
  503. lfx/components/vectorstores/weaviate.py +89 -0
  504. lfx/components/vertexai/__init__.py +37 -0
  505. lfx/components/vertexai/vertexai.py +71 -0
  506. lfx/components/vertexai/vertexai_embeddings.py +67 -0
  507. lfx/components/weaviate/__init__.py +34 -0
  508. lfx/components/weaviate/weaviate.py +89 -0
  509. lfx/components/wikipedia/__init__.py +4 -0
  510. lfx/components/wikipedia/wikidata.py +86 -0
  511. lfx/components/wikipedia/wikipedia.py +53 -0
  512. lfx/components/wolframalpha/__init__.py +3 -0
  513. lfx/components/wolframalpha/wolfram_alpha_api.py +54 -0
  514. lfx/components/xai/__init__.py +32 -0
  515. lfx/components/xai/xai.py +167 -0
  516. lfx/components/yahoosearch/__init__.py +3 -0
  517. lfx/components/yahoosearch/yahoo.py +137 -0
  518. lfx/components/youtube/__init__.py +52 -0
  519. lfx/components/youtube/channel.py +227 -0
  520. lfx/components/youtube/comments.py +231 -0
  521. lfx/components/youtube/playlist.py +33 -0
  522. lfx/components/youtube/search.py +120 -0
  523. lfx/components/youtube/trending.py +285 -0
  524. lfx/components/youtube/video_details.py +263 -0
  525. lfx/components/youtube/youtube_transcripts.py +118 -0
  526. lfx/components/zep/__init__.py +3 -0
  527. lfx/components/zep/zep.py +44 -0
  528. lfx/constants.py +6 -0
  529. lfx/custom/__init__.py +7 -0
  530. lfx/custom/attributes.py +86 -0
  531. lfx/custom/code_parser/__init__.py +3 -0
  532. lfx/custom/code_parser/code_parser.py +361 -0
  533. lfx/custom/custom_component/__init__.py +0 -0
  534. lfx/custom/custom_component/base_component.py +128 -0
  535. lfx/custom/custom_component/component.py +1808 -0
  536. lfx/custom/custom_component/component_with_cache.py +8 -0
  537. lfx/custom/custom_component/custom_component.py +588 -0
  538. lfx/custom/dependency_analyzer.py +165 -0
  539. lfx/custom/directory_reader/__init__.py +3 -0
  540. lfx/custom/directory_reader/directory_reader.py +359 -0
  541. lfx/custom/directory_reader/utils.py +171 -0
  542. lfx/custom/eval.py +12 -0
  543. lfx/custom/schema.py +32 -0
  544. lfx/custom/tree_visitor.py +21 -0
  545. lfx/custom/utils.py +877 -0
  546. lfx/custom/validate.py +488 -0
  547. lfx/events/__init__.py +1 -0
  548. lfx/events/event_manager.py +110 -0
  549. lfx/exceptions/__init__.py +0 -0
  550. lfx/exceptions/component.py +15 -0
  551. lfx/field_typing/__init__.py +91 -0
  552. lfx/field_typing/constants.py +215 -0
  553. lfx/field_typing/range_spec.py +35 -0
  554. lfx/graph/__init__.py +6 -0
  555. lfx/graph/edge/__init__.py +0 -0
  556. lfx/graph/edge/base.py +277 -0
  557. lfx/graph/edge/schema.py +119 -0
  558. lfx/graph/edge/utils.py +0 -0
  559. lfx/graph/graph/__init__.py +0 -0
  560. lfx/graph/graph/ascii.py +202 -0
  561. lfx/graph/graph/base.py +2238 -0
  562. lfx/graph/graph/constants.py +63 -0
  563. lfx/graph/graph/runnable_vertices_manager.py +133 -0
  564. lfx/graph/graph/schema.py +52 -0
  565. lfx/graph/graph/state_model.py +66 -0
  566. lfx/graph/graph/utils.py +1024 -0
  567. lfx/graph/schema.py +75 -0
  568. lfx/graph/state/__init__.py +0 -0
  569. lfx/graph/state/model.py +237 -0
  570. lfx/graph/utils.py +200 -0
  571. lfx/graph/vertex/__init__.py +0 -0
  572. lfx/graph/vertex/base.py +823 -0
  573. lfx/graph/vertex/constants.py +0 -0
  574. lfx/graph/vertex/exceptions.py +4 -0
  575. lfx/graph/vertex/param_handler.py +264 -0
  576. lfx/graph/vertex/schema.py +26 -0
  577. lfx/graph/vertex/utils.py +19 -0
  578. lfx/graph/vertex/vertex_types.py +489 -0
  579. lfx/helpers/__init__.py +1 -0
  580. lfx/helpers/base_model.py +71 -0
  581. lfx/helpers/custom.py +13 -0
  582. lfx/helpers/data.py +167 -0
  583. lfx/helpers/flow.py +194 -0
  584. lfx/inputs/__init__.py +68 -0
  585. lfx/inputs/constants.py +2 -0
  586. lfx/inputs/input_mixin.py +328 -0
  587. lfx/inputs/inputs.py +714 -0
  588. lfx/inputs/validators.py +19 -0
  589. lfx/interface/__init__.py +6 -0
  590. lfx/interface/components.py +489 -0
  591. lfx/interface/importing/__init__.py +5 -0
  592. lfx/interface/importing/utils.py +39 -0
  593. lfx/interface/initialize/__init__.py +3 -0
  594. lfx/interface/initialize/loading.py +224 -0
  595. lfx/interface/listing.py +26 -0
  596. lfx/interface/run.py +16 -0
  597. lfx/interface/utils.py +111 -0
  598. lfx/io/__init__.py +63 -0
  599. lfx/io/schema.py +289 -0
  600. lfx/load/__init__.py +8 -0
  601. lfx/load/load.py +256 -0
  602. lfx/load/utils.py +99 -0
  603. lfx/log/__init__.py +5 -0
  604. lfx/log/logger.py +385 -0
  605. lfx/memory/__init__.py +90 -0
  606. lfx/memory/stubs.py +283 -0
  607. lfx/processing/__init__.py +1 -0
  608. lfx/processing/process.py +238 -0
  609. lfx/processing/utils.py +25 -0
  610. lfx/py.typed +0 -0
  611. lfx/schema/__init__.py +66 -0
  612. lfx/schema/artifact.py +83 -0
  613. lfx/schema/content_block.py +62 -0
  614. lfx/schema/content_types.py +91 -0
  615. lfx/schema/data.py +308 -0
  616. lfx/schema/dataframe.py +210 -0
  617. lfx/schema/dotdict.py +74 -0
  618. lfx/schema/encoders.py +13 -0
  619. lfx/schema/graph.py +47 -0
  620. lfx/schema/image.py +131 -0
  621. lfx/schema/json_schema.py +141 -0
  622. lfx/schema/log.py +61 -0
  623. lfx/schema/message.py +473 -0
  624. lfx/schema/openai_responses_schemas.py +74 -0
  625. lfx/schema/properties.py +41 -0
  626. lfx/schema/schema.py +171 -0
  627. lfx/schema/serialize.py +13 -0
  628. lfx/schema/table.py +140 -0
  629. lfx/schema/validators.py +114 -0
  630. lfx/serialization/__init__.py +5 -0
  631. lfx/serialization/constants.py +2 -0
  632. lfx/serialization/serialization.py +314 -0
  633. lfx/services/__init__.py +23 -0
  634. lfx/services/base.py +28 -0
  635. lfx/services/cache/__init__.py +6 -0
  636. lfx/services/cache/base.py +183 -0
  637. lfx/services/cache/service.py +166 -0
  638. lfx/services/cache/utils.py +169 -0
  639. lfx/services/chat/__init__.py +1 -0
  640. lfx/services/chat/config.py +2 -0
  641. lfx/services/chat/schema.py +10 -0
  642. lfx/services/deps.py +129 -0
  643. lfx/services/factory.py +19 -0
  644. lfx/services/initialize.py +19 -0
  645. lfx/services/interfaces.py +103 -0
  646. lfx/services/manager.py +172 -0
  647. lfx/services/schema.py +20 -0
  648. lfx/services/session.py +82 -0
  649. lfx/services/settings/__init__.py +3 -0
  650. lfx/services/settings/auth.py +130 -0
  651. lfx/services/settings/base.py +539 -0
  652. lfx/services/settings/constants.py +31 -0
  653. lfx/services/settings/factory.py +23 -0
  654. lfx/services/settings/feature_flags.py +12 -0
  655. lfx/services/settings/service.py +35 -0
  656. lfx/services/settings/utils.py +40 -0
  657. lfx/services/shared_component_cache/__init__.py +1 -0
  658. lfx/services/shared_component_cache/factory.py +30 -0
  659. lfx/services/shared_component_cache/service.py +9 -0
  660. lfx/services/storage/__init__.py +5 -0
  661. lfx/services/storage/local.py +155 -0
  662. lfx/services/storage/service.py +54 -0
  663. lfx/services/tracing/__init__.py +1 -0
  664. lfx/services/tracing/service.py +21 -0
  665. lfx/settings.py +6 -0
  666. lfx/template/__init__.py +6 -0
  667. lfx/template/field/__init__.py +0 -0
  668. lfx/template/field/base.py +257 -0
  669. lfx/template/field/prompt.py +15 -0
  670. lfx/template/frontend_node/__init__.py +6 -0
  671. lfx/template/frontend_node/base.py +212 -0
  672. lfx/template/frontend_node/constants.py +65 -0
  673. lfx/template/frontend_node/custom_components.py +79 -0
  674. lfx/template/template/__init__.py +0 -0
  675. lfx/template/template/base.py +100 -0
  676. lfx/template/utils.py +217 -0
  677. lfx/type_extraction/__init__.py +19 -0
  678. lfx/type_extraction/type_extraction.py +75 -0
  679. lfx/type_extraction.py +80 -0
  680. lfx/utils/__init__.py +1 -0
  681. lfx/utils/async_helpers.py +42 -0
  682. lfx/utils/component_utils.py +154 -0
  683. lfx/utils/concurrency.py +60 -0
  684. lfx/utils/connection_string_parser.py +11 -0
  685. lfx/utils/constants.py +205 -0
  686. lfx/utils/data_structure.py +212 -0
  687. lfx/utils/exceptions.py +22 -0
  688. lfx/utils/helpers.py +28 -0
  689. lfx/utils/image.py +73 -0
  690. lfx/utils/lazy_load.py +15 -0
  691. lfx/utils/request_utils.py +18 -0
  692. lfx/utils/schemas.py +139 -0
  693. lfx/utils/util.py +481 -0
  694. lfx/utils/util_strings.py +56 -0
  695. lfx/utils/version.py +24 -0
  696. lfx_nightly-0.1.11.dev0.dist-info/METADATA +293 -0
  697. lfx_nightly-0.1.11.dev0.dist-info/RECORD +699 -0
  698. lfx_nightly-0.1.11.dev0.dist-info/WHEEL +4 -0
  699. lfx_nightly-0.1.11.dev0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,1291 @@
1
+ import copy
2
+ import re
3
+ from typing import Any
4
+
5
+ from composio import Composio
6
+ from composio_langchain import LangchainProvider
7
+ from langchain_core.tools import Tool
8
+
9
+ from lfx.custom.custom_component.component import Component
10
+ from lfx.inputs.inputs import AuthInput, FileInput, InputTypes, MessageTextInput, SecretStrInput, SortableListInput
11
+ from lfx.io import Output
12
+ from lfx.io.schema import flatten_schema, schema_to_langflow_inputs
13
+ from lfx.log.logger import logger
14
+ from lfx.schema.data import Data
15
+ from lfx.schema.dataframe import DataFrame
16
+ from lfx.schema.json_schema import create_input_schema_from_json_schema
17
+ from lfx.schema.message import Message
18
+
19
+
20
+ def _patch_graph_clean_null_input_types() -> None:
21
+ """Monkey-patch Graph._create_vertex to clean legacy templates."""
22
+ try:
23
+ from lfx.graph.graph.base import Graph
24
+
25
+ if getattr(Graph, "_composio_patch_applied", False):
26
+ return
27
+
28
+ original_create_vertex = Graph._create_vertex
29
+
30
+ def _create_vertex_with_cleanup(self, frontend_data):
31
+ try:
32
+ node_id: str | None = frontend_data.get("id") if isinstance(frontend_data, dict) else None
33
+ if node_id and "Composio" in node_id:
34
+ template = frontend_data.get("data", {}).get("node", {}).get("template", {})
35
+ if isinstance(template, dict):
36
+ for field_cfg in template.values():
37
+ if isinstance(field_cfg, dict) and field_cfg.get("input_types") is None:
38
+ field_cfg["input_types"] = []
39
+ except (AttributeError, TypeError, KeyError) as e:
40
+ logger.debug(f"Composio template cleanup encountered error: {e}")
41
+
42
+ return original_create_vertex(self, frontend_data)
43
+
44
+ Graph._create_vertex = _create_vertex_with_cleanup # type: ignore[method-assign]
45
+ Graph._composio_patch_applied = True # type: ignore[attr-defined]
46
+ logger.debug("Applied Composio template cleanup patch to Graph._create_vertex")
47
+
48
+ except (AttributeError, TypeError) as e:
49
+ logger.debug(f"Failed to apply Composio Graph patch: {e}")
50
+
51
+
52
+ # Apply the patch at import time
53
+ _patch_graph_clean_null_input_types()
54
+
55
+
56
+ class ComposioBaseComponent(Component):
57
+ """Base class for Composio components with common functionality."""
58
+
59
+ default_tools_limit: int = 5
60
+
61
+ _base_inputs = [
62
+ MessageTextInput(
63
+ name="entity_id",
64
+ display_name="Entity ID",
65
+ value="default",
66
+ advanced=True,
67
+ tool_mode=True,
68
+ ),
69
+ SecretStrInput(
70
+ name="api_key",
71
+ display_name="Composio API Key",
72
+ required=True,
73
+ real_time_refresh=True,
74
+ value="COMPOSIO_API_KEY",
75
+ ),
76
+ AuthInput(
77
+ name="auth_link",
78
+ value="",
79
+ auth_tooltip="Please insert a valid Composio API Key.",
80
+ show=False,
81
+ ),
82
+ SortableListInput(
83
+ name="action_button",
84
+ display_name="Action",
85
+ placeholder="Select action",
86
+ options=[],
87
+ value="disabled",
88
+ helper_text="Please connect before selecting actions.",
89
+ helper_text_metadata={"variant": "destructive"},
90
+ show=True,
91
+ required=False,
92
+ real_time_refresh=True,
93
+ limit=1,
94
+ ),
95
+ ]
96
+
97
+ _name_sanitizer = re.compile(r"[^a-zA-Z0-9_-]")
98
+
99
+ # Class-level caches
100
+ _actions_cache: dict[str, dict[str, Any]] = {}
101
+ _action_schema_cache: dict[str, dict[str, Any]] = {}
102
+
103
+ outputs = [
104
+ Output(name="dataFrame", display_name="DataFrame", method="as_dataframe"),
105
+ ]
106
+
107
+ inputs = list(_base_inputs)
108
+
109
+ def __init__(self, **kwargs):
110
+ """Initialize instance variables to prevent shared state between components."""
111
+ super().__init__(**kwargs)
112
+ self._all_fields: set[str] = set()
113
+ self._bool_variables: set[str] = set()
114
+ self._actions_data: dict[str, dict[str, Any]] = {}
115
+ self._default_tools: set[str] = set()
116
+ self._display_to_key_map: dict[str, str] = {}
117
+ self._key_to_display_map: dict[str, str] = {}
118
+ self._sanitized_names: dict[str, str] = {}
119
+ self._action_schemas: dict[str, Any] = {}
120
+
121
+ def as_message(self) -> Message:
122
+ result = self.execute_action()
123
+ if result is None:
124
+ return Message(text="Action execution returned no result")
125
+ return Message(text=str(result))
126
+
127
+ def as_dataframe(self) -> DataFrame:
128
+ result = self.execute_action()
129
+
130
+ if isinstance(result, dict):
131
+ result = [result]
132
+ # Build DataFrame and avoid exposing a 'data' attribute via column access,
133
+ result_dataframe = DataFrame(result)
134
+ if hasattr(result_dataframe, "columns"):
135
+ try:
136
+ if "data" in result_dataframe.columns:
137
+ result_dataframe = result_dataframe.rename(columns={"data": "_data"})
138
+ except (AttributeError, TypeError, ValueError, KeyError) as e:
139
+ logger.debug(f"Failed to rename 'data' column: {e}")
140
+ return result_dataframe
141
+
142
+ def as_data(self) -> Data:
143
+ result = self.execute_action()
144
+ return Data(results=result)
145
+
146
+ def _build_action_maps(self):
147
+ """Build lookup maps for action names."""
148
+ if not self._display_to_key_map or not self._key_to_display_map:
149
+ self._display_to_key_map = {data["display_name"]: key for key, data in self._actions_data.items()}
150
+ self._key_to_display_map = {key: data["display_name"] for key, data in self._actions_data.items()}
151
+ self._sanitized_names = {
152
+ action: self._name_sanitizer.sub("-", self.sanitize_action_name(action))
153
+ for action in self._actions_data
154
+ }
155
+
156
+ def sanitize_action_name(self, action_name: str) -> str:
157
+ """Convert action name to display name using lookup."""
158
+ self._build_action_maps()
159
+ return self._key_to_display_map.get(action_name, action_name)
160
+
161
+ def desanitize_action_name(self, action_name: str) -> str:
162
+ """Convert display name to action key using lookup."""
163
+ self._build_action_maps()
164
+ return self._display_to_key_map.get(action_name, action_name)
165
+
166
+ def _get_action_fields(self, action_key: str | None) -> set[str]:
167
+ """Get fields for an action."""
168
+ if action_key is None:
169
+ return set()
170
+ return set(self._actions_data[action_key]["action_fields"]) if action_key in self._actions_data else set()
171
+
172
+ def _build_wrapper(self) -> Composio:
173
+ """Build the Composio wrapper."""
174
+ try:
175
+ if not self.api_key:
176
+ msg = "Composio API Key is required"
177
+ raise ValueError(msg)
178
+ return Composio(api_key=self.api_key, provider=LangchainProvider())
179
+
180
+ except ValueError as e:
181
+ logger.error(f"Error building Composio wrapper: {e}")
182
+ msg = "Please provide a valid Composio API Key in the component settings"
183
+ raise ValueError(msg) from e
184
+
185
+ def show_hide_fields(self, build_config: dict, field_value: Any):
186
+ """Optimized field visibility updates by only modifying show values."""
187
+ if not field_value:
188
+ for field in self._all_fields:
189
+ build_config[field]["show"] = False
190
+ if field in self._bool_variables:
191
+ build_config[field]["value"] = False
192
+ else:
193
+ build_config[field]["value"] = ""
194
+ return
195
+
196
+ action_key = None
197
+ if isinstance(field_value, list) and field_value:
198
+ action_key = self.desanitize_action_name(field_value[0]["name"])
199
+ else:
200
+ action_key = field_value
201
+
202
+ fields_to_show = self._get_action_fields(action_key)
203
+
204
+ for field in self._all_fields:
205
+ should_show = field in fields_to_show
206
+ if build_config[field]["show"] != should_show:
207
+ build_config[field]["show"] = should_show
208
+ if not should_show:
209
+ if field in self._bool_variables:
210
+ build_config[field]["value"] = False
211
+ else:
212
+ build_config[field]["value"] = ""
213
+
214
+ def _populate_actions_data(self):
215
+ """Fetch the list of actions for the toolkit and build helper maps."""
216
+ if self._actions_data:
217
+ return
218
+
219
+ # Try to load from the class-level cache
220
+ toolkit_slug = self.app_name.lower()
221
+ if toolkit_slug in self.__class__._actions_cache:
222
+ # Deep-copy so that any mutation on this instance does not affect the
223
+ # cached master copy.
224
+ self._actions_data = copy.deepcopy(self.__class__._actions_cache[toolkit_slug])
225
+ self._action_schemas = copy.deepcopy(self.__class__._action_schema_cache.get(toolkit_slug, {}))
226
+ logger.debug(f"Loaded actions for {toolkit_slug} from in-process cache")
227
+ return
228
+
229
+ api_key = getattr(self, "api_key", None)
230
+ if not api_key:
231
+ logger.warning("API key is missing. Cannot populate actions data.")
232
+ return
233
+
234
+ try:
235
+ composio = self._build_wrapper()
236
+ toolkit_slug = self.app_name.lower()
237
+
238
+ raw_tools = composio.tools.get_raw_composio_tools(toolkits=[toolkit_slug], limit=999)
239
+
240
+ if not raw_tools:
241
+ msg = f"Toolkit '{toolkit_slug}' not found or has no available tools"
242
+ raise ValueError(msg)
243
+
244
+ for raw_tool in raw_tools:
245
+ try:
246
+ # Convert raw_tool to dict-like structure
247
+ tool_dict = raw_tool.__dict__ if hasattr(raw_tool, "__dict__") else raw_tool
248
+
249
+ if not tool_dict:
250
+ logger.warning(f"Tool is None or empty: {raw_tool}")
251
+ continue
252
+
253
+ action_key = tool_dict.get("slug")
254
+ if not action_key:
255
+ logger.warning(f"Action key (slug) is missing in tool: {tool_dict}")
256
+ continue
257
+
258
+ # Human-friendly display name
259
+ display_name = tool_dict.get("name") or tool_dict.get("display_name")
260
+ if not display_name:
261
+ # Better fallback: convert GMAIL_SEND_EMAIL to "Send Email"
262
+ # Remove app prefix and convert to title case
263
+ clean_name = action_key
264
+ clean_name = clean_name.removeprefix(f"{self.app_name.upper()}_")
265
+ # Convert underscores to spaces and title case
266
+ display_name = clean_name.replace("_", " ").title()
267
+
268
+ # Build list of parameter names and track bool fields
269
+ parameters_schema = tool_dict.get("input_parameters", {})
270
+ if parameters_schema is None:
271
+ logger.warning(f"Parameters schema is None for action key: {action_key}")
272
+ # Still add the action but with empty fields
273
+ self._action_schemas[action_key] = tool_dict
274
+ self._actions_data[action_key] = {
275
+ "display_name": display_name,
276
+ "action_fields": [],
277
+ "file_upload_fields": set(),
278
+ }
279
+ continue
280
+
281
+ try:
282
+ # Special handling for unusual schema structures
283
+ if not isinstance(parameters_schema, dict):
284
+ # Try to convert if it's a model object
285
+ if hasattr(parameters_schema, "model_dump"):
286
+ parameters_schema = parameters_schema.model_dump()
287
+ elif hasattr(parameters_schema, "__dict__"):
288
+ parameters_schema = parameters_schema.__dict__
289
+ else:
290
+ logger.warning(f"Cannot process parameters schema for {action_key}, skipping")
291
+ self._action_schemas[action_key] = tool_dict
292
+ self._actions_data[action_key] = {
293
+ "display_name": display_name,
294
+ "action_fields": [],
295
+ "file_upload_fields": set(),
296
+ }
297
+ continue
298
+
299
+ # Validate parameters_schema has required structure before flattening
300
+ if not parameters_schema.get("properties") and not parameters_schema.get("$defs"):
301
+ # Create a minimal valid schema to avoid errors
302
+ parameters_schema = {"type": "object", "properties": {}}
303
+
304
+ # Sanitize the schema before passing to flatten_schema
305
+ # Handle case where 'required' is explicitly None (causes "'NoneType' object is not iterable")
306
+ if parameters_schema.get("required") is None:
307
+ parameters_schema = parameters_schema.copy() # Don't modify the original
308
+ parameters_schema["required"] = []
309
+
310
+ try:
311
+ # Preserve original descriptions before flattening to restore if lost
312
+ original_descriptions = {}
313
+ original_props = parameters_schema.get("properties", {})
314
+ for prop_name, prop_schema in original_props.items():
315
+ if isinstance(prop_schema, dict) and "description" in prop_schema:
316
+ original_descriptions[prop_name] = prop_schema["description"]
317
+
318
+ flat_schema = flatten_schema(parameters_schema)
319
+
320
+ # Restore lost descriptions in flattened schema
321
+ if flat_schema and isinstance(flat_schema, dict) and "properties" in flat_schema:
322
+ flat_props = flat_schema["properties"]
323
+ for field_name, field_schema in flat_props.items():
324
+ # Check if this field lost its description during flattening
325
+ if isinstance(field_schema, dict) and "description" not in field_schema:
326
+ # Try to find the original description
327
+ # Handle array fields like bcc[0] -> bcc
328
+ base_field_name = field_name.replace("[0]", "")
329
+ if base_field_name in original_descriptions:
330
+ field_schema["description"] = original_descriptions[base_field_name]
331
+ elif field_name in original_descriptions:
332
+ field_schema["description"] = original_descriptions[field_name]
333
+ except (KeyError, TypeError, ValueError):
334
+ self._action_schemas[action_key] = tool_dict
335
+ self._actions_data[action_key] = {
336
+ "display_name": display_name,
337
+ "action_fields": [],
338
+ "file_upload_fields": set(),
339
+ }
340
+ continue
341
+
342
+ if flat_schema is None:
343
+ logger.warning(f"Flat schema is None for action key: {action_key}")
344
+ # Still add the action but with empty fields so the UI doesn't break
345
+ self._action_schemas[action_key] = tool_dict
346
+ self._actions_data[action_key] = {
347
+ "display_name": display_name,
348
+ "action_fields": [],
349
+ "file_upload_fields": set(),
350
+ }
351
+ continue
352
+
353
+ # Extract field names and detect file upload fields during parsing
354
+ raw_action_fields = list(flat_schema.get("properties", {}).keys())
355
+ action_fields = []
356
+ attachment_related_found = False
357
+ file_upload_fields = set()
358
+
359
+ # Check original schema properties for file_uploadable fields
360
+ original_props = parameters_schema.get("properties", {})
361
+ for field_name, field_schema in original_props.items():
362
+ if isinstance(field_schema, dict):
363
+ clean_field_name = field_name.replace("[0]", "")
364
+ # Check direct file_uploadable attribute
365
+ if field_schema.get("file_uploadable") is True:
366
+ file_upload_fields.add(clean_field_name)
367
+
368
+ # Check anyOf structures (like OUTLOOK_OUTLOOK_SEND_EMAIL)
369
+ if "anyOf" in field_schema:
370
+ for any_of_item in field_schema["anyOf"]:
371
+ if isinstance(any_of_item, dict) and any_of_item.get("file_uploadable") is True:
372
+ file_upload_fields.add(clean_field_name)
373
+
374
+ for field in raw_action_fields:
375
+ clean_field = field.replace("[0]", "")
376
+ # Check if this field is attachment-related
377
+ if clean_field.lower().startswith("attachment."):
378
+ attachment_related_found = True
379
+ continue # Skip individual attachment fields
380
+
381
+ # Handle conflicting field names - rename user_id to avoid conflicts with entity_id
382
+ if clean_field == "user_id":
383
+ clean_field = f"{self.app_name}_user_id"
384
+ elif clean_field == "status":
385
+ clean_field = f"{self.app_name}_status"
386
+
387
+ action_fields.append(clean_field)
388
+
389
+ # Add consolidated attachment field if we found attachment-related fields
390
+ if attachment_related_found:
391
+ action_fields.append("attachment")
392
+ file_upload_fields.add("attachment") # Attachment fields are also file upload fields
393
+
394
+ # Track boolean parameters so we can coerce them later
395
+ properties = flat_schema.get("properties", {})
396
+ if properties:
397
+ for p_name, p_schema in properties.items():
398
+ if isinstance(p_schema, dict) and p_schema.get("type") == "boolean":
399
+ # Use cleaned field name for boolean tracking
400
+ clean_field_name = p_name.replace("[0]", "")
401
+ self._bool_variables.add(clean_field_name)
402
+
403
+ self._action_schemas[action_key] = tool_dict
404
+ self._actions_data[action_key] = {
405
+ "display_name": display_name,
406
+ "action_fields": action_fields,
407
+ "file_upload_fields": file_upload_fields,
408
+ }
409
+
410
+ except (KeyError, TypeError, ValueError) as flatten_error:
411
+ logger.error(f"flatten_schema failed for {action_key}: {flatten_error}")
412
+ self._action_schemas[action_key] = tool_dict
413
+ self._actions_data[action_key] = {
414
+ "display_name": display_name,
415
+ "action_fields": [],
416
+ "file_upload_fields": set(),
417
+ }
418
+ continue
419
+
420
+ except ValueError as e:
421
+ logger.warning(f"Failed processing Composio tool for action {raw_tool}: {e}")
422
+
423
+ # Helper look-ups used elsewhere
424
+ self._all_fields = {f for d in self._actions_data.values() for f in d["action_fields"]}
425
+ self._build_action_maps()
426
+
427
+ # Cache actions for this toolkit so subsequent component instances
428
+ # can reuse them without hitting the Composio API again.
429
+ self.__class__._actions_cache[toolkit_slug] = copy.deepcopy(self._actions_data)
430
+ self.__class__._action_schema_cache[toolkit_slug] = copy.deepcopy(self._action_schemas)
431
+
432
+ except ValueError as e:
433
+ logger.debug(f"Could not populate Composio actions for {self.app_name}: {e}")
434
+
435
+ def _validate_schema_inputs(self, action_key: str) -> list[InputTypes]:
436
+ """Convert the JSON schema for *action_key* into Langflow input objects."""
437
+ # Skip validation for default/placeholder values
438
+ if action_key in ("disabled", "placeholder", ""):
439
+ logger.debug(f"Skipping schema validation for placeholder value: {action_key}")
440
+ return []
441
+
442
+ schema_dict = self._action_schemas.get(action_key)
443
+ if not schema_dict:
444
+ logger.warning(f"No schema found for action key: {action_key}")
445
+ return []
446
+
447
+ try:
448
+ parameters_schema = schema_dict.get("input_parameters", {})
449
+ if parameters_schema is None:
450
+ logger.warning(f"Parameters schema is None for action key: {action_key}")
451
+ return []
452
+
453
+ # Check if parameters_schema has the expected structure
454
+ if not isinstance(parameters_schema, dict):
455
+ logger.warning(
456
+ f"Parameters schema is not a dict for action key: {action_key}, got: {type(parameters_schema)}"
457
+ )
458
+ return []
459
+
460
+ # Validate parameters_schema has required structure before flattening
461
+ if not parameters_schema.get("properties") and not parameters_schema.get("$defs"):
462
+ # Create a minimal valid schema to avoid errors
463
+ parameters_schema = {"type": "object", "properties": {}}
464
+
465
+ # Sanitize the schema before passing to flatten_schema
466
+ # Handle case where 'required' is explicitly None (causes "'NoneType' object is not iterable")
467
+ if parameters_schema.get("required") is None:
468
+ parameters_schema = parameters_schema.copy() # Don't modify the original
469
+ parameters_schema["required"] = []
470
+
471
+ try:
472
+ # Preserve original descriptions before flattening to restore if lost
473
+ original_descriptions = {}
474
+ original_props = parameters_schema.get("properties", {})
475
+ for prop_name, prop_schema in original_props.items():
476
+ if isinstance(prop_schema, dict) and "description" in prop_schema:
477
+ original_descriptions[prop_name] = prop_schema["description"]
478
+
479
+ flat_schema = flatten_schema(parameters_schema)
480
+
481
+ # Restore lost descriptions in flattened schema
482
+ if flat_schema and isinstance(flat_schema, dict) and "properties" in flat_schema:
483
+ flat_props = flat_schema["properties"]
484
+ for field_name, field_schema in flat_props.items():
485
+ # Check if this field lost its description during flattening
486
+ if isinstance(field_schema, dict) and "description" not in field_schema:
487
+ # Try to find the original description
488
+ # Handle array fields like bcc[0] -> bcc
489
+ base_field_name = field_name.replace("[0]", "")
490
+ if base_field_name in original_descriptions:
491
+ field_schema["description"] = original_descriptions[base_field_name]
492
+ elif field_name in original_descriptions:
493
+ field_schema["description"] = original_descriptions[field_name]
494
+ except (KeyError, TypeError, ValueError) as flatten_error:
495
+ logger.error(f"flatten_schema failed for {action_key}: {flatten_error}")
496
+ return []
497
+
498
+ if flat_schema is None:
499
+ logger.warning(f"Flat schema is None for action key: {action_key}")
500
+ return []
501
+
502
+ # Additional check for flat_schema structure
503
+ if not isinstance(flat_schema, dict):
504
+ logger.warning(f"Flat schema is not a dict for action key: {action_key}, got: {type(flat_schema)}")
505
+ return []
506
+
507
+ # Ensure flat_schema has the expected structure for create_input_schema_from_json_schema
508
+ if flat_schema.get("type") != "object":
509
+ logger.warning(f"Flat schema for {action_key} is not of type 'object', got: {flat_schema.get('type')}")
510
+ # Fix the schema type if it's missing
511
+ flat_schema["type"] = "object"
512
+
513
+ if "properties" not in flat_schema:
514
+ flat_schema["properties"] = {}
515
+
516
+ # Clean up field names - remove [0] suffixes from array fields
517
+ cleaned_properties = {}
518
+ attachment_related_fields = set() # Track fields that are attachment-related
519
+
520
+ for field_name, field_schema in flat_schema.get("properties", {}).items():
521
+ # Remove [0] suffix from field names (e.g., "bcc[0]" -> "bcc", "cc[0]" -> "cc")
522
+ clean_field_name = field_name.replace("[0]", "")
523
+
524
+ # Check if this field is attachment-related (contains "attachment." prefix)
525
+ if clean_field_name.lower().startswith("attachment."):
526
+ attachment_related_fields.add(clean_field_name)
527
+ # Don't add individual attachment sub-fields to the schema
528
+ continue
529
+
530
+ # Handle conflicting field names - rename user_id to avoid conflicts with entity_id
531
+ if clean_field_name == "user_id":
532
+ clean_field_name = f"{self.app_name}_user_id"
533
+ # Update
534
+ field_schema_copy = field_schema.copy()
535
+ field_schema_copy["description"] = (
536
+ f"User ID for {self.app_name.title()}: " + field_schema["description"]
537
+ )
538
+ elif clean_field_name == "status":
539
+ clean_field_name = f"{self.app_name}_status"
540
+ # Update
541
+ field_schema_copy = field_schema.copy()
542
+ field_schema_copy["description"] = (
543
+ f"Status for {self.app_name.title()}: " + field_schema["description"]
544
+ )
545
+ else:
546
+ # Use the original field schema for all other fields
547
+ field_schema_copy = field_schema
548
+
549
+ # Preserve the full schema information, not just the type
550
+ cleaned_properties[clean_field_name] = field_schema_copy
551
+
552
+ # If we found attachment-related fields, add a single "attachment" field
553
+ if attachment_related_fields:
554
+ # Create a generic attachment field schema
555
+ attachment_schema = {
556
+ "type": "string",
557
+ "description": "File attachment for the email",
558
+ "title": "Attachment",
559
+ }
560
+ cleaned_properties["attachment"] = attachment_schema
561
+
562
+ # Update the flat schema with cleaned field names
563
+ flat_schema["properties"] = cleaned_properties
564
+
565
+ # Also update required fields to match cleaned names
566
+ if flat_schema.get("required"):
567
+ cleaned_required = [field.replace("[0]", "") for field in flat_schema["required"]]
568
+ flat_schema["required"] = cleaned_required
569
+
570
+ input_schema = create_input_schema_from_json_schema(flat_schema)
571
+ if input_schema is None:
572
+ logger.warning(f"Input schema is None for action key: {action_key}")
573
+ return []
574
+
575
+ # Additional safety check before calling schema_to_langflow_inputs
576
+ if not hasattr(input_schema, "model_fields"):
577
+ logger.warning(f"Input schema for {action_key} does not have model_fields attribute")
578
+ return []
579
+
580
+ if input_schema.model_fields is None:
581
+ logger.warning(f"Input schema model_fields is None for {action_key}")
582
+ return []
583
+
584
+ result = schema_to_langflow_inputs(input_schema)
585
+
586
+ # Process inputs to handle attachment fields and set advanced status
587
+ if result:
588
+ processed_inputs = []
589
+ required_fields_set = set(flat_schema.get("required", []))
590
+
591
+ # Get file upload fields from stored action data
592
+ file_upload_fields = self._actions_data.get(action_key, {}).get("file_upload_fields", set())
593
+ if attachment_related_fields: # If we consolidated attachment fields
594
+ file_upload_fields = file_upload_fields | {"attachment"}
595
+
596
+ for inp in result:
597
+ if hasattr(inp, "name") and inp.name is not None:
598
+ # Check if this specific field is a file upload field
599
+ if inp.name.lower() in file_upload_fields or inp.name.lower() == "attachment":
600
+ # Replace with FileInput for file upload fields
601
+ file_input = FileInput(
602
+ name=inp.name,
603
+ display_name=getattr(inp, "display_name", inp.name.replace("_", " ").title()),
604
+ required=inp.name in required_fields_set,
605
+ advanced=inp.name not in required_fields_set,
606
+ info=getattr(inp, "info", "Upload file for this field"),
607
+ show=True,
608
+ file_types=[
609
+ "csv",
610
+ "txt",
611
+ "doc",
612
+ "docx",
613
+ "xls",
614
+ "xlsx",
615
+ "pdf",
616
+ "png",
617
+ "jpg",
618
+ "jpeg",
619
+ "gif",
620
+ "zip",
621
+ "rar",
622
+ "ppt",
623
+ "pptx",
624
+ ],
625
+ )
626
+ processed_inputs.append(file_input)
627
+ else:
628
+ # Ensure proper display_name and info are set for regular fields
629
+ if not hasattr(inp, "display_name") or not inp.display_name:
630
+ inp.display_name = inp.name.replace("_", " ").title()
631
+
632
+ # Preserve description from schema if available
633
+ field_schema = flat_schema.get("properties", {}).get(inp.name, {})
634
+ schema_description = field_schema.get("description")
635
+ current_info = getattr(inp, "info", None)
636
+
637
+ # Use schema description if available, otherwise keep current info or create from name
638
+ if schema_description:
639
+ inp.info = schema_description
640
+ elif not current_info:
641
+ # Fallback: create a basic description from the field name if no description exists
642
+ inp.info = f"{inp.name.replace('_', ' ').title()} field"
643
+
644
+ # Set advanced status for non-file-upload fields
645
+ if inp.name not in required_fields_set:
646
+ inp.advanced = True
647
+
648
+ # Skip entity_id being mapped to user_id parameter
649
+ if inp.name == "user_id" and getattr(self, "entity_id", None) == getattr(
650
+ inp, "value", None
651
+ ):
652
+ continue
653
+
654
+ processed_inputs.append(inp)
655
+ else:
656
+ processed_inputs.append(inp)
657
+
658
+ return processed_inputs
659
+ return result # noqa: TRY300
660
+ except ValueError as e:
661
+ logger.warning(f"Error generating inputs for {action_key}: {e}")
662
+ return []
663
+
664
+ def _get_inputs_for_all_actions(self) -> dict[str, list[InputTypes]]:
665
+ """Return a mapping action_key → list[InputTypes] for every action."""
666
+ result: dict[str, list[InputTypes]] = {}
667
+ for key in self._actions_data:
668
+ result[key] = self._validate_schema_inputs(key)
669
+ return result
670
+
671
+ def _remove_inputs_from_build_config(self, build_config: dict, keep_for_action: str) -> None:
672
+ """Remove parameter UI fields that belong to other actions."""
673
+ protected_keys = {"code", "entity_id", "api_key", "auth_link", "action_button", "tool_mode"}
674
+
675
+ for action_key, lf_inputs in self._get_inputs_for_all_actions().items():
676
+ if action_key == keep_for_action:
677
+ continue
678
+ for inp in lf_inputs:
679
+ if inp.name is not None and inp.name not in protected_keys:
680
+ build_config.pop(inp.name, None)
681
+
682
+ def _update_action_config(self, build_config: dict, selected_value: Any) -> None:
683
+ """Add or update parameter input fields for the chosen action."""
684
+ if not selected_value:
685
+ return
686
+
687
+ # The UI passes either a list with dict [{name: display_name}] OR the raw key
688
+ if isinstance(selected_value, list) and selected_value:
689
+ display_name = selected_value[0]["name"]
690
+ else:
691
+ display_name = selected_value
692
+
693
+ action_key = self.desanitize_action_name(display_name)
694
+
695
+ # Skip validation for default/placeholder values
696
+ if action_key in ("disabled", "placeholder", ""):
697
+ logger.debug(f"Skipping action config update for placeholder value: {action_key}")
698
+ return
699
+
700
+ lf_inputs = self._validate_schema_inputs(action_key)
701
+
702
+ # First remove inputs belonging to other actions
703
+ self._remove_inputs_from_build_config(build_config, action_key)
704
+
705
+ # Add / update the inputs for this action
706
+ for inp in lf_inputs:
707
+ if inp.name is not None:
708
+ inp_dict = inp.to_dict() if hasattr(inp, "to_dict") else inp.__dict__.copy()
709
+
710
+ # Ensure input_types is always a list
711
+ if not isinstance(inp_dict.get("input_types"), list):
712
+ inp_dict["input_types"] = []
713
+
714
+ inp_dict.setdefault("show", True) # visible once action selected
715
+ # Preserve previously entered value if user already filled something
716
+ if inp.name in build_config:
717
+ existing_val = build_config[inp.name].get("value")
718
+ inp_dict.setdefault("value", existing_val)
719
+ build_config[inp.name] = inp_dict
720
+
721
+ # Ensure _all_fields includes new ones
722
+ self._all_fields.update({i.name for i in lf_inputs if i.name is not None})
723
+
724
+ def _is_tool_mode_enabled(self) -> bool:
725
+ """Check if tool_mode is currently enabled."""
726
+ return getattr(self, "tool_mode", False)
727
+
728
+ def _set_action_visibility(self, build_config: dict, *, force_show: bool | None = None) -> None:
729
+ """Set action field visibility based on tool_mode state or forced value."""
730
+ if force_show is not None:
731
+ build_config["action_button"]["show"] = force_show
732
+ else:
733
+ # When tool_mode is enabled, hide action field
734
+ build_config["action_button"]["show"] = not self._is_tool_mode_enabled()
735
+
736
+ def create_new_auth_config(self, app_name: str) -> str:
737
+ """Create a new auth config for the given app name."""
738
+ composio = self._build_wrapper()
739
+ auth_config = composio.auth_configs.create(toolkit=app_name, options={"type": "use_composio_managed_auth"})
740
+ return auth_config.id
741
+
742
+ def _initiate_connection(self, app_name: str) -> tuple[str, str]:
743
+ """Initiate OAuth connection and return (redirect_url, connection_id)."""
744
+ try:
745
+ composio = self._build_wrapper()
746
+
747
+ auth_configs = composio.auth_configs.list(toolkit_slug=app_name)
748
+ if len(auth_configs.items) == 0:
749
+ auth_config_id = self.create_new_auth_config(app_name)
750
+ else:
751
+ auth_config_id = None
752
+ for auth_config in auth_configs.items:
753
+ if auth_config.auth_scheme == "OAUTH2":
754
+ auth_config_id = auth_config.id
755
+
756
+ auth_config_id = auth_configs.items[0].id
757
+
758
+ connection_request = composio.connected_accounts.initiate(
759
+ user_id=self.entity_id, auth_config_id=auth_config_id
760
+ )
761
+
762
+ redirect_url = getattr(connection_request, "redirect_url", None)
763
+ connection_id = getattr(connection_request, "id", None)
764
+
765
+ if not redirect_url or not redirect_url.startswith(("http://", "https://")):
766
+ msg = "Invalid redirect URL received from Composio"
767
+ raise ValueError(msg)
768
+
769
+ if not connection_id:
770
+ msg = "No connection ID received from Composio"
771
+ raise ValueError(msg)
772
+
773
+ logger.info(f"OAuth connection initiated for {app_name}: {redirect_url} (ID: {connection_id})")
774
+ return redirect_url, connection_id # noqa: TRY300
775
+
776
+ except Exception as e:
777
+ logger.error(f"Error initiating connection for {app_name}: {e}")
778
+ msg = f"Failed to initiate OAuth connection: {e}"
779
+ raise ValueError(msg) from e
780
+
781
+ def _check_connection_status_by_id(self, connection_id: str) -> str | None:
782
+ """Check status of a specific connection by ID. Returns status or None if not found."""
783
+ try:
784
+ composio = self._build_wrapper()
785
+ connection = composio.connected_accounts.get(nanoid=connection_id)
786
+ status = getattr(connection, "status", None)
787
+ logger.info(f"Connection {connection_id} status: {status}")
788
+ except (ValueError, ConnectionError) as e:
789
+ logger.error(f"Error checking connection {connection_id}: {e}")
790
+ return None
791
+ else:
792
+ return status
793
+
794
+ def _find_active_connection_for_app(self, app_name: str) -> tuple[str, str] | None:
795
+ """Find any ACTIVE connection for this app/user. Returns (connection_id, status) or None."""
796
+ try:
797
+ composio = self._build_wrapper()
798
+ connection_list = composio.connected_accounts.list(
799
+ user_ids=[self.entity_id], toolkit_slugs=[app_name.lower()]
800
+ )
801
+
802
+ if connection_list and hasattr(connection_list, "items") and connection_list.items:
803
+ for connection in connection_list.items:
804
+ connection_id = getattr(connection, "id", None)
805
+ connection_status = getattr(connection, "status", None)
806
+ if connection_status == "ACTIVE" and connection_id:
807
+ logger.info(f"Found existing ACTIVE connection for {app_name}: {connection_id}")
808
+ return connection_id, connection_status
809
+
810
+ except (ValueError, ConnectionError) as e:
811
+ logger.error(f"Error finding active connection for {app_name}: {e}")
812
+ return None
813
+ else:
814
+ return None
815
+
816
+ def _disconnect_specific_connection(self, connection_id: str) -> None:
817
+ """Disconnect a specific Composio connection by ID."""
818
+ try:
819
+ composio = self._build_wrapper()
820
+ composio.connected_accounts.delete(nanoid=connection_id)
821
+ logger.info(f"✅ Disconnected specific connection: {connection_id}")
822
+
823
+ except Exception as e:
824
+ logger.error(f"Error disconnecting connection {connection_id}: {e}")
825
+ msg = f"Failed to disconnect connection {connection_id}: {e}"
826
+ raise ValueError(msg) from e
827
+
828
+ def update_build_config(self, build_config: dict, field_value: Any, field_name: str | None = None) -> dict:
829
+ """Update build config for auth and action selection."""
830
+ # Clean any legacy None values that may still be present
831
+ for _fconfig in build_config.values():
832
+ if isinstance(_fconfig, dict) and _fconfig.get("input_types") is None:
833
+ _fconfig["input_types"] = []
834
+
835
+ # BULLETPROOF tool_mode checking - check all possible places where tool_mode could be stored
836
+ instance_tool_mode = getattr(self, "tool_mode", False) if hasattr(self, "tool_mode") else False
837
+
838
+ # Check build_config for tool_mode in multiple possible structures
839
+ build_config_tool_mode = False
840
+ if "tool_mode" in build_config:
841
+ tool_mode_config = build_config["tool_mode"]
842
+ if isinstance(tool_mode_config, dict):
843
+ build_config_tool_mode = tool_mode_config.get("value", False)
844
+ else:
845
+ build_config_tool_mode = bool(tool_mode_config)
846
+
847
+ # If this is a tool_mode change, update BOTH instance variable AND build_config
848
+ if field_name == "tool_mode":
849
+ self.tool_mode = field_value
850
+ instance_tool_mode = field_value
851
+ # CRITICAL: Store tool_mode state in build_config so it persists
852
+ if "tool_mode" not in build_config:
853
+ build_config["tool_mode"] = {}
854
+ if isinstance(build_config["tool_mode"], dict):
855
+ build_config["tool_mode"]["value"] = field_value
856
+ build_config_tool_mode = field_value
857
+
858
+ # Current tool_mode is True if ANY source indicates it's enabled
859
+ current_tool_mode = instance_tool_mode or build_config_tool_mode or (field_name == "tool_mode" and field_value)
860
+
861
+ # CRITICAL: Ensure dynamic action metadata is available whenever we have an API key
862
+ # This must happen BEFORE any early returns to ensure tools are always loaded
863
+ api_key_available = hasattr(self, "api_key") and self.api_key
864
+
865
+ # Check if we need to populate actions - but also check cache availability
866
+ actions_available = bool(self._actions_data)
867
+ toolkit_slug = getattr(self, "app_name", "").lower()
868
+ cached_actions_available = toolkit_slug in self.__class__._actions_cache
869
+
870
+ should_populate = False
871
+
872
+ if (field_name == "api_key" and field_value) or (
873
+ api_key_available and not actions_available and not cached_actions_available
874
+ ):
875
+ should_populate = True
876
+ elif api_key_available and not actions_available and cached_actions_available:
877
+ self._populate_actions_data()
878
+
879
+ if should_populate:
880
+ logger.info(f"Populating actions data for {getattr(self, 'app_name', 'unknown')}...")
881
+ self._populate_actions_data()
882
+ logger.info(f"Actions populated: {len(self._actions_data)} actions found")
883
+
884
+ # CRITICAL: Set action options if we have actions (either from fresh population or cache)
885
+ if self._actions_data:
886
+ self._build_action_maps()
887
+ build_config["action_button"]["options"] = [
888
+ {"name": self.sanitize_action_name(action), "metadata": action} for action in self._actions_data
889
+ ]
890
+ logger.info(f"Action options set in build_config: {len(build_config['action_button']['options'])} options")
891
+ else:
892
+ build_config["action_button"]["options"] = []
893
+ logger.warning("No actions found, setting empty options")
894
+
895
+ # clear stored connection_id when api_key is changed
896
+ if field_name == "api_key" and field_value:
897
+ stored_connection_before = build_config.get("auth_link", {}).get("connection_id")
898
+ if "auth_link" in build_config and "connection_id" in build_config["auth_link"]:
899
+ build_config["auth_link"].pop("connection_id", None)
900
+ build_config["auth_link"]["value"] = "connect"
901
+ build_config["auth_link"]["auth_tooltip"] = "Connect"
902
+ logger.info(f"Cleared stored connection_id '{stored_connection_before}' due to API key change")
903
+ else:
904
+ logger.info("DEBUG: EARLY No stored connection_id to clear on API key change")
905
+
906
+ # Handle disconnect operations when tool mode is enabled
907
+ if field_name == "auth_link" and field_value == "disconnect":
908
+ try:
909
+ # Get the specific connection ID that's currently being used
910
+ stored_connection_id = build_config.get("auth_link", {}).get("connection_id")
911
+ if stored_connection_id:
912
+ self._disconnect_specific_connection(stored_connection_id)
913
+ else:
914
+ # No connection ID stored - nothing to disconnect
915
+ logger.warning("No connection ID found to disconnect")
916
+ build_config["auth_link"]["value"] = "connect"
917
+ build_config["auth_link"]["auth_tooltip"] = "Connect"
918
+ return build_config
919
+ except (ValueError, ConnectionError) as e:
920
+ logger.error(f"Error disconnecting: {e}")
921
+ build_config["auth_link"]["value"] = "error"
922
+ build_config["auth_link"]["auth_tooltip"] = f"Disconnect failed: {e!s}"
923
+ return build_config
924
+ else:
925
+ build_config["auth_link"]["value"] = "connect"
926
+ build_config["auth_link"]["auth_tooltip"] = "Connect"
927
+ build_config["auth_link"].pop("connection_id", None) # Clear stored connection ID
928
+ build_config["action_button"]["helper_text"] = "Please connect before selecting actions."
929
+ build_config["action_button"]["helper_text_metadata"] = {"variant": "destructive"}
930
+ return build_config
931
+
932
+ # Handle connection initiation when tool mode is enabled
933
+ if field_name == "auth_link" and isinstance(field_value, dict):
934
+ try:
935
+ toolkit_slug = self.app_name.lower()
936
+
937
+ # First check if we already have an ACTIVE connection
938
+ existing_active = self._find_active_connection_for_app(self.app_name)
939
+ if existing_active:
940
+ connection_id, _ = existing_active
941
+ build_config["auth_link"]["value"] = "validated"
942
+ build_config["auth_link"]["auth_tooltip"] = "Disconnect"
943
+ build_config["auth_link"]["connection_id"] = connection_id
944
+ build_config["action_button"]["helper_text"] = ""
945
+ build_config["action_button"]["helper_text_metadata"] = {}
946
+ logger.info(f"Using existing ACTIVE connection {connection_id} for {toolkit_slug}")
947
+ return build_config
948
+
949
+ # Check if we have a stored connection ID with INITIATED status
950
+ stored_connection_id = build_config.get("auth_link", {}).get("connection_id")
951
+ if stored_connection_id:
952
+ # Check status of existing connection
953
+ status = self._check_connection_status_by_id(stored_connection_id)
954
+ if status == "INITIATED":
955
+ # Get redirect URL from stored connection
956
+ try:
957
+ composio = self._build_wrapper()
958
+ connection = composio.connected_accounts.get(nanoid=stored_connection_id)
959
+ state = getattr(connection, "state", None)
960
+ if state and hasattr(state, "val"):
961
+ redirect_url = getattr(state.val, "redirect_url", None)
962
+ if redirect_url:
963
+ build_config["auth_link"]["value"] = redirect_url
964
+ logger.info(f"Reusing existing OAuth URL for {toolkit_slug}: {redirect_url}")
965
+ return build_config
966
+ except (AttributeError, ValueError, ConnectionError) as e:
967
+ logger.debug(f"Could not retrieve connection {stored_connection_id}: {e}")
968
+ # Continue to create new connection below
969
+
970
+ # Create new OAuth connection ONLY if we truly have no usable connection yet
971
+ if existing_active is None and not (stored_connection_id and status in ("ACTIVE", "INITIATED")):
972
+ try:
973
+ redirect_url, connection_id = self._initiate_connection(toolkit_slug)
974
+ build_config["auth_link"]["value"] = redirect_url
975
+ build_config["auth_link"]["connection_id"] = connection_id # Store connection ID
976
+ logger.info(f"New OAuth URL created for {toolkit_slug}: {redirect_url}")
977
+ except (ValueError, ConnectionError) as e:
978
+ logger.error(f"Error creating OAuth connection: {e}")
979
+ build_config["auth_link"]["value"] = "connect"
980
+ build_config["auth_link"]["auth_tooltip"] = f"Error: {e!s}"
981
+ else:
982
+ return build_config
983
+ else:
984
+ # We already have a usable connection; no new OAuth request
985
+ build_config["auth_link"]["auth_tooltip"] = "Disconnect"
986
+
987
+ except (ValueError, ConnectionError) as e:
988
+ logger.error(f"Error in connection initiation: {e}")
989
+ build_config["auth_link"]["value"] = "connect"
990
+ build_config["auth_link"]["auth_tooltip"] = f"Error: {e!s}"
991
+ build_config["action_button"]["helper_text"] = "Please connect before selecting actions."
992
+ build_config["action_button"]["helper_text_metadata"] = {"variant": "destructive"}
993
+ return build_config
994
+
995
+ # Check for ACTIVE connections and update status accordingly (tool mode)
996
+ if hasattr(self, "api_key") and self.api_key:
997
+ stored_connection_id = build_config.get("auth_link", {}).get("connection_id")
998
+ active_connection_id = None
999
+
1000
+ # First try to check stored connection ID
1001
+ if stored_connection_id:
1002
+ status = self._check_connection_status_by_id(stored_connection_id)
1003
+ if status == "ACTIVE":
1004
+ active_connection_id = stored_connection_id
1005
+
1006
+ # If no stored connection or stored connection is not ACTIVE, find any ACTIVE connection
1007
+ if not active_connection_id:
1008
+ active_connection = self._find_active_connection_for_app(self.app_name)
1009
+ if active_connection:
1010
+ active_connection_id, _ = active_connection
1011
+ # Store the found active connection ID for future use
1012
+ if "auth_link" not in build_config:
1013
+ build_config["auth_link"] = {}
1014
+ build_config["auth_link"]["connection_id"] = active_connection_id
1015
+
1016
+ if active_connection_id:
1017
+ # Show validated connection status
1018
+ build_config["auth_link"]["value"] = "validated"
1019
+ build_config["auth_link"]["auth_tooltip"] = "Disconnect"
1020
+ build_config["action_button"]["helper_text"] = ""
1021
+ build_config["action_button"]["helper_text_metadata"] = {}
1022
+ else:
1023
+ build_config["auth_link"]["value"] = "connect"
1024
+ build_config["auth_link"]["auth_tooltip"] = "Connect"
1025
+ build_config["action_button"]["helper_text"] = "Please connect before selecting actions."
1026
+ build_config["action_button"]["helper_text_metadata"] = {"variant": "destructive"}
1027
+
1028
+ # CRITICAL: If tool_mode is enabled from ANY source, immediately hide action field and return
1029
+ if current_tool_mode:
1030
+ build_config["action_button"]["show"] = False
1031
+
1032
+ # CRITICAL: Hide ALL action parameter fields when tool mode is enabled
1033
+ for field in self._all_fields:
1034
+ if field in build_config:
1035
+ build_config[field]["show"] = False
1036
+
1037
+ # Also hide any other action-related fields that might be in build_config
1038
+ for field_name_in_config in build_config: # noqa: PLC0206
1039
+ # Skip base fields like api_key, tool_mode, action, etc.
1040
+ if (
1041
+ field_name_in_config not in ["api_key", "tool_mode", "action_button", "auth_link", "entity_id"]
1042
+ and isinstance(build_config[field_name_in_config], dict)
1043
+ and "show" in build_config[field_name_in_config]
1044
+ ):
1045
+ build_config[field_name_in_config]["show"] = False
1046
+
1047
+ # ENSURE tool_mode state is preserved in build_config for future calls
1048
+ if "tool_mode" not in build_config:
1049
+ build_config["tool_mode"] = {"value": True}
1050
+ elif isinstance(build_config["tool_mode"], dict):
1051
+ build_config["tool_mode"]["value"] = True
1052
+ # Don't proceed with any other logic that might override this
1053
+ return build_config
1054
+
1055
+ if field_name == "tool_mode":
1056
+ if field_value is True:
1057
+ build_config["action_button"]["show"] = False # Hide action field when tool mode is enabled
1058
+ for field in self._all_fields:
1059
+ build_config[field]["show"] = False # Update show status for all fields based on tool mode
1060
+ elif field_value is False:
1061
+ build_config["action_button"]["show"] = True # Show action field when tool mode is disabled
1062
+ for field in self._all_fields:
1063
+ build_config[field]["show"] = True # Update show status for all fields based on tool mode
1064
+ return build_config
1065
+
1066
+ if field_name == "action_button":
1067
+ self._update_action_config(build_config, field_value)
1068
+ # Keep the existing show/hide behaviour
1069
+ self.show_hide_fields(build_config, field_value)
1070
+ return build_config
1071
+
1072
+ # Handle API key removal
1073
+ if field_name == "api_key" and len(field_value) == 0:
1074
+ build_config["auth_link"]["value"] = ""
1075
+ build_config["auth_link"]["auth_tooltip"] = "Please provide a valid Composio API Key."
1076
+ build_config["action_button"]["options"] = []
1077
+ build_config["action_button"]["helper_text"] = "Please connect before selecting actions."
1078
+ build_config["action_button"]["helper_text_metadata"] = {"variant": "destructive"}
1079
+ build_config["auth_link"].pop("connection_id", None)
1080
+ return build_config
1081
+
1082
+ # Only proceed with connection logic if we have an API key
1083
+ if not hasattr(self, "api_key") or not self.api_key:
1084
+ return build_config
1085
+
1086
+ # CRITICAL: If tool_mode is enabled (check both instance and build_config), skip all connection logic
1087
+ if current_tool_mode:
1088
+ build_config["action_button"]["show"] = False
1089
+ return build_config
1090
+
1091
+ # Update action options only if tool_mode is disabled
1092
+ self._build_action_maps()
1093
+ # Only set options if they haven't been set already during action population
1094
+ if "options" not in build_config.get("action_button", {}) or not build_config["action_button"]["options"]:
1095
+ build_config["action_button"]["options"] = [
1096
+ {"name": self.sanitize_action_name(action), "metadata": action} for action in self._actions_data
1097
+ ]
1098
+ logger.debug("Setting action options from main logic path")
1099
+ else:
1100
+ logger.debug("Action options already set, skipping duplicate setting")
1101
+ # Only set show=True if tool_mode is not enabled
1102
+ if not current_tool_mode:
1103
+ build_config["action_button"]["show"] = True
1104
+
1105
+ stored_connection_id = build_config.get("auth_link", {}).get("connection_id")
1106
+ active_connection_id = None
1107
+
1108
+ if stored_connection_id:
1109
+ status = self._check_connection_status_by_id(stored_connection_id)
1110
+ if status == "ACTIVE":
1111
+ active_connection_id = stored_connection_id
1112
+
1113
+ if not active_connection_id:
1114
+ active_connection = self._find_active_connection_for_app(self.app_name)
1115
+ if active_connection:
1116
+ active_connection_id, _ = active_connection
1117
+ if "auth_link" not in build_config:
1118
+ build_config["auth_link"] = {}
1119
+ build_config["auth_link"]["connection_id"] = active_connection_id
1120
+
1121
+ if active_connection_id:
1122
+ build_config["auth_link"]["value"] = "validated"
1123
+ build_config["auth_link"]["auth_tooltip"] = "Disconnect"
1124
+ build_config["action_button"]["helper_text"] = ""
1125
+ build_config["action_button"]["helper_text_metadata"] = {}
1126
+ elif stored_connection_id:
1127
+ status = self._check_connection_status_by_id(stored_connection_id)
1128
+ if status == "INITIATED":
1129
+ current_value = build_config.get("auth_link", {}).get("value")
1130
+ if not current_value or current_value == "connect":
1131
+ build_config["auth_link"]["value"] = "connect"
1132
+ build_config["auth_link"]["auth_tooltip"] = "Connect"
1133
+ build_config["action_button"]["helper_text"] = "Please connect before selecting actions."
1134
+ build_config["action_button"]["helper_text_metadata"] = {"variant": "destructive"}
1135
+ else:
1136
+ # Connection not found or other status
1137
+ build_config["auth_link"]["value"] = "connect"
1138
+ build_config["auth_link"]["auth_tooltip"] = "Connect"
1139
+ build_config["action_button"]["helper_text"] = "Please connect before selecting actions."
1140
+ build_config["action_button"]["helper_text_metadata"] = {"variant": "destructive"}
1141
+ else:
1142
+ build_config["auth_link"]["value"] = "connect"
1143
+ build_config["auth_link"]["auth_tooltip"] = "Connect"
1144
+ build_config["action_button"]["helper_text"] = "Please connect before selecting actions."
1145
+ build_config["action_button"]["helper_text_metadata"] = {"variant": "destructive"}
1146
+
1147
+ if self._is_tool_mode_enabled():
1148
+ build_config["action_button"]["show"] = False
1149
+
1150
+ return build_config
1151
+
1152
+ def configure_tools(self, composio: Composio, limit: int | None = None) -> list[Tool]:
1153
+ if limit is None:
1154
+ limit = 999
1155
+
1156
+ tools = composio.tools.get(user_id=self.entity_id, toolkits=[self.app_name.lower()], limit=limit)
1157
+ configured_tools = []
1158
+ for tool in tools:
1159
+ # Set the sanitized name
1160
+ display_name = self._actions_data.get(tool.name, {}).get(
1161
+ "display_name", self._sanitized_names.get(tool.name, self._name_sanitizer.sub("-", tool.name))
1162
+ )
1163
+ # Set the tags
1164
+ tool.tags = [tool.name]
1165
+ tool.metadata = {"display_name": display_name, "display_description": tool.description, "readonly": True}
1166
+ configured_tools.append(tool)
1167
+ return configured_tools
1168
+
1169
+ async def _get_tools(self) -> list[Tool]:
1170
+ """Get tools with cached results and optimized name sanitization."""
1171
+ composio = self._build_wrapper()
1172
+ self.set_default_tools()
1173
+ return self.configure_tools(composio)
1174
+
1175
+ @property
1176
+ def enabled_tools(self):
1177
+ """Return tag names for actions of this app that should be exposed to the agent.
1178
+
1179
+ If default tools are set via set_default_tools(), returns those.
1180
+ Otherwise, returns only the first few tools (limited by default_tools_limit)
1181
+ to prevent overwhelming the agent. Subclasses can override this behavior.
1182
+
1183
+ """
1184
+ if not self._actions_data:
1185
+ self._populate_actions_data()
1186
+
1187
+ if hasattr(self, "_default_tools") and self._default_tools:
1188
+ return list(self._default_tools)
1189
+
1190
+ all_tools = list(self._actions_data.keys())
1191
+ limit = getattr(self, "default_tools_limit", 5)
1192
+ return all_tools[:limit]
1193
+
1194
+ def execute_action(self):
1195
+ """Execute the selected Composio tool."""
1196
+ composio = self._build_wrapper()
1197
+ self._populate_actions_data()
1198
+ self._build_action_maps()
1199
+
1200
+ display_name = (
1201
+ self.action_button[0]["name"]
1202
+ if isinstance(getattr(self, "action_button", None), list) and self.action_button
1203
+ else self.action_button
1204
+ )
1205
+ action_key = self._display_to_key_map.get(display_name)
1206
+
1207
+ if not action_key:
1208
+ msg = f"Invalid action: {display_name}"
1209
+ raise ValueError(msg)
1210
+
1211
+ try:
1212
+ arguments: dict[str, Any] = {}
1213
+ param_fields = self._actions_data.get(action_key, {}).get("action_fields", [])
1214
+
1215
+ schema_dict = self._action_schemas.get(action_key, {})
1216
+ parameters_schema = schema_dict.get("input_parameters", {})
1217
+ schema_properties = parameters_schema.get("properties", {}) if parameters_schema else {}
1218
+ # Handle case where 'required' field is None (causes "'NoneType' object is not iterable")
1219
+ required_list = parameters_schema.get("required", []) if parameters_schema else []
1220
+ required_fields = set(required_list) if required_list is not None else set()
1221
+
1222
+ for field in param_fields:
1223
+ if not hasattr(self, field):
1224
+ continue
1225
+ value = getattr(self, field)
1226
+
1227
+ # Skip None, empty strings, and empty lists
1228
+ if value is None or value == "" or (isinstance(value, list) and len(value) == 0):
1229
+ continue
1230
+
1231
+ # For optional fields, be more strict about including them
1232
+ # Only include if the user has explicitly provided a meaningful value
1233
+ if field not in required_fields:
1234
+ # Get the default value from the schema
1235
+ field_schema = schema_properties.get(field, {})
1236
+ schema_default = field_schema.get("default")
1237
+
1238
+ # Skip if the current value matches the schema default
1239
+ if value == schema_default:
1240
+ continue
1241
+
1242
+ # Convert comma-separated to list for array parameters (heuristic)
1243
+ prop_schema = schema_properties.get(field, {})
1244
+ if prop_schema.get("type") == "array" and isinstance(value, str):
1245
+ value = [item.strip() for item in value.split(",")]
1246
+
1247
+ if field in self._bool_variables:
1248
+ value = bool(value)
1249
+
1250
+ # Handle renamed fields - map back to original names for API execution
1251
+ final_field_name = field
1252
+ if field.endswith("_user_id") and field.startswith(self.app_name):
1253
+ final_field_name = "user_id"
1254
+ elif field.endswith("_status") and field.startswith(self.app_name):
1255
+ final_field_name = "status"
1256
+
1257
+ arguments[final_field_name] = value
1258
+
1259
+ # Execute using new SDK
1260
+ result = composio.tools.execute(
1261
+ slug=action_key,
1262
+ arguments=arguments,
1263
+ user_id=self.entity_id,
1264
+ )
1265
+
1266
+ if isinstance(result, dict) and "successful" in result:
1267
+ if result["successful"]:
1268
+ raw_data = result.get("data", result)
1269
+ return self._apply_post_processor(action_key, raw_data)
1270
+ error_msg = result.get("error", "Tool execution failed")
1271
+ raise ValueError(error_msg)
1272
+
1273
+ except ValueError as e:
1274
+ logger.error(f"Failed to execute {action_key}: {e}")
1275
+ raise
1276
+
1277
+ def _apply_post_processor(self, action_key: str, raw_data: Any) -> Any:
1278
+ """Apply post-processor for the given action if defined."""
1279
+ if hasattr(self, "post_processors") and isinstance(self.post_processors, dict):
1280
+ processor_func = self.post_processors.get(action_key)
1281
+ if processor_func and callable(processor_func):
1282
+ try:
1283
+ return processor_func(raw_data)
1284
+ except (TypeError, ValueError, KeyError) as e:
1285
+ logger.error(f"Error in post-processor for {action_key}: {e} (Exception type: {type(e).__name__})")
1286
+ return raw_data
1287
+
1288
+ return raw_data
1289
+
1290
+ def set_default_tools(self):
1291
+ """Set the default tools."""