mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-30 18:48:14 +00:00
Compare commits
9 Commits
0.152.0
...
devin/1754
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78750edd1f | ||
|
|
d20f52367f | ||
|
|
75752479c2 | ||
|
|
477bc1f09e | ||
|
|
66567bdc2f | ||
|
|
0b31bbe957 | ||
|
|
246cf588cd | ||
|
|
d590210a61 | ||
|
|
88ed91561f |
@@ -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
|
||||
</Note>
|
||||
|
||||
<Note>
|
||||
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.
|
||||
</Note>
|
||||
|
||||
@@ -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 <key> <value>
|
||||
```
|
||||
|
||||
- `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
|
||||
```
|
||||
|
||||
<Note>
|
||||
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.
|
||||
</Note>
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -79,11 +79,11 @@ This feature is useful for debugging and understanding how agents interact with
|
||||
|
||||
### Crew + AgentOps Examples
|
||||
|
||||
<CardGroup cols={3}>
|
||||
<CardGroup cols={2}>
|
||||
<Card
|
||||
title="Job Posting"
|
||||
color="#F3A78B"
|
||||
href="https://github.com/joaomdmoura/crewAI-examples/tree/main/job-posting"
|
||||
href="https://github.com/AgentOps-AI/agentops/blob/main/examples/crewai/job_posting.py"
|
||||
icon="briefcase"
|
||||
iconType="solid"
|
||||
>
|
||||
@@ -92,21 +92,12 @@ This feature is useful for debugging and understanding how agents interact with
|
||||
<Card
|
||||
title="Markdown Validator"
|
||||
color="#F3A78B"
|
||||
href="https://github.com/joaomdmoura/crewAI-examples/tree/main/markdown_validator"
|
||||
href="https://github.com/AgentOps-AI/agentops/blob/main/examples/crewai/markdown_validator.py"
|
||||
icon="markdown"
|
||||
iconType="solid"
|
||||
>
|
||||
Example of a Crew agent that validates Markdown files.
|
||||
</Card>
|
||||
<Card
|
||||
title="Instagram Post"
|
||||
color="#F3A78B"
|
||||
href="https://github.com/joaomdmoura/crewAI-examples/tree/main/instagram_post"
|
||||
icon="square-instagram"
|
||||
iconType="brands"
|
||||
>
|
||||
Example of a Crew agent that generates Instagram posts.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
### Further Information
|
||||
|
||||
@@ -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 <chave> <valor>
|
||||
```
|
||||
|
||||
- `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
|
||||
```
|
||||
|
||||
<Note>
|
||||
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.
|
||||
</Note>
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -79,11 +79,11 @@ Esse recurso é útil para depuração e entendimento de como os agentes interag
|
||||
|
||||
### Exemplos de Crew + AgentOps
|
||||
|
||||
<CardGroup cols={3}>
|
||||
<CardGroup cols={2}>
|
||||
<Card
|
||||
title="Vaga de Emprego"
|
||||
color="#F3A78B"
|
||||
href="https://github.com/joaomdmoura/crewAI-examples/tree/main/job-posting"
|
||||
href="https://github.com/AgentOps-AI/agentops/blob/main/examples/crewai/job_posting.py"
|
||||
icon="briefcase"
|
||||
iconType="solid"
|
||||
>
|
||||
@@ -92,21 +92,12 @@ Esse recurso é útil para depuração e entendimento de como os agentes interag
|
||||
<Card
|
||||
title="Validador de Markdown"
|
||||
color="#F3A78B"
|
||||
href="https://github.com/joaomdmoura/crewAI-examples/tree/main/markdown_validator"
|
||||
href="https://github.com/AgentOps-AI/agentops/blob/main/examples/crewai/markdown_validator.py"
|
||||
icon="markdown"
|
||||
iconType="solid"
|
||||
>
|
||||
Exemplo de um agente Crew que valida arquivos Markdown.
|
||||
</Card>
|
||||
<Card
|
||||
title="Post no Instagram"
|
||||
color="#F3A78B"
|
||||
href="https://github.com/joaomdmoura/crewAI-examples/tree/main/instagram_post"
|
||||
icon="square-instagram"
|
||||
iconType="brands"
|
||||
>
|
||||
Exemplo de um agente Crew que gera posts para Instagram.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
### Mais Informações
|
||||
@@ -123,4 +114,4 @@ Para sugestões de funcionalidades ou relatos de bugs, entre em contato com o ti
|
||||
<span> • </span>
|
||||
<a href="https://app.agentops.ai/?=crew">🖇️ Dashboard AgentOps</a>
|
||||
<span> • </span>
|
||||
<a href="https://docs.agentops.ai/introduction">📙 Documentação</a>
|
||||
<a href="https://docs.agentops.ai/introduction">📙 Documentação</a>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
26
src/crewai/cli/authentication/providers/auth0.py
Normal file
26
src/crewai/cli/authentication/providers/auth0.py
Normal file
@@ -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
|
||||
30
src/crewai/cli/authentication/providers/base_provider.py
Normal file
30
src/crewai/cli/authentication/providers/base_provider.py
Normal file
@@ -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:
|
||||
...
|
||||
22
src/crewai/cli/authentication/providers/okta.py
Normal file
22
src/crewai/cli/authentication/providers/okta.py
Normal file
@@ -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
|
||||
25
src/crewai/cli/authentication/providers/workos.py
Normal file
25
src/crewai/cli/authentication/providers/workos.py
Normal file
@@ -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
|
||||
@@ -3,6 +3,7 @@ from typing import Optional
|
||||
|
||||
import click
|
||||
from crewai.cli.config import Settings
|
||||
from crewai.cli.settings.main import SettingsCommand
|
||||
from crewai.cli.add_crew_to_flow import add_crew_to_flow
|
||||
from crewai.cli.create_crew import create_crew
|
||||
from crewai.cli.create_flow import create_flow
|
||||
@@ -227,7 +228,7 @@ def update():
|
||||
@crewai.command()
|
||||
def login():
|
||||
"""Sign Up/Login to CrewAI Enterprise."""
|
||||
Settings().clear()
|
||||
Settings().clear_user_settings()
|
||||
AuthenticationCommand().login()
|
||||
|
||||
|
||||
@@ -369,8 +370,8 @@ def org():
|
||||
pass
|
||||
|
||||
|
||||
@org.command()
|
||||
def list():
|
||||
@org.command("list")
|
||||
def org_list():
|
||||
"""List available organizations."""
|
||||
org_command = OrganizationCommand()
|
||||
org_command.list()
|
||||
@@ -391,5 +392,34 @@ def current():
|
||||
org_command.current()
|
||||
|
||||
|
||||
@crewai.group()
|
||||
def config():
|
||||
"""CLI Configuration commands."""
|
||||
pass
|
||||
|
||||
|
||||
@config.command("list")
|
||||
def config_list():
|
||||
"""List all CLI configuration parameters."""
|
||||
config_command = SettingsCommand()
|
||||
config_command.list()
|
||||
|
||||
|
||||
@config.command("set")
|
||||
@click.argument("key")
|
||||
@click.argument("value")
|
||||
def config_set(key: str, value: str):
|
||||
"""Set a CLI configuration parameter."""
|
||||
config_command = SettingsCommand()
|
||||
config_command.set(key, value)
|
||||
|
||||
|
||||
@config.command("reset")
|
||||
def config_reset():
|
||||
"""Reset all CLI configuration parameters to default values."""
|
||||
config_command = SettingsCommand()
|
||||
config_command.reset_all_settings()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
crewai()
|
||||
|
||||
@@ -4,10 +4,60 @@ from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
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"
|
||||
|
||||
# Settings that are related to the user's account
|
||||
USER_SETTINGS_KEYS = [
|
||||
"tool_repository_username",
|
||||
"tool_repository_password",
|
||||
"org_name",
|
||||
"org_uuid",
|
||||
]
|
||||
|
||||
# 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
|
||||
READONLY_SETTINGS_KEYS = [
|
||||
"org_name",
|
||||
"org_uuid",
|
||||
]
|
||||
|
||||
# Hidden settings - not displayed by the 'list' command and cannot be set by the user
|
||||
HIDDEN_SETTINGS_KEYS = [
|
||||
"config_path",
|
||||
"tool_repository_username",
|
||||
"tool_repository_password",
|
||||
]
|
||||
|
||||
class Settings(BaseModel):
|
||||
enterprise_base_url: Optional[str] = Field(
|
||||
default=DEFAULT_CLI_SETTINGS["enterprise_base_url"],
|
||||
description="Base URL of the CrewAI Enterprise instance",
|
||||
)
|
||||
tool_repository_username: Optional[str] = Field(
|
||||
None, description="Username for interacting with the Tool Repository"
|
||||
)
|
||||
@@ -20,7 +70,27 @@ class Settings(BaseModel):
|
||||
org_uuid: Optional[str] = Field(
|
||||
None, description="UUID of the currently active organization"
|
||||
)
|
||||
config_path: Path = Field(default=DEFAULT_CONFIG_PATH, exclude=True)
|
||||
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"""
|
||||
@@ -37,9 +107,16 @@ class Settings(BaseModel):
|
||||
merged_data = {**file_data, **data}
|
||||
super().__init__(config_path=config_path, **merged_data)
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Clear all settings"""
|
||||
self.config_path.unlink(missing_ok=True)
|
||||
def clear_user_settings(self) -> None:
|
||||
"""Clear all user settings"""
|
||||
self._reset_user_settings()
|
||||
self.dump()
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Reset all settings to default values"""
|
||||
self._reset_user_settings()
|
||||
self._reset_cli_settings()
|
||||
self.dump()
|
||||
|
||||
def dump(self) -> None:
|
||||
"""Save current settings to settings.json"""
|
||||
@@ -52,3 +129,13 @@ class Settings(BaseModel):
|
||||
updated_data = {**existing_data, **self.model_dump(exclude_unset=True)}
|
||||
with self.config_path.open("w") as f:
|
||||
json.dump(updated_data, f, indent=4)
|
||||
|
||||
def _reset_user_settings(self) -> None:
|
||||
"""Reset all user settings to default values"""
|
||||
for key in USER_SETTINGS_KEYS:
|
||||
setattr(self, key, None)
|
||||
|
||||
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.get(key))
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
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": [
|
||||
{
|
||||
@@ -320,5 +326,4 @@ DEFAULT_LLM_MODEL = "gpt-4o-mini"
|
||||
|
||||
JSON_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json"
|
||||
|
||||
|
||||
LITELLM_PARAMS = ["api_key", "api_base", "api_version"]
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
from os import getenv
|
||||
from typing import List, Optional
|
||||
from urllib.parse import urljoin
|
||||
|
||||
@@ -6,6 +5,7 @@ import requests
|
||||
|
||||
from crewai.cli.config import Settings
|
||||
from crewai.cli.version import get_crewai_version
|
||||
from crewai.cli.constants import DEFAULT_CREWAI_ENTERPRISE_URL
|
||||
|
||||
|
||||
class PlusAPI:
|
||||
@@ -29,7 +29,10 @@ class PlusAPI:
|
||||
settings = Settings()
|
||||
if settings.org_uuid:
|
||||
self.headers["X-Crewai-Organization-Id"] = settings.org_uuid
|
||||
self.base_url = getenv("CREWAI_BASE_URL", "https://app.crewai.com")
|
||||
|
||||
self.base_url = (
|
||||
str(settings.enterprise_base_url) or DEFAULT_CREWAI_ENTERPRISE_URL
|
||||
)
|
||||
|
||||
def _make_request(self, method: str, endpoint: str, **kwargs) -> requests.Response:
|
||||
url = urljoin(self.base_url, endpoint)
|
||||
@@ -108,7 +111,6 @@ class PlusAPI:
|
||||
|
||||
def create_crew(self, payload) -> requests.Response:
|
||||
return self._make_request("POST", self.CREWS_RESOURCE, json=payload)
|
||||
|
||||
|
||||
def get_organizations(self) -> requests.Response:
|
||||
return self._make_request("GET", self.ORGANIZATIONS_RESOURCE)
|
||||
|
||||
0
src/crewai/cli/settings/__init__.py
Normal file
0
src/crewai/cli/settings/__init__.py
Normal file
67
src/crewai/cli/settings/main.py
Normal file
67
src/crewai/cli/settings/main.py
Normal file
@@ -0,0 +1,67 @@
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
from crewai.cli.command import BaseCommand
|
||||
from crewai.cli.config import Settings, READONLY_SETTINGS_KEYS, HIDDEN_SETTINGS_KEYS
|
||||
from typing import Any
|
||||
|
||||
console = Console()
|
||||
|
||||
|
||||
class SettingsCommand(BaseCommand):
|
||||
"""A class to handle CLI configuration commands."""
|
||||
|
||||
def __init__(self, settings_kwargs: dict[str, Any] = {}):
|
||||
super().__init__()
|
||||
self.settings = Settings(**settings_kwargs)
|
||||
|
||||
def list(self) -> None:
|
||||
"""List all CLI configuration parameters."""
|
||||
table = Table(title="CrewAI CLI Configuration")
|
||||
table.add_column("Setting", style="cyan", no_wrap=True)
|
||||
table.add_column("Value", style="green")
|
||||
table.add_column("Description", style="yellow")
|
||||
|
||||
# Add all settings to the table
|
||||
for field_name, field_info in Settings.model_fields.items():
|
||||
if field_name in HIDDEN_SETTINGS_KEYS:
|
||||
# Do not display hidden settings
|
||||
continue
|
||||
|
||||
current_value = getattr(self.settings, field_name)
|
||||
description = field_info.description or "No description available"
|
||||
display_value = (
|
||||
str(current_value) if current_value is not None else "Not set"
|
||||
)
|
||||
|
||||
table.add_row(field_name, display_value, description)
|
||||
|
||||
console.print(table)
|
||||
|
||||
def set(self, key: str, value: str) -> None:
|
||||
"""Set a CLI configuration parameter."""
|
||||
|
||||
readonly_settings = READONLY_SETTINGS_KEYS + HIDDEN_SETTINGS_KEYS
|
||||
|
||||
if not hasattr(self.settings, key) or key in readonly_settings:
|
||||
console.print(
|
||||
f"Error: Unknown or readonly configuration key '{key}'",
|
||||
style="bold red",
|
||||
)
|
||||
console.print("Available keys:", style="yellow")
|
||||
for field_name in Settings.model_fields.keys():
|
||||
if field_name not in readonly_settings:
|
||||
console.print(f" - {field_name}", style="yellow")
|
||||
raise SystemExit(1)
|
||||
|
||||
setattr(self.settings, key, value)
|
||||
self.settings.dump()
|
||||
|
||||
console.print(f"Successfully set '{key}' to '{value}'", style="bold green")
|
||||
|
||||
def reset_all_settings(self) -> None:
|
||||
"""Reset all CLI configuration parameters to default values."""
|
||||
self.settings.reset()
|
||||
console.print(
|
||||
"Successfully reset all configuration parameters to default values. It is recommended to run [bold yellow]'crewai login'[/bold yellow] to re-authenticate.",
|
||||
style="bold green",
|
||||
)
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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))
|
||||
self._messages.append(format_message_for_llm(text, role=role))
|
||||
|
||||
@@ -1387,6 +1387,7 @@ class ConsoleFormatter:
|
||||
theme="monokai",
|
||||
line_numbers=False,
|
||||
background_color="default",
|
||||
word_wrap=True,
|
||||
)
|
||||
|
||||
content.append("\n")
|
||||
|
||||
0
tests/cli/authentication/providers/__init__.py
Normal file
0
tests/cli/authentication/providers/__init__.py
Normal file
91
tests/cli/authentication/providers/test_auth0.py
Normal file
91
tests/cli/authentication/providers/test_auth0.py
Normal file
@@ -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"
|
||||
102
tests/cli/authentication/providers/test_okta.py
Normal file
102
tests/cli/authentication/providers/test_okta.py
Normal file
@@ -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"
|
||||
100
tests/cli/authentication/providers/test_workos.py
Normal file
100
tests/cli/authentication/providers/test_workos.py
Normal file
@@ -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"
|
||||
@@ -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()
|
||||
|
||||
@@ -4,7 +4,12 @@ import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from crewai.cli.config import Settings
|
||||
from crewai.cli.config import (
|
||||
Settings,
|
||||
USER_SETTINGS_KEYS,
|
||||
CLI_SETTINGS_KEYS,
|
||||
DEFAULT_CLI_SETTINGS,
|
||||
)
|
||||
|
||||
|
||||
class TestSettings(unittest.TestCase):
|
||||
@@ -52,6 +57,30 @@ class TestSettings(unittest.TestCase):
|
||||
self.assertEqual(settings.tool_repository_username, "new_user")
|
||||
self.assertEqual(settings.tool_repository_password, "file_pass")
|
||||
|
||||
def test_clear_user_settings(self):
|
||||
user_settings = {key: f"value_for_{key}" for key in USER_SETTINGS_KEYS}
|
||||
|
||||
settings = Settings(config_path=self.config_path, **user_settings)
|
||||
settings.clear_user_settings()
|
||||
|
||||
for key in user_settings.keys():
|
||||
self.assertEqual(getattr(settings, key), None)
|
||||
|
||||
def test_reset_settings(self):
|
||||
user_settings = {key: f"value_for_{key}" for key in USER_SETTINGS_KEYS}
|
||||
cli_settings = {key: f"value_for_{key}" for key in CLI_SETTINGS_KEYS}
|
||||
|
||||
settings = Settings(
|
||||
config_path=self.config_path, **user_settings, **cli_settings
|
||||
)
|
||||
|
||||
settings.reset()
|
||||
|
||||
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.get(key))
|
||||
|
||||
def test_dump_new_settings(self):
|
||||
settings = Settings(
|
||||
config_path=self.config_path, tool_repository_username="user1"
|
||||
|
||||
@@ -6,7 +6,7 @@ from click.testing import CliRunner
|
||||
import requests
|
||||
|
||||
from crewai.cli.organization.main import OrganizationCommand
|
||||
from crewai.cli.cli import list, switch, current
|
||||
from crewai.cli.cli import org_list, switch, current
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -16,44 +16,44 @@ def runner():
|
||||
|
||||
@pytest.fixture
|
||||
def org_command():
|
||||
with patch.object(OrganizationCommand, '__init__', return_value=None):
|
||||
with patch.object(OrganizationCommand, "__init__", return_value=None):
|
||||
command = OrganizationCommand()
|
||||
yield command
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_settings():
|
||||
with patch('crewai.cli.organization.main.Settings') as mock_settings_class:
|
||||
with patch("crewai.cli.organization.main.Settings") as mock_settings_class:
|
||||
mock_settings_instance = MagicMock()
|
||||
mock_settings_class.return_value = mock_settings_instance
|
||||
yield mock_settings_instance
|
||||
|
||||
|
||||
@patch('crewai.cli.cli.OrganizationCommand')
|
||||
@patch("crewai.cli.cli.OrganizationCommand")
|
||||
def test_org_list_command(mock_org_command_class, runner):
|
||||
mock_org_instance = MagicMock()
|
||||
mock_org_command_class.return_value = mock_org_instance
|
||||
|
||||
result = runner.invoke(list)
|
||||
result = runner.invoke(org_list)
|
||||
|
||||
assert result.exit_code == 0
|
||||
mock_org_command_class.assert_called_once()
|
||||
mock_org_instance.list.assert_called_once()
|
||||
|
||||
|
||||
@patch('crewai.cli.cli.OrganizationCommand')
|
||||
@patch("crewai.cli.cli.OrganizationCommand")
|
||||
def test_org_switch_command(mock_org_command_class, runner):
|
||||
mock_org_instance = MagicMock()
|
||||
mock_org_command_class.return_value = mock_org_instance
|
||||
|
||||
result = runner.invoke(switch, ['test-id'])
|
||||
result = runner.invoke(switch, ["test-id"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
mock_org_command_class.assert_called_once()
|
||||
mock_org_instance.switch.assert_called_once_with('test-id')
|
||||
mock_org_instance.switch.assert_called_once_with("test-id")
|
||||
|
||||
|
||||
@patch('crewai.cli.cli.OrganizationCommand')
|
||||
@patch("crewai.cli.cli.OrganizationCommand")
|
||||
def test_org_current_command(mock_org_command_class, runner):
|
||||
mock_org_instance = MagicMock()
|
||||
mock_org_command_class.return_value = mock_org_instance
|
||||
@@ -67,18 +67,18 @@ def test_org_current_command(mock_org_command_class, runner):
|
||||
|
||||
class TestOrganizationCommand(unittest.TestCase):
|
||||
def setUp(self):
|
||||
with patch.object(OrganizationCommand, '__init__', return_value=None):
|
||||
with patch.object(OrganizationCommand, "__init__", return_value=None):
|
||||
self.org_command = OrganizationCommand()
|
||||
self.org_command.plus_api_client = MagicMock()
|
||||
|
||||
@patch('crewai.cli.organization.main.console')
|
||||
@patch('crewai.cli.organization.main.Table')
|
||||
@patch("crewai.cli.organization.main.console")
|
||||
@patch("crewai.cli.organization.main.Table")
|
||||
def test_list_organizations_success(self, mock_table, mock_console):
|
||||
mock_response = MagicMock()
|
||||
mock_response.raise_for_status = MagicMock()
|
||||
mock_response.json.return_value = [
|
||||
{"name": "Org 1", "uuid": "org-123"},
|
||||
{"name": "Org 2", "uuid": "org-456"}
|
||||
{"name": "Org 2", "uuid": "org-456"},
|
||||
]
|
||||
self.org_command.plus_api_client = MagicMock()
|
||||
self.org_command.plus_api_client.get_organizations.return_value = mock_response
|
||||
@@ -89,16 +89,14 @@ class TestOrganizationCommand(unittest.TestCase):
|
||||
|
||||
self.org_command.plus_api_client.get_organizations.assert_called_once()
|
||||
mock_table.assert_called_once_with(title="Your Organizations")
|
||||
mock_table.return_value.add_column.assert_has_calls([
|
||||
call("Name", style="cyan"),
|
||||
call("ID", style="green")
|
||||
])
|
||||
mock_table.return_value.add_row.assert_has_calls([
|
||||
call("Org 1", "org-123"),
|
||||
call("Org 2", "org-456")
|
||||
])
|
||||
mock_table.return_value.add_column.assert_has_calls(
|
||||
[call("Name", style="cyan"), call("ID", style="green")]
|
||||
)
|
||||
mock_table.return_value.add_row.assert_has_calls(
|
||||
[call("Org 1", "org-123"), call("Org 2", "org-456")]
|
||||
)
|
||||
|
||||
@patch('crewai.cli.organization.main.console')
|
||||
@patch("crewai.cli.organization.main.console")
|
||||
def test_list_organizations_empty(self, mock_console):
|
||||
mock_response = MagicMock()
|
||||
mock_response.raise_for_status = MagicMock()
|
||||
@@ -110,33 +108,32 @@ class TestOrganizationCommand(unittest.TestCase):
|
||||
|
||||
self.org_command.plus_api_client.get_organizations.assert_called_once()
|
||||
mock_console.print.assert_called_once_with(
|
||||
"You don't belong to any organizations yet.",
|
||||
style="yellow"
|
||||
"You don't belong to any organizations yet.", style="yellow"
|
||||
)
|
||||
|
||||
@patch('crewai.cli.organization.main.console')
|
||||
@patch("crewai.cli.organization.main.console")
|
||||
def test_list_organizations_api_error(self, mock_console):
|
||||
self.org_command.plus_api_client = MagicMock()
|
||||
self.org_command.plus_api_client.get_organizations.side_effect = requests.exceptions.RequestException("API Error")
|
||||
self.org_command.plus_api_client.get_organizations.side_effect = (
|
||||
requests.exceptions.RequestException("API Error")
|
||||
)
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
self.org_command.list()
|
||||
|
||||
|
||||
self.org_command.plus_api_client.get_organizations.assert_called_once()
|
||||
mock_console.print.assert_called_once_with(
|
||||
"Failed to retrieve organization list: API Error",
|
||||
style="bold red"
|
||||
"Failed to retrieve organization list: API Error", style="bold red"
|
||||
)
|
||||
|
||||
@patch('crewai.cli.organization.main.console')
|
||||
@patch('crewai.cli.organization.main.Settings')
|
||||
@patch("crewai.cli.organization.main.console")
|
||||
@patch("crewai.cli.organization.main.Settings")
|
||||
def test_switch_organization_success(self, mock_settings_class, mock_console):
|
||||
mock_response = MagicMock()
|
||||
mock_response.raise_for_status = MagicMock()
|
||||
mock_response.json.return_value = [
|
||||
{"name": "Org 1", "uuid": "org-123"},
|
||||
{"name": "Test Org", "uuid": "test-id"}
|
||||
{"name": "Test Org", "uuid": "test-id"},
|
||||
]
|
||||
self.org_command.plus_api_client = MagicMock()
|
||||
self.org_command.plus_api_client.get_organizations.return_value = mock_response
|
||||
@@ -151,17 +148,16 @@ class TestOrganizationCommand(unittest.TestCase):
|
||||
assert mock_settings_instance.org_name == "Test Org"
|
||||
assert mock_settings_instance.org_uuid == "test-id"
|
||||
mock_console.print.assert_called_once_with(
|
||||
"Successfully switched to Test Org (test-id)",
|
||||
style="bold green"
|
||||
"Successfully switched to Test Org (test-id)", style="bold green"
|
||||
)
|
||||
|
||||
@patch('crewai.cli.organization.main.console')
|
||||
@patch("crewai.cli.organization.main.console")
|
||||
def test_switch_organization_not_found(self, mock_console):
|
||||
mock_response = MagicMock()
|
||||
mock_response.raise_for_status = MagicMock()
|
||||
mock_response.json.return_value = [
|
||||
{"name": "Org 1", "uuid": "org-123"},
|
||||
{"name": "Org 2", "uuid": "org-456"}
|
||||
{"name": "Org 2", "uuid": "org-456"},
|
||||
]
|
||||
self.org_command.plus_api_client = MagicMock()
|
||||
self.org_command.plus_api_client.get_organizations.return_value = mock_response
|
||||
@@ -170,12 +166,11 @@ class TestOrganizationCommand(unittest.TestCase):
|
||||
|
||||
self.org_command.plus_api_client.get_organizations.assert_called_once()
|
||||
mock_console.print.assert_called_once_with(
|
||||
"Organization with id 'non-existent-id' not found.",
|
||||
style="bold red"
|
||||
"Organization with id 'non-existent-id' not found.", style="bold red"
|
||||
)
|
||||
|
||||
@patch('crewai.cli.organization.main.console')
|
||||
@patch('crewai.cli.organization.main.Settings')
|
||||
@patch("crewai.cli.organization.main.console")
|
||||
@patch("crewai.cli.organization.main.Settings")
|
||||
def test_current_organization_with_org(self, mock_settings_class, mock_console):
|
||||
mock_settings_instance = MagicMock()
|
||||
mock_settings_instance.org_name = "Test Org"
|
||||
@@ -186,12 +181,11 @@ class TestOrganizationCommand(unittest.TestCase):
|
||||
|
||||
self.org_command.plus_api_client.get_organizations.assert_not_called()
|
||||
mock_console.print.assert_called_once_with(
|
||||
"Currently logged in to organization Test Org (test-id)",
|
||||
style="bold green"
|
||||
"Currently logged in to organization Test Org (test-id)", style="bold green"
|
||||
)
|
||||
|
||||
@patch('crewai.cli.organization.main.console')
|
||||
@patch('crewai.cli.organization.main.Settings')
|
||||
@patch("crewai.cli.organization.main.console")
|
||||
@patch("crewai.cli.organization.main.Settings")
|
||||
def test_current_organization_without_org(self, mock_settings_class, mock_console):
|
||||
mock_settings_instance = MagicMock()
|
||||
mock_settings_instance.org_uuid = None
|
||||
@@ -201,16 +195,14 @@ class TestOrganizationCommand(unittest.TestCase):
|
||||
|
||||
assert mock_console.print.call_count == 3
|
||||
mock_console.print.assert_any_call(
|
||||
"You're not currently logged in to any organization.",
|
||||
style="yellow"
|
||||
"You're not currently logged in to any organization.", style="yellow"
|
||||
)
|
||||
|
||||
@patch('crewai.cli.organization.main.console')
|
||||
@patch("crewai.cli.organization.main.console")
|
||||
def test_list_organizations_unauthorized(self, mock_console):
|
||||
mock_response = MagicMock()
|
||||
mock_http_error = requests.exceptions.HTTPError(
|
||||
"401 Client Error: Unauthorized",
|
||||
response=MagicMock(status_code=401)
|
||||
"401 Client Error: Unauthorized", response=MagicMock(status_code=401)
|
||||
)
|
||||
|
||||
mock_response.raise_for_status.side_effect = mock_http_error
|
||||
@@ -221,15 +213,14 @@ class TestOrganizationCommand(unittest.TestCase):
|
||||
self.org_command.plus_api_client.get_organizations.assert_called_once()
|
||||
mock_console.print.assert_called_once_with(
|
||||
"You are not logged in to any organization. Use 'crewai login' to login.",
|
||||
style="bold red"
|
||||
style="bold red",
|
||||
)
|
||||
|
||||
@patch('crewai.cli.organization.main.console')
|
||||
@patch("crewai.cli.organization.main.console")
|
||||
def test_switch_organization_unauthorized(self, mock_console):
|
||||
mock_response = MagicMock()
|
||||
mock_http_error = requests.exceptions.HTTPError(
|
||||
"401 Client Error: Unauthorized",
|
||||
response=MagicMock(status_code=401)
|
||||
"401 Client Error: Unauthorized", response=MagicMock(status_code=401)
|
||||
)
|
||||
|
||||
mock_response.raise_for_status.side_effect = mock_http_error
|
||||
@@ -240,5 +231,5 @@ class TestOrganizationCommand(unittest.TestCase):
|
||||
self.org_command.plus_api_client.get_organizations.assert_called_once()
|
||||
mock_console.print.assert_called_once_with(
|
||||
"You are not logged in to any organization. Use 'crewai login' to login.",
|
||||
style="bold red"
|
||||
style="bold red",
|
||||
)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import os
|
||||
import unittest
|
||||
from unittest.mock import MagicMock, patch, ANY
|
||||
|
||||
from crewai.cli.plus_api import PlusAPI
|
||||
from crewai.cli.constants import DEFAULT_CREWAI_ENTERPRISE_URL
|
||||
|
||||
|
||||
class TestPlusAPI(unittest.TestCase):
|
||||
@@ -30,29 +30,41 @@ class TestPlusAPI(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual(response, mock_response)
|
||||
|
||||
def assert_request_with_org_id(self, mock_make_request, method: str, endpoint: str, **kwargs):
|
||||
def assert_request_with_org_id(
|
||||
self, mock_make_request, method: str, endpoint: str, **kwargs
|
||||
):
|
||||
mock_make_request.assert_called_once_with(
|
||||
method, f"https://app.crewai.com{endpoint}", headers={'Authorization': ANY, 'Content-Type': ANY, 'User-Agent': ANY, 'X-Crewai-Version': ANY, 'X-Crewai-Organization-Id': self.org_uuid}, **kwargs
|
||||
method,
|
||||
f"{DEFAULT_CREWAI_ENTERPRISE_URL}{endpoint}",
|
||||
headers={
|
||||
"Authorization": ANY,
|
||||
"Content-Type": ANY,
|
||||
"User-Agent": ANY,
|
||||
"X-Crewai-Version": ANY,
|
||||
"X-Crewai-Organization-Id": self.org_uuid,
|
||||
},
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@patch("crewai.cli.plus_api.Settings")
|
||||
@patch("requests.Session.request")
|
||||
def test_login_to_tool_repository_with_org_uuid(self, mock_make_request, mock_settings_class):
|
||||
def test_login_to_tool_repository_with_org_uuid(
|
||||
self, mock_make_request, mock_settings_class
|
||||
):
|
||||
mock_settings = MagicMock()
|
||||
mock_settings.org_uuid = self.org_uuid
|
||||
mock_settings.enterprise_base_url = DEFAULT_CREWAI_ENTERPRISE_URL
|
||||
mock_settings_class.return_value = mock_settings
|
||||
# re-initialize Client
|
||||
self.api = PlusAPI(self.api_key)
|
||||
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_make_request.return_value = mock_response
|
||||
|
||||
response = self.api.login_to_tool_repository()
|
||||
|
||||
self.assert_request_with_org_id(
|
||||
mock_make_request,
|
||||
'POST',
|
||||
'/crewai_plus/api/v1/tools/login'
|
||||
mock_make_request, "POST", "/crewai_plus/api/v1/tools/login"
|
||||
)
|
||||
self.assertEqual(response, mock_response)
|
||||
|
||||
@@ -66,28 +78,27 @@ class TestPlusAPI(unittest.TestCase):
|
||||
"GET", "/crewai_plus/api/v1/agents/test_agent_handle"
|
||||
)
|
||||
self.assertEqual(response, mock_response)
|
||||
|
||||
|
||||
@patch("crewai.cli.plus_api.Settings")
|
||||
@patch("requests.Session.request")
|
||||
def test_get_agent_with_org_uuid(self, mock_make_request, mock_settings_class):
|
||||
mock_settings = MagicMock()
|
||||
mock_settings.org_uuid = self.org_uuid
|
||||
mock_settings.enterprise_base_url = DEFAULT_CREWAI_ENTERPRISE_URL
|
||||
mock_settings_class.return_value = mock_settings
|
||||
# re-initialize Client
|
||||
self.api = PlusAPI(self.api_key)
|
||||
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_make_request.return_value = mock_response
|
||||
|
||||
response = self.api.get_agent("test_agent_handle")
|
||||
|
||||
self.assert_request_with_org_id(
|
||||
mock_make_request,
|
||||
"GET",
|
||||
"/crewai_plus/api/v1/agents/test_agent_handle"
|
||||
mock_make_request, "GET", "/crewai_plus/api/v1/agents/test_agent_handle"
|
||||
)
|
||||
self.assertEqual(response, mock_response)
|
||||
|
||||
|
||||
@patch("crewai.cli.plus_api.PlusAPI._make_request")
|
||||
def test_get_tool(self, mock_make_request):
|
||||
mock_response = MagicMock()
|
||||
@@ -98,12 +109,13 @@ class TestPlusAPI(unittest.TestCase):
|
||||
"GET", "/crewai_plus/api/v1/tools/test_tool_handle"
|
||||
)
|
||||
self.assertEqual(response, mock_response)
|
||||
|
||||
|
||||
@patch("crewai.cli.plus_api.Settings")
|
||||
@patch("requests.Session.request")
|
||||
def test_get_tool_with_org_uuid(self, mock_make_request, mock_settings_class):
|
||||
mock_settings = MagicMock()
|
||||
mock_settings.org_uuid = self.org_uuid
|
||||
mock_settings.enterprise_base_url = DEFAULT_CREWAI_ENTERPRISE_URL
|
||||
mock_settings_class.return_value = mock_settings
|
||||
# re-initialize Client
|
||||
self.api = PlusAPI(self.api_key)
|
||||
@@ -115,9 +127,7 @@ class TestPlusAPI(unittest.TestCase):
|
||||
response = self.api.get_tool("test_tool_handle")
|
||||
|
||||
self.assert_request_with_org_id(
|
||||
mock_make_request,
|
||||
"GET",
|
||||
"/crewai_plus/api/v1/tools/test_tool_handle"
|
||||
mock_make_request, "GET", "/crewai_plus/api/v1/tools/test_tool_handle"
|
||||
)
|
||||
self.assertEqual(response, mock_response)
|
||||
|
||||
@@ -147,12 +157,13 @@ class TestPlusAPI(unittest.TestCase):
|
||||
"POST", "/crewai_plus/api/v1/tools", json=params
|
||||
)
|
||||
self.assertEqual(response, mock_response)
|
||||
|
||||
|
||||
@patch("crewai.cli.plus_api.Settings")
|
||||
@patch("requests.Session.request")
|
||||
def test_publish_tool_with_org_uuid(self, mock_make_request, mock_settings_class):
|
||||
mock_settings = MagicMock()
|
||||
mock_settings.org_uuid = self.org_uuid
|
||||
mock_settings.enterprise_base_url = DEFAULT_CREWAI_ENTERPRISE_URL
|
||||
mock_settings_class.return_value = mock_settings
|
||||
# re-initialize Client
|
||||
self.api = PlusAPI(self.api_key)
|
||||
@@ -160,7 +171,7 @@ class TestPlusAPI(unittest.TestCase):
|
||||
# Set up mock response
|
||||
mock_response = MagicMock()
|
||||
mock_make_request.return_value = mock_response
|
||||
|
||||
|
||||
handle = "test_tool_handle"
|
||||
public = True
|
||||
version = "1.0.0"
|
||||
@@ -180,12 +191,9 @@ class TestPlusAPI(unittest.TestCase):
|
||||
"description": description,
|
||||
"available_exports": None,
|
||||
}
|
||||
|
||||
|
||||
self.assert_request_with_org_id(
|
||||
mock_make_request,
|
||||
"POST",
|
||||
"/crewai_plus/api/v1/tools",
|
||||
json=expected_params
|
||||
mock_make_request, "POST", "/crewai_plus/api/v1/tools", json=expected_params
|
||||
)
|
||||
self.assertEqual(response, mock_response)
|
||||
|
||||
@@ -311,8 +319,11 @@ class TestPlusAPI(unittest.TestCase):
|
||||
"POST", "/crewai_plus/api/v1/crews", json=payload
|
||||
)
|
||||
|
||||
@patch.dict(os.environ, {"CREWAI_BASE_URL": "https://custom-url.com/api"})
|
||||
def test_custom_base_url(self):
|
||||
@patch("crewai.cli.plus_api.Settings")
|
||||
def test_custom_base_url(self, mock_settings_class):
|
||||
mock_settings = MagicMock()
|
||||
mock_settings.enterprise_base_url = "https://custom-url.com/api"
|
||||
mock_settings_class.return_value = mock_settings
|
||||
custom_api = PlusAPI("test_key")
|
||||
self.assertEqual(
|
||||
custom_api.base_url,
|
||||
|
||||
90
tests/cli/test_settings_command.py
Normal file
90
tests/cli/test_settings_command.py
Normal file
@@ -0,0 +1,90 @@
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch, MagicMock, call
|
||||
|
||||
from crewai.cli.settings.main import SettingsCommand
|
||||
from crewai.cli.config import (
|
||||
Settings,
|
||||
USER_SETTINGS_KEYS,
|
||||
CLI_SETTINGS_KEYS,
|
||||
DEFAULT_CLI_SETTINGS,
|
||||
HIDDEN_SETTINGS_KEYS,
|
||||
READONLY_SETTINGS_KEYS,
|
||||
)
|
||||
import shutil
|
||||
|
||||
|
||||
class TestSettingsCommand(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.test_dir = Path(tempfile.mkdtemp())
|
||||
self.config_path = self.test_dir / "settings.json"
|
||||
self.settings = Settings(config_path=self.config_path)
|
||||
self.settings_command = SettingsCommand(
|
||||
settings_kwargs={"config_path": self.config_path}
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.test_dir)
|
||||
|
||||
@patch("crewai.cli.settings.main.console")
|
||||
@patch("crewai.cli.settings.main.Table")
|
||||
def test_list_settings(self, mock_table_class, mock_console):
|
||||
mock_table_instance = MagicMock()
|
||||
mock_table_class.return_value = mock_table_instance
|
||||
|
||||
self.settings_command.list()
|
||||
|
||||
# Tests that the table is created skipping hidden settings
|
||||
mock_table_instance.add_row.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
field_name,
|
||||
getattr(self.settings, field_name) or "Not set",
|
||||
field_info.description,
|
||||
)
|
||||
for field_name, field_info in Settings.model_fields.items()
|
||||
if field_name not in HIDDEN_SETTINGS_KEYS
|
||||
]
|
||||
)
|
||||
|
||||
# Tests that the table is printed
|
||||
mock_console.print.assert_called_once_with(mock_table_instance)
|
||||
|
||||
def test_set_valid_keys(self):
|
||||
valid_keys = Settings.model_fields.keys() - (
|
||||
READONLY_SETTINGS_KEYS + HIDDEN_SETTINGS_KEYS
|
||||
)
|
||||
for key in valid_keys:
|
||||
test_value = f"some_value_for_{key}"
|
||||
self.settings_command.set(key, test_value)
|
||||
self.assertEqual(getattr(self.settings_command.settings, key), test_value)
|
||||
|
||||
def test_set_invalid_key(self):
|
||||
with self.assertRaises(SystemExit):
|
||||
self.settings_command.set("invalid_key", "value")
|
||||
|
||||
def test_set_readonly_keys(self):
|
||||
for key in READONLY_SETTINGS_KEYS:
|
||||
with self.assertRaises(SystemExit):
|
||||
self.settings_command.set(key, "some_readonly_key_value")
|
||||
|
||||
def test_set_hidden_keys(self):
|
||||
for key in HIDDEN_SETTINGS_KEYS:
|
||||
with self.assertRaises(SystemExit):
|
||||
self.settings_command.set(key, "some_hidden_key_value")
|
||||
|
||||
def test_reset_all_settings(self):
|
||||
for key in USER_SETTINGS_KEYS + CLI_SETTINGS_KEYS:
|
||||
setattr(self.settings_command.settings, key, f"custom_value_for_{key}")
|
||||
self.settings_command.settings.dump()
|
||||
|
||||
self.settings_command.reset_all_settings()
|
||||
|
||||
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.get(key)
|
||||
)
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user