Repository navigation
feat(api): let agents build, configure and ship Dify apps over openapi - #43067
Conversation
Pyrefly Type Coverage
|
Pyrefly Diffbase → PR--- /tmp/pyrefly_base.txt 2026-10-15 22:31:23.523699904 +0000
+++ /tmp/pyrefly_pr.txt 2026-10-15 22:31:14.307610721 +0000
@@ -555,17 +555,11 @@
ERROR Object of class `FunctionType` has no attribute `__handler__` [missing-attribute]
--> tests/test_containers_integration_tests/controllers/openapi/test_workspaces.py:27:22
ERROR Object of class `FunctionType` has no attribute `__handler__` [missing-attribute]
- --> tests/test_containers_integration_tests/controllers/openapi/test_workspaces.py:50:22
+ --> tests/test_containers_integration_tests/controllers/openapi/test_workspaces.py:47:22
ERROR Object of class `FunctionType` has no attribute `__handler__` [missing-attribute]
- --> tests/test_containers_integration_tests/controllers/openapi/test_workspaces.py:67:22
+ --> tests/test_containers_integration_tests/controllers/openapi/test_workspaces.py:64:22
ERROR Object of class `FunctionType` has no attribute `__handler__` [missing-attribute]
- --> tests/test_containers_integration_tests/controllers/openapi/test_workspaces.py:91:17
-ERROR Object of class `FunctionType` has no attribute `__handler__` [missing-attribute]
- --> tests/test_containers_integration_tests/controllers/openapi/test_workspaces.py:115:22
-ERROR Object of class `FunctionType` has no attribute `__handler__` [missing-attribute]
- --> tests/test_containers_integration_tests/controllers/openapi/test_workspaces.py:129:23
-ERROR Object of class `FunctionType` has no attribute `__handler__` [missing-attribute]
- --> tests/test_containers_integration_tests/controllers/openapi/test_workspaces.py:146:17
+ --> tests/test_containers_integration_tests/controllers/openapi/test_workspaces.py:87:17
ERROR Argument `Literal['emoji']` is not assignable to parameter `icon_type` with type `IconType | SQLCoreOperations[IconType | None] | None` in function `models.model.Site.__init__` [bad-argument-type]
--> tests/test_containers_integration_tests/controllers/web/test_site.py:64:19
ERROR Argument `SimpleNamespace` is not assignable to parameter `webapp_settings` with type `WebAppSettings | None` in function `controllers.web.wraps._validate_user_accessibility` [bad-argument-type]
@@ -1771,17 +1765,17 @@
ERROR Object of class `FunctionType` has no attribute `__handler__` [missing-attribute]
--> tests/unit_tests/controllers/openapi/test_account.py:69:18
ERROR Object of class `FunctionType` has no attribute `__handler__` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_app_run_streaming.py:87:18
+ --> tests/unit_tests/controllers/openapi/test_app_run_streaming.py:89:18
ERROR Object of class `Resource` has no attribute `post` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_app_run_streaming.py:147:20
+ --> tests/unit_tests/controllers/openapi/test_app_run_streaming.py:149:20
ERROR Object of class `Resource` has no attribute `post` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_app_run_streaming.py:162:13
+ --> tests/unit_tests/controllers/openapi/test_app_run_streaming.py:164:13
ERROR Object of class `Resource` has no attribute `post` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_app_run_streaming.py:187:9
+ --> tests/unit_tests/controllers/openapi/test_app_run_streaming.py:189:9
ERROR Object of class `Resource` has no attribute `post` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_app_run_streaming.py:210:20
+ --> tests/unit_tests/controllers/openapi/test_app_run_streaming.py:212:20
ERROR Object of class `Resource` has no attribute `post` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_app_run_streaming.py:228:20
+ --> tests/unit_tests/controllers/openapi/test_app_run_streaming.py:230:20
ERROR Object of class `FunctionType` has no attribute `view_class` [missing-attribute]
--> tests/unit_tests/controllers/openapi/test_device_approve_deny.py:41:12
ERROR Object of class `FunctionType` has no attribute `view_class` [missing-attribute]
@@ -1791,23 +1785,25 @@
ERROR `in` is not supported between `Literal['POST']` and `None` [not-iterable]
--> tests/unit_tests/controllers/openapi/test_device_approve_deny.py:53:12
ERROR Object of class `UnprocessableEntity` has no attribute `data` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_error_contract.py:99:9
+ --> tests/unit_tests/controllers/openapi/test_error_contract.py:101:9
ERROR Object of class `UnprocessableEntity` has no attribute `data` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_error_contract.py:112:16
+ --> tests/unit_tests/controllers/openapi/test_error_contract.py:114:16
ERROR Object of class `UnprocessableEntity` has no attribute `data` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_error_contract.py:116:9
+ --> tests/unit_tests/controllers/openapi/test_error_contract.py:118:9
ERROR Object of class `UnprocessableEntity` has no attribute `data` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_error_contract.py:131:9
+ --> tests/unit_tests/controllers/openapi/test_error_contract.py:133:9
ERROR Object of class `FunctionType` has no attribute `__handler__` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_human_input_form.py:116:20
+ --> tests/unit_tests/controllers/openapi/test_human_input_form.py:126:20
ERROR Object of class `FunctionType` has no attribute `__handler__` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_human_input_form.py:135:17
+ --> tests/unit_tests/controllers/openapi/test_human_input_form.py:145:17
ERROR Object of class `FunctionType` has no attribute `__handler__` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_human_input_form.py:148:17
+ --> tests/unit_tests/controllers/openapi/test_human_input_form.py:158:17
ERROR Object of class `FunctionType` has no attribute `__handler__` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_human_input_form.py:166:22
+ --> tests/unit_tests/controllers/openapi/test_human_input_form.py:176:22
ERROR Object of class `FunctionType` has no attribute `__handler__` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_human_input_form.py:193:22
+ --> tests/unit_tests/controllers/openapi/test_human_input_form.py:203:22
+ERROR Object of class `FunctionType` has no attribute `__handler__` [missing-attribute]
+ --> tests/unit_tests/controllers/openapi/test_human_input_form.py:229:20
ERROR Object of class `FunctionType` has no attribute `__handler__` [missing-attribute]
--> tests/unit_tests/controllers/openapi/test_workflow_events_openapi.py:135:17
ERROR Object of class `FunctionType` has no attribute `__handler__` [missing-attribute]
@@ -1823,29 +1819,25 @@
ERROR Object of class `FunctionType` has no attribute `__handler__` [missing-attribute]
--> tests/unit_tests/controllers/openapi/test_workflow_events_openapi.py:280:20
ERROR Object of class `FunctionType` has no attribute `__handler__` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:243:14
-ERROR Object of class `FunctionType` has no attribute `__handler__` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:279:9
-ERROR Object of class `FunctionType` has no attribute `__handler__` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:301:14
+ --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:242:14
ERROR Object of class `FunctionType` has no attribute `__handler__` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:328:14
+ --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:269:14
ERROR Object of class `FunctionType` has no attribute `__handler__` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:398:14
+ --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:339:14
ERROR Object of class `FunctionType` has no attribute `__handler__` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:491:9
+ --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:432:9
ERROR Object of class `FunctionType` has no attribute `__handler__` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:522:14
+ --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:463:14
ERROR Object of class `FunctionType` has no attribute `__handler__` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:557:9
+ --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:498:9
ERROR Object of class `FunctionType` has no attribute `__handler__` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:575:13
+ --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:516:13
ERROR Object of class `FunctionType` has no attribute `__handler__` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:578:13
+ --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:519:13
ERROR Object of class `FunctionType` has no attribute `__handler__` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:609:14
+ --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:550:14
ERROR Object of class `FunctionType` has no attribute `__handler__` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:723:9
+ --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:664:9
ERROR Argument `list[FromClause]` is not assignable to parameter `tables` with type `Sequence[Table] | None` in function `sqlalchemy.sql.schema.MetaData.create_all` [bad-argument-type]
--> tests/unit_tests/controllers/service_api/app/test_app.py:90:56
ERROR Returned type `AppDatabase` is not assignable to declared return type `Iterator[AppDatabase]` [bad-return]
@@ -3277,9 +3269,9 @@
ERROR Object of class `NoneType` has no attribute `id` [missing-attribute]
--> tests/unit_tests/core/entities/test_entities_provider_configuration.py:921:12
ERROR Argument `str | None` is not assignable to parameter `s` with type `bytearray | bytes | str` in function `json.loads` [bad-argument-type]
- --> tests/unit_tests/core/entities/test_entities_provider_configuration.py:973:23
+ --> tests/unit_tests/core/entities/test_entities_provider_configuration.py:974:23
ERROR Argument `str | None` is not assignable to parameter `s` with type `bytearray | bytes | str` in function `json.loads` [bad-argument-type]
- --> tests/unit_tests/core/entities/test_entities_provider_configuration.py:1172:23
+ --> tests/unit_tests/core/entities/test_entities_provider_configuration.py:1174:23
ERROR Argument `list[FromClause]` is not assignable to parameter `tables` with type `Sequence[Table] | None` in function `sqlalchemy.sql.schema.MetaData.create_all` [bad-argument-type]
--> tests/unit_tests/core/file/test_remote_fetcher.py:41:58
ERROR Class member `_ModelInstance.invoke_llm` overrides parent class `ModelInstance` in an inconsistent manner [bad-override]
@@ -5627,13 +5619,13 @@
ERROR `Literal['2']` is not assignable to TypedDict key `limit` with type `int` [bad-assignment]
--> tests/unit_tests/services/test_workflow_run_service.py:104:28
ERROR `dict[str, str]` is not assignable to dict value type `list[Unknown] | str` [bad-assignment]
- --> tests/unit_tests/services/test_workflow_service.py:207:30
+ --> tests/unit_tests/services/test_workflow_service.py:208:30
ERROR `dict[@_, @_]` is not assignable to TypedDict key `data` with type `BaseNodeData` [bad-assignment]
- --> tests/unit_tests/services/test_workflow_service.py:3560:54
+ --> tests/unit_tests/services/test_workflow_service.py:3591:54
ERROR Argument `Literal['invalid']` is not assignable to parameter `value` with type `AppMode | SQLCoreOperations[AppMode]` in function `sqlalchemy.orm.base.Mapped.__set__` [bad-argument-type]
- --> tests/unit_tests/services/test_workflow_service.py:3606:20
+ --> tests/unit_tests/services/test_workflow_service.py:3637:20
ERROR Argument `dict[str, str | dict[str, str]]` is not assignable to parameter `node_config` with type `NodeConfigDict` in function `services.workflow_service.WorkflowService._build_human_input_node_for_debugging` [bad-argument-type]
- --> tests/unit_tests/services/test_workflow_service.py:3637:65
+ --> tests/unit_tests/services/test_workflow_service.py:3668:65
ERROR Object of class `MCPToolProvider` has no attribute `provider_icon` [missing-attribute]
--> tests/unit_tests/services/tools/test_mcp_tools_transform.py:71:5
ERROR Object of class `MCPToolProvider` has no attribute `masked_server_url` [missing-attribute]
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #43067 +/- ##
==========================================
- Coverage 88.93% 88.89% -0.05%
==========================================
Files 5287 5305 +18
Lines 324108 326542 +2434
Branches 63711 63833 +122
==========================================
+ Hits 288247 290280 +2033
- Misses 30682 31065 +383
- Partials 5179 5197 +18
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
c27bbde to
fcf1dd5
Compare
cb0ca06 to
110dd98
Compare
110dd98 to
94d9134
Compare
6ac2535 to
d0170b8
Compare
DSL export returns a draft_hash covering the graph, features, environment and conversation variables; an import that sends an older hash fails instead of wiping newer edits. A blank secret imported over an app keeps the stored value. The incompatible-node error names the nodes. New openapi ops: draft test runs, run history with node steps, publish, version list and restore, and environment variables (secrets masked on read). Publish shares one service method with the console.
d0170b8 to
8b1ba1d
Compare
…nd node test runs Adds guards and enterprise calls for app settings. Adds node types, blank workflow and advanced-chat creation, and single-node test runs. Adds per-mode app info, Service API, web app and web-app access settings. Null means keep on patches. Regenerates the openapi markdown.
Adds marketplace search, install, install task, upgrade and uninstall. Adds model provider credentials, custom-model credentials, models and default models. Adds tool provider API-key credentials. Adds hints that lead from a missing dependency to a working credential, and a dependency-check install hint.
…and lint One skill with progressive disclosure: setup, operations, build-workflow phases (spec, plan, build, handover, change, dsl), and plugins. Adds a lint that checks links, depth, sizes and every command and flag against the catalog fixture.
Offline install skills from the embedded copy (Bun 1.4 --asset). Adds a CI smoke check, a stderr notice on difyctl help, and a next-step line in all four installers.
257c33a to
7973c3e
Compare
The ops added for the skill-tree lint now carry only field names, not full schemas or examples.
Enterprise's inner API has no routes for reading or searching the member list, so describe and set failed with 503. describe now returns only access_mode, set accepts public, private_all and sso_verified, and private is refused with a pointer to the console. get.access_subject is removed. set no longer reads a result field that Enterprise never returns.
With no pending login, --resume now prints the saved login or fails with not_logged_in. Root help says how to log in, and the setup guide tells agents to check first, wait up to expires_in, and check again before starting over.
graphon's default HTTP body has no data list. A DSL copied from describe.node_type kept that shape, and the workflow editor crashed on load when it read body.data. The editor now fills in an empty list when it loads a graph, and describe.node_type returns one.
…own model Each sub-object of a node type's default config is filled with the keys its model defaults, and keys the default already has are kept. This replaces the http-request special case.
Web app access reads map Enterprise failures to 503 and set returns the written mode. Plugin install and upgrade map marketplace errors to 503. Tool credential errors return 422 only for failed validation. Env var ops return 404 for a missing draft or id. login --resume works under an env login and checks the token; login and logout clear a stale pending login. The skill pipes DSL through stdin, covers pending imports, and warns that turning off the Service API hides the app from lists.
A refused overwrite cached the DSL's plugin dependencies and edited the app before the draft check raised draft_changed. Lock and compare the draft first.
… and a set.service_api hint The gate raised a bare Forbidden, so the body had the generic forbidden code and no next step. Raise ServiceApiDisabled, whose hint holds the op that turns the Service API on (set.service_api.agent for agent apps) with app_id and enabled ready.
…der status The hint to ask the user for credentials showed whenever the caller could not see a saved credential, even when the provider was already active through one it could not see. Gate it on the provider's configured status, as describe.tool_provider does.
A timed-out or expired form kept its token, so the chat rendered live buttons for it. Now that the snapshot drops forms that are no longer waiting, a click on such a form waited for a pause that never came. Without a token the buttons are disabled next to the expired tip.
…p tests Main now rejects new Mock, MagicMock and SimpleNamespace calls in tests. Rewrite the tests this branch added to use real rows on the sqlite session, real services and contexts, and small recorders. Also set config through config_overrides, and give the starter-draft test a signal with no receivers so its result doesn't depend on test order.
Main moved DSL export onto AppExportService, which owns its session. The openapi export now uses it too and reads the draft_hash first, in its own short read: if the draft changes in between, the hash is older than the DSL and a later import refuses with draft_changed instead of overwriting the edit. A missing app answers not_found; a missing draft or version keeps draft_not_found and version_not_found.
…current field difyctl keeps its workspace pin on the device and no longer calls the server switch, so the op had no caller. `current` on the workspace list and detail reported the web console's workspace, which read as the one difyctl uses.
The openapi DSL import rules opened their own sessions in the service layer. DslImportService now takes a DslImportStore; DslImportRepository owns the session and returns plain data: the target app's mode, its draft token and stored secret ids, and the credential check scoped to one session.
There was a problem hiding this comment.
🟡 Changes recommended
Release checks can certify untested configuration, and DSL preprocessing can bypass the original input-size limit.
2 open findings
What changed in this PR
Extends OpenAPI so agents can build, configure, test and publish Dify apps, supporting the stacked difyctl changes and broader infrastructure-as-code work.
Changes:
- Adds app creation, draft validation, guarded imports, testing and release operations.
- Exposes app settings and workspace resource discovery with actionable hints.
- Reuses shared services, updates human-input handling and adds regression coverage.
| File | Description |
|---|---|
| packages/contracts/generated/api/console/apps/types.gen.ts | Generated import-conflict response type. |
| docker/envs/core-services/api.env.example | Documents optional OAuth token lifetime. |
| api/tests/unit_tests/services/workflow/test_workflow_event_snapshot_service.py | Tests filtering closed forms from snapshots. |
| api/tests/unit_tests/services/workflow/test_node_defaults.py | Tests editor defaults and legacy normalization. |
| api/tests/unit_tests/services/workflow/test_graph_diff.py | Tests workflow comparisons and draft tokens. |
| api/tests/unit_tests/services/workflow/test_dsl_import.py | Tests import validation and secret preservation. |
| api/tests/unit_tests/services/test_workflow_variable_reference_validator.py | Tests branch handles and route analysis. |
| api/tests/unit_tests/services/test_workflow_service.py | Tests version filtering and environment-name conflicts. |
| api/tests/unit_tests/services/test_webapp_access_adapters.py | Extends access-policy test double. |
| api/tests/unit_tests/services/test_oauth_device_adapters.py | Tests configured token lifetime. |
| api/tests/unit_tests/services/test_model_provider_service.py | Tests model filters and credential IDs. |
| api/tests/unit_tests/services/test_app_dsl_service.py | Tests guarded imports and confirmations. |
| api/tests/unit_tests/services/enterprise/test_enterprise_service.py | Tests SSO-verified access updates. |
| api/tests/unit_tests/services/app/test_console_service.py | Guards unexpected draft creation in tests. |
| api/tests/unit_tests/services/app/test_console_gateway.py | Tests starter drafts and hash forwarding. |
| api/tests/unit_tests/repositories/test_sqlalchemy_execution_extra_content_repository.py | Tests waiting-only form tokens. |
| api/tests/unit_tests/repositories/app/test_dsl_import_repository.py | Tests tenant-scoped draft reads. |
| api/tests/unit_tests/core/entities/test_entities_provider_configuration.py | Verifies returned credential IDs. |
| api/tests/unit_tests/controllers/openapi/test_workspaces_members.py | Removes workspace-switch tests. |
| api/tests/unit_tests/controllers/openapi/test_tool_providers.py | Tests tool fragments and filtering. |
| api/tests/unit_tests/controllers/openapi/test_plugins.py | Tests marketplace network-error translation. |
| api/tests/unit_tests/controllers/openapi/test_node_types.py | Tests node default responses. |
| api/tests/unit_tests/controllers/openapi/test_models.py | Updates describe-response expectations. |
| api/tests/unit_tests/controllers/openapi/test_knowledge_bases.py | Tests retrieval fragments and usability. |
| api/tests/unit_tests/controllers/openapi/test_human_input_form.py | Tests resumed-run hints. |
| api/tests/unit_tests/controllers/openapi/test_hints.py | Updates pagination-hint tests. |
| api/tests/unit_tests/controllers/openapi/test_error_contract.py | Tests error hint serialization. |
| api/tests/unit_tests/controllers/openapi/test_contract.py | Tests preserving pagination hints. |
| api/tests/unit_tests/controllers/openapi/test_catalog.py | Updates operation-catalog expectations. |
| api/tests/unit_tests/controllers/openapi/test_app_settings.py | Tests access settings and web-app paths. |
| api/tests/unit_tests/controllers/openapi/test_app_run_streaming.py | Tests mode errors and draft hints. |
| api/tests/unit_tests/controllers/openapi/test_app_release.py | Tests mode-specific draft-test hints. |
| api/tests/unit_tests/controllers/openapi/test_app_describe_builder.py | Tests settings discovery hints. |
| api/tests/unit_tests/controllers/openapi/auth/test_resource_access.py | Updates workspace access expectations. |
| api/tests/unit_tests/controllers/openapi/auth/test_requirements.py | Tests Service API enablement hints. |
| api/tests/unit_tests/controllers/openapi/auth/test_pipelines.py | Updates disabled-API error expectations. |
| api/tests/unit_tests/controllers/openapi/auth/_world.py | Supports app modes in fixtures. |
| api/tests/unit_tests/controllers/console/app/test_workflow.py | Updates shared validator mocks. |
| api/tests/unit_tests/controllers/console/app/test_app_import_api.py | Tests stale-import conflict responses. |
| api/tests/unit_tests/controllers/conftest.py | Wires DSL preprocessing into fixtures. |
| api/tests/unit_tests/configs/test_auth_config.py | Tests token-lifetime defaults and clamping. |
| api/tests/test_containers_integration_tests/controllers/openapi/test_workspaces.py | Removes server-switch expectations. |
| api/tests/test_containers_integration_tests/controllers/openapi/test_app_dsl.py | Updates missing-draft error expectations. |
| api/services/workspace/service.py | Removes workspace-switch delegation. |
| api/services/workflow/node_defaults.py | Supplies editor-compatible node defaults. |
| api/services/workflow/graph_diff.py | Adds workflow comparisons and draft fingerprints. |
| api/services/workflow/dsl_import.py | Adds OpenAPI import preprocessing. |
| api/services/workflow_variable_reference_validator.py | Shares branch analysis and publish warnings. |
| api/services/workflow_service.py | Exposes draft locking and credential validation. |
| api/services/workflow_event_snapshot_service.py | Replays only waiting human-input forms. |
| api/services/webapp_access_adapters.py | Adds access-mode write error mapping. |
| api/services/tools/builtin_tools_manage_service.py | Returns created tool credential IDs. |
| api/services/oauth_device_adapters.py | Reads token lifetime from configuration. |
| api/services/model_provider/service.py | Adds discovery helpers and credential IDs. |
| api/services/errors/app.py | Defines draft-change conflicts. |
| api/services/entities/dsl_entities.py | Adds guarded import parameters. |
| api/services/entities/app_entities.py | Defines partial app updates. |
| api/services/enterprise/enterprise_service.py | Accepts SSO-verified access mode. |
| api/services/app/console_service.py | Adds partial updates and starter-draft creation. |
| api/services/app/console_gateway.py | Creates starter graphs and forwards draft hashes. |
| api/services/app_dsl_service.py | Locks and checks guarded imports. |
| api/repositories/sqlalchemy_execution_extra_content_repository.py | Withholds tokens for closed forms. |
| api/repositories/app/dsl_import_repository.py | Loads import targets and credential context. |
| api/repositories/app/console_repository.py | Implements partial updates and agent settings reads. |
| api/openapi/markdown/console-openapi.md | Generated documentation for import conflicts. |
| api/extensions/application_services/app.py | Registers DSL preprocessing services. |
| api/core/workflow/variable_prefixes.py | Defines root variable selectors. |
| api/core/helper/marketplace.py | Adds typed marketplace search. |
| api/core/entities/provider_configuration.py | Returns newly created credential IDs. |
| api/core/entities/execution_extra_content.py | Clarifies form-token availability comment. |
| api/controllers/openapi/workspaces.py | Removes server-side workspace switching. |
| api/controllers/openapi/workflow_events.py | Clarifies run-ID input documentation. |
| api/controllers/openapi/node_types.py | Exposes node schemas and defaults. |
| api/controllers/openapi/knowledge_bases.py | Lists knowledge bases with retrieval fragments. |
| api/controllers/openapi/human_input_form.py | Adds follow-run hints after submission. |
| api/controllers/openapi/auth/requirements.py | Adds reusable authorization and settings guards. |
| api/controllers/openapi/apps.py | Adds mode-specific settings hints. |
| api/controllers/openapi/app_run.py | Adds draft-test streaming routes. |
| api/controllers/openapi/app_release.py | Reports release readiness and published differences. |
| api/controllers/openapi/app_create.py | Creates Workflow and Chatflow starter apps. |
| api/controllers/openapi/_search.py | Adds shared text filtering. |
| api/controllers/openapi/_i18n.py | Selects localized resource labels. |
| api/controllers/openapi/_hints.py | Supports page and cursor hints. |
| api/controllers/openapi/_contract.py | Preserves hints and serializes aliases. |
| api/controllers/openapi/_app_settings.py | Shares app-settings reads and writes. |
| api/controllers/openapi/__init__.py | Registers new routes and schemas. |
| api/controllers/console/app/workflow.py | Reuses shared publish warnings. |
| api/controllers/console/app/app_import.py | Maps stale confirmations to HTTP 409. |
| api/configs/feature/__init__.py | Configures and clamps OAuth token lifetime. |
| api/.env.example | Documents optional OAuth token lifetime. |
🧠 Review effort: Balanced
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- The DSL import rules parse and re-dump the YAML, which drops comments, so a DSL padded past 10MB could shrink under the import's own limit. They now refuse oversized input before parsing; the import then rejects it as sent. - The release check counted a passing run as tested when only the graph matched. A run keeps only its graph, so a draft saved after the run started (environment or conversation variables, features) now needs a new test.


