diff --git a/docs/en/concepts/cli.mdx b/docs/en/concepts/cli.mdx index 804f9d767..e14c264a9 100644 --- a/docs/en/concepts/cli.mdx +++ b/docs/en/concepts/cli.mdx @@ -88,7 +88,7 @@ crewai replay [OPTIONS] - `-t, --task_id TEXT`: Replay the crew from this task ID, including all subsequent tasks Example: -```shell Terminal +```shell Terminal crewai replay -t task_123456 ``` @@ -134,7 +134,7 @@ crewai test [OPTIONS] - `-m, --model TEXT`: LLM Model to run the tests on the Crew (default: "gpt-4o-mini") Example: -```shell Terminal +```shell Terminal crewai test -n 5 -m gpt-3.5-turbo ``` @@ -151,7 +151,7 @@ Starting from version 0.103.0, the `crewai run` command can be used to run both -Make sure to run these commands from the directory where your CrewAI project is set up. +Make sure to run these commands from the directory where your CrewAI project is set up. Some commands may require additional configuration or setup within your project structure. @@ -235,7 +235,7 @@ You must be authenticated to CrewAI Enterprise to use these organization managem - **Deploy the Crew**: Once you are authenticated, you can deploy your crew or flow to CrewAI Enterprise. ```shell Terminal crewai deploy push - ``` + ``` - Initiates the deployment process on the CrewAI Enterprise platform. - Upon successful initiation, it will output the Deployment created successfully! message along with the Deployment Name and a unique Deployment ID (UUID). @@ -309,3 +309,82 @@ When you select a provider, the CLI will prompt you to enter the Key name and th See the following link for each provider's key name: * [LiteLLM Providers](https://docs.litellm.ai/docs/providers) + +### 12. Configuration Management + +Manage CLI configuration settings for CrewAI. + +```shell Terminal +crewai config [COMMAND] [OPTIONS] +``` + +#### Commands: + +- `list`: Display all CLI configuration parameters +```shell Terminal +crewai config list +``` + +- `set`: Set a CLI configuration parameter +```shell Terminal +crewai config set +``` + +- `reset`: Reset all CLI configuration parameters to default values +```shell Terminal +crewai config reset +``` + +#### Available Configuration Parameters + +- `enterprise_base_url`: Base URL of the CrewAI Enterprise instance +- `oauth2_provider`: OAuth2 provider used for authentication (e.g., workos, okta, auth0) +- `oauth2_audience`: OAuth2 audience value, typically used to identify the target API or resource +- `oauth2_client_id`: OAuth2 client ID issued by the provider, used during authentication requests +- `oauth2_domain`: OAuth2 provider's domain (e.g., your-org.auth0.com) used for issuing tokens + +#### Examples + +Display current configuration: +```shell Terminal +crewai config list +``` + +Example output: +``` +CrewAI CLI Configuration +┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ Setting ┃ Value ┃ Description ┃ +┡━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ enterprise_base_url│ https://app.crewai.com │ Base URL of the CrewAI Enterprise instance │ +│ org_name │ Not set │ Name of the currently active organization │ +│ org_uuid │ Not set │ UUID of the currently active organization │ +│ oauth2_provider │ workos │ OAuth2 provider used for authentication (e.g., workos, okta, auth0). │ +│ oauth2_audience │ client_01YYY │ OAuth2 audience value, typically used to identify the target API or resource. │ +│ oauth2_client_id │ client_01XXX │ OAuth2 client ID issued by the provider, used during authentication requests. │ +│ oauth2_domain │ login.crewai.com │ OAuth2 provider's domain (e.g., your-org.auth0.com) used for issuing tokens. │ +``` + +Set the enterprise base URL: +```shell Terminal +crewai config set enterprise_base_url https://my-enterprise.crewai.com +``` + +Set OAuth2 provider: +```shell Terminal +crewai config set oauth2_provider auth0 +``` + +Set OAuth2 domain: +```shell Terminal +crewai config set oauth2_domain my-company.auth0.com +``` + +Reset all configuration to defaults: +```shell Terminal +crewai config reset +``` + + +Configuration settings are stored in `~/.config/crewai/settings.json`. Some settings like organization name and UUID are read-only and managed through authentication and organization commands. Tool repository related settings are hidden and cannot be set directly by users. + diff --git a/docs/en/mcp/overview.mdx b/docs/en/mcp/overview.mdx index a5c18b106..6b2268454 100644 --- a/docs/en/mcp/overview.mdx +++ b/docs/en/mcp/overview.mdx @@ -44,6 +44,19 @@ The `MCPServerAdapter` class from `crewai-tools` is the primary way to connect t Using a Python context manager (`with` statement) is the **recommended approach** for `MCPServerAdapter`. It automatically handles starting and stopping the connection to the MCP server. +## Connection Configuration + +The `MCPServerAdapter` supports several configuration options to customize the connection behavior: + +- **`connect_timeout`** (optional): Maximum time in seconds to wait for establishing a connection to the MCP server. Defaults to 30 seconds if not specified. This is particularly useful for remote servers that may have variable response times. + +```python +# Example with custom connection timeout +with MCPServerAdapter(server_params, connect_timeout=60) as tools: + # Connection will timeout after 60 seconds if not established + pass +``` + ```python from crewai import Agent from crewai_tools import MCPServerAdapter @@ -70,7 +83,7 @@ server_params = { } # Example usage (uncomment and adapt once server_params is set): -with MCPServerAdapter(server_params) as mcp_tools: +with MCPServerAdapter(server_params, connect_timeout=60) as mcp_tools: print(f"Available tools: {[tool.name for tool in mcp_tools]}") my_agent = Agent( @@ -95,7 +108,7 @@ There are two ways to filter tools: ### Accessing a specific tool using dictionary-style indexing. ```python -with MCPServerAdapter(server_params) as mcp_tools: +with MCPServerAdapter(server_params, connect_timeout=60) as mcp_tools: print(f"Available tools: {[tool.name for tool in mcp_tools]}") my_agent = Agent( @@ -112,7 +125,7 @@ with MCPServerAdapter(server_params) as mcp_tools: ### Pass a list of tool names to the `MCPServerAdapter` constructor. ```python -with MCPServerAdapter(server_params, "tool_name") as mcp_tools: +with MCPServerAdapter(server_params, "tool_name", connect_timeout=60) as mcp_tools: print(f"Available tools: {[tool.name for tool in mcp_tools]}") my_agent = Agent( diff --git a/docs/pt-BR/concepts/cli.mdx b/docs/pt-BR/concepts/cli.mdx index bc5c36a51..277abd50b 100644 --- a/docs/pt-BR/concepts/cli.mdx +++ b/docs/pt-BR/concepts/cli.mdx @@ -324,3 +324,82 @@ Ao escolher um provedor, o CLI solicitará que você informe o nome da chave e a Veja o seguinte link para o nome de chave de cada provedor: * [LiteLLM Providers](https://docs.litellm.ai/docs/providers) + +### 12. Gerenciamento de Configuração + +Gerencie as configurações do CLI para CrewAI. + +```shell Terminal +crewai config [COMANDO] [OPÇÕES] +``` + +#### Comandos: + +- `list`: Exibir todos os parâmetros de configuração do CLI +```shell Terminal +crewai config list +``` + +- `set`: Definir um parâmetro de configuração do CLI +```shell Terminal +crewai config set +``` + +- `reset`: Redefinir todos os parâmetros de configuração do CLI para valores padrão +```shell Terminal +crewai config reset +``` + +#### Parâmetros de Configuração Disponíveis + +- `enterprise_base_url`: URL base da instância CrewAI Enterprise +- `oauth2_provider`: Provedor OAuth2 usado para autenticação (ex: workos, okta, auth0) +- `oauth2_audience`: Valor de audiência OAuth2, tipicamente usado para identificar a API ou recurso de destino +- `oauth2_client_id`: ID do cliente OAuth2 emitido pelo provedor, usado durante solicitações de autenticação +- `oauth2_domain`: Domínio do provedor OAuth2 (ex: sua-org.auth0.com) usado para emissão de tokens + +#### Exemplos + +Exibir configuração atual: +```shell Terminal +crewai config list +``` + +Exemplo de saída: +``` +CrewAI CLI Configuration +┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ Setting ┃ Value ┃ Description ┃ +┡━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ enterprise_base_url│ https://app.crewai.com │ Base URL of the CrewAI Enterprise instance │ +│ org_name │ Not set │ Name of the currently active organization │ +│ org_uuid │ Not set │ UUID of the currently active organization │ +│ oauth2_provider │ workos │ OAuth2 provider used for authentication (e.g., workos, okta, auth0). │ +│ oauth2_audience │ client_01YYY │ OAuth2 audience value, typically used to identify the target API or resource. │ +│ oauth2_client_id │ client_01XXX │ OAuth2 client ID issued by the provider, used during authentication requests. │ +│ oauth2_domain │ login.crewai.com │ OAuth2 provider's domain (e.g., your-org.auth0.com) used for issuing tokens. │ +``` + +Definir a URL base do enterprise: +```shell Terminal +crewai config set enterprise_base_url https://minha-empresa.crewai.com +``` + +Definir provedor OAuth2: +```shell Terminal +crewai config set oauth2_provider auth0 +``` + +Definir domínio OAuth2: +```shell Terminal +crewai config set oauth2_domain minha-empresa.auth0.com +``` + +Redefinir todas as configurações para padrões: +```shell Terminal +crewai config reset +``` + + +As configurações são armazenadas em `~/.config/crewai/settings.json`. Algumas configurações como nome da organização e UUID são somente leitura e gerenciadas através de comandos de autenticação e organização. Configurações relacionadas ao repositório de ferramentas são ocultas e não podem ser definidas diretamente pelo usuário. + diff --git a/docs/pt-BR/mcp/overview.mdx b/docs/pt-BR/mcp/overview.mdx index 5ce4444c9..5ceaaa144 100644 --- a/docs/pt-BR/mcp/overview.mdx +++ b/docs/pt-BR/mcp/overview.mdx @@ -44,6 +44,19 @@ A classe `MCPServerAdapter` da `crewai-tools` é a principal forma de conectar-s O uso de um gerenciador de contexto Python (`with`) é a **abordagem recomendada** para o `MCPServerAdapter`. Ele lida automaticamente com a abertura e o fechamento da conexão com o servidor MCP. +## Configuração de Conexão + +O `MCPServerAdapter` suporta várias opções de configuração para personalizar o comportamento da conexão: + +- **`connect_timeout`** (opcional): Tempo máximo em segundos para aguardar o estabelecimento de uma conexão com o servidor MCP. O padrão é 30 segundos se não especificado. Isso é particularmente útil para servidores remotos que podem ter tempos de resposta variáveis. + +```python +# Exemplo com timeout personalizado para conexão +with MCPServerAdapter(server_params, connect_timeout=60) as tools: + # A conexão terá timeout após 60 segundos se não estabelecida + pass +``` + ```python from crewai import Agent from crewai_tools import MCPServerAdapter @@ -70,7 +83,7 @@ server_params = { } # Exemplo de uso (descomente e adapte após definir server_params): -with MCPServerAdapter(server_params) as mcp_tools: +with MCPServerAdapter(server_params, connect_timeout=60) as mcp_tools: print(f"Available tools: {[tool.name for tool in mcp_tools]}") meu_agente = Agent( @@ -88,7 +101,7 @@ Este padrão geral mostra como integrar ferramentas. Para exemplos específicos ## Filtrando Ferramentas ```python -with MCPServerAdapter(server_params) as mcp_tools: +with MCPServerAdapter(server_params, connect_timeout=60) as mcp_tools: print(f"Available tools: {[tool.name for tool in mcp_tools]}") meu_agente = Agent( diff --git a/src/crewai/cli/authentication/constants.py b/src/crewai/cli/authentication/constants.py index 0616d5407..22a212822 100644 --- a/src/crewai/cli/authentication/constants.py +++ b/src/crewai/cli/authentication/constants.py @@ -1,8 +1,6 @@ ALGORITHMS = ["RS256"] + +#TODO: The AUTH0 constants should be removed after WorkOS migration is completed AUTH0_DOMAIN = "crewai.us.auth0.com" AUTH0_CLIENT_ID = "DEVC5Fw6NlRoSzmDCcOhVq85EfLBjKa8" AUTH0_AUDIENCE = "https://crewai.us.auth0.com/api/v2/" - -WORKOS_DOMAIN = "login.crewai.com" -WORKOS_CLI_CONNECT_APP_ID = "client_01JYT06R59SP0NXYGD994NFXXX" -WORKOS_ENVIRONMENT_ID = "client_01JNJQWBJ4SPFN3SWJM5T7BDG8" diff --git a/src/crewai/cli/authentication/main.py b/src/crewai/cli/authentication/main.py index 303c7a1fe..26a354aea 100644 --- a/src/crewai/cli/authentication/main.py +++ b/src/crewai/cli/authentication/main.py @@ -1,76 +1,92 @@ import time import webbrowser -from typing import Any, Dict +from typing import Any, Dict, Optional import requests from rich.console import Console +from pydantic import BaseModel, Field -from .constants import ( - AUTH0_AUDIENCE, - AUTH0_CLIENT_ID, - AUTH0_DOMAIN, - WORKOS_DOMAIN, - WORKOS_CLI_CONNECT_APP_ID, - WORKOS_ENVIRONMENT_ID, -) from .utils import TokenManager, validate_jwt_token from urllib.parse import quote from crewai.cli.plus_api import PlusAPI from crewai.cli.config import Settings +from crewai.cli.authentication.constants import ( + AUTH0_AUDIENCE, + AUTH0_CLIENT_ID, + AUTH0_DOMAIN, +) console = Console() +class Oauth2Settings(BaseModel): + provider: str = Field(description="OAuth2 provider used for authentication (e.g., workos, okta, auth0).") + client_id: str = Field(description="OAuth2 client ID issued by the provider, used during authentication requests.") + domain: str = Field(description="OAuth2 provider's domain (e.g., your-org.auth0.com) used for issuing tokens.") + audience: Optional[str] = Field(description="OAuth2 audience value, typically used to identify the target API or resource.", default=None) + + @classmethod + def from_settings(cls): + settings = Settings() + + return cls( + provider=settings.oauth2_provider, + domain=settings.oauth2_domain, + client_id=settings.oauth2_client_id, + audience=settings.oauth2_audience, + ) + + +class ProviderFactory: + @classmethod + def from_settings(cls, settings: Optional[Oauth2Settings] = None): + settings = settings or Oauth2Settings.from_settings() + + import importlib + module = importlib.import_module(f"crewai.cli.authentication.providers.{settings.provider.lower()}") + provider = getattr(module, f"{settings.provider.capitalize()}Provider") + + return provider(settings) + class AuthenticationCommand: - AUTH0_DEVICE_CODE_URL = f"https://{AUTH0_DOMAIN}/oauth/device/code" - AUTH0_TOKEN_URL = f"https://{AUTH0_DOMAIN}/oauth/token" - - WORKOS_DEVICE_CODE_URL = f"https://{WORKOS_DOMAIN}/oauth2/device_authorization" - WORKOS_TOKEN_URL = f"https://{WORKOS_DOMAIN}/oauth2/token" - def __init__(self): self.token_manager = TokenManager() - # TODO: WORKOS - This variable is temporary until migration to WorkOS is complete. - self.user_provider = "workos" + self.oauth2_provider = ProviderFactory.from_settings() def login(self) -> None: """Sign up to CrewAI+""" - - device_code_url = self.WORKOS_DEVICE_CODE_URL - token_url = self.WORKOS_TOKEN_URL - client_id = WORKOS_CLI_CONNECT_APP_ID - audience = None - console.print("Signing in to CrewAI Enterprise...\n", style="bold blue") # TODO: WORKOS - Next line and conditional are temporary until migration to WorkOS is complete. user_provider = self._determine_user_provider() if user_provider == "auth0": - device_code_url = self.AUTH0_DEVICE_CODE_URL - token_url = self.AUTH0_TOKEN_URL - client_id = AUTH0_CLIENT_ID - audience = AUTH0_AUDIENCE - self.user_provider = "auth0" + settings = Oauth2Settings( + provider="auth0", + client_id=AUTH0_CLIENT_ID, + domain=AUTH0_DOMAIN, + audience=AUTH0_AUDIENCE + ) + self.oauth2_provider = ProviderFactory.from_settings(settings) # End of temporary code. - device_code_data = self._get_device_code(client_id, device_code_url, audience) + device_code_data = self._get_device_code() self._display_auth_instructions(device_code_data) - return self._poll_for_token(device_code_data, client_id, token_url) + return self._poll_for_token(device_code_data) def _get_device_code( - self, client_id: str, device_code_url: str, audience: str | None = None + self ) -> Dict[str, Any]: """Get the device code to authenticate the user.""" device_code_payload = { - "client_id": client_id, + "client_id": self.oauth2_provider.get_client_id(), "scope": "openid", - "audience": audience, + "audience": self.oauth2_provider.get_audience(), } response = requests.post( - url=device_code_url, data=device_code_payload, timeout=20 + url=self.oauth2_provider.get_authorize_url(), data=device_code_payload, timeout=20 ) response.raise_for_status() return response.json() @@ -82,21 +98,21 @@ class AuthenticationCommand: webbrowser.open(device_code_data["verification_uri_complete"]) def _poll_for_token( - self, device_code_data: Dict[str, Any], client_id: str, token_poll_url: str + self, device_code_data: Dict[str, Any] ) -> None: """Polls the server for the token until it is received, or max attempts are reached.""" token_payload = { "grant_type": "urn:ietf:params:oauth:grant-type:device_code", "device_code": device_code_data["device_code"], - "client_id": client_id, + "client_id": self.oauth2_provider.get_client_id(), } console.print("\nWaiting for authentication... ", style="bold blue", end="") attempts = 0 while True and attempts < 10: - response = requests.post(token_poll_url, data=token_payload, timeout=30) + response = requests.post(self.oauth2_provider.get_token_url(), data=token_payload, timeout=30) token_data = response.json() if response.status_code == 200: @@ -128,19 +144,14 @@ class AuthenticationCommand: """Validates the JWT token and saves the token to the token manager.""" jwt_token = token_data["access_token"] + issuer = self.oauth2_provider.get_issuer() jwt_token_data = { "jwt_token": jwt_token, - "jwks_url": f"https://{WORKOS_DOMAIN}/oauth2/jwks", - "issuer": f"https://{WORKOS_DOMAIN}", - "audience": WORKOS_ENVIRONMENT_ID, + "jwks_url": self.oauth2_provider.get_jwks_url(), + "issuer": issuer, + "audience": self.oauth2_provider.get_audience(), } - # TODO: WORKOS - The following conditional is temporary until migration to WorkOS is complete. - if self.user_provider == "auth0": - jwt_token_data["jwks_url"] = f"https://{AUTH0_DOMAIN}/.well-known/jwks.json" - jwt_token_data["issuer"] = f"https://{AUTH0_DOMAIN}/" - jwt_token_data["audience"] = AUTH0_AUDIENCE - decoded_token = validate_jwt_token(**jwt_token_data) expires_at = decoded_token.get("exp", 0) diff --git a/src/crewai/cli/authentication/providers/auth0.py b/src/crewai/cli/authentication/providers/auth0.py new file mode 100644 index 000000000..8538550db --- /dev/null +++ b/src/crewai/cli/authentication/providers/auth0.py @@ -0,0 +1,26 @@ +from crewai.cli.authentication.providers.base_provider import BaseProvider + +class Auth0Provider(BaseProvider): + def get_authorize_url(self) -> str: + return f"https://{self._get_domain()}/oauth/device/code" + + def get_token_url(self) -> str: + return f"https://{self._get_domain()}/oauth/token" + + def get_jwks_url(self) -> str: + return f"https://{self._get_domain()}/.well-known/jwks.json" + + def get_issuer(self) -> str: + return f"https://{self._get_domain()}/" + + def get_audience(self) -> str: + assert self.settings.audience is not None, "Audience is required" + return self.settings.audience + + def get_client_id(self) -> str: + assert self.settings.client_id is not None, "Client ID is required" + return self.settings.client_id + + def _get_domain(self) -> str: + assert self.settings.domain is not None, "Domain is required" + return self.settings.domain diff --git a/src/crewai/cli/authentication/providers/base_provider.py b/src/crewai/cli/authentication/providers/base_provider.py new file mode 100644 index 000000000..c321de9f7 --- /dev/null +++ b/src/crewai/cli/authentication/providers/base_provider.py @@ -0,0 +1,30 @@ +from abc import ABC, abstractmethod +from crewai.cli.authentication.main import Oauth2Settings + +class BaseProvider(ABC): + def __init__(self, settings: Oauth2Settings): + self.settings = settings + + @abstractmethod + def get_authorize_url(self) -> str: + ... + + @abstractmethod + def get_token_url(self) -> str: + ... + + @abstractmethod + def get_jwks_url(self) -> str: + ... + + @abstractmethod + def get_issuer(self) -> str: + ... + + @abstractmethod + def get_audience(self) -> str: + ... + + @abstractmethod + def get_client_id(self) -> str: + ... diff --git a/src/crewai/cli/authentication/providers/okta.py b/src/crewai/cli/authentication/providers/okta.py new file mode 100644 index 000000000..14227ae2b --- /dev/null +++ b/src/crewai/cli/authentication/providers/okta.py @@ -0,0 +1,22 @@ +from crewai.cli.authentication.providers.base_provider import BaseProvider + +class OktaProvider(BaseProvider): + def get_authorize_url(self) -> str: + return f"https://{self.settings.domain}/oauth2/default/v1/device/authorize" + + def get_token_url(self) -> str: + return f"https://{self.settings.domain}/oauth2/default/v1/token" + + def get_jwks_url(self) -> str: + return f"https://{self.settings.domain}/oauth2/default/v1/keys" + + def get_issuer(self) -> str: + return f"https://{self.settings.domain}/oauth2/default" + + def get_audience(self) -> str: + assert self.settings.audience is not None + return self.settings.audience + + def get_client_id(self) -> str: + assert self.settings.client_id is not None + return self.settings.client_id diff --git a/src/crewai/cli/authentication/providers/workos.py b/src/crewai/cli/authentication/providers/workos.py new file mode 100644 index 000000000..8cf475a4d --- /dev/null +++ b/src/crewai/cli/authentication/providers/workos.py @@ -0,0 +1,25 @@ +from crewai.cli.authentication.providers.base_provider import BaseProvider + +class WorkosProvider(BaseProvider): + def get_authorize_url(self) -> str: + return f"https://{self._get_domain()}/oauth2/device_authorization" + + def get_token_url(self) -> str: + return f"https://{self._get_domain()}/oauth2/token" + + def get_jwks_url(self) -> str: + return f"https://{self._get_domain()}/oauth2/jwks" + + def get_issuer(self) -> str: + return f"https://{self._get_domain()}" + + def get_audience(self) -> str: + return self.settings.audience or "" + + def get_client_id(self) -> str: + assert self.settings.client_id is not None, "Client ID is required" + return self.settings.client_id + + def _get_domain(self) -> str: + assert self.settings.domain is not None, "Domain is required" + return self.settings.domain diff --git a/src/crewai/cli/config.py b/src/crewai/cli/config.py index f2a87792e..a8da400a8 100644 --- a/src/crewai/cli/config.py +++ b/src/crewai/cli/config.py @@ -4,7 +4,13 @@ from typing import Optional from pydantic import BaseModel, Field -from crewai.cli.constants import DEFAULT_CREWAI_ENTERPRISE_URL +from crewai.cli.constants import ( + DEFAULT_CREWAI_ENTERPRISE_URL, + CREWAI_ENTERPRISE_DEFAULT_OAUTH2_PROVIDER, + CREWAI_ENTERPRISE_DEFAULT_OAUTH2_AUDIENCE, + CREWAI_ENTERPRISE_DEFAULT_OAUTH2_CLIENT_ID, + CREWAI_ENTERPRISE_DEFAULT_OAUTH2_DOMAIN, +) DEFAULT_CONFIG_PATH = Path.home() / ".config" / "crewai" / "settings.json" @@ -19,11 +25,19 @@ USER_SETTINGS_KEYS = [ # Settings that are related to the CLI CLI_SETTINGS_KEYS = [ "enterprise_base_url", + "oauth2_provider", + "oauth2_audience", + "oauth2_client_id", + "oauth2_domain", ] # Default values for CLI settings DEFAULT_CLI_SETTINGS = { "enterprise_base_url": DEFAULT_CREWAI_ENTERPRISE_URL, + "oauth2_provider": CREWAI_ENTERPRISE_DEFAULT_OAUTH2_PROVIDER, + "oauth2_audience": CREWAI_ENTERPRISE_DEFAULT_OAUTH2_AUDIENCE, + "oauth2_client_id": CREWAI_ENTERPRISE_DEFAULT_OAUTH2_CLIENT_ID, + "oauth2_domain": CREWAI_ENTERPRISE_DEFAULT_OAUTH2_DOMAIN, } # Readonly settings - cannot be set by the user @@ -39,10 +53,9 @@ HIDDEN_SETTINGS_KEYS = [ "tool_repository_password", ] - class Settings(BaseModel): enterprise_base_url: Optional[str] = Field( - default=DEFAULT_CREWAI_ENTERPRISE_URL, + default=DEFAULT_CLI_SETTINGS["enterprise_base_url"], description="Base URL of the CrewAI Enterprise instance", ) tool_repository_username: Optional[str] = Field( @@ -59,6 +72,26 @@ class Settings(BaseModel): ) config_path: Path = Field(default=DEFAULT_CONFIG_PATH, frozen=True, exclude=True) + oauth2_provider: str = Field( + description="OAuth2 provider used for authentication (e.g., workos, okta, auth0).", + default=DEFAULT_CLI_SETTINGS["oauth2_provider"] + ) + + oauth2_audience: Optional[str] = Field( + description="OAuth2 audience value, typically used to identify the target API or resource.", + default=DEFAULT_CLI_SETTINGS["oauth2_audience"] + ) + + oauth2_client_id: str = Field( + default=DEFAULT_CLI_SETTINGS["oauth2_client_id"], + description="OAuth2 client ID issued by the provider, used during authentication requests.", + ) + + oauth2_domain: str = Field( + description="OAuth2 provider's domain (e.g., your-org.auth0.com) used for issuing tokens.", + default=DEFAULT_CLI_SETTINGS["oauth2_domain"] + ) + def __init__(self, config_path: Path = DEFAULT_CONFIG_PATH, **data): """Load Settings from config path""" config_path.parent.mkdir(parents=True, exist_ok=True) @@ -105,4 +138,4 @@ class Settings(BaseModel): def _reset_cli_settings(self) -> None: """Reset all CLI settings to default values""" for key in CLI_SETTINGS_KEYS: - setattr(self, key, DEFAULT_CLI_SETTINGS[key]) + setattr(self, key, DEFAULT_CLI_SETTINGS.get(key)) diff --git a/src/crewai/cli/constants.py b/src/crewai/cli/constants.py index 06a02bee5..d0e867c41 100644 --- a/src/crewai/cli/constants.py +++ b/src/crewai/cli/constants.py @@ -1,4 +1,8 @@ DEFAULT_CREWAI_ENTERPRISE_URL = "https://app.crewai.com" +CREWAI_ENTERPRISE_DEFAULT_OAUTH2_PROVIDER = "workos" +CREWAI_ENTERPRISE_DEFAULT_OAUTH2_AUDIENCE = "client_01JNJQWBJ4SPFN3SWJM5T7BDG8" +CREWAI_ENTERPRISE_DEFAULT_OAUTH2_CLIENT_ID = "client_01JYT06R59SP0NXYGD994NFXXX" +CREWAI_ENTERPRISE_DEFAULT_OAUTH2_DOMAIN = "login.crewai.com" ENV_VARS = { "openai": [ diff --git a/src/crewai/crew.py b/src/crewai/crew.py index e9d4b8d0a..5c6fba27a 100644 --- a/src/crewai/crew.py +++ b/src/crewai/crew.py @@ -133,7 +133,7 @@ class Crew(FlowTrackable, BaseModel): default_factory=TaskOutputStorageHandler ) - name: Optional[str] = Field(default=None) + name: Optional[str] = Field(default="crew") cache: bool = Field(default=True) tasks: List[Task] = Field(default_factory=list) agents: List[BaseAgent] = Field(default_factory=list) @@ -575,7 +575,7 @@ class Crew(FlowTrackable, BaseModel): crewai_event_bus.emit( self, CrewTrainStartedEvent( - crew_name=self.name or "crew", + crew_name=self.name, n_iterations=n_iterations, filename=filename, inputs=inputs, @@ -602,7 +602,7 @@ class Crew(FlowTrackable, BaseModel): crewai_event_bus.emit( self, CrewTrainCompletedEvent( - crew_name=self.name or "crew", + crew_name=self.name, n_iterations=n_iterations, filename=filename, ), @@ -610,7 +610,7 @@ class Crew(FlowTrackable, BaseModel): except Exception as e: crewai_event_bus.emit( self, - CrewTrainFailedEvent(error=str(e), crew_name=self.name or "crew"), + CrewTrainFailedEvent(error=str(e), crew_name=self.name), ) self._logger.log("error", f"Training failed: {e}", color="red") CrewTrainingHandler(TRAINING_DATA_FILE).clear() @@ -634,7 +634,7 @@ class Crew(FlowTrackable, BaseModel): crewai_event_bus.emit( self, - CrewKickoffStartedEvent(crew_name=self.name or "crew", inputs=inputs), + CrewKickoffStartedEvent(crew_name=self.name, inputs=inputs), ) # Starts the crew to work on its assigned tasks. @@ -683,7 +683,7 @@ class Crew(FlowTrackable, BaseModel): except Exception as e: crewai_event_bus.emit( self, - CrewKickoffFailedEvent(error=str(e), crew_name=self.name or "crew"), + CrewKickoffFailedEvent(error=str(e), crew_name=self.name), ) raise finally: @@ -1077,7 +1077,7 @@ class Crew(FlowTrackable, BaseModel): crewai_event_bus.emit( self, CrewKickoffCompletedEvent( - crew_name=self.name or "crew", output=final_task_output + crew_name=self.name, output=final_task_output ), ) return CrewOutput( @@ -1325,7 +1325,7 @@ class Crew(FlowTrackable, BaseModel): crewai_event_bus.emit( self, CrewTestStartedEvent( - crew_name=self.name or "crew", + crew_name=self.name, n_iterations=n_iterations, eval_llm=llm_instance, inputs=inputs, @@ -1344,13 +1344,13 @@ class Crew(FlowTrackable, BaseModel): crewai_event_bus.emit( self, CrewTestCompletedEvent( - crew_name=self.name or "crew", + crew_name=self.name, ), ) except Exception as e: crewai_event_bus.emit( self, - CrewTestFailedEvent(error=str(e), crew_name=self.name or "crew"), + CrewTestFailedEvent(error=str(e), crew_name=self.name), ) raise diff --git a/src/crewai/lite_agent.py b/src/crewai/lite_agent.py index e14f4576f..0e99fb563 100644 --- a/src/crewai/lite_agent.py +++ b/src/crewai/lite_agent.py @@ -147,7 +147,7 @@ class LiteAgent(FlowTrackable, BaseModel): default=15, description="Maximum number of iterations for tool usage" ) max_execution_time: Optional[int] = Field( - default=None, description="Maximum execution time in seconds" + default=None, description=". Maximum execution time in seconds" ) respect_context_window: bool = Field( default=True, @@ -622,4 +622,4 @@ class LiteAgent(FlowTrackable, BaseModel): def _append_message(self, text: str, role: str = "assistant") -> None: """Append a message to the message list with the given role.""" - self._messages.append(format_message_for_llm(text, role=role)) \ No newline at end of file + self._messages.append(format_message_for_llm(text, role=role)) diff --git a/src/crewai/utilities/events/utils/console_formatter.py b/src/crewai/utilities/events/utils/console_formatter.py index 24f92e386..3088603bb 100644 --- a/src/crewai/utilities/events/utils/console_formatter.py +++ b/src/crewai/utilities/events/utils/console_formatter.py @@ -1387,6 +1387,7 @@ class ConsoleFormatter: theme="monokai", line_numbers=False, background_color="default", + word_wrap=True, ) content.append("\n") diff --git a/tests/cli/authentication/providers/__init__.py b/tests/cli/authentication/providers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/cli/authentication/providers/test_auth0.py b/tests/cli/authentication/providers/test_auth0.py new file mode 100644 index 000000000..e513a1fb7 --- /dev/null +++ b/tests/cli/authentication/providers/test_auth0.py @@ -0,0 +1,91 @@ +import pytest +from crewai.cli.authentication.main import Oauth2Settings +from crewai.cli.authentication.providers.auth0 import Auth0Provider + + + +class TestAuth0Provider: + + @pytest.fixture(autouse=True) + def setup_method(self): + self.valid_settings = Oauth2Settings( + provider="auth0", + domain="test-domain.auth0.com", + client_id="test-client-id", + audience="test-audience" + ) + self.provider = Auth0Provider(self.valid_settings) + + def test_initialization_with_valid_settings(self): + provider = Auth0Provider(self.valid_settings) + assert provider.settings == self.valid_settings + assert provider.settings.provider == "auth0" + assert provider.settings.domain == "test-domain.auth0.com" + assert provider.settings.client_id == "test-client-id" + assert provider.settings.audience == "test-audience" + + def test_get_authorize_url(self): + expected_url = "https://test-domain.auth0.com/oauth/device/code" + assert self.provider.get_authorize_url() == expected_url + + def test_get_authorize_url_with_different_domain(self): + settings = Oauth2Settings( + provider="auth0", + domain="my-company.auth0.com", + client_id="test-client", + audience="test-audience" + ) + provider = Auth0Provider(settings) + expected_url = "https://my-company.auth0.com/oauth/device/code" + assert provider.get_authorize_url() == expected_url + + def test_get_token_url(self): + expected_url = "https://test-domain.auth0.com/oauth/token" + assert self.provider.get_token_url() == expected_url + + def test_get_token_url_with_different_domain(self): + settings = Oauth2Settings( + provider="auth0", + domain="another-domain.auth0.com", + client_id="test-client", + audience="test-audience" + ) + provider = Auth0Provider(settings) + expected_url = "https://another-domain.auth0.com/oauth/token" + assert provider.get_token_url() == expected_url + + def test_get_jwks_url(self): + expected_url = "https://test-domain.auth0.com/.well-known/jwks.json" + assert self.provider.get_jwks_url() == expected_url + + def test_get_jwks_url_with_different_domain(self): + settings = Oauth2Settings( + provider="auth0", + domain="dev.auth0.com", + client_id="test-client", + audience="test-audience" + ) + provider = Auth0Provider(settings) + expected_url = "https://dev.auth0.com/.well-known/jwks.json" + assert provider.get_jwks_url() == expected_url + + def test_get_issuer(self): + expected_issuer = "https://test-domain.auth0.com/" + assert self.provider.get_issuer() == expected_issuer + + def test_get_issuer_with_different_domain(self): + settings = Oauth2Settings( + provider="auth0", + domain="prod.auth0.com", + client_id="test-client", + audience="test-audience" + ) + provider = Auth0Provider(settings) + expected_issuer = "https://prod.auth0.com/" + assert provider.get_issuer() == expected_issuer + + def test_get_audience(self): + assert self.provider.get_audience() == "test-audience" + + def test_get_client_id(self): + assert self.provider.get_client_id() == "test-client-id" diff --git a/tests/cli/authentication/providers/test_okta.py b/tests/cli/authentication/providers/test_okta.py new file mode 100644 index 000000000..b952464ba --- /dev/null +++ b/tests/cli/authentication/providers/test_okta.py @@ -0,0 +1,102 @@ +import pytest +from crewai.cli.authentication.main import Oauth2Settings +from crewai.cli.authentication.providers.okta import OktaProvider + + +class TestOktaProvider: + + @pytest.fixture(autouse=True) + def setup_method(self): + self.valid_settings = Oauth2Settings( + provider="okta", + domain="test-domain.okta.com", + client_id="test-client-id", + audience="test-audience" + ) + self.provider = OktaProvider(self.valid_settings) + + def test_initialization_with_valid_settings(self): + provider = OktaProvider(self.valid_settings) + assert provider.settings == self.valid_settings + assert provider.settings.provider == "okta" + assert provider.settings.domain == "test-domain.okta.com" + assert provider.settings.client_id == "test-client-id" + assert provider.settings.audience == "test-audience" + + def test_get_authorize_url(self): + expected_url = "https://test-domain.okta.com/oauth2/default/v1/device/authorize" + assert self.provider.get_authorize_url() == expected_url + + def test_get_authorize_url_with_different_domain(self): + settings = Oauth2Settings( + provider="okta", + domain="my-company.okta.com", + client_id="test-client", + audience="test-audience" + ) + provider = OktaProvider(settings) + expected_url = "https://my-company.okta.com/oauth2/default/v1/device/authorize" + assert provider.get_authorize_url() == expected_url + + def test_get_token_url(self): + expected_url = "https://test-domain.okta.com/oauth2/default/v1/token" + assert self.provider.get_token_url() == expected_url + + def test_get_token_url_with_different_domain(self): + settings = Oauth2Settings( + provider="okta", + domain="another-domain.okta.com", + client_id="test-client", + audience="test-audience" + ) + provider = OktaProvider(settings) + expected_url = "https://another-domain.okta.com/oauth2/default/v1/token" + assert provider.get_token_url() == expected_url + + def test_get_jwks_url(self): + expected_url = "https://test-domain.okta.com/oauth2/default/v1/keys" + assert self.provider.get_jwks_url() == expected_url + + def test_get_jwks_url_with_different_domain(self): + settings = Oauth2Settings( + provider="okta", + domain="dev.okta.com", + client_id="test-client", + audience="test-audience" + ) + provider = OktaProvider(settings) + expected_url = "https://dev.okta.com/oauth2/default/v1/keys" + assert provider.get_jwks_url() == expected_url + + def test_get_issuer(self): + expected_issuer = "https://test-domain.okta.com/oauth2/default" + assert self.provider.get_issuer() == expected_issuer + + def test_get_issuer_with_different_domain(self): + settings = Oauth2Settings( + provider="okta", + domain="prod.okta.com", + client_id="test-client", + audience="test-audience" + ) + provider = OktaProvider(settings) + expected_issuer = "https://prod.okta.com/oauth2/default" + assert provider.get_issuer() == expected_issuer + + def test_get_audience(self): + assert self.provider.get_audience() == "test-audience" + + def test_get_audience_assertion_error_when_none(self): + settings = Oauth2Settings( + provider="okta", + domain="test-domain.okta.com", + client_id="test-client-id", + audience=None + ) + provider = OktaProvider(settings) + + with pytest.raises(AssertionError): + provider.get_audience() + + def test_get_client_id(self): + assert self.provider.get_client_id() == "test-client-id" diff --git a/tests/cli/authentication/providers/test_workos.py b/tests/cli/authentication/providers/test_workos.py new file mode 100644 index 000000000..7eda774d6 --- /dev/null +++ b/tests/cli/authentication/providers/test_workos.py @@ -0,0 +1,100 @@ +import pytest +from crewai.cli.authentication.main import Oauth2Settings +from crewai.cli.authentication.providers.workos import WorkosProvider + + +class TestWorkosProvider: + + @pytest.fixture(autouse=True) + def setup_method(self): + self.valid_settings = Oauth2Settings( + provider="workos", + domain="login.company.com", + client_id="test-client-id", + audience="test-audience" + ) + self.provider = WorkosProvider(self.valid_settings) + + def test_initialization_with_valid_settings(self): + provider = WorkosProvider(self.valid_settings) + assert provider.settings == self.valid_settings + assert provider.settings.provider == "workos" + assert provider.settings.domain == "login.company.com" + assert provider.settings.client_id == "test-client-id" + assert provider.settings.audience == "test-audience" + + def test_get_authorize_url(self): + expected_url = "https://login.company.com/oauth2/device_authorization" + assert self.provider.get_authorize_url() == expected_url + + def test_get_authorize_url_with_different_domain(self): + settings = Oauth2Settings( + provider="workos", + domain="login.example.com", + client_id="test-client", + audience="test-audience" + ) + provider = WorkosProvider(settings) + expected_url = "https://login.example.com/oauth2/device_authorization" + assert provider.get_authorize_url() == expected_url + + def test_get_token_url(self): + expected_url = "https://login.company.com/oauth2/token" + assert self.provider.get_token_url() == expected_url + + def test_get_token_url_with_different_domain(self): + settings = Oauth2Settings( + provider="workos", + domain="api.workos.com", + client_id="test-client", + audience="test-audience" + ) + provider = WorkosProvider(settings) + expected_url = "https://api.workos.com/oauth2/token" + assert provider.get_token_url() == expected_url + + def test_get_jwks_url(self): + expected_url = "https://login.company.com/oauth2/jwks" + assert self.provider.get_jwks_url() == expected_url + + def test_get_jwks_url_with_different_domain(self): + settings = Oauth2Settings( + provider="workos", + domain="auth.enterprise.com", + client_id="test-client", + audience="test-audience" + ) + provider = WorkosProvider(settings) + expected_url = "https://auth.enterprise.com/oauth2/jwks" + assert provider.get_jwks_url() == expected_url + + def test_get_issuer(self): + expected_issuer = "https://login.company.com" + assert self.provider.get_issuer() == expected_issuer + + def test_get_issuer_with_different_domain(self): + settings = Oauth2Settings( + provider="workos", + domain="sso.company.com", + client_id="test-client", + audience="test-audience" + ) + provider = WorkosProvider(settings) + expected_issuer = "https://sso.company.com" + assert provider.get_issuer() == expected_issuer + + def test_get_audience(self): + assert self.provider.get_audience() == "test-audience" + + def test_get_audience_fallback_to_default(self): + settings = Oauth2Settings( + provider="workos", + domain="login.company.com", + client_id="test-client-id", + audience=None + ) + provider = WorkosProvider(settings) + assert provider.get_audience() == "" + + def test_get_client_id(self): + assert self.provider.get_client_id() == "test-client-id" diff --git a/tests/cli/authentication/test_auth_main.py b/tests/cli/authentication/test_auth_main.py index 61511b5a1..d608c9ba4 100644 --- a/tests/cli/authentication/test_auth_main.py +++ b/tests/cli/authentication/test_auth_main.py @@ -6,10 +6,12 @@ from crewai.cli.authentication.main import AuthenticationCommand from crewai.cli.authentication.constants import ( AUTH0_AUDIENCE, AUTH0_CLIENT_ID, - AUTH0_DOMAIN, - WORKOS_DOMAIN, - WORKOS_CLI_CONNECT_APP_ID, - WORKOS_ENVIRONMENT_ID, + AUTH0_DOMAIN +) +from crewai.cli.constants import ( + CREWAI_ENTERPRISE_DEFAULT_OAUTH2_DOMAIN, + CREWAI_ENTERPRISE_DEFAULT_OAUTH2_CLIENT_ID, + CREWAI_ENTERPRISE_DEFAULT_OAUTH2_AUDIENCE, ) @@ -27,14 +29,17 @@ class TestAuthenticationCommand: "token_url": f"https://{AUTH0_DOMAIN}/oauth/token", "client_id": AUTH0_CLIENT_ID, "audience": AUTH0_AUDIENCE, + "domain": AUTH0_DOMAIN, }, ), ( "workos", { - "device_code_url": f"https://{WORKOS_DOMAIN}/oauth2/device_authorization", - "token_url": f"https://{WORKOS_DOMAIN}/oauth2/token", - "client_id": WORKOS_CLI_CONNECT_APP_ID, + "device_code_url": f"https://{CREWAI_ENTERPRISE_DEFAULT_OAUTH2_DOMAIN}/oauth2/device_authorization", + "token_url": f"https://{CREWAI_ENTERPRISE_DEFAULT_OAUTH2_DOMAIN}/oauth2/token", + "client_id": CREWAI_ENTERPRISE_DEFAULT_OAUTH2_CLIENT_ID, + "audience": CREWAI_ENTERPRISE_DEFAULT_OAUTH2_AUDIENCE, + "domain": CREWAI_ENTERPRISE_DEFAULT_OAUTH2_DOMAIN, }, ), ], @@ -70,19 +75,16 @@ class TestAuthenticationCommand: "Signing in to CrewAI Enterprise...\n", style="bold blue" ) mock_determine_provider.assert_called_once() - mock_get_device.assert_called_once_with( - expected_urls["client_id"], - expected_urls["device_code_url"], - expected_urls.get("audience", None), - ) + mock_get_device.assert_called_once() mock_display.assert_called_once_with( {"device_code": "test_code", "user_code": "123456"} ) mock_poll.assert_called_once_with( {"device_code": "test_code", "user_code": "123456"}, - expected_urls["client_id"], - expected_urls["token_url"], ) + assert self.auth_command.oauth2_provider.get_client_id() == expected_urls["client_id"] + assert self.auth_command.oauth2_provider.get_audience() == expected_urls["audience"] + assert self.auth_command.oauth2_provider._get_domain() == expected_urls["domain"] @patch("crewai.cli.authentication.main.webbrowser") @patch("crewai.cli.authentication.main.console.print") @@ -115,9 +117,9 @@ class TestAuthenticationCommand: ( "workos", { - "jwks_url": f"https://{WORKOS_DOMAIN}/oauth2/jwks", - "issuer": f"https://{WORKOS_DOMAIN}", - "audience": WORKOS_ENVIRONMENT_ID, + "jwks_url": f"https://{CREWAI_ENTERPRISE_DEFAULT_OAUTH2_DOMAIN}/oauth2/jwks", + "issuer": f"https://{CREWAI_ENTERPRISE_DEFAULT_OAUTH2_DOMAIN}", + "audience": CREWAI_ENTERPRISE_DEFAULT_OAUTH2_AUDIENCE, }, ), ], @@ -133,7 +135,15 @@ class TestAuthenticationCommand: jwt_config, has_expiration, ): - self.auth_command.user_provider = user_provider + from crewai.cli.authentication.providers.auth0 import Auth0Provider + from crewai.cli.authentication.providers.workos import WorkosProvider + from crewai.cli.authentication.main import Oauth2Settings + + if user_provider == "auth0": + self.auth_command.oauth2_provider = Auth0Provider(settings=Oauth2Settings(provider=user_provider, client_id="test-client-id", domain=AUTH0_DOMAIN, audience=jwt_config["audience"])) + elif user_provider == "workos": + self.auth_command.oauth2_provider = WorkosProvider(settings=Oauth2Settings(provider=user_provider, client_id="test-client-id", domain=CREWAI_ENTERPRISE_DEFAULT_OAUTH2_DOMAIN, audience=jwt_config["audience"])) + token_data = {"access_token": "test_access_token", "id_token": "test_id_token"} if has_expiration: @@ -311,11 +321,12 @@ class TestAuthenticationCommand: } mock_post.return_value = mock_response - result = self.auth_command._get_device_code( - client_id="test_client", - device_code_url="https://example.com/device", - audience="test_audience", - ) + self.auth_command.oauth2_provider = MagicMock() + self.auth_command.oauth2_provider.get_client_id.return_value = "test_client" + self.auth_command.oauth2_provider.get_authorize_url.return_value = "https://example.com/device" + self.auth_command.oauth2_provider.get_audience.return_value = "test_audience" + + result = self.auth_command._get_device_code() mock_post.assert_called_once_with( url="https://example.com/device", @@ -354,8 +365,12 @@ class TestAuthenticationCommand: self.auth_command, "_login_to_tool_repository" ) as mock_tool_login, ): + self.auth_command.oauth2_provider = MagicMock() + self.auth_command.oauth2_provider.get_token_url.return_value = "https://example.com/token" + self.auth_command.oauth2_provider.get_client_id.return_value = "test_client" + self.auth_command._poll_for_token( - device_code_data, "test_client", "https://example.com/token" + device_code_data ) mock_post.assert_called_once_with( @@ -392,7 +407,7 @@ class TestAuthenticationCommand: } self.auth_command._poll_for_token( - device_code_data, "test_client", "https://example.com/token" + device_code_data ) mock_console_print.assert_any_call( @@ -415,5 +430,14 @@ class TestAuthenticationCommand: with pytest.raises(requests.HTTPError): self.auth_command._poll_for_token( - device_code_data, "test_client", "https://example.com/token" + device_code_data ) + # @patch( + # "crewai.cli.authentication.main.AuthenticationCommand._determine_user_provider" + # ) + # def test_login_with_auth0(self, mock_determine_provider): + # from crewai.cli.authentication.providers.auth0 import Auth0Provider + # from crewai.cli.authentication.main import Oauth2Settings + + # self.auth_command.oauth2_provider = Auth0Provider(settings=Oauth2Settings(provider="auth0", client_id=AUTH0_CLIENT_ID, domain=AUTH0_DOMAIN, audience=AUTH0_AUDIENCE)) + # self.auth_command.login() diff --git a/tests/cli/config_test.py b/tests/cli/config_test.py index 06cbfcf2c..a492da54a 100644 --- a/tests/cli/config_test.py +++ b/tests/cli/config_test.py @@ -79,7 +79,7 @@ class TestSettings(unittest.TestCase): for key in user_settings.keys(): self.assertEqual(getattr(settings, key), None) for key in cli_settings.keys(): - self.assertEqual(getattr(settings, key), DEFAULT_CLI_SETTINGS[key]) + self.assertEqual(getattr(settings, key), DEFAULT_CLI_SETTINGS.get(key)) def test_dump_new_settings(self): settings = Settings( diff --git a/tests/cli/test_settings_command.py b/tests/cli/test_settings_command.py index 71d016a52..f15deb821 100644 --- a/tests/cli/test_settings_command.py +++ b/tests/cli/test_settings_command.py @@ -81,11 +81,10 @@ class TestSettingsCommand(unittest.TestCase): self.settings_command.reset_all_settings() - print(USER_SETTINGS_KEYS) for key in USER_SETTINGS_KEYS: self.assertEqual(getattr(self.settings_command.settings, key), None) for key in CLI_SETTINGS_KEYS: self.assertEqual( - getattr(self.settings_command.settings, key), DEFAULT_CLI_SETTINGS[key] + getattr(self.settings_command.settings, key), DEFAULT_CLI_SETTINGS.get(key) ) diff --git a/tests/crew_test.py b/tests/crew_test.py index f91528b4a..eb45d1e37 100644 --- a/tests/crew_test.py +++ b/tests/crew_test.py @@ -4756,3 +4756,13 @@ def test_reset_agent_knowledge_with_only_agent_knowledge(researcher, writer): mock_reset_agent_knowledge.assert_called_once_with( [mock_ks_research, mock_ks_writer] ) + +def test_default_crew_name(researcher, writer): + crew = Crew( + agents=[researcher, writer], + tasks=[ + Task(description="Task 1", expected_output="output", agent=researcher), + Task(description="Task 2", expected_output="output", agent=writer), + ], + ) + assert crew.name == "crew"