Skip to content

feat(api): let agents build, configure and ship Dify apps over openapi - #43067

Merged
GareArc merged 76 commits into
mainfrom
feat/openapi-app-build-loop
Oct 16, 2026
Merged

GareArc merged 76 commits into
mainfrom
feat/openapi-app-build-loop

Conversation

@GareArc

@GareArc GareArc commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

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

  • Create a blank app; export the DSL with a 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).
  • List node types with their schema and defaults; test one node (local files included) or the whole draft; read runs and node steps.
  • A release check reports whether the draft is valid and tested, and what changed against the published version. Publish, list and restore versions; manage environment variables.

App settings

  • App info, web app, web app access mode and Service API, one op per app mode; agent apps keep their own ops.

Plugins, models, tools and knowledge bases

  • Search the marketplace, install and remove plugins, poll the install task.
  • List models across providers (each LLM row carries a ready node model block), tools (each with ready tool node data) and knowledge bases (each with ready retrieval node data).
  • Create and set provider, model and tool credentials. Same guards as the console: workspace role, RBAC and the plugin install setting.

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 in api/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):

Change Used by
WorkflowService.validate_node_credentials: the per-node check pulled out of publish's _validate_workflow_credentials Console publish (unchanged), openapi graph check
branch_handles and advisory_variable_reference_warning in workflow_variable_reference_validator.py; the warning moved there from the console controller Console publish warning, openapi publish and graph check
openapi publish calls WorkflowService.publish_workflow, the console's publish path openapi
WorkflowService.get_draft_workflow_for_update made public (was _get_draft_workflow_for_update) Draft sync, env variables, the DSL draft guard
AppDslService takes an optional draft_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 changes openapi import and confirm

Additions (no existing caller uses them):

Change Why
ModelProviderService.list_models, get_provider Models across providers; one provider read per request.
Credential creation returns the new id (ProviderConfiguration, ModelProviderService, BuiltinToolManageService.add_builtin_tool_provider) The id exists only inside the create call. Console callers ignore it; the console's tool response model drops the extra key.
EnterpriseWebAppAccessPolicyGateway.update_access_mode The access-mode write maps the same "Enterprise unavailable" errors as the reads.
Console app repository and service: partial update (AppFieldChanges), access_ready, create_draft with a starter graph (Chatflow: start → LLM → answer; Workflow: empty) openapi app create and settings. Console create, copy and import don't use it.
search_plugins in core/helper/marketplace.py Server-side marketplace search.
ROOT_VARIABLE_NODE_IDS in core/workflow/variable_prefixes.py The graph check's root selectors.
services/workflow/ dsl_import, graph_check, graph_diff, node_defaults openapi only. app_dsl_service imports graph_diff.draft_token for the draft guard.

Behavior changes for existing features

Where Before Now
Console publish warning (advisory, never blocks) An agent node with output routes fanned out to every edge. A branch node with a fail-branch got a source handle it never emits. A branch item with a missing id made the whole check quietly return no warning. Route ids are the agent's handles, as the engine runs them. No source on branch nodes. Bad items are skipped and the check still runs. Some apps get a new warning; some lose a false one.
Console POST .../apps/imports/<id>/confirm — 409 draft_workflow_not_sync when a pending import carries a draft_hash and the draft changed. Only reachable for an import started over openapi.
Workflow event snapshot (include_state_snapshot), all surfaces Replayed every form of a paused run, including ones already answered while the resume was queued. Replays only forms still WAITING. When none is left, no workflow_paused is sent and the stream waits for the resumed run. Scheduling pauses pass through unchanged.
Chat history form token A timed-out or expired form kept its token, so the chat showed live buttons next to the expired tip. Only a WAITING form gets a token. Others render with disabled buttons.
Enterprise update_app_access_mode Refused sso_verified before calling the enterprise API, so copying an app with that mode failed. Accepts public, private, private_all and sso_verified.
OAUTH_TTL_DAYS Read from os.environ on every token issue. A setting in AuthConfig, read once at startup (so a value only in api/.env now applies). Same default (14), same clamp (1–365), bad values fall back to the default.

