fix(api): add expiration_time to form definition and events / response (vibe-kanban 3290f924)

This commit is contained in:
QuantumGhost
2026-01-27 18:09:36 +08:00
parent 51ed03c9e0
commit f3eb342883
14 changed files with 87 additions and 45 deletions

View File

@ -5,6 +5,9 @@ from dataclasses import dataclass
from datetime import datetime
from typing import Any, NewType, Union
from sqlalchemy import select
from sqlalchemy.orm import Session
from core.app.entities.app_invoke_entities import AdvancedChatAppGenerateEntity, InvokeFrom, WorkflowAppGenerateEntity
from core.app.entities.queue_entities import (
QueueAgentLogEvent,
@ -58,6 +61,8 @@ from core.workflow.enums import (
WorkflowNodeExecutionStatus,
)
from core.workflow.runtime import GraphRuntimeState
from extensions.ext_database import db
from models.human_input import HumanInputForm
from core.workflow.system_variable import SystemVariable
from core.workflow.workflow_entry import WorkflowEntry
from core.workflow.workflow_type_encoder import WorkflowRuntimeTypeConverter
@ -296,11 +301,23 @@ class WorkflowResponseConverter:
if self._application_generate_entity.invoke_from == InvokeFrom.SERVICE_API:
encoded_outputs = {}
pause_reasons = [reason.model_dump(mode="json") for reason in event.reasons]
human_input_form_ids = [reason.form_id for reason in event.reasons if isinstance(reason, HumanInputRequired)]
expiration_times_by_form_id: dict[str, datetime] = {}
if human_input_form_ids:
stmt = select(HumanInputForm.id, HumanInputForm.expiration_time).where(
HumanInputForm.id.in_(human_input_form_ids)
)
with Session(bind=db.engine) as session:
for form_id, expiration_time in session.execute(stmt):
expiration_times_by_form_id[str(form_id)] = expiration_time
responses: list[StreamResponse] = []
for reason in event.reasons:
if isinstance(reason, HumanInputRequired):
expiration_time = expiration_times_by_form_id.get(reason.form_id)
if expiration_time is None:
raise ValueError(f"HumanInputForm not found for pause reason, form_id={reason.form_id}")
responses.append(
HumanInputRequiredResponse(
task_id=task_id,
@ -315,6 +332,7 @@ class WorkflowResponseConverter:
display_in_ui=reason.display_in_ui,
form_token=reason.form_token,
resolved_default_values=reason.resolved_default_values,
expiration_time=int(expiration_time.timestamp()),
),
)
)

View File

@ -288,6 +288,7 @@ class HumanInputRequiredResponse(StreamResponse):
display_in_ui: bool = False
form_token: str | None = None
resolved_default_values: Mapping[str, Any] = Field(default_factory=dict)
expiration_time: int = Field(..., description="Unix timestamp in seconds")
event: StreamEvent = StreamEvent.HUMAN_INPUT_REQUIRED
workflow_run_id: str

View File

@ -1,6 +1,7 @@
from __future__ import annotations
from collections.abc import Mapping, Sequence
from datetime import datetime
from typing import Any, TypeAlias
from pydantic import BaseModel, ConfigDict, Field
@ -21,6 +22,7 @@ class HumanInputFormDefinition(BaseModel):
display_in_ui: bool = False
form_token: str | None = None
resolved_default_values: Mapping[str, Any] = Field(default_factory=dict)
expiration_time: datetime
class HumanInputFormSubmissionData(BaseModel):

View File

@ -164,6 +164,9 @@ class HumanInputFormRecord:
def from_models(
cls, form_model: HumanInputForm, recipient_model: HumanInputFormRecipient | None
) -> "HumanInputFormRecord":
definition_payload = json.loads(form_model.form_definition)
if "expiration_time" not in definition_payload:
definition_payload["expiration_time"] = form_model.expiration_time
return cls(
form_id=form_model.id,
workflow_run_id=form_model.workflow_run_id,
@ -171,7 +174,7 @@ class HumanInputFormRecord:
tenant_id=form_model.tenant_id,
app_id=form_model.app_id,
form_kind=form_model.form_kind,
definition=FormDefinition.model_validate_json(form_model.form_definition),
definition=FormDefinition.model_validate(definition_payload),
rendered_content=form_model.rendered_content,
created_at=form_model.created_at,
expiration_time=form_model.expiration_time,
@ -341,8 +344,7 @@ class HumanInputFormRepositoryImpl:
inputs=form_config.inputs,
user_actions=form_config.user_actions,
rendered_content=params.rendered_content,
timeout=form_config.timeout,
timeout_unit=form_config.timeout_unit,
expiration_time=node_expiration,
default_values=dict(params.resolved_default_values),
display_in_ui=params.display_in_ui,
node_title=form_config.title,

View File

@ -312,9 +312,7 @@ class FormDefinition(BaseModel):
inputs: list[FormInput] = Field(default_factory=list)
user_actions: list[UserAction] = Field(default_factory=list)
rendered_content: str
timeout: int
timeout_unit: TimeoutUnit
expiration_time: datetime
# this is used to store the resolved default values
default_values: dict[str, Any] = Field(default_factory=dict)