diff --git a/litellm/proxy/agent_endpoints/agent_registry.py b/litellm/proxy/agent_endpoints/agent_registry.py index 7929f67720d..56919b1a76b 100644 --- a/litellm/proxy/agent_endpoints/agent_registry.py +++ b/litellm/proxy/agent_endpoints/agent_registry.py @@ -73,6 +73,9 @@ class AgentRecord(Protocol): @property def agent_name(self) -> str: ... + @property + def static_headers(self) -> object: ... + @property def litellm_params(self) -> Mapping[str, object] | None: ... @@ -280,6 +283,43 @@ def parse_agent_litellm_params(value: object) -> Mapping[str, object]: _KILL_SWITCH_ADAPTER: Final[TypeAdapter[AgentKillSwitchConfig | None]] = TypeAdapter(AgentKillSwitchConfig | None) +_AGENT_STATIC_HEADERS_ADAPTER: Final[TypeAdapter[Mapping[str, str]]] = TypeAdapter(Mapping[str, str]) +_EMPTY_AGENT_STATIC_HEADERS: Final[Mapping[str, str]] = MappingProxyType({}) + + +def redact_agent_static_headers( + headers: Mapping[str, str] | None, +) -> dict[str, str] | None: # mutable-ok: AgentResponse.static_headers is a plain dict + if headers is None: + return None + return {name: REDACTED_BY_LITELM_STRING for name in headers} + + +def parse_agent_static_headers(value: object) -> Mapping[str, str]: + if value is None: + return _EMPTY_AGENT_STATIC_HEADERS + try: + parsed: Final = ( + _AGENT_STATIC_HEADERS_ADAPTER.validate_json(value) + if isinstance(value, str) + else _AGENT_STATIC_HEADERS_ADAPTER.validate_python(value) + ) + except ValidationError: + return _EMPTY_AGENT_STATIC_HEADERS + return MappingProxyType(dict(parsed)) + + +def serialize_agent_static_headers(incoming: Mapping[str, str] | None, existing: object) -> str: + if incoming is None: + return safe_dumps({}) + existing_headers: Final = parse_agent_static_headers(existing) + return safe_dumps( + { + name: existing_headers[name] if value == REDACTED_BY_LITELM_STRING else value + for name, value in incoming.items() + if value != REDACTED_BY_LITELM_STRING or name in existing_headers + } + ) def parse_agent_kill_switch(value: object) -> AgentKillSwitchConfig | None: @@ -615,7 +655,9 @@ class AgentRegistry: # Serialize static_headers static_headers_obj: Final = agent.get("static_headers") - static_headers_val: Final[str | None] = safe_dumps(dict(static_headers_obj)) if static_headers_obj else None + static_headers_val: Final[str | None] = ( + serialize_agent_static_headers(static_headers_obj, None) if static_headers_obj else None + ) extra_headers_val: Final = agent.get("extra_headers") access_group_ids_val: Final = agent.get("access_group_ids") @@ -751,7 +793,9 @@ class AgentRegistry: update_data[rate_field] = agent.get(rate_field) if "static_headers" in agent: headers_value: Final = agent.get("static_headers") - update_data["static_headers"] = safe_dumps(dict(headers_value) if headers_value is not None else {}) + update_data["static_headers"] = serialize_agent_static_headers( + headers_value, existing_agent.get("static_headers") + ) if "extra_headers" in agent: extra_headers_value: Final = agent.get("extra_headers") update_data["extra_headers"] = extra_headers_value if extra_headers_value is not None else [] @@ -828,8 +872,8 @@ class AgentRegistry: # Serialize static_headers for update static_headers_obj_u: Final = agent.get("static_headers") - static_headers_val_u: Final[str] = ( - safe_dumps(dict(static_headers_obj_u)) if static_headers_obj_u is not None else safe_dumps({}) + static_headers_val_u: Final[str] = serialize_agent_static_headers( + static_headers_obj_u, existing_row.static_headers if existing_row is not None else None ) extra_headers_val_u: Final = agent.get("extra_headers") or [] access_group_ids_val_u: Final = tuple(dict.fromkeys(agent.get("access_group_ids") or ())) diff --git a/litellm/proxy/agent_endpoints/endpoints.py b/litellm/proxy/agent_endpoints/endpoints.py index 89794fc2480..10c4a0357ff 100644 --- a/litellm/proxy/agent_endpoints/endpoints.py +++ b/litellm/proxy/agent_endpoints/endpoints.py @@ -38,6 +38,7 @@ from litellm.proxy.agent_endpoints.agent_registry import ( AgentIdWhere, parse_agent_kill_switch, parse_agent_litellm_params, + redact_agent_static_headers, redact_sensitive_agent_litellm_params, ) from litellm.proxy.agent_endpoints.agent_search import ( @@ -187,10 +188,10 @@ def _redact_sensitive_agent_fields( ) -> list[AgentResponse]: """ Return copies of the given agents with credential-bearing litellm_params - values and kill-switch auth secrets replaced by a fixed marker (never - returned to ANY caller, admin included) and, for non-admin callers, - virtual-key, header and kill-switch fields stripped entirely. The original - objects are not modified. + values, static-header values and kill-switch auth secrets replaced by a + fixed marker (never returned to ANY caller, admin included) and, for + non-admin callers, virtual-key, header and kill-switch fields stripped + entirely. The original objects are not modified. """ from litellm.proxy.proxy_server import general_settings, jwt_handler @@ -207,6 +208,7 @@ def _redact_sensitive_agent_fields( copy.keys = None copy.kill_switch = None copy.identity = None + copy.static_headers = redact_agent_static_headers(copy.static_headers) if copy.litellm_params: copy.litellm_params = _redact_agent_litellm_params_dict(copy.litellm_params) copy.kill_switch = redact_kill_switch(copy.kill_switch)