Contract notes

  • A wrong app mode answers 422 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 run defaults triggered_from to debugging.
  • import console_app dsl applies the same rules to yaml-url and yaml-content: refuse a graph with errors (422 dsl_invalid), refuse a stale draft_hash (409 draft_changed), fill editor defaults, keep stored secrets. A confirm checks the hash the import was started with.
  • export console_app dsl uses main's AppExportService and reads the draft_hash first, in its own short read. If the draft changes in between, the hash is older than the DSL, so a later import refuses with 409 draft_changed instead of overwriting the edit. A missing app answers 404 not_found; a missing draft or version answers 404 draft_not_found / version_not_found.
  • Setting a web app public when WEBAPP_PUBLIC_ACCESS_ENABLED is off answers 403 webapp_public_access_disabled. Calling a Service API op on an app with the Service API off answers 403 service_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

  • This change requires a documentation update, included: Dify Document
  • I understand that this PR may be closed in case there was no previous discussion or issues. (This doesn't apply to typos!)
  • I've verified the change and added or updated tests where meaningful regression risk justifies coverage.
  • I've updated the documentation accordingly.
  • I ran make lint && make type-check (backend) and vp staged (frontend) to appease the lint gods

@github-actions github-actions Bot added size:XXL This PR changes 1000+ lines, ignoring generated files. web This relates to changes on the web. and removed size:XXL This PR changes 1000+ lines, ignoring generated files. labels Sep 28, 2026
@GareArc
GareArc added this pull request to stack #41621 September 28, 2026 10:41
@ghfind-review ghfind-review Bot added the review: high ghfind author score; see https://ghfind.com label Sep 28, 2026
@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Pyrefly Type Coverage

Metric Base PR Delta
Type coverage 74.83% 75.03% +0.20%
Strict coverage 74.49% 74.70% +0.21%
Typed symbols 61,069 61,969 +900
Untyped symbols 20,639 20,716 +77
Modules 3761 3793 +32

@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Pyrefly Diff

base → 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

codecov Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.22653% with 415 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.89%. Comparing base (310ce87) to head (4c2b2be).

Files with missing lines Patch % Lines
api/controllers/openapi/plugins.py 50.79% 62 Missing ⚠️
api/controllers/openapi/_app_settings.py 57.93% 52 Missing and 1 partial ⚠️
api/controllers/openapi/tool_providers.py 63.11% 45 Missing ⚠️
api/controllers/openapi/app_workflow.py 79.89% 34 Missing and 3 partials ⚠️
api/controllers/openapi/model_providers.py 77.94% 26 Missing and 4 partials ⚠️
api/repositories/app/console_repository.py 27.58% 21 Missing ⚠️
api/services/workflow/dsl_import.py 82.64% 20 Missing and 1 partial ⚠️
api/controllers/openapi/auth/requirements.py 74.68% 16 Missing and 4 partials ⚠️
api/controllers/openapi/app_webapp.py 81.63% 18 Missing ⚠️
api/controllers/openapi/app_release.py 58.53% 17 Missing ⚠️
... and 18 more
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     
Flag Coverage Δ
api 88.52% <84.18%> (-0.09%) ⬇️
cli 90.56% <ø> (ø)
dify-ui 92.25% <ø> (ø)
web 89.21% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@GareArc
GareArc force-pushed the feat/openapi-app-build-loop branch from c27bbde to fcf1dd5 Compare September 28, 2026 11:51
@github-actions github-actions Bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Sep 28, 2026
@GareArc
GareArc force-pushed the feat/openapi-app-build-loop branch 2 times, most recently from cb0ca06 to 110dd98 Compare September 28, 2026 12:32
Base automatically changed from feat/difyctl-v2 to main September 30, 2026 02:06
@GareArc
GareArc force-pushed the feat/openapi-app-build-loop branch from 110dd98 to 94d9134 Compare September 30, 2026 02:06
@GareArc
GareArc force-pushed the feat/openapi-app-build-loop branch 3 times, most recently from 6ac2535 to d0170b8 Compare October 7, 2026 21:32
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.
@GareArc
GareArc force-pushed the feat/openapi-app-build-loop branch from d0170b8 to 8b1ba1d Compare October 7, 2026 22:14
…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.
@GareArc
GareArc force-pushed the feat/openapi-app-build-loop branch from 257c33a to 7973c3e Compare October 9, 2026 02:02
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.
@GareArc
GareArc marked this pull request as ready for review October 14, 2026 21:46
…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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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.

Comment thread api/services/workflow/dsl_import.py
Comment thread api/controllers/openapi/app_release.py Outdated
- 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.
@GareArc
GareArc requested a review from wylswz October 15, 2026 04:33
wylswz
wylswz previously approved these changes Oct 15, 2026
@GareArc
GareArc added this pull request to the merge queue Oct 16, 2026
Merged via the queue into main with commit 8c5eb6d Oct 16, 2026
132 of 135 checks passed
@GareArc
GareArc deleted the feat/openapi-app-build-loop branch October 16, 2026 00:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review: high ghfind author score; see https://ghfind.com size:XXL This PR changes 1000+ lines, ignoring generated files. web This relates to changes on the web.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants