Skip to content

Commit f3df2b8

Browse files
authored
Merge branch 'main' into fix/table-archive
2 parents aef9829 + 17bee5f commit f3df2b8

391 files changed

Lines changed: 21770 additions & 5267 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

api/clients/agent_backend/request_builder.py

Lines changed: 55 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,22 @@ def _filter_snapshot_to_specs(
7878
return CompositorSessionSnapshot(schema_version=snapshot.schema_version, layers=filtered_layers)
7979

8080

81-
def _shell_layer_deps(*, include_drive: bool) -> dict[str, str]:
82-
deps = {"execution_context": DIFY_EXECUTION_CONTEXT_LAYER_ID}
83-
if include_drive:
84-
deps["drive"] = DIFY_DRIVE_LAYER_ID
85-
return deps
81+
def _shell_layer_deps() -> dict[str, str]:
82+
return {"execution_context": DIFY_EXECUTION_CONTEXT_LAYER_ID}
83+
84+
85+
def _drive_layer_deps() -> dict[str, str]:
86+
return {"shell": DIFY_SHELL_LAYER_ID}
87+
88+
89+
def _shell_config_with_drive_ref(
90+
shell_config: DifyShellLayerConfig | None,
91+
drive_config: DifyDriveLayerConfig | None,
92+
) -> DifyShellLayerConfig:
93+
config = shell_config or DifyShellLayerConfig()
94+
if drive_config is None:
95+
return config
96+
return config.model_copy(update={"agent_stub_drive_ref": drive_config.drive_ref})
8697

8798

8899
class AgentBackendModelConfig(BaseModel):
@@ -263,14 +274,29 @@ def build_for_agent_app(self, run_input: AgentBackendAgentAppRunInput) -> Create
263274
]
264275
)
265276

277+
include_shell = run_input.include_shell or run_input.drive_config is not None
278+
if include_shell:
279+
# Sandboxed bash workspace (dify.shell). It enters before drive so
280+
# drive can materialize mentioned targets with `dify-agent drive pull`
281+
# in the same shell-visible filesystem used by model commands.
282+
layers.append(
283+
RunLayerSpec(
284+
name=DIFY_SHELL_LAYER_ID,
285+
type=DIFY_SHELL_LAYER_TYPE_ID,
286+
deps=_shell_layer_deps(),
287+
metadata=run_input.metadata,
288+
config=_shell_config_with_drive_ref(run_input.shell_config, run_input.drive_config),
289+
)
290+
)
291+
266292
if run_input.drive_config is not None:
267-
# Drive Skills & Files declaration (dify.drive): a config-only index;
268-
# the agent pulls listed entries through the back proxy by drive_ref.
293+
# Drive Skills & Files declaration (dify.drive): the catalog plus
294+
# prompt-mentioned entries eagerly pulled through the shell layer.
269295
layers.append(
270296
RunLayerSpec(
271297
name=DIFY_DRIVE_LAYER_ID,
272298
type=DIFY_DRIVE_LAYER_TYPE_ID,
273-
deps={"execution_context": DIFY_EXECUTION_CONTEXT_LAYER_ID},
299+
deps=_drive_layer_deps(),
274300
metadata=run_input.metadata,
275301
config=run_input.drive_config,
276302
)
@@ -312,7 +338,7 @@ def build_for_agent_app(self, run_input: AgentBackendAgentAppRunInput) -> Create
312338
)
313339
)
314340

315-
if run_input.knowledge is not None and run_input.knowledge.dataset_ids:
341+
if run_input.knowledge is not None and run_input.knowledge.sets:
316342
layers.append(
317343
RunLayerSpec(
318344
name=DIFY_KNOWLEDGE_BASE_LAYER_ID,
@@ -336,21 +362,6 @@ def build_for_agent_app(self, run_input: AgentBackendAgentAppRunInput) -> Create
336362
)
337363
)
338364

