Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions api/oss/src/core/secrets/dtos.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ def validate_secret_data_based_on_kind(cls, values: Dict[str, Any]):
values["data"] = data

if kind == SecretKind.PROVIDER_KEY.value:
# Fix inconsistent API naming - normalize 'together_ai' to 'togetherai'
if data.get("kind", "") == "together_ai":
data["kind"] = "togetherai"

if not isinstance(data, dict):
raise ValueError(
"The provided request secret dto is not a valid type for StandardProviderDTO"
Comment on lines +75 to 81
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Normalization calls data.get() before verifying data is a dict in the PROVIDER_KEY branch

The new normalization code at line 76 calls data.get("kind", "") which assumes data is a dict, but the isinstance(data, dict) guard doesn't happen until line 79. If a caller passes a non-dict, non-BaseModel value for data (e.g. a string or list), the .get() call will raise an AttributeError instead of the intended clean ValueError from the isinstance check.

Root Cause and Impact

In mode="before" Pydantic validators, values haven't been type-checked yet so data could be any raw input. On line 69, data = values.get("data", {}) defaults to {}, and BaseModel instances are converted on lines 70-72, so data is usually a dict. However, if someone passes e.g. {"kind": "provider_key", "data": "not_a_dict"}, the code reaches:

if data.get("kind", "") == "together_ai":  # AttributeError: 'str' has no attribute 'get'
    data["kind"] = "togetherai"

if not isinstance(data, dict):  # never reached
    raise ValueError("...")

The fix should move the normalization block after the isinstance(data, dict) check (or swap their order), so the validation error is raised cleanly. Note: the CUSTOM_PROVIDER branch at lines 93-97 has this same pre-existing issue, but for PROVIDER_KEY this ordering problem is newly introduced by this PR.

Impact: Malformed API requests get an unhandled AttributeError instead of a descriptive ValueError, resulting in a 500 Internal Server Error rather than a 422 Validation Error.

(Refers to lines 75-82)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Expand All @@ -86,10 +90,9 @@ def validate_secret_data_based_on_kind(cls, values: Dict[str, Any]):
)

elif kind == SecretKind.CUSTOM_PROVIDER.value:
# Fix inconsistent API naming - Users might enter 'togetherai' but the API requires 'together_ai'
# This ensures compatibility with LiteLLM which requires the provider in "together_ai" format
if data.get("kind", "") == "togetherai":
data["kind"] = "together_ai"
# Fix inconsistent API naming - normalize 'together_ai' to 'togetherai'
if data.get("kind", "") == "together_ai":
data["kind"] = "togetherai"

