From 49fd3ed1ba5c503be4c099e995448a86cdb75591 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Thu, 7 May 2026 10:43:03 -0700 Subject: [PATCH] fix(v2 managed agents): enforce idle_timeout_minutes <= timeout_minutes on SandboxSpec Per contract \xc2\xa76.2, idle_timeout_minutes must be in [1, timeout_minutes]. Previously only ge=1 was enforced, so idle=1440 with timeout=10 would pass. Add a Pydantic v2 model_validator(mode="after") that raises ValueError when idle_timeout_minutes > timeout_minutes; FastAPI surfaces it as a 422. --- litellm/managed_agents/types.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/litellm/managed_agents/types.py b/litellm/managed_agents/types.py index 4c0c83290bf..10e260f13e5 100644 --- a/litellm/managed_agents/types.py +++ b/litellm/managed_agents/types.py @@ -10,7 +10,7 @@ forward-compatibility with proxy-side enrichment. from datetime import datetime from typing import Any, Dict, List, Literal, Optional -from pydantic import BaseModel, ConfigDict, Field +from pydantic import BaseModel, ConfigDict, Field, model_validator # --------------------------------------------------------------------------- # Status / sandbox enums @@ -73,7 +73,7 @@ class SandboxSpec(BaseModel): `image` is optional and falls back to a pinned built-in. `timeout_minutes` is bounded to [1, 1440]. - `idle_timeout_minutes` must be in [1, timeout_minutes] (enforced by handler). + `idle_timeout_minutes` must be in [1, timeout_minutes]. """ model_config = ConfigDict(extra="forbid") @@ -84,6 +84,16 @@ class SandboxSpec(BaseModel): idle_timeout_minutes: int = Field(default=10, ge=1) image: Optional[str] = None + @model_validator(mode="after") + def _validate_idle_timeout_within_timeout(self) -> "SandboxSpec": + """Enforce contract §6.2: `idle_timeout_minutes` ∈ [1, `timeout_minutes`].""" + if self.idle_timeout_minutes > self.timeout_minutes: + raise ValueError( + f"idle_timeout_minutes ({self.idle_timeout_minutes}) must be " + f"<= timeout_minutes ({self.timeout_minutes})" + ) + return self + class Repo(BaseModel): model_config = ConfigDict(extra="forbid")