339-
if run_input.include_shell:
340-
# Sandboxed bash workspace (dify.shell). Depends on execution_context
341-
# so the agent server can mint per-command Agent Stub env, and on
342-
# drive when present so that env points at /mnt/drive/<drive_ref>.
343-
# shellctl connection itself is server-injected.
344-
layers.append(
345-
RunLayerSpec(
346-
name=DIFY_SHELL_LAYER_ID,
347-
type=DIFY_SHELL_LAYER_TYPE_ID,
348-
deps=_shell_layer_deps(include_drive=run_input.drive_config is not None),
349-
metadata=run_input.metadata,
350-
config=run_input.shell_config or DifyShellLayerConfig(),
351-
)
352-
)
353-
354365
if run_input.output is not None:
355366
layers.append(
356367
RunLayerSpec(
@@ -445,7 +456,7 @@ def build_for_workflow_node(self, run_input: AgentBackendWorkflowNodeRunInput) -
445456
name=WORKFLOW_NODE_JOB_PROMPT_LAYER_ID,
446457
type=PLAIN_PROMPT_LAYER_TYPE_ID,
447458
metadata={**run_input.metadata, "origin": "workflow_node_job"},
448-
config=PromptLayerConfig(prefix=run_input.workflow_node_job_prompt),
459+
config=PromptLayerConfig(user=run_input.workflow_node_job_prompt),
449460
),
450461
RunLayerSpec(
451462
name=WORKFLOW_USER_PROMPT_LAYER_ID,
@@ -462,14 +473,29 @@ def build_for_workflow_node(self, run_input: AgentBackendWorkflowNodeRunInput) -
462473
]
463474
)
464475

476+
include_shell = run_input.include_shell or run_input.drive_config is not None
477+
if include_shell:
478+
# Sandboxed bash workspace (dify.shell). It enters before drive so
479+
# drive can materialize mentioned targets with `dify-agent drive pull`
480+
# in the same shell-visible filesystem used by model commands.
481+
layers.append(
482+
RunLayerSpec(
483+
name=DIFY_SHELL_LAYER_ID,
484+
type=DIFY_SHELL_LAYER_TYPE_ID,
485+
deps=_shell_layer_deps(),
486+
metadata=run_input.metadata,
487+
config=_shell_config_with_drive_ref(run_input.shell_config, run_input.drive_config),
488+
)
489+
)
490+
465491
if run_input.drive_config is not None:
466-
# Drive Skills & Files declaration (dify.drive): a config-only index;
467-
# the agent pulls listed entries through the back proxy by drive_ref.
492+
# Drive Skills & Files declaration (dify.drive): the catalog plus
493+
# prompt-mentioned entries eagerly pulled through the shell layer.
468494
layers.append(
469495
RunLayerSpec(
470496
name=DIFY_DRIVE_LAYER_ID,
471497
type=DIFY_DRIVE_LAYER_TYPE_ID,
472-
deps={"execution_context": DIFY_EXECUTION_CONTEXT_LAYER_ID},
498+
deps=_drive_layer_deps(),
473499
metadata=run_input.metadata,
474500
config=run_input.drive_config,
475501
)
@@ -513,7 +539,7 @@ def build_for_workflow_node(self, run_input: AgentBackendWorkflowNodeRunInput) -
513539
)
514540
)
515541

516-
if run_input.knowledge is not None and run_input.knowledge.dataset_ids:
542+
if run_input.knowledge is not None and run_input.knowledge.sets:
517543
layers.append(
518544
RunLayerSpec(
519545
name=DIFY_KNOWLEDGE_BASE_LAYER_ID,
@@ -537,21 +563,6 @@ def build_for_workflow_node(self, run_input: AgentBackendWorkflowNodeRunInput) -
537563
)
538564
)
539565