if not isinstance(data, dict):
raise ValueError(
Expand Down
4 changes: 2 additions & 2 deletions api/oss/src/core/secrets/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class StandardProviderKind(str, Enum):
MISTRALAI = "mistralai"
ANTHROPIC = "anthropic"
PERPLEXITYAI = "perplexityai"
TOGETHERAI = "together_ai"
TOGETHERAI = "togetherai"
OPENROUTER = "openrouter"
GEMINI = "gemini"

Expand All @@ -39,6 +39,6 @@ class CustomProviderKind(str, Enum):
MISTRALAI = "mistralai"
ANTHROPIC = "anthropic"
PERPLEXITYAI = "perplexityai"
TOGETHERAI = "together_ai"
TOGETHERAI = "togetherai"
OPENROUTER = "openrouter"
GEMINI = "gemini"
2 changes: 1 addition & 1 deletion sdk/agenta/client/backend/types/custom_provider_kind.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"mistralai",
"anthropic",
"perplexityai",
"together_ai",
"togetherai",
"openrouter",
"gemini",
],
Expand Down
2 changes: 1 addition & 1 deletion sdk/agenta/client/backend/types/standard_provider_kind.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"mistralai",
"anthropic",
"perplexityai",
"together_ai",
"togetherai",
"openrouter",
"gemini",
],
Expand Down
2 changes: 1 addition & 1 deletion sdk/agenta/sdk/assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@
"perplexity/sonar-reasoning",
"perplexity/sonar-reasoning-pro",
],
"together_ai": [
"togetherai": [
"together_ai/deepseek-ai/DeepSeek-R1",
"together_ai/deepseek-ai/DeepSeek-R1-Distill-Llama-70B",
"together_ai/deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B",
Expand Down
3 changes: 1 addition & 2 deletions sdk/agenta/sdk/workflows/runners/daytona.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,7 @@ def _get_provider_env_vars(self) -> Dict[str, str]:
"mistralai": "MISTRALAI_API_KEY",
"anthropic": "ANTHROPIC_API_KEY",
"perplexityai": "PERPLEXITYAI_API_KEY",
# Secret kind is "together_ai" (underscore) even though the env var is TOGETHERAI_API_KEY
"together_ai": "TOGETHERAI_API_KEY",
"togetherai": "TOGETHERAI_API_KEY",
"openrouter": "OPENROUTER_API_KEY",
"gemini": "GEMINI_API_KEY",
}
Expand Down
2 changes: 1 addition & 1 deletion web/oss/src/components/SelectLLMProvider/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const PROVIDER_ICON_MAP: Record<string, string> = {
deepinfra: "DeepInfra",
openrouter: "OpenRouter",
perplexityai: "Perplexity AI",
together_ai: "Together AI",
togetherai: "Together AI",
vertex_ai: "Google Vertex AI",
bedrock: "AWS Bedrock",
azure: "Azure OpenAI",
Expand Down
4 changes: 2 additions & 2 deletions web/oss/src/lib/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ export enum SecretDTOProvider {
MISTRALAI = "mistralai",
ANTHROPIC = "anthropic",
PERPLEXITYAI = "perplexityai",
TOGETHERAI = "together_ai",
TOGETHERAI = "togetherai",
OPENROUTER = "openrouter",
GEMINI = "gemini",
}
Expand All @@ -467,7 +467,7 @@ export const PROVIDER_LABELS: Record<string, string> = {
mistralai: "Mistral AI",
anthropic: "Anthropic",
perplexityai: "Perplexity AI",
together_ai: "Together AI",
togetherai: "Together AI",
openrouter: "OpenRouter",
gemini: "Google Gemini",
vertex_ai: "Google Vertex AI",
Expand Down
2 changes: 1 addition & 1 deletion web/oss/src/lib/helpers/llmProviders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const transformSecret = (secrets: CustomSecretDTO[] | StandardSecretDTO[]
mistralai: "MISTRALAI_API_KEY",
anthropic: "ANTHROPIC_API_KEY",
perplexityai: "PERPLEXITYAI_API_KEY",
together_ai: "TOGETHERAI_API_KEY",
togetherai: "TOGETHERAI_API_KEY",
openrouter: "OPENROUTER_API_KEY",
gemini: "GEMINI_API_KEY",
}
Expand Down
2 changes: 1 addition & 1 deletion web/packages/agenta-ui/src/SelectLLMProvider/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ if (Icon) return <Icon className="w-4 h-4" />
Get the display name for a provider key.

```typescript
getProviderDisplayName('together_ai') // "Together AI"
getProviderDisplayName('togetherai') // "Together AI"
```

#### `PROVIDER_ICON_MAP`
Expand Down
2 changes: 1 addition & 1 deletion web/packages/agenta-ui/src/SelectLLMProvider/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const PROVIDER_ICON_MAP: Record<string, string> = {
deepinfra: "DeepInfra",
openrouter: "OpenRouter",
perplexity: "Perplexity AI",
together_ai: "Together AI",
togetherai: "Together AI",
vertex_ai: "Google Vertex AI",
bedrock: "AWS Bedrock",
azure: "Azure OpenAI",
Expand Down