Summary
Server ops that let an agent build, configure and ship a Workflow or Chatflow app over
/openapi/v1. The difyctl side is #43819, stacked on this PR.The new APIs also contribute to #42075
Build loop
draft_hash; check a DSL without importing it; import it (refused with every problem located when the graph is invalid, and refused when the draft changed since the export).App settings
Plugins, models, tools and knowledge bases
modelblock), tools (each with ready tool node data) and knowledge bases (each with ready retrieval node data).Errors carry
hints: the next op to run, with its input filled in.Reviewing: about 9.3k of the added lines are generated: the openapi contracts in
packages/contracts/generated/and the docs inapi/openapi/markdown/. CI regenerates them; skip them. The hand-written code is about 5.5k lines, plus 2.3k of tests.Changes to shared code
openapi reuses the console's code instead of keeping its own copies. Where openapi needed something the console code didn't offer, the shared code was extended in place. The console, service API and web app keep their behavior, apart from the changes listed under "Behavior changes for existing features".
Reused or extracted (same logic for existing callers):
WorkflowService.validate_node_credentials: the per-node check pulled out of publish's_validate_workflow_credentialsbranch_handlesandadvisory_variable_reference_warninginworkflow_variable_reference_validator.py; the warning moved there from the console controllerWorkflowService.publish_workflow, the console's publish pathWorkflowService.get_draft_workflow_for_updatemade public (was_get_draft_workflow_for_update)AppDslServicetakes an optionaldraft_hash(DraftGuardedImportParams,PendingData.draft_hash). With it, the import locks the draft row and refuses before any write if the draft changed. Without it (every console and service caller), nothing changesAdditions (no existing caller uses them):
ModelProviderService.list_models,get_providerProviderConfiguration,ModelProviderService,BuiltinToolManageService.add_builtin_tool_provider)EnterpriseWebAppAccessPolicyGateway.update_access_modeAppFieldChanges),access_ready,create_draftwith a starter graph (Chatflow: start → LLM → answer; Workflow: empty)search_pluginsincore/helper/marketplace.pyROOT_VARIABLE_NODE_IDSincore/workflow/variable_prefixes.pyservices/workflow/dsl_import,graph_check,graph_diff,node_defaultsapp_dsl_serviceimportsgraph_diff.draft_tokenfor the draft guard.Behavior changes for existing features
sourcehandle it never emits. A branch item with a missing id made the whole check quietly return no warning.sourceon branch nodes. Bad items are skipped and the check still runs. Some apps get a new warning; some lose a false one.POST .../apps/imports/<id>/confirmdraft_workflow_not_syncwhen a pending import carries adraft_hashand the draft changed. Only reachable for an import started over openapi.include_state_snapshot), all surfacesWAITING. When none is left, noworkflow_pausedis sent and the stream waits for the resumed run. Scheduling pauses pass through unchanged.WAITINGform gets a token. Others render with disabled buttons.update_app_access_modesso_verifiedbefore calling the enterprise API, so copying an app with that mode failed.OAUTH_TTL_DAYSos.environon every token issue.AuthConfig, read once at startup (so a value only inapi/.envnow applies). Same default (14), same clamp (1–365), bad values fall back to the default.Contract notes
app_mode_mismatch, checked in the route guards after workspace membership. The per-mode run routes check it after the access check, so a refused caller can't learn the app's mode.get rundefaultstriggered_fromtodebugging.import console_app dslapplies the same rules toyaml-urlandyaml-content: refuse a graph with errors (422dsl_invalid), refuse a staledraft_hash(409draft_changed), fill editor defaults, keep stored secrets. A confirm checks the hash the import was started with.export console_app dsluses main'sAppExportServiceand reads thedraft_hashfirst, in its own short read. If the draft changes in between, the hash is older than the DSL, so a later import refuses with 409draft_changedinstead of overwriting the edit. A missing app answers 404not_found; a missing draft or version answers 404draft_not_found/version_not_found.WEBAPP_PUBLIC_ACCESS_ENABLEDis off answers 403webapp_public_access_disabled. Calling a Service API op on an app with the Service API off answers 403service_api_disabled, with a hint to turn it on.Verified: unit tests, lint and type checks pass. The build loop, plugin ops and app settings were run against an Enterprise dev server with difyctl and a DeepSeek-driven agent.
Screenshots
N/A (API)
Checklist
make lint && make type-check(backend) andvp staged(frontend) to appease the lint gods