Files
dify/api/services/human_input_v2/credential_update.py
T
8cbb6c0130 feat: HITL IM integration (#44864)
Co-authored-by: zxhlyh <jasonapring2015@outlook.com>
Co-authored-by: JzoNg <jzongcode@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-10-30 02:43:31 +00:00

170 lines
6.5 KiB
Python

"""Typed credential edits; persisted credentials always remain complete values."""
from dataclasses import dataclass, field
from enum import Enum
from typing import Literal
from core.human_input_v2.entities import IMProvider
from core.human_input_v2.im_integration.adapters.credentials import (
DingTalkCredentials,
FeishuCredentials,
IMProviderCredentials,
LarkCredentials,
MSTeamsCredentials,
SlackCredentials,
WeComCredentials,
)
from core.human_input_v2.shared import NormalizedEmail
from services.human_input_v2.errors import ReplacementRequiredError
class Unchanged(Enum):
VALUE = "unchanged"
def updated_value[T](edit: T | Unchanged, current: T) -> T:
return current if isinstance(edit, Unchanged) else edit
@dataclass(frozen=True, slots=True)
class FeishuCredentialsUpdate:
app_id: str | Unchanged
app_secret: str | Unchanged = field(repr=False)
verification_token: str | None | Unchanged = field(repr=False)
encrypt_key: str | None | Unchanged = field(repr=False)
provider: Literal[IMProvider.FEISHU] = field(init=False, default=IMProvider.FEISHU)
def apply(self, current: FeishuCredentials) -> FeishuCredentials:
return FeishuCredentials(
provider=self.provider,
app_id=updated_value(self.app_id, current.app_id),
app_secret=updated_value(self.app_secret, current.app_secret),
verification_token=updated_value(self.verification_token, current.verification_token),
encrypt_key=updated_value(self.encrypt_key, current.encrypt_key),
)
@dataclass(frozen=True, slots=True)
class LarkCredentialsUpdate:
app_id: str | Unchanged
app_secret: str | Unchanged = field(repr=False)
verification_token: str | None | Unchanged = field(repr=False)
encrypt_key: str | None | Unchanged = field(repr=False)
provider: Literal[IMProvider.LARK] = field(init=False, default=IMProvider.LARK)
def apply(self, current: LarkCredentials) -> LarkCredentials:
return LarkCredentials(
provider=self.provider,
app_id=updated_value(self.app_id, current.app_id),
app_secret=updated_value(self.app_secret, current.app_secret),
verification_token=updated_value(self.verification_token, current.verification_token),
encrypt_key=updated_value(self.encrypt_key, current.encrypt_key),
)
@dataclass(frozen=True, slots=True)
class SlackCredentialsUpdate:
client_id: str | Unchanged
client_secret: str | Unchanged = field(repr=False)
signing_secret: str | Unchanged = field(repr=False)
bot_token: str | Unchanged = field(repr=False)
app_token: str | None | Unchanged = field(repr=False)
provider: Literal[IMProvider.SLACK] = field(init=False, default=IMProvider.SLACK)
def apply(self, current: SlackCredentials) -> SlackCredentials:
return SlackCredentials(
provider=self.provider,
client_id=updated_value(self.client_id, current.client_id),
client_secret=updated_value(self.client_secret, current.client_secret),
signing_secret=updated_value(self.signing_secret, current.signing_secret),
bot_token=updated_value(self.bot_token, current.bot_token),
app_token=updated_value(self.app_token, current.app_token),
)
@dataclass(frozen=True, slots=True)
class DingTalkCredentialsUpdate:
corp_id: str | Unchanged
client_id: str | Unchanged
client_secret: str | Unchanged = field(repr=False)
provider: Literal[IMProvider.DING_TALK] = field(init=False, default=IMProvider.DING_TALK)
def apply(self, current: DingTalkCredentials) -> DingTalkCredentials:
return DingTalkCredentials(
provider=self.provider,
corp_id=updated_value(self.corp_id, current.corp_id),
client_id=updated_value(self.client_id, current.client_id),
client_secret=updated_value(self.client_secret, current.client_secret),
)
@dataclass(frozen=True, slots=True)
class MSTeamsCredentialsUpdate:
tenant_id: str | Unchanged
client_id: str | Unchanged
client_secret: str | Unchanged = field(repr=False)
provider: Literal[IMProvider.MS_TEAMS] = field(init=False, default=IMProvider.MS_TEAMS)
def apply(self, current: MSTeamsCredentials) -> MSTeamsCredentials:
return MSTeamsCredentials(
provider=self.provider,
tenant_id=updated_value(self.tenant_id, current.tenant_id),
client_id=updated_value(self.client_id, current.client_id),
client_secret=updated_value(self.client_secret, current.client_secret),
)
@dataclass(frozen=True, slots=True)
class WeComCredentialsUpdate:
corp_id: str | Unchanged
agent_id: str | Unchanged
secret: str | Unchanged = field(repr=False)
provider: Literal[IMProvider.WE_COM] = field(init=False, default=IMProvider.WE_COM)
def apply(self, current: WeComCredentials) -> WeComCredentials:
return WeComCredentials(
provider=self.provider,
corp_id=updated_value(self.corp_id, current.corp_id),
agent_id=updated_value(self.agent_id, current.agent_id),
secret=updated_value(self.secret, current.secret),
)
type IMCredentialsUpdate = (
FeishuCredentialsUpdate
| LarkCredentialsUpdate
| SlackCredentialsUpdate
| DingTalkCredentialsUpdate
| MSTeamsCredentialsUpdate
| WeComCredentialsUpdate
)
def merge_im_credentials(current: IMProviderCredentials, edit: IMCredentialsUpdate) -> IMProviderCredentials:
match current, edit:
case FeishuCredentials(), FeishuCredentialsUpdate():
return edit.apply(current)
case LarkCredentials(), LarkCredentialsUpdate():
return edit.apply(current)
case SlackCredentials(), SlackCredentialsUpdate():
return edit.apply(current)
case DingTalkCredentials(), DingTalkCredentialsUpdate():
return edit.apply(current)
case MSTeamsCredentials(), MSTeamsCredentialsUpdate():
return edit.apply(current)
case WeComCredentials(), WeComCredentialsUpdate():
return edit.apply(current)
raise ReplacementRequiredError("IM provider replacement is required")
@dataclass(frozen=True, slots=True)
class ResendCredentialsUpdate:
api_key: str | Unchanged = field(repr=False)
@dataclass(frozen=True, slots=True)
class ResendChannelUpdate:
sender_email: NormalizedEmail | Unchanged
sender_name: str | Unchanged
credentials: ResendCredentialsUpdate = field(repr=False)