Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 2 additions & 0 deletions api/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
from .tools import (
ApiToolProvider,
BuiltinToolProvider,
EndUserAuthenticationProvider,
ToolConversationVariables,
ToolFile,
ToolLabelBinding,
Expand Down Expand Up @@ -148,6 +149,7 @@
"DocumentSegment",
"Embedding",
"EndUser",
"EndUserAuthenticationProvider",
"ExternalKnowledgeApis",
"ExternalKnowledgeBindings",
"IconType",
Expand Down
52 changes: 52 additions & 0 deletions api/models/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,58 @@ def credentials(self) -> dict[str, Any]:
return cast(dict[str, Any], json.loads(self.encrypted_credentials))


class EndUserAuthenticationProvider(TypeBase):
"""
This table stores the authentication credentials for end users in tools.
Mimics the BuiltinToolProvider structure but for end users instead of tenants.
"""

__tablename__ = "tool_enduser_authentication_providers"
__table_args__ = (
sa.PrimaryKeyConstraint("id", name="tool_enduser_authentication_provider_pkey"),
sa.UniqueConstraint("tenant_id", "provider", "end_user_id", "name", name="unique_enduser_authentication_provider"),
sa.Index("tool_enduser_authentication_provider_tenant_id_idx", "tenant_id"),
sa.Index("tool_enduser_authentication_provider_end_user_id_idx", "end_user_id"),
)

# id of the authentication provider
id: Mapped[str] = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"), init=False)
name: Mapped[str] = mapped_column(
Copy link
Collaborator

Choose a reason for hiding this comment

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

IMO, enforcing name uniqueness for a (provider, end_user_id) pair creates more overhead than value.

Ideally, we should rely on IDs as the primary identifier and treat names merely as user-facing hints. Therefore, strict uniqueness shouldn't be required. If a user creates duplicate names, they should bear the responsibility for any ambiguity.

Enforcing name uniqueness has caused implementation issues in the past, such as with credential management in the EE version. Furthermore, it complicates name generation.

That said, I suggest discussing this with the PM. I’ve skimmed the PRD and found no explicit requirement for name uniqueness.

Copy link
Author

@CourTeous33 CourTeous33 Nov 21, 2025

Choose a reason for hiding this comment

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

Agreed, should we remove the unique constraint at all? Since ID is enough for uniqueness

String(256),
nullable=False,
server_default=sa.text("'API KEY 1'::character varying"),
)
# id of the tenant
tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
# id of the end user
end_user_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
# name of the tool provider
provider: Mapped[str] = mapped_column(String(256), nullable=False)
# encrypted credentials for the end user
encrypted_credentials: Mapped[str | None] = mapped_column(sa.Text, nullable=True, default=None)
created_at: Mapped[datetime] = mapped_column(
sa.DateTime, nullable=False, server_default=sa.text("CURRENT_TIMESTAMP(0)"), init=False
)
updated_at: Mapped[datetime] = mapped_column(
sa.DateTime,
nullable=False,
server_default=sa.text("CURRENT_TIMESTAMP(0)"),
onupdate=func.current_timestamp(),
init=False,
)
# credential type, e.g., "api-key", "oauth2"
credential_type: Mapped[str] = mapped_column(
String(32), nullable=False, server_default=sa.text("'api-key'::character varying"), default="api-key"
)
expires_at: Mapped[int] = mapped_column(sa.BigInteger, nullable=False, server_default=sa.text("-1"), default=-1)

@property
def credentials(self) -> dict[str, Any]:
if not self.encrypted_credentials:
return {}
return cast(dict[str, Any], json.loads(self.encrypted_credentials))


class ApiToolProvider(TypeBase):
"""
The table stores the api providers.
Expand Down