From 004f7b58d56bba4291beea96c9a0f79fab0d7d4a Mon Sep 17 00:00:00 2001 From: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com> Date: Fri, 11 Sep 2026 19:53:03 +0530 Subject: [PATCH 1/6] test(bedrock): isolate session credential test (#7388) --- lib/crewai/tests/llms/bedrock/test_bedrock.py | 33 ++++--------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/lib/crewai/tests/llms/bedrock/test_bedrock.py b/lib/crewai/tests/llms/bedrock/test_bedrock.py index 87c0d6c09..fd79198fc 100644 --- a/lib/crewai/tests/llms/bedrock/test_bedrock.py +++ b/lib/crewai/tests/llms/bedrock/test_bedrock.py @@ -1,6 +1,4 @@ import os -import sys -import types from unittest.mock import patch, MagicMock import pytest @@ -8,6 +6,7 @@ from crewai.llm import LLM from crewai.crew import Crew from crewai.agent import Agent from crewai.task import Task +from crewai.llms.providers.bedrock import completion as bedrock_completion def _create_bedrock_mocks(): @@ -134,25 +133,6 @@ def test_bedrock_completion_is_used_when_bedrock_provider(): assert llm.model == "anthropic.claude-3-5-sonnet-20241022-v2:0" -def test_bedrock_completion_module_is_imported(monkeypatch): - """ - Test that the completion module is properly imported when using Bedrock provider - """ - module_name = "crewai.llms.providers.bedrock.completion" - - # Restore the original module after this test so collected class references - # still match the provider returned by LLM in subsequent tests. - monkeypatch.delitem(sys.modules, module_name, raising=False) - - LLM(model="bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0") - - assert module_name in sys.modules - completion_mod = sys.modules[module_name] - assert isinstance(completion_mod, types.ModuleType) - - assert hasattr(completion_mod, 'BedrockCompletion') - - def test_native_bedrock_raises_error_when_initialization_fails(): """ Test that LLM raises ImportError when native Bedrock completion fails. @@ -602,8 +582,10 @@ def test_bedrock_tool_conversion(): assert "inputSchema" in bedrock_tools[0]["toolSpec"] -def test_bedrock_environment_variable_credentials(): +def test_bedrock_environment_variable_credentials(monkeypatch): """Pass AWS credentials and region from the environment to boto3.""" + monkeypatch.delenv("AWS_SESSION_TOKEN", raising=False) + with ( patch.dict( os.environ, @@ -614,13 +596,12 @@ def test_bedrock_environment_variable_credentials(): }, clear=False, ), - patch( - "crewai.llms.providers.bedrock.completion.Session" - ) as mock_session_class, + patch.object(bedrock_completion, "Session") as mock_session_class, ): mock_session_class.return_value.client.return_value = MagicMock() - LLM(model="bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0") + llm = LLM(model="bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0") + assert type(llm) is bedrock_completion.BedrockCompletion mock_session_class.assert_called_once_with( aws_access_key_id="test-access-key-123", aws_secret_access_key="test-secret-key-456", From d5c7bac5051ffaf2d03e6ab8da792b737f3e43b3 Mon Sep 17 00:00:00 2001 From: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com> Date: Fri, 11 Sep 2026 20:46:05 +0530 Subject: [PATCH 2/6] chore: update OpenRouter tool specifications (#7387) --- lib/crewai-tools/tool.specs.json | 1154 +++++++++++++++++++++++++++++- 1 file changed, 1143 insertions(+), 11 deletions(-) diff --git a/lib/crewai-tools/tool.specs.json b/lib/crewai-tools/tool.specs.json index 674cc33c1..c82d71a65 100644 --- a/lib/crewai-tools/tool.specs.json +++ b/lib/crewai-tools/tool.specs.json @@ -3560,6 +3560,70 @@ "title": "OpenCLIPProviderSpec", "type": "object" }, + "OpenRouterProviderConfig": { + "description": "Configuration for OpenRouter provider.", + "properties": { + "api_base": { + "title": "Api Base", + "type": "string" + }, + "api_key": { + "title": "Api Key", + "type": "string" + }, + "default_headers": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Default Headers" + }, + "dimensions": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Dimensions" + }, + "model": { + "title": "Model", + "type": "string" + }, + "model_name": { + "title": "Model Name", + "type": "string" + } + }, + "title": "OpenRouterProviderConfig", + "type": "object" + }, + "OpenRouterProviderSpec": { + "description": "OpenRouter provider specification.", + "properties": { + "config": { + "$ref": "#/$defs/OpenRouterProviderConfig" + }, + "provider": { + "const": "openrouter", + "title": "Provider", + "type": "string" + } + }, + "required": [ + "provider" + ], + "title": "OpenRouterProviderSpec", + "type": "object" + }, "RagToolConfig": { "description": "Configuration accepted by RAG tools.\n\nSupports embedding model and vector database configuration.\n\nAttributes:\n embedding_model: Embedding model configuration accepted by RAG tools.\n vectordb: Vector database configuration accepted by RAG tools.", "properties": { @@ -3601,6 +3665,9 @@ { "$ref": "#/$defs/OpenCLIPProviderSpec" }, + { + "$ref": "#/$defs/OpenRouterProviderSpec" + }, { "$ref": "#/$defs/RoboflowProviderSpec" }, @@ -4619,6 +4686,70 @@ "title": "OpenCLIPProviderSpec", "type": "object" }, + "OpenRouterProviderConfig": { + "description": "Configuration for OpenRouter provider.", + "properties": { + "api_base": { + "title": "Api Base", + "type": "string" + }, + "api_key": { + "title": "Api Key", + "type": "string" + }, + "default_headers": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Default Headers" + }, + "dimensions": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Dimensions" + }, + "model": { + "title": "Model", + "type": "string" + }, + "model_name": { + "title": "Model Name", + "type": "string" + } + }, + "title": "OpenRouterProviderConfig", + "type": "object" + }, + "OpenRouterProviderSpec": { + "description": "OpenRouter provider specification.", + "properties": { + "config": { + "$ref": "#/$defs/OpenRouterProviderConfig" + }, + "provider": { + "const": "openrouter", + "title": "Provider", + "type": "string" + } + }, + "required": [ + "provider" + ], + "title": "OpenRouterProviderSpec", + "type": "object" + }, "RagToolConfig": { "description": "Configuration accepted by RAG tools.\n\nSupports embedding model and vector database configuration.\n\nAttributes:\n embedding_model: Embedding model configuration accepted by RAG tools.\n vectordb: Vector database configuration accepted by RAG tools.", "properties": { @@ -4660,6 +4791,9 @@ { "$ref": "#/$defs/OpenCLIPProviderSpec" }, + { + "$ref": "#/$defs/OpenRouterProviderSpec" + }, { "$ref": "#/$defs/RoboflowProviderSpec" }, @@ -6589,6 +6723,70 @@ "title": "OpenCLIPProviderSpec", "type": "object" }, + "OpenRouterProviderConfig": { + "description": "Configuration for OpenRouter provider.", + "properties": { + "api_base": { + "title": "Api Base", + "type": "string" + }, + "api_key": { + "title": "Api Key", + "type": "string" + }, + "default_headers": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Default Headers" + }, + "dimensions": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Dimensions" + }, + "model": { + "title": "Model", + "type": "string" + }, + "model_name": { + "title": "Model Name", + "type": "string" + } + }, + "title": "OpenRouterProviderConfig", + "type": "object" + }, + "OpenRouterProviderSpec": { + "description": "OpenRouter provider specification.", + "properties": { + "config": { + "$ref": "#/$defs/OpenRouterProviderConfig" + }, + "provider": { + "const": "openrouter", + "title": "Provider", + "type": "string" + } + }, + "required": [ + "provider" + ], + "title": "OpenRouterProviderSpec", + "type": "object" + }, "RagToolConfig": { "description": "Configuration accepted by RAG tools.\n\nSupports embedding model and vector database configuration.\n\nAttributes:\n embedding_model: Embedding model configuration accepted by RAG tools.\n vectordb: Vector database configuration accepted by RAG tools.", "properties": { @@ -6630,6 +6828,9 @@ { "$ref": "#/$defs/OpenCLIPProviderSpec" }, + { + "$ref": "#/$defs/OpenRouterProviderSpec" + }, { "$ref": "#/$defs/RoboflowProviderSpec" }, @@ -7116,16 +7317,9 @@ "description": "Input for DOCXSearchTool.", "properties": { "docx": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], "description": "File path or URL of a DOCX file to be searched", - "title": "Docx" + "title": "Docx", + "type": "string" }, "search_query": { "description": "Mandatory search query you want to use to search the DOCX's content", @@ -7134,8 +7328,8 @@ } }, "required": [ - "docx", - "search_query" + "search_query", + "docx" ], "title": "DOCXSearchToolSchema", "type": "object" @@ -8941,6 +9135,70 @@ "title": "OpenCLIPProviderSpec", "type": "object" }, + "OpenRouterProviderConfig": { + "description": "Configuration for OpenRouter provider.", + "properties": { + "api_base": { + "title": "Api Base", + "type": "string" + }, + "api_key": { + "title": "Api Key", + "type": "string" + }, + "default_headers": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Default Headers" + }, + "dimensions": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Dimensions" + }, + "model": { + "title": "Model", + "type": "string" + }, + "model_name": { + "title": "Model Name", + "type": "string" + } + }, + "title": "OpenRouterProviderConfig", + "type": "object" + }, + "OpenRouterProviderSpec": { + "description": "OpenRouter provider specification.", + "properties": { + "config": { + "$ref": "#/$defs/OpenRouterProviderConfig" + }, + "provider": { + "const": "openrouter", + "title": "Provider", + "type": "string" + } + }, + "required": [ + "provider" + ], + "title": "OpenRouterProviderSpec", + "type": "object" + }, "RagToolConfig": { "description": "Configuration accepted by RAG tools.\n\nSupports embedding model and vector database configuration.\n\nAttributes:\n embedding_model: Embedding model configuration accepted by RAG tools.\n vectordb: Vector database configuration accepted by RAG tools.", "properties": { @@ -8982,6 +9240,9 @@ { "$ref": "#/$defs/OpenCLIPProviderSpec" }, + { + "$ref": "#/$defs/OpenRouterProviderSpec" + }, { "$ref": "#/$defs/RoboflowProviderSpec" }, @@ -11721,6 +11982,70 @@ "title": "OpenCLIPProviderSpec", "type": "object" }, + "OpenRouterProviderConfig": { + "description": "Configuration for OpenRouter provider.", + "properties": { + "api_base": { + "title": "Api Base", + "type": "string" + }, + "api_key": { + "title": "Api Key", + "type": "string" + }, + "default_headers": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Default Headers" + }, + "dimensions": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Dimensions" + }, + "model": { + "title": "Model", + "type": "string" + }, + "model_name": { + "title": "Model Name", + "type": "string" + } + }, + "title": "OpenRouterProviderConfig", + "type": "object" + }, + "OpenRouterProviderSpec": { + "description": "OpenRouter provider specification.", + "properties": { + "config": { + "$ref": "#/$defs/OpenRouterProviderConfig" + }, + "provider": { + "const": "openrouter", + "title": "Provider", + "type": "string" + } + }, + "required": [ + "provider" + ], + "title": "OpenRouterProviderSpec", + "type": "object" + }, "RagToolConfig": { "description": "Configuration accepted by RAG tools.\n\nSupports embedding model and vector database configuration.\n\nAttributes:\n embedding_model: Embedding model configuration accepted by RAG tools.\n vectordb: Vector database configuration accepted by RAG tools.", "properties": { @@ -11762,6 +12087,9 @@ { "$ref": "#/$defs/OpenCLIPProviderSpec" }, + { + "$ref": "#/$defs/OpenRouterProviderSpec" + }, { "$ref": "#/$defs/RoboflowProviderSpec" }, @@ -13027,6 +13355,70 @@ "title": "OpenCLIPProviderSpec", "type": "object" }, + "OpenRouterProviderConfig": { + "description": "Configuration for OpenRouter provider.", + "properties": { + "api_base": { + "title": "Api Base", + "type": "string" + }, + "api_key": { + "title": "Api Key", + "type": "string" + }, + "default_headers": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Default Headers" + }, + "dimensions": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Dimensions" + }, + "model": { + "title": "Model", + "type": "string" + }, + "model_name": { + "title": "Model Name", + "type": "string" + } + }, + "title": "OpenRouterProviderConfig", + "type": "object" + }, + "OpenRouterProviderSpec": { + "description": "OpenRouter provider specification.", + "properties": { + "config": { + "$ref": "#/$defs/OpenRouterProviderConfig" + }, + "provider": { + "const": "openrouter", + "title": "Provider", + "type": "string" + } + }, + "required": [ + "provider" + ], + "title": "OpenRouterProviderSpec", + "type": "object" + }, "RagToolConfig": { "description": "Configuration accepted by RAG tools.\n\nSupports embedding model and vector database configuration.\n\nAttributes:\n embedding_model: Embedding model configuration accepted by RAG tools.\n vectordb: Vector database configuration accepted by RAG tools.", "properties": { @@ -13068,6 +13460,9 @@ { "$ref": "#/$defs/OpenCLIPProviderSpec" }, + { + "$ref": "#/$defs/OpenRouterProviderSpec" + }, { "$ref": "#/$defs/RoboflowProviderSpec" }, @@ -14339,6 +14734,70 @@ "title": "OpenCLIPProviderSpec", "type": "object" }, + "OpenRouterProviderConfig": { + "description": "Configuration for OpenRouter provider.", + "properties": { + "api_base": { + "title": "Api Base", + "type": "string" + }, + "api_key": { + "title": "Api Key", + "type": "string" + }, + "default_headers": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Default Headers" + }, + "dimensions": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Dimensions" + }, + "model": { + "title": "Model", + "type": "string" + }, + "model_name": { + "title": "Model Name", + "type": "string" + } + }, + "title": "OpenRouterProviderConfig", + "type": "object" + }, + "OpenRouterProviderSpec": { + "description": "OpenRouter provider specification.", + "properties": { + "config": { + "$ref": "#/$defs/OpenRouterProviderConfig" + }, + "provider": { + "const": "openrouter", + "title": "Provider", + "type": "string" + } + }, + "required": [ + "provider" + ], + "title": "OpenRouterProviderSpec", + "type": "object" + }, "RagToolConfig": { "description": "Configuration accepted by RAG tools.\n\nSupports embedding model and vector database configuration.\n\nAttributes:\n embedding_model: Embedding model configuration accepted by RAG tools.\n vectordb: Vector database configuration accepted by RAG tools.", "properties": { @@ -14380,6 +14839,9 @@ { "$ref": "#/$defs/OpenCLIPProviderSpec" }, + { + "$ref": "#/$defs/OpenRouterProviderSpec" + }, { "$ref": "#/$defs/RoboflowProviderSpec" }, @@ -15834,6 +16296,70 @@ "title": "OpenCLIPProviderSpec", "type": "object" }, + "OpenRouterProviderConfig": { + "description": "Configuration for OpenRouter provider.", + "properties": { + "api_base": { + "title": "Api Base", + "type": "string" + }, + "api_key": { + "title": "Api Key", + "type": "string" + }, + "default_headers": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Default Headers" + }, + "dimensions": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Dimensions" + }, + "model": { + "title": "Model", + "type": "string" + }, + "model_name": { + "title": "Model Name", + "type": "string" + } + }, + "title": "OpenRouterProviderConfig", + "type": "object" + }, + "OpenRouterProviderSpec": { + "description": "OpenRouter provider specification.", + "properties": { + "config": { + "$ref": "#/$defs/OpenRouterProviderConfig" + }, + "provider": { + "const": "openrouter", + "title": "Provider", + "type": "string" + } + }, + "required": [ + "provider" + ], + "title": "OpenRouterProviderSpec", + "type": "object" + }, "RagToolConfig": { "description": "Configuration accepted by RAG tools.\n\nSupports embedding model and vector database configuration.\n\nAttributes:\n embedding_model: Embedding model configuration accepted by RAG tools.\n vectordb: Vector database configuration accepted by RAG tools.", "properties": { @@ -15875,6 +16401,9 @@ { "$ref": "#/$defs/OpenCLIPProviderSpec" }, + { + "$ref": "#/$defs/OpenRouterProviderSpec" + }, { "$ref": "#/$defs/RoboflowProviderSpec" }, @@ -18322,6 +18851,70 @@ "title": "OpenCLIPProviderSpec", "type": "object" }, + "OpenRouterProviderConfig": { + "description": "Configuration for OpenRouter provider.", + "properties": { + "api_base": { + "title": "Api Base", + "type": "string" + }, + "api_key": { + "title": "Api Key", + "type": "string" + }, + "default_headers": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Default Headers" + }, + "dimensions": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Dimensions" + }, + "model": { + "title": "Model", + "type": "string" + }, + "model_name": { + "title": "Model Name", + "type": "string" + } + }, + "title": "OpenRouterProviderConfig", + "type": "object" + }, + "OpenRouterProviderSpec": { + "description": "OpenRouter provider specification.", + "properties": { + "config": { + "$ref": "#/$defs/OpenRouterProviderConfig" + }, + "provider": { + "const": "openrouter", + "title": "Provider", + "type": "string" + } + }, + "required": [ + "provider" + ], + "title": "OpenRouterProviderSpec", + "type": "object" + }, "RagToolConfig": { "description": "Configuration accepted by RAG tools.\n\nSupports embedding model and vector database configuration.\n\nAttributes:\n embedding_model: Embedding model configuration accepted by RAG tools.\n vectordb: Vector database configuration accepted by RAG tools.", "properties": { @@ -18363,6 +18956,9 @@ { "$ref": "#/$defs/OpenCLIPProviderSpec" }, + { + "$ref": "#/$defs/OpenRouterProviderSpec" + }, { "$ref": "#/$defs/RoboflowProviderSpec" }, @@ -20078,6 +20674,70 @@ "title": "OpenCLIPProviderSpec", "type": "object" }, + "OpenRouterProviderConfig": { + "description": "Configuration for OpenRouter provider.", + "properties": { + "api_base": { + "title": "Api Base", + "type": "string" + }, + "api_key": { + "title": "Api Key", + "type": "string" + }, + "default_headers": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Default Headers" + }, + "dimensions": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Dimensions" + }, + "model": { + "title": "Model", + "type": "string" + }, + "model_name": { + "title": "Model Name", + "type": "string" + } + }, + "title": "OpenRouterProviderConfig", + "type": "object" + }, + "OpenRouterProviderSpec": { + "description": "OpenRouter provider specification.", + "properties": { + "config": { + "$ref": "#/$defs/OpenRouterProviderConfig" + }, + "provider": { + "const": "openrouter", + "title": "Provider", + "type": "string" + } + }, + "required": [ + "provider" + ], + "title": "OpenRouterProviderSpec", + "type": "object" + }, "RagToolConfig": { "description": "Configuration accepted by RAG tools.\n\nSupports embedding model and vector database configuration.\n\nAttributes:\n embedding_model: Embedding model configuration accepted by RAG tools.\n vectordb: Vector database configuration accepted by RAG tools.", "properties": { @@ -20119,6 +20779,9 @@ { "$ref": "#/$defs/OpenCLIPProviderSpec" }, + { + "$ref": "#/$defs/OpenRouterProviderSpec" + }, { "$ref": "#/$defs/RoboflowProviderSpec" }, @@ -22263,6 +22926,70 @@ "title": "OpenCLIPProviderSpec", "type": "object" }, + "OpenRouterProviderConfig": { + "description": "Configuration for OpenRouter provider.", + "properties": { + "api_base": { + "title": "Api Base", + "type": "string" + }, + "api_key": { + "title": "Api Key", + "type": "string" + }, + "default_headers": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Default Headers" + }, + "dimensions": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Dimensions" + }, + "model": { + "title": "Model", + "type": "string" + }, + "model_name": { + "title": "Model Name", + "type": "string" + } + }, + "title": "OpenRouterProviderConfig", + "type": "object" + }, + "OpenRouterProviderSpec": { + "description": "OpenRouter provider specification.", + "properties": { + "config": { + "$ref": "#/$defs/OpenRouterProviderConfig" + }, + "provider": { + "const": "openrouter", + "title": "Provider", + "type": "string" + } + }, + "required": [ + "provider" + ], + "title": "OpenRouterProviderSpec", + "type": "object" + }, "RagToolConfig": { "description": "Configuration accepted by RAG tools.\n\nSupports embedding model and vector database configuration.\n\nAttributes:\n embedding_model: Embedding model configuration accepted by RAG tools.\n vectordb: Vector database configuration accepted by RAG tools.", "properties": { @@ -22304,6 +23031,9 @@ { "$ref": "#/$defs/OpenCLIPProviderSpec" }, + { + "$ref": "#/$defs/OpenRouterProviderSpec" + }, { "$ref": "#/$defs/RoboflowProviderSpec" }, @@ -23768,6 +24498,70 @@ "title": "OpenCLIPProviderSpec", "type": "object" }, + "OpenRouterProviderConfig": { + "description": "Configuration for OpenRouter provider.", + "properties": { + "api_base": { + "title": "Api Base", + "type": "string" + }, + "api_key": { + "title": "Api Key", + "type": "string" + }, + "default_headers": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Default Headers" + }, + "dimensions": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Dimensions" + }, + "model": { + "title": "Model", + "type": "string" + }, + "model_name": { + "title": "Model Name", + "type": "string" + } + }, + "title": "OpenRouterProviderConfig", + "type": "object" + }, + "OpenRouterProviderSpec": { + "description": "OpenRouter provider specification.", + "properties": { + "config": { + "$ref": "#/$defs/OpenRouterProviderConfig" + }, + "provider": { + "const": "openrouter", + "title": "Provider", + "type": "string" + } + }, + "required": [ + "provider" + ], + "title": "OpenRouterProviderSpec", + "type": "object" + }, "RagToolConfig": { "description": "Configuration accepted by RAG tools.\n\nSupports embedding model and vector database configuration.\n\nAttributes:\n embedding_model: Embedding model configuration accepted by RAG tools.\n vectordb: Vector database configuration accepted by RAG tools.", "properties": { @@ -23809,6 +24603,9 @@ { "$ref": "#/$defs/OpenCLIPProviderSpec" }, + { + "$ref": "#/$defs/OpenRouterProviderSpec" + }, { "$ref": "#/$defs/RoboflowProviderSpec" }, @@ -25660,6 +26457,70 @@ "title": "OpenCLIPProviderSpec", "type": "object" }, + "OpenRouterProviderConfig": { + "description": "Configuration for OpenRouter provider.", + "properties": { + "api_base": { + "title": "Api Base", + "type": "string" + }, + "api_key": { + "title": "Api Key", + "type": "string" + }, + "default_headers": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Default Headers" + }, + "dimensions": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Dimensions" + }, + "model": { + "title": "Model", + "type": "string" + }, + "model_name": { + "title": "Model Name", + "type": "string" + } + }, + "title": "OpenRouterProviderConfig", + "type": "object" + }, + "OpenRouterProviderSpec": { + "description": "OpenRouter provider specification.", + "properties": { + "config": { + "$ref": "#/$defs/OpenRouterProviderConfig" + }, + "provider": { + "const": "openrouter", + "title": "Provider", + "type": "string" + } + }, + "required": [ + "provider" + ], + "title": "OpenRouterProviderSpec", + "type": "object" + }, "RagToolConfig": { "description": "Configuration accepted by RAG tools.\n\nSupports embedding model and vector database configuration.\n\nAttributes:\n embedding_model: Embedding model configuration accepted by RAG tools.\n vectordb: Vector database configuration accepted by RAG tools.", "properties": { @@ -25701,6 +26562,9 @@ { "$ref": "#/$defs/OpenCLIPProviderSpec" }, + { + "$ref": "#/$defs/OpenRouterProviderSpec" + }, { "$ref": "#/$defs/RoboflowProviderSpec" }, @@ -27904,6 +28768,70 @@ "title": "OpenCLIPProviderSpec", "type": "object" }, + "OpenRouterProviderConfig": { + "description": "Configuration for OpenRouter provider.", + "properties": { + "api_base": { + "title": "Api Base", + "type": "string" + }, + "api_key": { + "title": "Api Key", + "type": "string" + }, + "default_headers": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Default Headers" + }, + "dimensions": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Dimensions" + }, + "model": { + "title": "Model", + "type": "string" + }, + "model_name": { + "title": "Model Name", + "type": "string" + } + }, + "title": "OpenRouterProviderConfig", + "type": "object" + }, + "OpenRouterProviderSpec": { + "description": "OpenRouter provider specification.", + "properties": { + "config": { + "$ref": "#/$defs/OpenRouterProviderConfig" + }, + "provider": { + "const": "openrouter", + "title": "Provider", + "type": "string" + } + }, + "required": [ + "provider" + ], + "title": "OpenRouterProviderSpec", + "type": "object" + }, "RagToolConfig": { "description": "Configuration accepted by RAG tools.\n\nSupports embedding model and vector database configuration.\n\nAttributes:\n embedding_model: Embedding model configuration accepted by RAG tools.\n vectordb: Vector database configuration accepted by RAG tools.", "properties": { @@ -27945,6 +28873,9 @@ { "$ref": "#/$defs/OpenCLIPProviderSpec" }, + { + "$ref": "#/$defs/OpenRouterProviderSpec" + }, { "$ref": "#/$defs/RoboflowProviderSpec" }, @@ -28963,6 +29894,70 @@ "title": "OpenCLIPProviderSpec", "type": "object" }, + "OpenRouterProviderConfig": { + "description": "Configuration for OpenRouter provider.", + "properties": { + "api_base": { + "title": "Api Base", + "type": "string" + }, + "api_key": { + "title": "Api Key", + "type": "string" + }, + "default_headers": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Default Headers" + }, + "dimensions": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Dimensions" + }, + "model": { + "title": "Model", + "type": "string" + }, + "model_name": { + "title": "Model Name", + "type": "string" + } + }, + "title": "OpenRouterProviderConfig", + "type": "object" + }, + "OpenRouterProviderSpec": { + "description": "OpenRouter provider specification.", + "properties": { + "config": { + "$ref": "#/$defs/OpenRouterProviderConfig" + }, + "provider": { + "const": "openrouter", + "title": "Provider", + "type": "string" + } + }, + "required": [ + "provider" + ], + "title": "OpenRouterProviderSpec", + "type": "object" + }, "RagToolConfig": { "description": "Configuration accepted by RAG tools.\n\nSupports embedding model and vector database configuration.\n\nAttributes:\n embedding_model: Embedding model configuration accepted by RAG tools.\n vectordb: Vector database configuration accepted by RAG tools.", "properties": { @@ -29004,6 +29999,9 @@ { "$ref": "#/$defs/OpenCLIPProviderSpec" }, + { + "$ref": "#/$defs/OpenRouterProviderSpec" + }, { "$ref": "#/$defs/RoboflowProviderSpec" }, @@ -30022,6 +31020,70 @@ "title": "OpenCLIPProviderSpec", "type": "object" }, + "OpenRouterProviderConfig": { + "description": "Configuration for OpenRouter provider.", + "properties": { + "api_base": { + "title": "Api Base", + "type": "string" + }, + "api_key": { + "title": "Api Key", + "type": "string" + }, + "default_headers": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Default Headers" + }, + "dimensions": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Dimensions" + }, + "model": { + "title": "Model", + "type": "string" + }, + "model_name": { + "title": "Model Name", + "type": "string" + } + }, + "title": "OpenRouterProviderConfig", + "type": "object" + }, + "OpenRouterProviderSpec": { + "description": "OpenRouter provider specification.", + "properties": { + "config": { + "$ref": "#/$defs/OpenRouterProviderConfig" + }, + "provider": { + "const": "openrouter", + "title": "Provider", + "type": "string" + } + }, + "required": [ + "provider" + ], + "title": "OpenRouterProviderSpec", + "type": "object" + }, "RagToolConfig": { "description": "Configuration accepted by RAG tools.\n\nSupports embedding model and vector database configuration.\n\nAttributes:\n embedding_model: Embedding model configuration accepted by RAG tools.\n vectordb: Vector database configuration accepted by RAG tools.", "properties": { @@ -30063,6 +31125,9 @@ { "$ref": "#/$defs/OpenCLIPProviderSpec" }, + { + "$ref": "#/$defs/OpenRouterProviderSpec" + }, { "$ref": "#/$defs/RoboflowProviderSpec" }, @@ -31081,6 +32146,70 @@ "title": "OpenCLIPProviderSpec", "type": "object" }, + "OpenRouterProviderConfig": { + "description": "Configuration for OpenRouter provider.", + "properties": { + "api_base": { + "title": "Api Base", + "type": "string" + }, + "api_key": { + "title": "Api Key", + "type": "string" + }, + "default_headers": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Default Headers" + }, + "dimensions": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Dimensions" + }, + "model": { + "title": "Model", + "type": "string" + }, + "model_name": { + "title": "Model Name", + "type": "string" + } + }, + "title": "OpenRouterProviderConfig", + "type": "object" + }, + "OpenRouterProviderSpec": { + "description": "OpenRouter provider specification.", + "properties": { + "config": { + "$ref": "#/$defs/OpenRouterProviderConfig" + }, + "provider": { + "const": "openrouter", + "title": "Provider", + "type": "string" + } + }, + "required": [ + "provider" + ], + "title": "OpenRouterProviderSpec", + "type": "object" + }, "RagToolConfig": { "description": "Configuration accepted by RAG tools.\n\nSupports embedding model and vector database configuration.\n\nAttributes:\n embedding_model: Embedding model configuration accepted by RAG tools.\n vectordb: Vector database configuration accepted by RAG tools.", "properties": { @@ -31122,6 +32251,9 @@ { "$ref": "#/$defs/OpenCLIPProviderSpec" }, + { + "$ref": "#/$defs/OpenRouterProviderSpec" + }, { "$ref": "#/$defs/RoboflowProviderSpec" }, From e1f3c4bdd4d3f065edd560b148763bcd4e008a68 Mon Sep 17 00:00:00 2001 From: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com> Date: Fri, 11 Sep 2026 21:02:00 +0530 Subject: [PATCH 3/6] feat: expose CrewAI Platform application catalog (#7383) * feat: expose platform application catalog * refactor: centralize platform application catalog * fix: preserve platform application typing * chore: remove unused platform app export --- .../src/crewai_core/platform_apps.py | 25 +++++++++++++++++++ .../tools/crewai_platform_tools/__init__.py | 3 +++ .../crewai_platform_tools/test_constants.py | 22 ++++++++++++++++ .../crewai/agents/agent_builder/base_agent.py | 20 +-------------- 4 files changed, 51 insertions(+), 19 deletions(-) create mode 100644 lib/crewai-core/src/crewai_core/platform_apps.py create mode 100644 lib/crewai-tools/tests/tools/crewai_platform_tools/test_constants.py diff --git a/lib/crewai-core/src/crewai_core/platform_apps.py b/lib/crewai-core/src/crewai_core/platform_apps.py new file mode 100644 index 000000000..5817fb412 --- /dev/null +++ b/lib/crewai-core/src/crewai_core/platform_apps.py @@ -0,0 +1,25 @@ +"""CrewAI Platform application catalog.""" + +from typing import Final, Literal, get_args + + +PlatformApp = Literal[ + "asana", + "box", + "clickup", + "github", + "gmail", + "google_calendar", + "google_sheets", + "hubspot", + "jira", + "linear", + "notion", + "salesforce", + "shopify", + "slack", + "stripe", + "zendesk", +] + +PLATFORM_APPS: Final[tuple[str, ...]] = (*get_args(PlatformApp),) diff --git a/lib/crewai-tools/src/crewai_tools/tools/crewai_platform_tools/__init__.py b/lib/crewai-tools/src/crewai_tools/tools/crewai_platform_tools/__init__.py index 4eb9d7d43..89bb29366 100644 --- a/lib/crewai-tools/src/crewai_tools/tools/crewai_platform_tools/__init__.py +++ b/lib/crewai-tools/src/crewai_tools/tools/crewai_platform_tools/__init__.py @@ -4,6 +4,8 @@ This module provides tools for integrating with various platform applications through the CrewAI platform API. """ +from crewai_core.platform_apps import PLATFORM_APPS + from crewai_tools.tools.crewai_platform_tools.crewai_platform_action_tool import ( CrewAIPlatformActionTool, ) @@ -13,6 +15,7 @@ from crewai_tools.tools.crewai_platform_tools.crewai_platform_tools import ( __all__ = [ + "PLATFORM_APPS", "CrewAIPlatformActionTool", "CrewaiPlatformTools", ] diff --git a/lib/crewai-tools/tests/tools/crewai_platform_tools/test_constants.py b/lib/crewai-tools/tests/tools/crewai_platform_tools/test_constants.py new file mode 100644 index 000000000..c1080223c --- /dev/null +++ b/lib/crewai-tools/tests/tools/crewai_platform_tools/test_constants.py @@ -0,0 +1,22 @@ +from crewai_core.platform_apps import PLATFORM_APPS + + +def test_platform_apps_contains_supported_application_catalog() -> None: + assert PLATFORM_APPS == ( + "asana", + "box", + "clickup", + "github", + "gmail", + "google_calendar", + "google_sheets", + "hubspot", + "jira", + "linear", + "notion", + "salesforce", + "shopify", + "slack", + "stripe", + "zendesk", + ) diff --git a/lib/crewai/src/crewai/agents/agent_builder/base_agent.py b/lib/crewai/src/crewai/agents/agent_builder/base_agent.py index 49fad6aac..a4b0e6675 100644 --- a/lib/crewai/src/crewai/agents/agent_builder/base_agent.py +++ b/lib/crewai/src/crewai/agents/agent_builder/base_agent.py @@ -9,6 +9,7 @@ import re from typing import TYPE_CHECKING, Annotated, Any, Final, Literal import uuid +from crewai_core.platform_apps import PlatformApp from pydantic import ( UUID4, BaseModel, @@ -180,25 +181,6 @@ _SLUG_RE: Final[re.Pattern[str]] = re.compile( ) -PlatformApp = Literal[ - "asana", - "box", - "clickup", - "github", - "gmail", - "google_calendar", - "google_sheets", - "hubspot", - "jira", - "linear", - "notion", - "salesforce", - "shopify", - "slack", - "stripe", - "zendesk", -] - PlatformAppOrAction = PlatformApp | str From d20845f0a3fc0db9ec2837abcf021780f295730a Mon Sep 17 00:00:00 2001 From: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com> Date: Sat, 12 Sep 2026 00:43:55 +0530 Subject: [PATCH 4/6] feat: add platform tools to JSON crew wizard (#7384) * feat: support platform tools in JSON crews * style: clarify platform integration labels * fix: avoid repeating tool picker title * fix(platform): surface JSON tool discovery errors --- lib/cli/src/crewai_cli/create_json_crew.py | 9 ++- .../src/crewai_cli/platform_tools_catalog.py | 29 +++++++++ lib/cli/src/crewai_cli/tui_picker.py | 5 +- lib/cli/tests/test_create_crew.py | 47 +++++++++++++++ .../crewai_platform_tools.py | 23 ++------ .../test_crewai_platform_tools.py | 6 +- lib/crewai/src/crewai/project/json_loader.py | 57 ++++++++++++++---- lib/crewai/tests/project/test_json_loader.py | 59 +++++++++++++++++++ 8 files changed, 199 insertions(+), 36 deletions(-) create mode 100644 lib/cli/src/crewai_cli/platform_tools_catalog.py diff --git a/lib/cli/src/crewai_cli/create_json_crew.py b/lib/cli/src/crewai_cli/create_json_crew.py index a4b04975f..00f1cd4db 100644 --- a/lib/cli/src/crewai_cli/create_json_crew.py +++ b/lib/cli/src/crewai_cli/create_json_crew.py @@ -16,6 +16,7 @@ from rich.text import Text from crewai_cli.constants import ENV_VARS from crewai_cli.git import initialize_if_git_available from crewai_cli.model_catalog import get_provider_models +from crewai_cli.platform_tools_catalog import PLATFORM_TOOLS from crewai_cli.tui_picker import pick_many, pick_one from crewai_cli.utils import ( enable_prompt_line_editing, @@ -103,6 +104,7 @@ _TEMPLATES_DIR = Path(__file__).parent / "templates" / "json_crew" # ── Common tools for picker ──────────────────────────────────── _TOOL_CATEGORIES: list[tuple[str, list[tuple[str, str]]]] = [ + ("CrewAI Platform", PLATFORM_TOOLS), ( "Search & Research", [ @@ -304,6 +306,9 @@ def _show_interpolation_hint(kind: str) -> None: def _tool_label(name: str, description: str) -> str: + if name.startswith("platform:"): + app_name = description.removesuffix(" Integration").replace(" ", "") + return f"{description:<48s} Platform: {app_name.replace(' ', '')}Integration" return f"{description:<48s} {name}" @@ -351,6 +356,7 @@ def _select_tools() -> list[str]: selected: set[str] = set() expanded: str | None = None focus_category: str | None = None + first_render = True while True: labels: list[str] = [] @@ -387,13 +393,14 @@ def _select_tools() -> list[str]: labels.append(_tool_label(name, desc)) indices, action = pick_many( - "Tools (space to toggle, enter to confirm):", + "Tools (space to toggle, enter to confirm):" if first_render else "", labels, action_indices=action_indices, separator_indices=separator_indices, preselected=preselected, initial_cursor=initial_cursor, ) + first_render = False # Carry over toggles made on this screen; tools not visible in this # render keep their previous state. diff --git a/lib/cli/src/crewai_cli/platform_tools_catalog.py b/lib/cli/src/crewai_cli/platform_tools_catalog.py new file mode 100644 index 000000000..6de79343f --- /dev/null +++ b/lib/cli/src/crewai_cli/platform_tools_catalog.py @@ -0,0 +1,29 @@ +"""CrewAI AMP platform tools exposed by the crew-creation wizard.""" + +from crewai_core.platform_apps import PLATFORM_APPS + + +PLATFORM_TOOL_PREFIX = "platform:" + +_APP_DESCRIPTIONS: dict[str, str] = { + "asana": "Asana Integration", + "box": "Box Integration", + "clickup": "ClickUp Integration", + "github": "GitHub Integration", + "gmail": "Gmail Integration", + "google_calendar": "Google Calendar Integration", + "google_sheets": "Google Sheets Integration", + "hubspot": "HubSpot Integration", + "jira": "Jira Integration", + "linear": "Linear Integration", + "notion": "Notion Integration", + "salesforce": "Salesforce Integration", + "shopify": "Shopify Integration", + "slack": "Slack Integration", + "stripe": "Stripe Integration", + "zendesk": "Zendesk Integration", +} + +PLATFORM_TOOLS: list[tuple[str, str]] = [ + (f"{PLATFORM_TOOL_PREFIX}{app}", _APP_DESCRIPTIONS[app]) for app in PLATFORM_APPS +] diff --git a/lib/cli/src/crewai_cli/tui_picker.py b/lib/cli/src/crewai_cli/tui_picker.py index 69157658c..41b630620 100644 --- a/lib/cli/src/crewai_cli/tui_picker.py +++ b/lib/cli/src/crewai_cli/tui_picker.py @@ -388,8 +388,9 @@ def pick_many( Sorted list of selected indices, or ``(indices, action_index)`` when ``action_indices`` is provided. """ - click.echo() - click.secho(f" {title}", fg="cyan") + if title: + click.echo() + click.secho(f" {title}", fg="cyan") if _is_interactive(): try: diff --git a/lib/cli/tests/test_create_crew.py b/lib/cli/tests/test_create_crew.py index cb2a4820b..b2937dc33 100644 --- a/lib/cli/tests/test_create_crew.py +++ b/lib/cli/tests/test_create_crew.py @@ -7,6 +7,7 @@ from unittest import mock import pytest import tomli from click.testing import CliRunner +from crewai_core.platform_apps import PLATFORM_APPS from packaging.requirements import Requirement from packaging.version import Version import crewai_cli.create_json_crew as json_crew @@ -620,6 +621,52 @@ def test_json_wizard_tool_picker_lists_builtin_tools_across_categories(monkeypat }.isdisjoint(tool_names) +def test_json_wizard_platform_tool_selection_stays_in_agent_tools(monkeypatch): + picker_calls = 0 + + def pick_many(title: str, labels: list[str], **kwargs): + nonlocal picker_calls + picker_calls += 1 + if picker_calls == 1: + platform_row = next( + idx for idx, label in enumerate(labels) if "CrewAI Platform" in label + ) + return [], platform_row + + github = next( + idx + for idx, label in enumerate(labels) + if label.startswith("GitHub Integration") + and label.endswith("Platform: GitHubIntegration") + ) + return [github], None + + monkeypatch.setattr(json_crew, "pick_many", pick_many) + monkeypatch.setattr( + json_crew, "_prompt_text", lambda label, **kwargs: label.lower() + ) + monkeypatch.setattr(json_crew, "_select_model", lambda: "openai/gpt-5.5") + monkeypatch.setattr(json_crew, "_confirm", lambda *_args, **_kwargs: False) + + agent = json_crew._wizard_agent(agent_num=1, existing_names=[]) + + assert agent is not None + assert agent["tools"] == ["platform:github"] + assert '"tools": ["platform:github"]' in json_crew._agent_to_jsonc(agent) + + +def test_json_wizard_platform_catalog_contains_every_supported_app(): + platform_category = next( + tools + for category, tools in json_crew._TOOL_CATEGORIES + if category == "CrewAI Platform" + ) + + assert [name for name, _description in platform_category] == [ + f"platform:{app}" for app in PLATFORM_APPS + ] + + def test_multi_picker_skips_separator_on_initial_cursor(monkeypatch): cursors: list[int] = [] diff --git a/lib/crewai-tools/src/crewai_tools/tools/crewai_platform_tools/crewai_platform_tools.py b/lib/crewai-tools/src/crewai_tools/tools/crewai_platform_tools/crewai_platform_tools.py index 2dc083b98..a344f8cae 100644 --- a/lib/crewai-tools/src/crewai_tools/tools/crewai_platform_tools/crewai_platform_tools.py +++ b/lib/crewai-tools/src/crewai_tools/tools/crewai_platform_tools/crewai_platform_tools.py @@ -1,5 +1,3 @@ -import logging - from crewai.tools import BaseTool from crewai_tools.tools.crewai_platform_tools.crewai_platform_action_tool import ( @@ -11,9 +9,6 @@ from crewai_tools.tools.crewai_platform_tools.integrations_client import ( ) -logger = logging.getLogger(__name__) - - def CrewaiPlatformTools( # noqa: N802 apps: list[str], ) -> list[BaseTool]: @@ -28,17 +23,11 @@ def CrewaiPlatformTools( # noqa: N802 selectors = [ApplicationSelector.from_string(app) for app in apps] tools: list[BaseTool] = [] - try: - for selector in selectors: - client = client_for_selector(selector) - tools.extend( - CrewAIPlatformActionTool(tool_info, client=client) - for tool_info in client.get_actions([selector]) - ) - except ValueError: - raise - except Exception as error: - logger.error(f"Failed to fetch platform tools for apps {apps}: {error}") - return [] + for selector in selectors: + client = client_for_selector(selector) + tools.extend( + CrewAIPlatformActionTool(tool_info, client=client) + for tool_info in client.get_actions([selector]) + ) return tools diff --git a/lib/crewai-tools/tests/tools/crewai_platform_tools/test_crewai_platform_tools.py b/lib/crewai-tools/tests/tools/crewai_platform_tools/test_crewai_platform_tools.py index 62b0814fc..f3013675f 100644 --- a/lib/crewai-tools/tests/tools/crewai_platform_tools/test_crewai_platform_tools.py +++ b/lib/crewai-tools/tests/tools/crewai_platform_tools/test_crewai_platform_tools.py @@ -157,10 +157,8 @@ class TestCrewaiPlatformTools(unittest.TestCase): def test_crewai_platform_tools_api_error_handling(self, mock_get): mock_get.side_effect = Exception("API Error") - tools = CrewaiPlatformTools(apps=["github"]) - assert tools is not None - assert isinstance(tools, list) - assert len(tools) == 0 + with self.assertRaisesRegex(Exception, "API Error"): + CrewaiPlatformTools(apps=["github"]) def test_crewai_platform_tools_no_token(self): with patch.dict("os.environ", {}, clear=True): diff --git a/lib/crewai/src/crewai/project/json_loader.py b/lib/crewai/src/crewai/project/json_loader.py index c28d42cc3..c1c95a83a 100644 --- a/lib/crewai/src/crewai/project/json_loader.py +++ b/lib/crewai/src/crewai/project/json_loader.py @@ -1820,6 +1820,9 @@ def _resolve_tools(tool_defs: list[Any], project_root: Path | None = None) -> li ) if not tool_def: continue + if tool_def.startswith("platform:"): + tools.extend(_resolve_platform_tools(tool_def.removeprefix("platform:"))) + continue if tool_def.startswith("custom:"): tools.append(_resolve_custom_tool(tool_def[7:], project_root=project_root)) continue @@ -1847,6 +1850,30 @@ def _resolve_tools(tool_defs: list[Any], project_root: Path | None = None) -> li return tools +def _resolve_platform_tools(selector: str) -> list[Any]: + """Materialize an AMP application selector into CrewAI tools.""" + if not selector: + raise JSONProjectError( + "Invalid platform tool reference 'platform:': expected " + "'platform:'" + ) + + try: + from crewai_tools import CrewaiPlatformTools + except ImportError as e: + raise JSONProjectError( + "Platform tools require the 'crewai-tools' package. " + "Install CrewAI with the tools extra." + ) from e + + try: + return CrewaiPlatformTools(apps=[selector]) + except Exception as e: + raise JSONProjectError( + f"Failed to initialize platform tool 'platform:{selector}': {e}" + ) from e + + def _instantiate_tool_import_ref(ref: str) -> Any: from crewai.tools import BaseTool @@ -1990,19 +2017,25 @@ def _tool_definition_errors( f"got {type(tool_def).__name__}" ) continue - if not tool_def.startswith("custom:"): + if tool_def.startswith("platform:"): + if not tool_def.removeprefix("platform:"): + errors.append( + f"{source}: invalid platform tool reference 'platform:': " + "expected 'platform:'" + ) continue - try: - tool_file = _custom_tool_file(tool_def[7:], project_root) - except JSONProjectError as exc: - errors.append(f"{source}: {exc}") - continue - if not tool_file.exists(): - errors.append( - f"{source}: custom tool '{tool_def}' not found: expected " - f"{tool_file}. Create the file with a BaseTool subclass, or " - f"remove the tool from your crew JSON." - ) + if tool_def.startswith("custom:"): + try: + tool_file = _custom_tool_file(tool_def[7:], project_root) + except JSONProjectError as exc: + errors.append(f"{source}: {exc}") + continue + if not tool_file.exists(): + errors.append( + f"{source}: custom tool '{tool_def}' not found: expected " + f"{tool_file}. Create the file with a BaseTool subclass, or " + f"remove the tool from your crew JSON." + ) return errors diff --git a/lib/crewai/tests/project/test_json_loader.py b/lib/crewai/tests/project/test_json_loader.py index 638c0d500..727aebbb9 100644 --- a/lib/crewai/tests/project/test_json_loader.py +++ b/lib/crewai/tests/project/test_json_loader.py @@ -403,6 +403,52 @@ class TestLoadAgentFromDefinition: class TestResolveTools: + def test_platform_tool_refs_materialize_multiple_application_tools( + self, monkeypatch + ): + from crewai.project.json_loader import _resolve_tools + + github_tools = [object()] + linear_tools = [object(), object()] + calls: list[list[str]] = [] + + def build_platform_tools(apps: list[str]): + calls.append(apps) + return { + "github": github_tools, + "linear": linear_tools, + }[apps[0]] + + monkeypatch.setattr( + "crewai_tools.CrewaiPlatformTools", build_platform_tools + ) + + tools = _resolve_tools(["platform:github", "platform:linear"]) + + assert tools == github_tools + linear_tools + assert calls == [["github"], ["linear"]] + + def test_empty_platform_tool_ref_raises_with_guidance(self): + from crewai.project.json_loader import JSONProjectError, _resolve_tools + + with pytest.raises(JSONProjectError, match="platform:"): + _resolve_tools(["platform:"]) + + def test_platform_tool_discovery_errors_are_json_project_errors( + self, monkeypatch + ): + from crewai.project.json_loader import JSONProjectError, _resolve_tools + + def fail_platform_tool_discovery(apps: list[str]): + raise RuntimeError("discovery unavailable") + + monkeypatch.setattr( + "crewai_tools.CrewaiPlatformTools", fail_platform_tool_discovery + ) + + with pytest.raises(JSONProjectError, match="discovery unavailable"): + _resolve_tools(["platform:github"]) + def test_import_ref_tool_resolves(self, tmp_path, monkeypatch): from crewai.project.json_loader import _resolve_tools @@ -614,6 +660,19 @@ class TestValidationDoesNotExecuteTools: assert "Invalid custom tool name" in str(exc_info.value) + def test_validate_rejects_empty_platform_tool_ref(self, tmp_path): + from crewai.project.json_loader import ( + JSONProjectValidationError, + validate_crew_project, + ) + + crew_path = self._write_project(tmp_path, tool_line='"platform:"') + + with pytest.raises(JSONProjectValidationError) as exc_info: + validate_crew_project(crew_path, tmp_path / "agents") + + assert "platform:" in str(exc_info.value) + def test_validate_rejects_deep_python_ref_nesting(self, tmp_path): from crewai.project.json_loader import validate_crew_project From 93ad4d67c47c634d0b413a707d8bdc2b2cdfd62f Mon Sep 17 00:00:00 2001 From: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com> Date: Sat, 12 Sep 2026 00:50:33 +0530 Subject: [PATCH 5/6] feat: validate platform integrations during crew setup (#7385) * feat: validate platform integrations during crew setup * fix: clarify platform integration validation * refactor: split platform authentication workflow * feat: list connected platform integrations --- lib/cli/src/crewai_cli/create_json_crew.py | 194 ++++++++++++++++++++- 1 file changed, 193 insertions(+), 1 deletion(-) diff --git a/lib/cli/src/crewai_cli/create_json_crew.py b/lib/cli/src/crewai_cli/create_json_crew.py index 00f1cd4db..362f98e16 100644 --- a/lib/cli/src/crewai_cli/create_json_crew.py +++ b/lib/cli/src/crewai_cli/create_json_crew.py @@ -3,6 +3,7 @@ from __future__ import annotations import json +import os from pathlib import Path import re import sys @@ -619,6 +620,10 @@ def _wizard_agents_and_tasks( "inputs": {}, } + # Platform authentication belongs to the final wizard step, after the + # user has finished configuring agents, tasks, and crew settings. + _setup_platform_auth(agents) + return agents, tasks, crew_settings @@ -871,6 +876,181 @@ def _setup_env(folder_path: Path, llm_model: str) -> None: click.secho(" API keys and model saved to .env file", fg="green") +def _platform_apps_from_agents(agents: list[dict[str, Any]]) -> list[str]: + """Return unique platform applications selected across all agents.""" + apps: list[str] = [] + for agent in agents: + for tool in agent.get("tools", []): + if isinstance(tool, str) and tool.startswith("platform:"): + app = tool.removeprefix("platform:") + if app and app not in apps: + apps.append(app) + return apps + + +def _platform_app_name(app: str) -> str: + """Return the display name for a platform application slug.""" + return ( + dict(PLATFORM_TOOLS) + .get(f"platform:{app}", app.replace("_", " ").title()) + .removesuffix(" Integration") + ) + + +def _prompt_platform_token() -> str: + """Explain how to obtain and securely prompt for an AMP integration token.""" + click.secho( + " To use CrewAI Platform tools, you need a CrewAI Platform Integration Token.", + fg="yellow", + ) + click.secho( + " Get your token from CrewAI AMP: https://app.crewai.com " + "→ Settings → Integration Tokens.", + fg="cyan", + ) + return str( + click.prompt( + click.style(" CREWAI_PLATFORM_INTEGRATION_TOKEN", fg="cyan"), + hide_input=True, + prompt_suffix=click.style(" > ", fg="bright_white"), + ) + ).strip() + + +def _validate_platform_apps( + apps: list[str], application_selector: Any, client_for_selector: Any +) -> tuple[list[str], bool]: + """Check selected AMP applications and return failures and token validity.""" + failed: list[str] = [] + for app in apps: + app_name = _platform_app_name(app) + click.echo() + click.secho( + " Checking CrewAI Platform Integration Token and " + f"{app_name} integration on AMP...", + fg="cyan", + ) + try: + selector = application_selector.from_string(app) + actions = client_for_selector(selector).get_actions([selector]) + except Exception as error: + status_code = getattr(getattr(error, "response", None), "status_code", None) + if status_code in {401, 403}: + click.secho( + " ✘ CrewAI Platform Integration Token is invalid or expired", + fg="red", + ) + return failed, True + click.secho( + f" ✘ {app_name} integration could not be validated: {error}", + fg="red", + ) + failed.append(app) + continue + + if not actions: + click.secho( + f" ✘ {app_name} integration is not connected on CrewAI Platform", + fg="red", + ) + failed.append(app) + else: + click.secho( + f" ✔ {app_name} integration is connected on CrewAI Platform", + fg="green", + ) + return failed, False + + +def _show_platform_validation_guidance( + failed_apps: list[str], token_invalid: bool +) -> None: + """Tell the user what to fix before revalidating AMP integrations.""" + click.echo() + if token_invalid: + click.secho( + " Check your CrewAI Platform Integration Token in AMP.", + fg="yellow", + ) + return + + failed_app_names = [_platform_app_name(app) for app in failed_apps] + click.secho( + " Check the " + f"{', '.join(failed_app_names)} integration" + f"{'s' if len(failed_app_names) != 1 else ''} and your CrewAI " + "Platform Integration Token in AMP.", + fg="yellow", + ) + + +def _prompt_platform_revalidation_token() -> str: + """Prompt for an optional replacement token before the next validation pass.""" + click.echo() + return str( + click.prompt( + click.style( + " Press Enter to revalidate, or enter a replacement token", + fg="cyan", + ), + default="", + show_default=False, + hide_input=True, + prompt_suffix=click.style(" > ", fg="bright_white"), + ) + ).strip() + + +def _setup_platform_auth(agents: list[dict[str, Any]]) -> str | None: + """Get and validate AMP authentication for selected platform applications.""" + apps = _platform_apps_from_agents(agents) + if not apps: + return None + + click.echo() + try: + from crewai_tools.tools.crewai_platform_tools.integrations_client import ( + ApplicationSelector, + client_for_selector, + ) + except ImportError as error: + raise click.ClickException( + "Platform tools require the 'crewai-tools' package. " + "Install it with `uv add crewai-tools` or " + "`pip install 'crewai[tools]'`." + ) from error + + token = os.environ.get("CREWAI_PLATFORM_INTEGRATION_TOKEN", "") + while True: + if not token: + token = _prompt_platform_token() + if not token: + click.secho( + " A CrewAI Platform Integration Token is required to validate " + "the selected integrations.", + fg="yellow", + ) + continue + + os.environ["CREWAI_PLATFORM_INTEGRATION_TOKEN"] = token + failed_apps, token_invalid = _validate_platform_apps( + apps, ApplicationSelector, client_for_selector + ) + if not failed_apps and not token_invalid: + _success("CrewAI Platform integration token set", bold=True) + _success( + "CrewAI Platform integrations connected: " + f"{', '.join(_platform_app_name(app) for app in apps)}" + ) + return token + + _show_platform_validation_guidance(failed_apps, token_invalid) + replacement_token = _prompt_platform_revalidation_token() + if replacement_token: + token = replacement_token + os.environ["CREWAI_PLATFORM_INTEGRATION_TOKEN"] = token + + # ── Main ──────────────────────────────────────────────────────── @@ -923,13 +1103,25 @@ def create_json_crew( default_llm=default_llm, ) - # Create directories + platform_token = ( + os.environ.get("CREWAI_PLATFORM_INTEGRATION_TOKEN") + if _platform_apps_from_agents(agents) and not dmn_mode + else None + ) + + # Create directories only after platform authentication succeeds. folder_path.mkdir(parents=True) (folder_path / "agents").mkdir() (folder_path / "tools").mkdir() (folder_path / "skills").mkdir() (folder_path / "knowledge").mkdir() + if platform_token: + os.environ["CREWAI_PLATFORM_INTEGRATION_TOKEN"] = platform_token + env_vars = load_env_vars(folder_path) + env_vars["CREWAI_PLATFORM_INTEGRATION_TOKEN"] = platform_token + write_env_file(folder_path, env_vars) + for agent in agents: _write_jsonc( folder_path / "agents" / f"{agent['name']}.jsonc", From 894898f84c4ac0a89f24bf7bee6c381eb0e67f51 Mon Sep 17 00:00:00 2001 From: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com> Date: Sat, 12 Sep 2026 01:16:17 +0530 Subject: [PATCH 6/6] ci: welcome first-time contributors after merge (#7397) * ci: welcome first-time contributors after merge * ci: welcome first-time contributors after merge * ci: support fork contributor welcome comments * ci: minimize contributor welcome permissions --- .../workflows/first-contributor-welcome.yml | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 .github/workflows/first-contributor-welcome.yml diff --git a/.github/workflows/first-contributor-welcome.yml b/.github/workflows/first-contributor-welcome.yml new file mode 100644 index 000000000..69c153db8 --- /dev/null +++ b/.github/workflows/first-contributor-welcome.yml @@ -0,0 +1,88 @@ +name: Welcome First-Time Contributors + +on: + pull_request_target: + types: [closed] + +permissions: + contents: read + issues: write + +jobs: + welcome: + # Match the first-time contributor definition used by ftc-require-issue. + if: > + github.event.pull_request.merged == true && + github.event.pull_request.user.type != 'Bot' && + !contains(fromJSON('["MEMBER","OWNER","COLLABORATOR","CONTRIBUTOR"]'), + github.event.pull_request.author_association) + runs-on: ubuntu-latest + steps: + - name: Thank first-time contributors + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + script: | + const pullRequest = context.payload.pull_request; + const owner = context.repo.owner; + const repo = context.repo.repo; + const marker = ""; + + const existingComments = await github.paginate( + github.rest.issues.listComments, + { + owner, + repo, + issue_number: pullRequest.number, + per_page: 100, + }, + ); + if (existingComments.some((comment) => comment.body?.includes(marker))) { + core.info("Welcome comment already exists; skipping duplicate."); + return; + } + + const prUrl = pullRequest.html_url; + const shareText = [ + "I just made my first contribution to @crewAIInc!", + "", + "Excited to help build the future of AI agents with CrewAI.", + "", + prUrl, + "", + "#OpenSource #AI #CrewAI", + ].join("\n"); + const xShareUrl = `https://x.com/intent/post?text=${encodeURIComponent(shareText)}`; + const linkedInShareUrl = `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(prUrl)}`; + + const body = [ + marker, + "", + `## Thanks for your first contribution to CrewAI, @${pullRequest.user.login}!`, + "", + "We really appreciate the time and care you put into this PR. Your work is now part of CrewAI — welcome to the contributor community.", + "", + "Want to share your contribution? Totally optional:", + "", + `[Share on X](${xShareUrl}) · [Share the PR on LinkedIn](${linkedInShareUrl})`, + "", + "If the links don't work, feel free to copy and personalize this:", + "", + "```text", + "I just made my first contribution to @crewAIInc!", + "", + "Excited to help build the future of AI agents with CrewAI.", + "", + prUrl, + "", + "#OpenSource #AI #CrewAI", + "```", + "", + "If you share, we'd love to see it — tag **@crewAIInc** on X and **CrewAI** on LinkedIn.", + ].join("\n"); + + await github.rest.issues.createComment({ + owner, + repo, + issue_number: pullRequest.number, + body, + });