Files
dify/api/controllers/console/dify_builder_fields.py

411 lines
12 KiB
Python

"""Pydantic request and response schemas for the Console Dify Builder API."""
from typing import Annotated, Any, Literal
from pydantic import BaseModel, ConfigDict, Field, RootModel, StrictInt, StringConstraints, model_validator
from core.dify_builder.contract import (
ActionKind,
AssistantTurnItem,
BuilderErrorCode,
CanvasEvent,
Decision,
DecisionItem,
ExecutionActivityState,
ExecutionProgressStatus,
FormCard,
InteractionResponseItem,
NoticeItem,
Phase,
PlanCard,
PreflightContextCard,
RecoveryRef,
ResourceSelectCard,
RunContextCard,
RunStatus,
SessionModel,
TestResultCard,
UserItem,
)
from fields.base import ResponseModel
from fields.workflow_stream_fields import WorkflowStreamPayload
NonEmptyString = Annotated[str, StringConstraints(strip_whitespace=True, min_length=1)]
ClientTurnId = Annotated[str, StringConstraints(strip_whitespace=True, min_length=1, max_length=128)]
class DifyBuilderPayload(BaseModel):
model_config = ConfigDict(extra="forbid", populate_by_name=True)
class DifyBuilderCreateBuildSessionPayload(DifyBuilderPayload):
app_id: NonEmptyString
scenario: Literal["build"]
goal_text: NonEmptyString
# True when the app was created from this same prompt and still carries the
# name cut from it, so Builder may rewrite it once it understands the goal.
# False is create-from-blank: the user named the app, Builder inherits it.
derive_app_name: bool = False
model_config_data: SessionModel | None = Field(default=None, alias="model_config")
class DifyBuilderCreateEditSessionPayload(DifyBuilderPayload):
app_id: NonEmptyString
scenario: Literal["edit"]
goal_text: NonEmptyString
model_config_data: SessionModel | None = Field(default=None, alias="model_config")
class DifyBuilderCreateFixSessionPayload(DifyBuilderPayload):
app_id: NonEmptyString
scenario: Literal["fix"]
failed_run_id: NonEmptyString
model_config_data: SessionModel | None = Field(default=None, alias="model_config")
class DifyBuilderChecklistErrorPayload(DifyBuilderPayload):
node_id: str
node_type: str
title: str
messages: list[str]
unconnected: bool
plugin_missing: bool
class DifyBuilderCreateChecklistFixSessionPayload(DifyBuilderPayload):
app_id: NonEmptyString
scenario: Literal["fix"]
checklist_errors: Annotated[list[DifyBuilderChecklistErrorPayload], Field(min_length=1)]
failed_run_id: NonEmptyString | None = None
model_config_data: SessionModel | None = Field(default=None, alias="model_config")
class DifyBuilderCreateSessionPayload(
RootModel[
DifyBuilderCreateBuildSessionPayload
| DifyBuilderCreateEditSessionPayload
| DifyBuilderCreateFixSessionPayload
| DifyBuilderCreateChecklistFixSessionPayload
]
):
"""Create a Build, Edit, failed-run Fix, or checklist Fix session."""
class DifyBuilderSubmitActionPayload(DifyBuilderPayload):
action_id: NonEmptyString
payload: dict[str, Any] = Field(default_factory=dict)
base_version: StrictInt
base_app_revision: NonEmptyString
class DifyBuilderSubmitMessagePayload(DifyBuilderPayload):
text: NonEmptyString
base_version: StrictInt
client_turn_id: ClientTurnId
class DifyBuilderConversationListQuery(DifyBuilderPayload):
before_seq: int | None = Field(default=None, ge=0, description="Load groups before this sequence")
after_seq: int | None = Field(default=None, ge=-1, description="Load groups after this sequence")
limit: int = Field(default=20, ge=1, le=100, description="Number of conversation groups to return")
@model_validator(mode="after")
def validate_cursor(self) -> "DifyBuilderConversationListQuery":
if self.before_seq is not None and self.after_seq is not None:
raise ValueError("before_seq and after_seq are mutually exclusive")
return self
class DifyBuilderConversationItemBase(ResponseModel):
seq: int
at_version: int
class DifyBuilderUserConversationItemResponse(DifyBuilderConversationItemBase):
kind: Literal["user"]
payload: UserItem
class DifyBuilderDecisionConversationItemResponse(DifyBuilderConversationItemBase):
kind: Literal["decision"]
payload: DecisionItem
class DifyBuilderInteractionResponseConversationItemResponse(DifyBuilderConversationItemBase):
kind: Literal["interaction_response"]
payload: InteractionResponseItem
class DifyBuilderNoticeConversationItemResponse(DifyBuilderConversationItemBase):
kind: Literal["notice"]
payload: NoticeItem
class DifyBuilderRunContextConversationItemResponse(DifyBuilderConversationItemBase):
kind: Literal["run_context"]
payload: RunContextCard
class DifyBuilderPreflightContextConversationItemResponse(DifyBuilderConversationItemBase):
kind: Literal["preflight_context"]
payload: PreflightContextCard
class DifyBuilderAssistantTurnConversationItemResponse(DifyBuilderConversationItemBase):
kind: Literal["assistant_turn"]
payload: AssistantTurnItem
class DifyBuilderPlanConversationItemResponse(DifyBuilderConversationItemBase):
kind: Literal["plan"]
payload: PlanCard
class DifyBuilderFormConversationItemResponse(DifyBuilderConversationItemBase):
kind: Literal["form"]
payload: FormCard
class DifyBuilderResourceSelectConversationItemResponse(DifyBuilderConversationItemBase):
kind: Literal["resource_select"]
payload: ResourceSelectCard
class DifyBuilderTestResultConversationItemResponse(DifyBuilderConversationItemBase):
kind: Literal["test_result"]
payload: TestResultCard
DifyBuilderConversationItem = Annotated[
DifyBuilderUserConversationItemResponse
| DifyBuilderDecisionConversationItemResponse
| DifyBuilderInteractionResponseConversationItemResponse
| DifyBuilderNoticeConversationItemResponse
| DifyBuilderRunContextConversationItemResponse
| DifyBuilderPreflightContextConversationItemResponse
| DifyBuilderAssistantTurnConversationItemResponse
| DifyBuilderPlanConversationItemResponse
| DifyBuilderFormConversationItemResponse
| DifyBuilderResourceSelectConversationItemResponse
| DifyBuilderTestResultConversationItemResponse,
Field(discriminator="kind"),
]
# Live item events intentionally exclude assistant_turn. Assistant text has a
# dedicated streaming contract with a preallocated sequence; allowing it here
# would reintroduce a second, conflicting text source for the frontend.
DifyBuilderAppendedConversationItem = Annotated[
DifyBuilderUserConversationItemResponse
| DifyBuilderDecisionConversationItemResponse
| DifyBuilderInteractionResponseConversationItemResponse
| DifyBuilderNoticeConversationItemResponse
| DifyBuilderRunContextConversationItemResponse
| DifyBuilderPreflightContextConversationItemResponse
| DifyBuilderPlanConversationItemResponse
| DifyBuilderFormConversationItemResponse
| DifyBuilderResourceSelectConversationItemResponse
| DifyBuilderTestResultConversationItemResponse,
Field(discriminator="kind"),
]
class DifyBuilderActiveInteractionResponse(ResponseModel):
action_id: str
card_seq: int
valid_at_version: int
class DifyBuilderActionResponse(ResponseModel):
id: str
label: str
kind: ActionKind
class DifyBuilderAppRevisionResponse(ResponseModel):
current: str
conflicted: bool
class DifyBuilderSessionStateResponse(ResponseModel):
session_id: str
version: int
canvas_read_only: bool
run_status: RunStatus
interrupted: bool
conversation_last_seq: int
phase: Phase
actions: list[DifyBuilderActionResponse] = Field(default_factory=list)
decision: Decision | None = None
active_interaction: DifyBuilderActiveInteractionResponse | None = None
recovery: RecoveryRef | None = None
model: SessionModel | None = None
app_revision: DifyBuilderAppRevisionResponse | None = None
class DifyBuilderSessionViewResponse(DifyBuilderSessionStateResponse):
last_command_id: str = ""
class DifyBuilderConversationPageResponse(ResponseModel):
data: list[DifyBuilderConversationItem]
has_more: bool
first_seq: int | None
last_seq: int | None
class DifyBuilderCommandFinishedEventData(DifyBuilderSessionStateResponse):
command_id: str
post_canvas_action_id: str | None = None
class DifyBuilderCommandStartedEventData(ResponseModel):
session_id: str
command_id: str
version: int
phase: Phase
run_status: RunStatus
class DifyBuilderCommandStartedEventResponse(ResponseModel):
event: Literal["command_started"]
data: DifyBuilderCommandStartedEventData
class DifyBuilderWorkflowEventData(ResponseModel):
session_id: str
operation_id: str
at_version: int
revision: int
payload: WorkflowStreamPayload
class DifyBuilderWorkflowEventResponse(ResponseModel):
event: Literal["workflow"]
data: DifyBuilderWorkflowEventData
class DifyBuilderCanvasEventData(ResponseModel):
session_id: str
operation_id: str
at_version: int
revision: int
event: CanvasEvent
node_id: str | None = None
class DifyBuilderCanvasEventResponse(ResponseModel):
event: Literal["canvas"]
data: DifyBuilderCanvasEventData
class DifyBuilderExecutionActivityResponse(ResponseModel):
id: str
label: str
state: ExecutionActivityState
parent_id: str | None = None
class DifyBuilderExecutionProgressResponse(ResponseModel):
status: ExecutionProgressStatus
activities: list[DifyBuilderExecutionActivityResponse] = Field(default_factory=list)
class DifyBuilderAgentMessageEventData(ResponseModel):
session_id: str
command_id: str
operation_id: str
turn_id: str
delta: str
seq: int
at_version: int
revision: int
done: bool
text_bytes: int
execution: DifyBuilderExecutionProgressResponse | None = None
cards: list[str] = Field(default_factory=list)
class DifyBuilderAgentMessageEventResponse(ResponseModel):
event: Literal["agent_message"]
data: DifyBuilderAgentMessageEventData
class DifyBuilderConversationItemAppendedEventData(ResponseModel):
session_id: str
command_id: str
item: DifyBuilderAppendedConversationItem
class DifyBuilderConversationItemAppendedEventResponse(ResponseModel):
event: Literal["conversation_item_appended"]
data: DifyBuilderConversationItemAppendedEventData
class DifyBuilderReasoningEventData(ResponseModel):
session_id: str
operation_id: str
at_version: int
revision: int
delta: str
class DifyBuilderReasoningEventResponse(ResponseModel):
event: Literal["reasoning"]
data: DifyBuilderReasoningEventData
class DifyBuilderProgressEventData(ResponseModel):
session_id: str
operation_id: str
at_version: int
revision: int
status: ExecutionProgressStatus
activity: DifyBuilderExecutionActivityResponse | None
class DifyBuilderProgressEventResponse(ResponseModel):
event: Literal["progress"]
data: DifyBuilderProgressEventData
class DifyBuilderCommandFinishedEventResponse(ResponseModel):
event: Literal["command_finished"]
data: DifyBuilderCommandFinishedEventData
class DifyBuilderErrorEventData(ResponseModel):
session_id: str | None = None
command_id: str | None = None
code: str | None = None
message: str
class DifyBuilderErrorEventResponse(ResponseModel):
event: Literal["error"]
data: DifyBuilderErrorEventData
class DifyBuilderStreamEventResponse(
RootModel[
Annotated[
DifyBuilderCommandStartedEventResponse
| DifyBuilderWorkflowEventResponse
| DifyBuilderCanvasEventResponse
| DifyBuilderAgentMessageEventResponse
| DifyBuilderConversationItemAppendedEventResponse
| DifyBuilderReasoningEventResponse
| DifyBuilderProgressEventResponse
| DifyBuilderCommandFinishedEventResponse
| DifyBuilderErrorEventResponse,
Field(discriminator="event"),
]
]
):
"""One JSON object carried by an SSE ``data:`` frame."""
class DifyBuilderErrorResponse(ResponseModel):
code: BuilderErrorCode
message: str | None = None
recoverable: bool | None = None