Backup-bdg's picture
Upload 964 files
51ff9e5 verified
raw
history blame
5.72 kB
from types import MappingProxyType
from typing import Any
from pydantic import (
BaseModel,
Field,
SerializationInfo,
field_serializer,
model_validator,
)
from pydantic.json import pydantic_encoder
from openhands.events.stream import EventStream
from openhands.integrations.provider import (
CUSTOM_SECRETS_TYPE,
CUSTOM_SECRETS_TYPE_WITH_JSON_SCHEMA,
PROVIDER_TOKEN_TYPE,
PROVIDER_TOKEN_TYPE_WITH_JSON_SCHEMA,
CustomSecret,
ProviderToken,
)
from openhands.integrations.service_types import ProviderType
class UserSecrets(BaseModel):
provider_tokens: PROVIDER_TOKEN_TYPE_WITH_JSON_SCHEMA = Field(
default_factory=lambda: MappingProxyType({})
)
custom_secrets: CUSTOM_SECRETS_TYPE_WITH_JSON_SCHEMA = Field(
default_factory=lambda: MappingProxyType({})
)
model_config = {
'frozen': True,
'validate_assignment': True,
'arbitrary_types_allowed': True,
}
@field_serializer('provider_tokens')
def provider_tokens_serializer(
self, provider_tokens: PROVIDER_TOKEN_TYPE, info: SerializationInfo
) -> dict[str, dict[str, str | Any]]:
tokens = {}
expose_secrets = info.context and info.context.get('expose_secrets', False)
for token_type, provider_token in provider_tokens.items():
if not provider_token or not provider_token.token:
continue
token_type_str = (
token_type.value
if isinstance(token_type, ProviderType)
else str(token_type)
)
token = None
if provider_token.token:
token = (
provider_token.token.get_secret_value()
if expose_secrets
else pydantic_encoder(provider_token.token)
)
tokens[token_type_str] = {
'token': token,
'host': provider_token.host,
'user_id': provider_token.user_id,
}
return tokens
@field_serializer('custom_secrets')
def custom_secrets_serializer(
self, custom_secrets: CUSTOM_SECRETS_TYPE, info: SerializationInfo
):
secrets = {}
expose_secrets = info.context and info.context.get('expose_secrets', False)
if custom_secrets:
for secret_name, secret_value in custom_secrets.items():
secrets[secret_name] = {
'secret': secret_value.secret.get_secret_value()
if expose_secrets
else pydantic_encoder(secret_value.secret),
'description': secret_value.description,
}
return secrets
@model_validator(mode='before')
@classmethod
def convert_dict_to_mappingproxy(
cls, data: dict[str, dict[str, Any] | MappingProxyType] | PROVIDER_TOKEN_TYPE
) -> dict[str, MappingProxyType | None]:
"""Custom deserializer to convert dictionary into MappingProxyType"""
if not isinstance(data, dict):
raise ValueError('UserSecrets must be initialized with a dictionary')
new_data: dict[str, MappingProxyType | None] = {}
if 'provider_tokens' in data:
tokens = data['provider_tokens']
if isinstance(
tokens, dict
): # Ensure conversion happens only for dict inputs
converted_tokens = {}
for key, value in tokens.items():
try:
provider_type = (
ProviderType(key) if isinstance(key, str) else key
)
converted_tokens[provider_type] = ProviderToken.from_value(
value
)
except ValueError:
# Skip invalid provider types or tokens
continue
# Convert to MappingProxyType
new_data['provider_tokens'] = MappingProxyType(converted_tokens)
elif isinstance(tokens, MappingProxyType):
new_data['provider_tokens'] = tokens
if 'custom_secrets' in data:
secrets = data['custom_secrets']
if isinstance(secrets, dict):
converted_secrets = {}
for key, value in secrets.items():
try:
converted_secrets[key] = CustomSecret.from_value(value)
except ValueError:
continue
new_data['custom_secrets'] = MappingProxyType(converted_secrets)
elif isinstance(secrets, MappingProxyType):
new_data['custom_secrets'] = secrets
return new_data
def set_event_stream_secrets(self, event_stream: EventStream) -> None:
"""
This ensures that provider tokens and custom secrets masked from the event stream
Args:
event_stream: Agent session's event stream
"""
secrets = self.get_env_vars()
event_stream.set_secrets(secrets)
def get_env_vars(self) -> dict[str, str]:
secret_store = self.model_dump(context={'expose_secrets': True})
custom_secrets = secret_store.get('custom_secrets', {})
secrets = {}
for secret_name, value in custom_secrets.items():
secrets[secret_name] = value['secret']
return secrets
def get_custom_secrets_descriptions(self) -> dict[str, str]:
secrets = {}
for secret_name, secret in self.custom_secrets.items():
secrets[secret_name] = secret.description
return secrets