feat(openapi): publish the op catalog, per-mode run ops and server-built hints on /openapi/v1 (#42487)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Xiyuan Chenandautofix-ci[bot] authored and GitHub committed 2026-09-22 10:44:50 +00:00
1 parent dced031c95
commit dfc3bbe513
75 files changed
+3324 -803

No files matched your search

+63 -8
View File
@@ -6,8 +6,14 @@ from werkzeug.exceptions import Forbidden
from controllers.common.rbac import PlainApp, RBACCheck, RBACPermission, Workspace
from controllers.openapi import openapi_ns
from controllers.openapi._contract import endpoint
from controllers.openapi._models import AppDslExportQuery, AppDslExportResponse, AppDslImportPayload
from controllers.openapi._contract import Example, Kind, endpoint, op_of
from controllers.openapi._models import (
AppDslExportQuery,
AppDslExportResponse,
AppDslImportPayload,
AppDslImportResponse,
Hint,
)
from controllers.openapi.auth.context import Context
from controllers.openapi.auth.requirements import (
CheckAppApiEnabled,
@@ -27,6 +33,19 @@ from services.errors.account import NoPermissionError
from services.errors.app import WorkflowNotFoundError
def _import_response(result: Import, *, workspace_id: str) -> AppDslImportResponse:
hints: list[Hint] = []
if result.status == ImportStatus.PENDING:
hints.append(
Hint(
summary="Confirm the pending import",
op=op_of(AppDslImportConfirmApi.post),
input={"workspace_id": workspace_id, "import_id": result.id},
)
)
return AppDslImportResponse(**result.model_dump(), hints=hints)
@openapi_ns.route("/workspaces/<string:workspace_id>/apps/imports")
class AppDslImportApi(Resource):
"""Import a DSL YAML string into the specified workspace.
@@ -42,6 +61,23 @@ class AppDslImportApi(Resource):
"""
@endpoint(
op="console_app.dsl.import",
kind=Kind.OBJECT,
summary="Import an app from DSL text or URL",
examples=(
Example(
title="Import an app from inline YAML",
input={"mode": "yaml-content", "yaml_content": "<yaml text of the app DSL>"},
),
Example(
title="Import an app from a URL",
input={"mode": "yaml-url", "yaml_url": "https://example.com/apps/release-summary.yml"},
),
Example(
title="Overwrite an existing workflow app from YAML",
input={"mode": "yaml-content", "yaml_content": "<yaml text of the app DSL>", "app_id": "<app_id>"},
),
),
requirements=(
CheckSubject(allowed=(AccountSubject,)),
CheckScope(Scope.WORKSPACE_WRITE),
@@ -51,9 +87,9 @@ class AppDslImportApi(Resource):
),
body=AppDslImportPayload,
returns=(
(200, Import, "Import completed"),
(202, Import, "Import pending confirmation"),
(400, Import, "Import failed"),
(200, AppDslImportResponse, "Import completed"),
(202, AppDslImportResponse, "Import pending confirmation"),
(400, AppDslImportResponse, "Import failed"),
),
)
def post(self, ctx: Context, workspace_id: str, *, body: AppDslImportPayload):
@@ -81,13 +117,14 @@ class AppDslImportApi(Resource):
else:
session.commit()
response = _import_response(result, workspace_id=workspace_id)
match result.status:
case ImportStatus.FAILED:
return result, 400
return response, 400
case ImportStatus.PENDING:
return result, 202
return response, 202
case _:
return result, 200
return response, 200
@openapi_ns.route("/workspaces/<string:workspace_id>/apps/imports/<string:import_id>:confirm")
@@ -103,6 +140,10 @@ class AppDslImportConfirmApi(Resource):
"""
@endpoint(
op="console_app.dsl.import_confirm",
kind=Kind.OBJECT,
summary="Confirm a pending DSL import",
examples=(Example(title="Confirm an import that is pending confirmation", input={"import_id": "<import_id>"}),),
requirements=(
CheckSubject(allowed=(AccountSubject,)),
CheckScope(Scope.WORKSPACE_WRITE),
@@ -145,6 +186,16 @@ class AppDslExportApi(Resource):
"""
@endpoint(
op="console_app.dsl.export",
kind=Kind.OBJECT,
summary="Export app DSL as YAML text inside a JSON object",
examples=(
Example(title="Export the current draft as YAML", input={"app_id": "<app_id>"}),
Example(
title="Export a published version including secrets",
input={"app_id": "<app_id>", "workflow_id": "<workflow_id>", "include_secret": True},
),
),
requirements=(
CheckSubject(allowed=(AccountSubject,)),
CheckAppApiEnabled(),
@@ -180,6 +231,10 @@ class AppDslCheckDependenciesApi(Resource):
"""
@endpoint(
op="console_app.dependencies.check",
kind=Kind.OBJECT,
summary="Check plugin dependencies of an app",
examples=(Example(title="Check which plugins an app needs", input={"app_id": "<app_id>"}),),
requirements=(
CheckSubject(allowed=(AccountSubject,)),
CheckAppApiEnabled(),