refactor: fix OpenAPI contract generation schemas (#37387)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Stephen Zhouandautofix-ci[bot] authored and GitHub committed 2026-06-12 14:25:53 +00:00
1 parent 7cf75c3cc5
commit 5d77c0af08
222 files changed
+15425 -10146

No files matched your search

+6
View File
@@ -20,6 +20,7 @@ openapi_ns = Namespace("openapi", description="User-scoped operations", path="/"
# Register response/query models BEFORE importing controller modules so that
# @openapi_ns.response / @openapi_ns.expect decorators can resolve model names.
from controllers.common.fields import EventStreamResponse
from controllers.common.schema import register_enum_models, register_response_schema_models, register_schema_models
from controllers.openapi._models import (
AccountPayload,
@@ -42,8 +43,10 @@ from controllers.openapi._models import (
DeviceMutateRequest,
DeviceMutateResponse,
DevicePollRequest,
DeviceTokenResponse,
FormSubmitResponse,
HealthResponse,
HumanInputFormDefinitionResponse,
MemberActionResponse,
MemberInvitePayload,
MemberInviteResponse,
@@ -92,6 +95,7 @@ register_schema_models(
register_response_schema_models(
openapi_ns,
ErrorBody,
EventStreamResponse,
TagItem,
UsageInfo,
MessageMetadata,
@@ -120,7 +124,9 @@ register_response_schema_models(
MemberActionResponse,
TaskStopResponse,
FormSubmitResponse,
HumanInputFormDefinitionResponse,
DeviceCodeResponse,
DeviceTokenResponse,
DeviceLookupResponse,
DeviceMutateResponse,
FileResponse,
+23 -7
View File
@@ -87,12 +87,8 @@ class AppDescribeInfo(AppInfoResponse):
class AppDescribeResponse(BaseModel):
info: AppDescribeInfo | None = None
# `parameters` (the app-config blob) and `input_schema` (a Draft 2020-12 JSON Schema derived
# per-app) are deliberately open JSON, not under-annotated. The `x-dify-opaque` marker tells the
# contract generator's readiness detector to treat them as intentional, so the route is not
# flagged "annotations incomplete". CLI/web consume them as opaque objects either way.
parameters: dict[str, Any] | None = Field(default=None, json_schema_extra={"x-dify-opaque": True})
input_schema: dict[str, Any] | None = Field(default=None, json_schema_extra={"x-dify-opaque": True})
parameters: dict[str, Any] | None = Field(default=None)
input_schema: dict[str, Any] | None = Field(default=None)
class ChatMessageResponse(BaseModel):
@@ -150,6 +146,18 @@ class WorkspacePayload(BaseModel):
role: str
class DeviceTokenResponse(BaseModel):
token: str
expires_at: str
subject_type: Literal["account", "external_sso"]
account: AccountPayload | None = None
workspaces: list[WorkspacePayload] = []
default_workspace_id: str | None = None
token_id: str
subject_email: str | None = None
subject_issuer: str | None = None
class AccountResponse(BaseModel):
subject_type: str
subject_email: str | None = None
@@ -292,7 +300,7 @@ class AppListQuery(BaseModel):
class AppRunRequest(BaseModel):
inputs: dict[str, Any]
query: str | None = None
files: list[dict[str, Any]] | None = None
files: list[dict[str, Any]] | None = Field(default=None)
conversation_id: UUIDStrOrEmpty | None = None
auto_generate_name: bool = True
workflow_id: str | None = None
@@ -469,3 +477,11 @@ class FormSubmitResponse(BaseModel):
than an under-annotated open object."""
model_config = ConfigDict(extra="forbid")
class HumanInputFormDefinitionResponse(BaseModel):
form_content: str
inputs: list[dict[str, Any]] = Field(default_factory=list)
resolved_default_values: dict[str, str]
user_actions: list[dict[str, Any]] = Field(default_factory=list)
expiration_time: int | None = None
+2 -1
View File
@@ -18,6 +18,7 @@ from werkzeug.exceptions import (
)
import services
from controllers.common.fields import EventStreamResponse
from controllers.openapi import openapi_ns
from controllers.openapi._audit import emit_app_run
from controllers.openapi._contract import accepts, returns
@@ -136,7 +137,7 @@ _DISPATCH: dict[AppMode, Callable[[App, Any, AppRunRequest], Any]] = {
@openapi_ns.route("/apps/<string:app_id>/run")
class AppRunApi(Resource):
@auth_router.guard(scope=Scope.APPS_RUN)
@openapi_ns.response(200, "Run result (SSE stream)")
@openapi_ns.response(200, "Run result (SSE stream)", openapi_ns.models[EventStreamResponse.__name__])
@accepts(body=AppRunRequest)
def post(self, app_id: str, *, auth_data: AuthData, body: AppRunRequest):
app_model, caller, caller_kind = auth_data.require_app_context()
+2 -2
View File
@@ -18,7 +18,7 @@ from controllers.common.human_input import HumanInputFormSubmitPayload, stringif
from controllers.common.schema import register_schema_models
from controllers.openapi import openapi_ns
from controllers.openapi._contract import accepts, returns
from controllers.openapi._models import FormSubmitResponse
from controllers.openapi._models import FormSubmitResponse, HumanInputFormDefinitionResponse
from controllers.openapi.auth.composition import auth_router
from controllers.openapi.auth.data import AuthData
from core.workflow.human_input_policy import HumanInputSurface, is_recipient_type_allowed_for_surface
@@ -57,7 +57,7 @@ def _ensure_form_is_allowed_for_openapi(form) -> None:
@openapi_ns.route("/apps/<string:app_id>/form/human_input/<string:form_token>")
class OpenApiWorkflowHumanInputFormApi(Resource):
@openapi_ns.response(200, "Form definition")
@openapi_ns.response(200, "Form definition", openapi_ns.models[HumanInputFormDefinitionResponse.__name__])
@auth_router.guard(scope=Scope.APPS_RUN)
def get(self, app_id: str, form_token: str, *, auth_data: AuthData):
app_model, caller, caller_kind = auth_data.require_app_context()
+2
View File
@@ -42,6 +42,7 @@ from controllers.openapi._models import (
DeviceMutateRequest,
DeviceMutateResponse,
DevicePollRequest,
DeviceTokenResponse,
WorkspacePayload,
)
from extensions.ext_database import db
@@ -130,6 +131,7 @@ class OAuthDeviceTokenApi(Resource):
"""RFC 8628 poll."""
@openapi_ns.expect(openapi_ns.models[DevicePollRequest.__name__])
@openapi_ns.response(200, "Device token", openapi_ns.models[DeviceTokenResponse.__name__])
def post(self):
payload = _validate_json(DevicePollRequest)
device_code = payload.device_code
+10 -1
View File
@@ -13,9 +13,12 @@ from collections.abc import Generator
from flask import Response, request
from flask_restx import Resource
from pydantic import BaseModel, Field
from sqlalchemy.orm import sessionmaker
from werkzeug.exceptions import NotFound, UnprocessableEntity
from controllers.common.fields import EventStreamResponse
from controllers.common.schema import query_params_from_model
from controllers.openapi import openapi_ns
from controllers.openapi.auth.composition import auth_router
from controllers.openapi.auth.data import AuthData
@@ -34,9 +37,15 @@ from repositories.factory import DifyAPIRepositoryFactory
from services.workflow_event_snapshot_service import build_workflow_event_stream
class WorkflowEventsQuery(BaseModel):
include_state_snapshot: bool = Field(default=False, description="Whether to include workflow state snapshots")
continue_on_pause: bool = Field(default=False, description="Whether to keep the event stream open on pause")
@openapi_ns.route("/apps/<string:app_id>/tasks/<string:task_id>/events")
class OpenApiWorkflowEventsApi(Resource):
@openapi_ns.response(200, "SSE event stream")
@openapi_ns.doc(params=query_params_from_model(WorkflowEventsQuery))
@openapi_ns.response(200, "SSE event stream", openapi_ns.models[EventStreamResponse.__name__])
@auth_router.guard(scope=Scope.APPS_RUN)
def get(self, app_id: str, task_id: str, *, auth_data: AuthData):
app_model, caller, caller_kind = auth_data.require_app_context()