540-
if run_input.include_shell:
541-
# Sandboxed bash workspace (dify.shell). Depends on execution_context
542-
# so the agent server can mint per-command Agent Stub env, and on
543-
# drive when present so that env points at /mnt/drive/<drive_ref>.
544-
# shellctl connection itself is server-injected.
545-
layers.append(
546-
RunLayerSpec(
547-
name=DIFY_SHELL_LAYER_ID,
548-
type=DIFY_SHELL_LAYER_TYPE_ID,
549-
deps=_shell_layer_deps(include_drive=run_input.drive_config is not None),
550-
metadata=run_input.metadata,
551-
config=run_input.shell_config or DifyShellLayerConfig(),
552-
)
553-
)
554-
555566
if run_input.output is not None:
556567
layers.append(
557568
RunLayerSpec(

api/configs/extra/agent_backend_config.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ class AgentBackendConfig(BaseSettings):
3636
description=(
3737
"Inject the dify.drive layer (Skills & Files drive manifest declaration) "
3838
"into Agent runs. The declaration is an index only — the agent backend "
39-
"pulls the actual SKILL.md / files through the back proxy. Keep it off "
40-
"until the agent backend registers the dify.drive layer type."
39+
"pulls the actual SKILL.md / files through the back proxy. Set this to "
40+
"false only when temporarily rolling back the drive integration."
4141
),
42-
default=False,
42+
default=True,
4343
)

api/controllers/common/fields.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ class Site(BaseModel):
183183
description: str | None = None
184184
copyright: str | None = None
185185
privacy_policy: str | None = None
186+
input_placeholder: str | None = None
186187
custom_disclaimer: str | None = None
187188
default_language: str
188189
show_workflow_steps: bool

api/controllers/console/agent/app_helpers.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,15 @@
66

77

88
def resolve_agent_app_model(*, tenant_id: str, agent_id: UUID) -> App:
9-
"""Resolve the hidden Agent App backing an Agent Console resource."""
9+
"""Resolve a roster Agent's public Agent App."""
1010
return AgentRosterService(db.session).get_agent_app_model(tenant_id=tenant_id, agent_id=str(agent_id))
11+
12+
13+
def resolve_agent_runtime_app_model(*, tenant_id: str, agent_id: UUID) -> App:
14+
"""Resolve the App that backs an Agent runtime surface.
15+
16+
This accepts both roster Agent Apps and workflow-only inline Agents with a
17+
hidden backing App.
18+
"""
19+
20+
return AgentRosterService(db.session).get_agent_runtime_app_model(tenant_id=tenant_id, agent_id=str(agent_id))

api/controllers/console/agent/composer.py

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
from uuid import UUID
22

3+
from flask import request
34
from flask_restx import Resource
45

5-
from controllers.common.schema import register_response_schema_models, register_schema_models
6+
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
67
from controllers.console import console_ns
7-
from controllers.console.agent.app_helpers import resolve_agent_app_model
88
from controllers.console.app.wraps import get_app_model
99
from controllers.console.wraps import (
1010
RBACPermission,
@@ -28,9 +28,15 @@
2828
from models.model import App, AppMode
2929
from services.agent.composer_service import AgentComposerService
3030
from services.agent.composer_validator import ComposerConfigValidator
31-
from services.entities.agent_entities import ComposerSavePayload, WorkflowComposerCopyFromRosterPayload
31+
from services.entities.agent_entities import (
32+
ComposerSavePayload,
33+
WorkflowAgentComposerQuery,
34+
WorkflowComposerCopyFromRosterPayload,
35+
)
3236

33-
register_schema_models(console_ns, ComposerSavePayload, WorkflowComposerCopyFromRosterPayload)
37+
register_schema_models(
38+
console_ns, ComposerSavePayload, WorkflowAgentComposerQuery, WorkflowComposerCopyFromRosterPayload
39+
)
3440
register_response_schema_models(
3541
console_ns,
3642
AgentAppComposerResponse,
@@ -41,27 +47,26 @@
4147
)
4248

4349

44-
def _resolve_agent_app_id(*, tenant_id: str, agent_id: UUID) -> str:
45-
return resolve_agent_app_model(tenant_id=tenant_id, agent_id=agent_id).id
46-
47-
4850
@console_ns.route("/apps/<uuid:app_id>/workflows/draft/nodes/<string:node_id>/agent-composer")
4951
class WorkflowAgentComposerApi(Resource):
5052
@console_ns.response(
5153
200, "Workflow agent composer state", console_ns.models[WorkflowAgentComposerResponse.__name__]
5254
)
55+
@console_ns.doc(params=query_params_from_model(WorkflowAgentComposerQuery))
5356
@setup_required
5457
@login_required
5558
@account_initialization_required
5659
@get_app_model(mode=[AppMode.WORKFLOW, AppMode.ADVANCED_CHAT])
5760
@with_current_tenant_id
5861
def get(self, tenant_id: str, app_model: App, node_id: str):
62+
query = WorkflowAgentComposerQuery.model_validate(request.args.to_dict(flat=True))
5963
return dump_response(
6064
WorkflowAgentComposerResponse,
6165
AgentComposerService.load_workflow_composer(
6266
tenant_id=tenant_id,
6367
app_id=app_model.id,
6468
node_id=node_id,
69+
snapshot_id=query.snapshot_id,
6570
),
6671
)
6772

@@ -137,6 +142,7 @@ class WorkflowAgentComposerValidateApi(Resource):
137142
def post(self, tenant_id: str, app_model: App, node_id: str):
138143
payload = ComposerSavePayload.model_validate(console_ns.payload or {})
139144
ComposerConfigValidator.validate_publish_payload(payload)
145+
AgentComposerService.validate_knowledge_datasets(tenant_id=tenant_id, agent_soul=payload.agent_soul)
140146
findings = AgentComposerService.collect_validation_findings(
141147
tenant_id=tenant_id,
142148
payload=payload,
@@ -228,10 +234,9 @@ class AgentComposerApi(Resource):
228234
@account_initialization_required
229235
@with_current_tenant_id
230236
def get(self, tenant_id: str, agent_id: UUID):
231-
app_id = _resolve_agent_app_id(tenant_id=tenant_id, agent_id=agent_id)
232237
return dump_response(
233238
AgentAppComposerResponse,
234-
AgentComposerService.load_agent_app_composer(tenant_id=tenant_id, app_id=app_id),
239+
AgentComposerService.load_agent_composer(tenant_id=tenant_id, agent_id=str(agent_id)),
235240
)
236241

237242
@console_ns.expect(console_ns.models[ComposerSavePayload.__name__])
@@ -244,13 +249,12 @@ def get(self, tenant_id: str, agent_id: UUID):
244249
@with_current_user_id
245250
@with_current_tenant_id
246251
def put(self, tenant_id: str, account_id: str, agent_id: UUID):
247-
app_id = _resolve_agent_app_id(tenant_id=tenant_id, agent_id=agent_id)
248252
payload = ComposerSavePayload.model_validate(console_ns.payload or {})
249253
return dump_response(
250254
AgentAppComposerResponse,
251-
AgentComposerService.save_agent_app_composer(
255+
AgentComposerService.save_agent_composer(
252256
tenant_id=tenant_id,
253-
app_id=app_id,
257+
agent_id=str(agent_id),
254258
account_id=account_id,
255259
payload=payload,
256260
),
@@ -268,9 +272,10 @@ class AgentComposerValidateApi(Resource):
268272
@account_initialization_required
269273
@with_current_tenant_id
270274
def post(self, tenant_id: str, agent_id: UUID):
271-
_resolve_agent_app_id(tenant_id=tenant_id, agent_id=agent_id)
275+
AgentComposerService.load_agent_composer(tenant_id=tenant_id, agent_id=str(agent_id))
272276
payload = ComposerSavePayload.model_validate(console_ns.payload or {})
273277
ComposerConfigValidator.validate_publish_payload(payload)
278+
AgentComposerService.validate_knowledge_datasets(tenant_id=tenant_id, agent_soul=payload.agent_soul)
274279
findings = AgentComposerService.collect_validation_findings(
275280
tenant_id=tenant_id,
276281
payload=payload,
@@ -290,12 +295,11 @@ class AgentComposerCandidatesApi(Resource):
290295
@with_current_user_id
291296
@with_current_tenant_id
292297
def get(self, tenant_id: str, current_user_id: str, agent_id: UUID):
293-
app_id = _resolve_agent_app_id(tenant_id=tenant_id, agent_id=agent_id)
294298
return dump_response(
295299
AgentComposerCandidatesResponse,
296300
AgentComposerService.get_agent_app_candidates(
297301
tenant_id=tenant_id,
298-
app_id=app_id,
302+
agent_id=str(agent_id),
299303
user_id=current_user_id,
300304
),
301305
)

0 commit comments

Comments
 (0)