mirror of
https://github.com/langgenius/dify.git
synced 2026-06-17 23:21:12 +08:00
fix(agent): align config detail and output contracts (#37535)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
7cb4a30040
commit
bacc48d16e
@ -95,7 +95,8 @@ def _serialize_agent_app_detail(app_model) -> dict:
|
||||
app_setting = EnterpriseService.WebAppAuth.get_app_access_mode_by_id(app_id=str(app_model.id))
|
||||
app_model.access_mode = app_setting.access_mode # type: ignore[attr-defined]
|
||||
|
||||
agent = _agent_roster_service().get_app_backing_agent(tenant_id=app_model.tenant_id, app_id=app_model.id)
|
||||
roster_service = _agent_roster_service()
|
||||
agent = roster_service.get_app_backing_agent(tenant_id=app_model.tenant_id, app_id=app_model.id)
|
||||
if not agent:
|
||||
raise AgentNotFoundError()
|
||||
payload = AppDetailWithSite.model_validate(app_model, from_attributes=True).model_dump(mode="json")
|
||||
@ -103,15 +104,24 @@ def _serialize_agent_app_detail(app_model) -> dict:
|
||||
payload["app_id"] = str(app_model.id)
|
||||
payload["id"] = agent.id
|
||||
payload["role"] = agent.role or ""
|
||||
payload["active_config_is_published"] = roster_service.active_config_is_published(
|
||||
tenant_id=app_model.tenant_id,
|
||||
agent=agent,
|
||||
)
|
||||
return payload
|
||||
|
||||
|
||||
def _serialize_agent_app_pagination(app_pagination, *, tenant_id: str) -> dict:
|
||||
app_ids = [str(app.id) for app in app_pagination.items]
|
||||
agents_by_app_id = _agent_roster_service().load_app_backing_agents_by_app_id(
|
||||
roster_service = _agent_roster_service()
|
||||
agents_by_app_id = roster_service.load_app_backing_agents_by_app_id(
|
||||
tenant_id=tenant_id,
|
||||
app_ids=app_ids,
|
||||
)
|
||||
active_config_is_published_by_agent_id = roster_service.load_active_config_is_published_by_agent_id(
|
||||
tenant_id=tenant_id,
|
||||
agents=list(agents_by_app_id.values()),
|
||||
)
|
||||
payload = AppPagination.model_validate(app_pagination, from_attributes=True).model_dump(mode="json")
|
||||
for item in payload["data"]:
|
||||
app_id = item["id"]
|
||||
@ -121,6 +131,7 @@ def _serialize_agent_app_pagination(app_pagination, *, tenant_id: str) -> dict:
|
||||
item["app_id"] = app_id
|
||||
item["id"] = agent.id
|
||||
item["role"] = agent.role or ""
|
||||
item["active_config_is_published"] = active_config_is_published_by_agent_id.get(agent.id, False)
|
||||
return payload
|
||||
|
||||
|
||||
|
||||
@ -403,6 +403,7 @@ class AppPartial(ResponseModel):
|
||||
# For Agent App responses exposed through /agent.
|
||||
app_id: str | None = None
|
||||
role: str | None = None
|
||||
active_config_is_published: bool = False
|
||||
is_starred: bool = False
|
||||
|
||||
@computed_field(return_type=str | None) # type: ignore
|
||||
@ -457,6 +458,7 @@ class AppDetailWithSite(AppDetail):
|
||||
# For Agent App responses exposed through /agent.
|
||||
app_id: str | None = None
|
||||
role: str | None = None
|
||||
active_config_is_published: bool = False
|
||||
|
||||
@computed_field(return_type=str | None) # type: ignore
|
||||
@property
|
||||
|
||||
@ -42,6 +42,7 @@ from models.agent import Agent, AgentConfigSnapshot, WorkflowAgentNodeBinding
|
||||
from models.agent_config_entities import (
|
||||
AgentSoulConfig,
|
||||
DeclaredArrayItem,
|
||||
DeclaredOutputChildConfig,
|
||||
DeclaredOutputConfig,
|
||||
DeclaredOutputType,
|
||||
WorkflowNodeJobConfig,
|
||||
@ -395,7 +396,11 @@ class WorkflowAgentRuntimeRequestBuilder:
|
||||
|
||||
@staticmethod
|
||||
def _schema_for_declared_output(output: DeclaredOutputConfig) -> dict[str, Any]:
|
||||
schema = WorkflowAgentRuntimeRequestBuilder._schema_for_type(output.type, array_item=output.array_item)
|
||||
schema = WorkflowAgentRuntimeRequestBuilder._schema_for_type(
|
||||
output.type,
|
||||
array_item=output.array_item,
|
||||
children=output.children,
|
||||
)
|
||||
if output.description:
|
||||
schema["description"] = output.description
|
||||
return schema
|
||||
@ -405,6 +410,7 @@ class WorkflowAgentRuntimeRequestBuilder:
|
||||
output_type: DeclaredOutputType,
|
||||
*,
|
||||
array_item: DeclaredArrayItem | None = None,
|
||||
children: Sequence[DeclaredOutputChildConfig] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
match output_type:
|
||||
case DeclaredOutputType.STRING:
|
||||
@ -414,18 +420,23 @@ class WorkflowAgentRuntimeRequestBuilder:
|
||||
case DeclaredOutputType.BOOLEAN:
|
||||
return {"type": "boolean"}
|
||||
case DeclaredOutputType.OBJECT:
|
||||
return {"type": "object"}
|
||||
object_schema: dict[str, Any] = {"type": "object"}
|
||||
WorkflowAgentRuntimeRequestBuilder._apply_child_properties(object_schema, children or [])
|
||||
return object_schema
|
||||
case DeclaredOutputType.ARRAY:
|
||||
# Stage 4 §4.2: items shape mirrors the declared array_item.
|
||||
# Validator guarantees array_item is set when type is array.
|
||||
item_type = array_item.type if array_item else DeclaredOutputType.OBJECT
|
||||
schema: dict[str, Any] = {
|
||||
array_schema: dict[str, Any] = {
|
||||
"type": "array",
|
||||
"items": WorkflowAgentRuntimeRequestBuilder._schema_for_type(item_type),
|
||||
"items": WorkflowAgentRuntimeRequestBuilder._schema_for_type(
|
||||
item_type,
|
||||
children=array_item.children if array_item else None,
|
||||
),
|
||||
}
|
||||
if array_item is not None and array_item.description:
|
||||
schema["items"]["description"] = array_item.description
|
||||
return schema
|
||||
array_schema["items"]["description"] = array_item.description
|
||||
return array_schema
|
||||
case DeclaredOutputType.FILE:
|
||||
return {
|
||||
"oneOf": [
|
||||
@ -469,6 +480,27 @@ class WorkflowAgentRuntimeRequestBuilder:
|
||||
}
|
||||
assert_never(output_type)
|
||||
|
||||
@staticmethod
|
||||
def _apply_child_properties(schema: dict[str, Any], children: Sequence[DeclaredOutputChildConfig]) -> None:
|
||||
if not children:
|
||||
return
|
||||
properties: dict[str, Any] = {}
|
||||
required: list[str] = []
|
||||
for child in children:
|
||||
child_schema = WorkflowAgentRuntimeRequestBuilder._schema_for_type(
|
||||
child.type,
|
||||
array_item=child.array_item,
|
||||
children=child.children,
|
||||
)
|
||||
if child.description:
|
||||
child_schema["description"] = child.description
|
||||
properties[child.name] = child_schema
|
||||
if child.required:
|
||||
required.append(child.name)
|
||||
schema["properties"] = properties
|
||||
if required:
|
||||
schema["required"] = required
|
||||
|
||||
@staticmethod
|
||||
def _normalize_credentials(credentials: Mapping[str, Any]) -> dict[str, str | int | float | bool | None]:
|
||||
normalized: dict[str, str | int | float | bool | None] = {}
|
||||
@ -669,7 +701,13 @@ def _shell_secret_ref(item: object) -> DifyShellSecretRefConfig | None:
|
||||
name = _name_from_mapping(data)
|
||||
if name is None:
|
||||
return None
|
||||
ref = data.get("ref") or data.get("id") or data.get("credential_id") or data.get("provider_credential_id")
|
||||
ref = (
|
||||
data.get("ref")
|
||||
or data.get("value")
|
||||
or data.get("id")
|
||||
or data.get("credential_id")
|
||||
or data.get("provider_credential_id")
|
||||
)
|
||||
return DifyShellSecretRefConfig(name=name, ref=str(ref) if ref is not None else None)
|
||||
|
||||
|
||||
|
||||
@ -70,6 +70,7 @@ class AgentRosterResponse(ResponseModel):
|
||||
workflow_node_id: str | None = None
|
||||
active_config_snapshot_id: str | None = None
|
||||
active_config_snapshot: AgentConfigSnapshotSummaryResponse | None = None
|
||||
active_config_is_published: bool = False
|
||||
status: AgentStatus
|
||||
created_by: str | None = None
|
||||
updated_by: str | None = None
|
||||
|
||||
@ -2,9 +2,9 @@ from __future__ import annotations
|
||||
|
||||
import re
|
||||
from enum import StrEnum
|
||||
from typing import Any, Final, Literal
|
||||
from typing import Annotated, Any, Final, Literal
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
||||
from pydantic import BaseModel, ConfigDict, Field, WithJsonSchema, field_validator, model_validator
|
||||
|
||||
from core.workflow.file_reference import is_canonical_file_reference
|
||||
from graphon.file import FileTransferMethod
|
||||
@ -29,6 +29,44 @@ class DeclaredOutputType(StrEnum):
|
||||
FILE = "file"
|
||||
|
||||
|
||||
_DECLARED_OUTPUT_CHILDREN_JSON_SCHEMA = {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [item.value for item in DeclaredOutputType],
|
||||
},
|
||||
"description": {"anyOf": [{"type": "string"}, {"type": "null"}]},
|
||||
"required": {"type": "boolean"},
|
||||
"file": {"type": "object", "additionalProperties": True},
|
||||
"array_item": {
|
||||
"type": "object",
|
||||
"additionalProperties": True,
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [item.value for item in DeclaredOutputType],
|
||||
},
|
||||
"description": {"anyOf": [{"type": "string"}, {"type": "null"}]},
|
||||
"children": {"type": "array", "items": {"type": "object", "additionalProperties": True}},
|
||||
},
|
||||
},
|
||||
"children": {"type": "array", "items": {"type": "object", "additionalProperties": True}},
|
||||
},
|
||||
"required": ["name", "type"],
|
||||
},
|
||||
}
|
||||
|
||||
DeclaredOutputChildren = Annotated[
|
||||
list["DeclaredOutputChildConfig"],
|
||||
WithJsonSchema(_DECLARED_OUTPUT_CHILDREN_JSON_SCHEMA),
|
||||
]
|
||||
|
||||
|
||||
class AgentCliToolAuthorizationStatus(StrEnum):
|
||||
"""Authorization state for Agent-scoped CLI tools.
|
||||
|
||||
@ -148,6 +186,9 @@ class AgentSecretRefConfig(AgentFlexibleConfig):
|
||||
env_name: str | None = Field(default=None, max_length=255)
|
||||
variable: str | None = Field(default=None, max_length=255)
|
||||
type: str | None = Field(default=None, max_length=64)
|
||||
# UI-facing selected secret reference. This is a credential/ref id, not the
|
||||
# plaintext secret value; runtime maps it to the shell-layer ``ref``.
|
||||
value: str | None = Field(default=None, max_length=255)
|
||||
id: str | None = Field(default=None, max_length=255)
|
||||
ref: str | None = Field(default=None, max_length=255)
|
||||
credential_id: str | None = Field(default=None, max_length=255)
|
||||
@ -507,11 +548,55 @@ class DeclaredArrayItem(BaseModel):
|
||||
|
||||
type: DeclaredOutputType
|
||||
description: str | None = None
|
||||
children: DeclaredOutputChildren = Field(default_factory=list)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def _reject_nested_array(self) -> DeclaredArrayItem:
|
||||
if self.type == DeclaredOutputType.ARRAY:
|
||||
raise ValueError("nested arrays are not supported as array_item.type")
|
||||
if self.children and self.type != DeclaredOutputType.OBJECT:
|
||||
raise ValueError("array_item.children is only allowed when array_item.type is object")
|
||||
return self
|
||||
|
||||
|
||||
class DeclaredOutputChildConfig(BaseModel):
|
||||
"""Nested field under an object-shaped declared output.
|
||||
|
||||
The first backend version keeps child fields lightweight: they describe the
|
||||
variable-picker/schema tree but do not own independent retry/check behavior.
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
name: str = Field(min_length=1, max_length=255)
|
||||
type: DeclaredOutputType
|
||||
description: str | None = None
|
||||
required: bool = True
|
||||
file: DeclaredOutputFileConfig | None = None
|
||||
array_item: DeclaredArrayItem | None = None
|
||||
children: DeclaredOutputChildren = Field(default_factory=list)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def _validate_shape(self) -> DeclaredOutputChildConfig:
|
||||
if not _OUTPUT_NAME_PATTERN.fullmatch(self.name):
|
||||
raise ValueError(
|
||||
f"output child name {self.name!r} must match {_OUTPUT_NAME_PATTERN.pattern} "
|
||||
"(JSON-schema-friendly identifier)"
|
||||
)
|
||||
if self.type == DeclaredOutputType.FILE:
|
||||
if self.file is None:
|
||||
self.file = DeclaredOutputFileConfig()
|
||||
elif self.file is not None:
|
||||
raise ValueError("file metadata is only allowed for file output children")
|
||||
|
||||
if self.type == DeclaredOutputType.ARRAY:
|
||||
if self.array_item is None:
|
||||
self.array_item = DeclaredArrayItem(type=DeclaredOutputType.OBJECT)
|
||||
elif self.array_item is not None:
|
||||
raise ValueError("array_item is only allowed when child type is array")
|
||||
|
||||
if self.children and self.type != DeclaredOutputType.OBJECT:
|
||||
raise ValueError("children is only allowed for object output children")
|
||||
return self
|
||||
|
||||
|
||||
@ -592,6 +677,7 @@ class DeclaredOutputConfig(BaseModel):
|
||||
required: bool = True
|
||||
file: DeclaredOutputFileConfig | None = None
|
||||
array_item: DeclaredArrayItem | None = None
|
||||
children: DeclaredOutputChildren = Field(default_factory=list)
|
||||
check: DeclaredOutputCheckConfig | None = None
|
||||
failure_strategy: DeclaredOutputFailureStrategy = Field(default_factory=DeclaredOutputFailureStrategy)
|
||||
|
||||
@ -625,6 +711,9 @@ class DeclaredOutputConfig(BaseModel):
|
||||
elif self.array_item is not None:
|
||||
raise ValueError("array_item is only allowed when type is array")
|
||||
|
||||
if self.children and self.type != DeclaredOutputType.OBJECT:
|
||||
raise ValueError("children is only allowed for object outputs")
|
||||
|
||||
# Per PRD §OUTPUT 配置框: output check is file-only.
|
||||
if self.check is not None and self.check.enabled and self.type != DeclaredOutputType.FILE:
|
||||
raise ValueError("output check is only allowed for file outputs")
|
||||
|
||||
@ -11703,6 +11703,7 @@ Supported icon storage formats for Agent roster entries.
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| active_config_is_published | boolean | | No |
|
||||
| active_config_snapshot | [AgentConfigSnapshotSummaryResponse](#agentconfigsnapshotsummaryresponse) | | No |
|
||||
| active_config_snapshot_id | string | | No |
|
||||
| agent_kind | [AgentKind](#agentkind) | | Yes |
|
||||
@ -11924,6 +11925,7 @@ the current roster/workflow APIs scoped to Dify Agent.
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| active_config_is_published | boolean | | No |
|
||||
| active_config_snapshot | [AgentConfigSnapshotSummaryResponse](#agentconfigsnapshotsummaryresponse) | | No |
|
||||
| active_config_snapshot_id | string | | No |
|
||||
| agent_kind | [AgentKind](#agentkind) | | Yes |
|
||||
@ -11989,6 +11991,7 @@ Visibility and lifecycle scope of an Agent record.
|
||||
| provider_credential_id | string | | No |
|
||||
| ref | string | | No |
|
||||
| type | string | | No |
|
||||
| value | string | | No |
|
||||
| variable | string | | No |
|
||||
|
||||
#### AgentSensitiveWordAvoidanceFeatureConfig
|
||||
@ -12515,6 +12518,7 @@ Enum class for api provider schema type.
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| access_mode | string | | No |
|
||||
| active_config_is_published | boolean | | No |
|
||||
| api_base_url | string | | No |
|
||||
| app_id | string | | No |
|
||||
| bound_agent_id | string | | No |
|
||||
@ -12638,6 +12642,7 @@ AppMCPServer Status Enum
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| access_mode | string | | No |
|
||||
| active_config_is_published | boolean | | No |
|
||||
| app_id | string | | No |
|
||||
| author_name | string | | No |
|
||||
| bound_agent_id | string | | No |
|
||||
@ -14201,6 +14206,7 @@ about. Stage 4 §4.2.
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| children | [ { **"array_item"**: { **"children"**: [ object ], **"description"**: , **"type"**: string, <br>**Available values:** "array", "boolean", "file", "number", "object", "string" }, **"children"**: [ object ], **"description"**: , **"file"**: object, **"name"**: string, **"required"**: boolean, **"type"**: string, <br>**Available values:** "array", "boolean", "file", "number", "object", "string" } ] | | No |
|
||||
| description | string | | No |
|
||||
| type | [DeclaredOutputType](#declaredoutputtype) | | Yes |
|
||||
|
||||
@ -14229,6 +14235,7 @@ code can call ``output.failure_strategy.on_failure`` without None-guards.
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| array_item | [DeclaredArrayItem](#declaredarrayitem) | | No |
|
||||
| check | [DeclaredOutputCheckConfig](#declaredoutputcheckconfig) | | No |
|
||||
| children | [ { **"array_item"**: { **"children"**: [ object ], **"description"**: , **"type"**: string, <br>**Available values:** "array", "boolean", "file", "number", "object", "string" }, **"children"**: [ object ], **"description"**: , **"file"**: object, **"name"**: string, **"required"**: boolean, **"type"**: string, <br>**Available values:** "array", "boolean", "file", "number", "object", "string" } ] | | No |
|
||||
| description | string | | No |
|
||||
| failure_strategy | [DeclaredOutputFailureStrategy](#declaredoutputfailurestrategy) | | No |
|
||||
| file | [DeclaredOutputFileConfig](#declaredoutputfileconfig) | | No |
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
from typing import Any, TypedDict
|
||||
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy import and_, func, or_, select
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
@ -56,6 +56,7 @@ class AgentRosterService:
|
||||
agent: Agent,
|
||||
active_version: AgentConfigSnapshot | None = None,
|
||||
published_references: list[AgentReferencingWorkflow] | None = None,
|
||||
active_config_is_published: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
published_references = published_references or []
|
||||
return {
|
||||
@ -74,6 +75,7 @@ class AgentRosterService:
|
||||
"workflow_node_id": agent.workflow_node_id,
|
||||
"active_config_snapshot_id": agent.active_config_snapshot_id,
|
||||
"active_config_snapshot": AgentRosterService.serialize_version(active_version) if active_version else None,
|
||||
"active_config_is_published": active_config_is_published,
|
||||
"status": agent.status.value,
|
||||
"created_by": agent.created_by,
|
||||
"updated_by": agent.updated_by,
|
||||
@ -128,6 +130,10 @@ class AgentRosterService:
|
||||
tenant_id=tenant_id,
|
||||
agent_ids=[agent.id for agent in agents],
|
||||
)
|
||||
active_config_is_published_by_agent_id = self.load_active_config_is_published_by_agent_id(
|
||||
tenant_id=tenant_id,
|
||||
agents=agents,
|
||||
)
|
||||
|
||||
data = []
|
||||
for agent in agents:
|
||||
@ -139,6 +145,7 @@ class AgentRosterService:
|
||||
agent,
|
||||
active_version,
|
||||
published_references_by_agent_id.get(agent.id, []),
|
||||
active_config_is_published_by_agent_id.get(agent.id, False),
|
||||
)
|
||||
)
|
||||
|
||||
@ -165,11 +172,16 @@ class AgentRosterService:
|
||||
tenant_id=tenant_id,
|
||||
agent_ids=[agent.id for agent in agents],
|
||||
)
|
||||
active_config_is_published_by_agent_id = self.load_active_config_is_published_by_agent_id(
|
||||
tenant_id=tenant_id,
|
||||
agents=agents,
|
||||
)
|
||||
data = [
|
||||
self.serialize_agent(
|
||||
agent,
|
||||
versions_by_id.get(agent.active_config_snapshot_id) if agent.active_config_snapshot_id else None,
|
||||
published_references_by_agent_id.get(agent.id, []),
|
||||
active_config_is_published_by_agent_id.get(agent.id, False),
|
||||
)
|
||||
for agent in agents
|
||||
]
|
||||
@ -429,7 +441,16 @@ class AgentRosterService:
|
||||
tenant_id=tenant_id,
|
||||
agent_ids=[agent.id],
|
||||
)
|
||||
return self.serialize_agent(agent, active_version, published_references_by_agent_id.get(agent.id, []))
|
||||
active_config_is_published_by_agent_id = self.load_active_config_is_published_by_agent_id(
|
||||
tenant_id=tenant_id,
|
||||
agents=[agent],
|
||||
)
|
||||
return self.serialize_agent(
|
||||
agent,
|
||||
active_version,
|
||||
published_references_by_agent_id.get(agent.id, []),
|
||||
active_config_is_published_by_agent_id.get(agent.id, False),
|
||||
)
|
||||
|
||||
def update_roster_agent(
|
||||
self, *, tenant_id: str, agent_id: str, account_id: str, payload: RosterAgentUpdatePayload
|
||||
@ -471,6 +492,18 @@ class AgentRosterService:
|
||||
AgentConfigRevisionOperation.SAVE_TO_ROSTER,
|
||||
}
|
||||
|
||||
def active_config_is_published(self, *, tenant_id: str, agent: Agent) -> bool:
|
||||
"""Return whether the Agent's current active snapshot is a visible published version."""
|
||||
return self.load_active_config_is_published_by_agent_id(tenant_id=tenant_id, agents=[agent]).get(
|
||||
agent.id,
|
||||
False,
|
||||
)
|
||||
|
||||
def load_active_config_is_published_by_agent_id(self, *, tenant_id: str, agents: list[Agent]) -> dict[str, bool]:
|
||||
"""Return publish-state flags for the active config snapshots of the given Agents."""
|
||||
published_agent_ids = self._load_published_active_snapshot_agent_ids(tenant_id=tenant_id, agents=agents)
|
||||
return {agent.id: agent.id in published_agent_ids for agent in agents}
|
||||
|
||||
def list_agent_versions(self, *, tenant_id: str, agent_id: str) -> list[dict[str, Any]]:
|
||||
agent = self._get_agent(tenant_id=tenant_id, agent_id=agent_id, roster_only=True)
|
||||
visible_version_ids = (
|
||||
@ -568,6 +601,29 @@ class AgentRosterService:
|
||||
raise AgentVersionNotFoundError()
|
||||
return version
|
||||
|
||||
def _load_published_active_snapshot_agent_ids(self, *, tenant_id: str, agents: list[Agent]) -> set[str]:
|
||||
predicates = [
|
||||
and_(
|
||||
AgentConfigRevision.agent_id == agent.id,
|
||||
AgentConfigRevision.current_snapshot_id == agent.active_config_snapshot_id,
|
||||
AgentConfigRevision.operation.in_(self._visible_version_operations(agent)),
|
||||
)
|
||||
for agent in agents
|
||||
if agent.active_config_snapshot_id
|
||||
]
|
||||
if not predicates:
|
||||
return set()
|
||||
|
||||
agent_ids = self._session.scalars(
|
||||
select(AgentConfigRevision.agent_id)
|
||||
.where(
|
||||
AgentConfigRevision.tenant_id == tenant_id,
|
||||
or_(*predicates),
|
||||
)
|
||||
.distinct()
|
||||
).all()
|
||||
return set(agent_ids)
|
||||
|
||||
def _load_published_references_by_agent_id(
|
||||
self, *, tenant_id: str, agent_ids: list[str]
|
||||
) -> dict[str, list[AgentReferencingWorkflow]]:
|
||||
|
||||
@ -199,12 +199,16 @@ def test_agent_app_list_and_create_use_agent_route(
|
||||
monkeypatch.setattr(
|
||||
roster_controller.AgentRosterService,
|
||||
"load_app_backing_agents_by_app_id",
|
||||
lambda _self, **kwargs: {"app-list": SimpleNamespace(id="agent-list", role="List role")},
|
||||
lambda _self, **kwargs: {
|
||||
"app-list": SimpleNamespace(id="agent-list", role="List role", active_config_snapshot_id=None)
|
||||
},
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
roster_controller.AgentRosterService,
|
||||
"get_app_backing_agent",
|
||||
lambda _self, **kwargs: SimpleNamespace(id="agent-created", role="Created role"),
|
||||
lambda _self, **kwargs: SimpleNamespace(
|
||||
id="agent-created", role="Created role", active_config_snapshot_id=None
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
roster_controller.FeatureService,
|
||||
@ -221,6 +225,7 @@ def test_agent_app_list_and_create_use_agent_route(
|
||||
assert listed["data"][0]["id"] == "agent-list"
|
||||
assert listed["data"][0]["app_id"] == "app-list"
|
||||
assert listed["data"][0]["role"] == "List role"
|
||||
assert listed["data"][0]["active_config_is_published"] is False
|
||||
assert "bound_agent_id" not in listed["data"][0]
|
||||
list_call = cast(dict[str, object], captured["list"])
|
||||
list_params = cast(Any, list_call["params"])
|
||||
@ -237,6 +242,7 @@ def test_agent_app_list_and_create_use_agent_route(
|
||||
assert created["id"] == "agent-created"
|
||||
assert created["app_id"] == "app-created"
|
||||
assert created["role"] == "Created role"
|
||||
assert created["active_config_is_published"] is False
|
||||
assert "bound_agent_id" not in created
|
||||
create_call = cast(dict[str, object], captured["create"])
|
||||
create_params = cast(Any, create_call["params"])
|
||||
@ -258,7 +264,7 @@ def test_agent_app_detail_update_delete_resolve_app_from_agent_id(
|
||||
monkeypatch.setattr(
|
||||
roster_controller.AgentRosterService,
|
||||
"get_app_backing_agent",
|
||||
lambda _self, **kwargs: SimpleNamespace(id=agent_id, role="Resolved role"),
|
||||
lambda _self, **kwargs: SimpleNamespace(id=agent_id, role="Resolved role", active_config_snapshot_id=None),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
roster_controller.FeatureService,
|
||||
@ -284,6 +290,7 @@ def test_agent_app_detail_update_delete_resolve_app_from_agent_id(
|
||||
assert detail["id"] == agent_id
|
||||
assert detail["app_id"] == "app-1"
|
||||
assert detail["role"] == "Resolved role"
|
||||
assert detail["active_config_is_published"] is False
|
||||
assert "bound_agent_id" not in detail
|
||||
|
||||
with app.test_request_context(
|
||||
@ -296,6 +303,7 @@ def test_agent_app_detail_update_delete_resolve_app_from_agent_id(
|
||||
assert updated["id"] == agent_id
|
||||
assert updated["app_id"] == "app-1"
|
||||
assert updated["role"] == "Resolved role"
|
||||
assert updated["active_config_is_published"] is False
|
||||
assert "bound_agent_id" not in updated
|
||||
update_call = cast(dict[str, object], captured["update"])
|
||||
assert update_call["app"] is app_model
|
||||
|
||||
@ -21,6 +21,9 @@ from models.agent import Agent, AgentConfigSnapshot, WorkflowAgentNodeBinding
|
||||
from models.agent_config_entities import (
|
||||
AgentSoulConfig,
|
||||
AgentSoulModelConfig,
|
||||
DeclaredArrayItem,
|
||||
DeclaredOutputChildConfig,
|
||||
DeclaredOutputConfig,
|
||||
DeclaredOutputType,
|
||||
WorkflowNodeJobConfig,
|
||||
)
|
||||
@ -321,6 +324,7 @@ def test_build_shell_layer_config_accepts_legacy_fallback_keys():
|
||||
"secret_refs": [
|
||||
{"variable": "TOKEN", "credential_id": "credential-1"},
|
||||
{"name": "API_KEY", "provider_credential_id": "credential-2"},
|
||||
{"name": "EDITABLE_TOKEN", "value": "credential-3"},
|
||||
{"ref": "missing-name"},
|
||||
],
|
||||
},
|
||||
@ -341,6 +345,7 @@ def test_build_shell_layer_config_accepts_legacy_fallback_keys():
|
||||
assert config["secret_refs"] == [
|
||||
{"name": "TOKEN", "ref": "credential-1"},
|
||||
{"name": "API_KEY", "ref": "credential-2"},
|
||||
{"name": "EDITABLE_TOKEN", "ref": "credential-3"},
|
||||
]
|
||||
assert config["sandbox"] is None
|
||||
|
||||
@ -630,6 +635,40 @@ def test_array_output_emits_typed_items_per_array_item():
|
||||
assert output_schema["required"] == ["tags"]
|
||||
|
||||
|
||||
def test_nested_declared_output_emits_object_and_array_child_schema():
|
||||
profile_output = DeclaredOutputConfig(
|
||||
name="profile",
|
||||
type=DeclaredOutputType.OBJECT,
|
||||
children=[
|
||||
DeclaredOutputChildConfig(name="email", type=DeclaredOutputType.STRING),
|
||||
DeclaredOutputChildConfig(
|
||||
name="nickname",
|
||||
type=DeclaredOutputType.STRING,
|
||||
required=False,
|
||||
description="Optional display name",
|
||||
),
|
||||
DeclaredOutputChildConfig(
|
||||
name="addresses",
|
||||
type=DeclaredOutputType.ARRAY,
|
||||
array_item=DeclaredArrayItem(
|
||||
type=DeclaredOutputType.OBJECT,
|
||||
description="Address item",
|
||||
children=[DeclaredOutputChildConfig(name="city", type=DeclaredOutputType.STRING)],
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
schema = WorkflowAgentRuntimeRequestBuilder._schema_for_declared_output(profile_output)
|
||||
|
||||
assert schema["properties"]["email"] == {"type": "string"}
|
||||
assert schema["properties"]["nickname"] == {"type": "string", "description": "Optional display name"}
|
||||
assert schema["properties"]["addresses"]["items"]["properties"]["city"] == {"type": "string"}
|
||||
assert schema["properties"]["addresses"]["items"]["description"] == "Address item"
|
||||
assert schema["properties"]["addresses"]["items"]["required"] == ["city"]
|
||||
assert schema["required"] == ["email", "addresses"]
|
||||
|
||||
|
||||
def test_effective_declared_outputs_passthrough_when_user_declared():
|
||||
"""effective_declared_outputs() must return user-provided outputs verbatim
|
||||
when non-empty; only empty input gets PRD defaults injected."""
|
||||
|
||||
@ -1,7 +1,12 @@
|
||||
import pytest
|
||||
|
||||
from core.workflow.file_reference import build_file_reference
|
||||
from models.agent_config_entities import DeclaredOutputConfig, DeclaredOutputType
|
||||
from models.agent_config_entities import (
|
||||
DeclaredArrayItem,
|
||||
DeclaredOutputChildConfig,
|
||||
DeclaredOutputConfig,
|
||||
DeclaredOutputType,
|
||||
)
|
||||
|
||||
|
||||
def test_file_default_value_accepts_canonical_reference_mapping() -> None:
|
||||
@ -92,3 +97,90 @@ def test_array_file_default_value_rejects_legacy_item_shape() -> None:
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_declared_array_item_rejects_nested_arrays_and_non_object_children() -> None:
|
||||
with pytest.raises(ValueError, match="nested arrays"):
|
||||
DeclaredArrayItem(type=DeclaredOutputType.ARRAY)
|
||||
|
||||
with pytest.raises(ValueError, match="array_item.children"):
|
||||
DeclaredArrayItem(
|
||||
type=DeclaredOutputType.STRING,
|
||||
children=[DeclaredOutputChildConfig(name="label", type=DeclaredOutputType.STRING)],
|
||||
)
|
||||
|
||||
|
||||
def test_declared_output_child_validates_shape_and_defaults() -> None:
|
||||
file_child = DeclaredOutputChildConfig(name="report", type=DeclaredOutputType.FILE)
|
||||
assert file_child.file is not None
|
||||
|
||||
array_child = DeclaredOutputChildConfig(name="items", type=DeclaredOutputType.ARRAY)
|
||||
assert array_child.array_item is not None
|
||||
assert array_child.array_item.type == DeclaredOutputType.OBJECT
|
||||
|
||||
with pytest.raises(ValueError, match="output child name"):
|
||||
DeclaredOutputChildConfig(name="bad-name", type=DeclaredOutputType.STRING)
|
||||
|
||||
with pytest.raises(ValueError, match="file metadata"):
|
||||
DeclaredOutputChildConfig(name="title", type=DeclaredOutputType.STRING, file={})
|
||||
|
||||
with pytest.raises(ValueError, match="array_item is only allowed"):
|
||||
DeclaredOutputChildConfig(
|
||||
name="title",
|
||||
type=DeclaredOutputType.STRING,
|
||||
array_item={"type": DeclaredOutputType.STRING},
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match="children is only allowed"):
|
||||
DeclaredOutputChildConfig(
|
||||
name="title",
|
||||
type=DeclaredOutputType.STRING,
|
||||
children=[DeclaredOutputChildConfig(name="label", type=DeclaredOutputType.STRING)],
|
||||
)
|
||||
|
||||
|
||||
def test_declared_output_validates_shape_and_defaults() -> None:
|
||||
file_output = DeclaredOutputConfig(name="report", type=DeclaredOutputType.FILE)
|
||||
assert file_output.file is not None
|
||||
|
||||
array_output = DeclaredOutputConfig(name="items", type=DeclaredOutputType.ARRAY)
|
||||
assert array_output.array_item is not None
|
||||
assert array_output.array_item.type == DeclaredOutputType.OBJECT
|
||||
|
||||
default_failure_strategy = DeclaredOutputConfig.model_validate(
|
||||
{"name": "summary", "type": "string", "failure_strategy": None}
|
||||
)
|
||||
assert default_failure_strategy.failure_strategy.on_failure == "stop"
|
||||
|
||||
with pytest.raises(ValueError, match="output name"):
|
||||
DeclaredOutputConfig(name="bad-name", type=DeclaredOutputType.STRING)
|
||||
|
||||
with pytest.raises(ValueError, match="file metadata"):
|
||||
DeclaredOutputConfig(name="summary", type=DeclaredOutputType.STRING, file={})
|
||||
|
||||
with pytest.raises(ValueError, match="array_item is only allowed"):
|
||||
DeclaredOutputConfig(
|
||||
name="summary",
|
||||
type=DeclaredOutputType.STRING,
|
||||
array_item={"type": DeclaredOutputType.STRING},
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match="children is only allowed"):
|
||||
DeclaredOutputConfig(
|
||||
name="summary",
|
||||
type=DeclaredOutputType.STRING,
|
||||
children=[DeclaredOutputChildConfig(name="title", type=DeclaredOutputType.STRING)],
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match="output check is only allowed"):
|
||||
DeclaredOutputConfig.model_validate(
|
||||
{
|
||||
"name": "summary",
|
||||
"type": "string",
|
||||
"check": {
|
||||
"enabled": True,
|
||||
"prompt": "Compare output",
|
||||
"benchmark_file_ref": {"name": "expected.pdf"},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
@ -17,6 +17,8 @@ from models.agent import (
|
||||
)
|
||||
from models.agent_config_entities import (
|
||||
AgentFileRefConfig,
|
||||
DeclaredArrayItem,
|
||||
DeclaredOutputChildConfig,
|
||||
DeclaredOutputConfig,
|
||||
DeclaredOutputType,
|
||||
WorkflowNodeJobConfig,
|
||||
@ -112,7 +114,7 @@ def test_load_workflow_composer_returns_empty_state(monkeypatch):
|
||||
effective = result["effective_declared_outputs"]
|
||||
assert [o["name"] for o in effective] == ["text", "files", "json"]
|
||||
files_output = next(o for o in effective if o["name"] == "files")
|
||||
assert files_output["array_item"] == {"type": "file", "description": None}
|
||||
assert files_output["array_item"] == {"type": "file", "description": None, "children": []}
|
||||
|
||||
|
||||
def test_load_workflow_composer_serializes_existing_binding(monkeypatch):
|
||||
@ -649,6 +651,7 @@ def test_roster_list_and_invite_options(monkeypatch):
|
||||
lambda version_ids: {"version-1": version, "version-2": unconfigured_version},
|
||||
)
|
||||
monkeypatch.setattr(service, "_load_published_references_by_agent_id", lambda **kwargs: {})
|
||||
monkeypatch.setattr(service, "_load_published_active_snapshot_agent_ids", lambda **kwargs: {"agent-1"})
|
||||
|
||||
listed = service.list_roster_agents(tenant_id="tenant-1", page=1, limit=20)
|
||||
invited = service.list_invite_options(tenant_id="tenant-1", page=1, limit=20, app_id="app-1")
|
||||
@ -661,6 +664,8 @@ def test_roster_list_and_invite_options(monkeypatch):
|
||||
assert listed["data"][0]["created_at"] == int(created_at.timestamp())
|
||||
assert listed["data"][0]["updated_at"] == int(updated_at.timestamp())
|
||||
assert listed["data"][0]["active_config_snapshot"]["created_at"] == int(version_created_at.timestamp())
|
||||
assert listed["data"][0]["active_config_is_published"] is True
|
||||
assert listed["data"][1]["active_config_is_published"] is False
|
||||
assert invited["data"][0]["is_in_current_workflow"] is True
|
||||
assert invited["data"][0]["existing_node_ids"] == ["node-1"]
|
||||
|
||||
@ -690,6 +695,7 @@ def test_invite_options_uses_db_filtered_pagination(monkeypatch):
|
||||
},
|
||||
)
|
||||
monkeypatch.setattr(service, "_load_published_references_by_agent_id", lambda **kwargs: {})
|
||||
monkeypatch.setattr(service, "_load_published_active_snapshot_agent_ids", lambda **kwargs: set())
|
||||
|
||||
result = service.list_invite_options(tenant_id="tenant-1", page=1, limit=1)
|
||||
|
||||
@ -698,6 +704,41 @@ def test_invite_options_uses_db_filtered_pagination(monkeypatch):
|
||||
assert [item["id"] for item in result["data"]] == ["agent-2"]
|
||||
|
||||
|
||||
def test_active_config_is_published_flags_handle_matching_and_empty_snapshots():
|
||||
agent = Agent(
|
||||
id="agent-1",
|
||||
tenant_id="tenant-1",
|
||||
name="Published",
|
||||
description="",
|
||||
agent_kind=AgentKind.DIFY_AGENT,
|
||||
scope=AgentScope.ROSTER,
|
||||
source=AgentSource.AGENT_APP,
|
||||
status=AgentStatus.ACTIVE,
|
||||
active_config_snapshot_id="version-1",
|
||||
)
|
||||
draft_agent = Agent(
|
||||
id="agent-2",
|
||||
tenant_id="tenant-1",
|
||||
name="Draft",
|
||||
description="",
|
||||
agent_kind=AgentKind.DIFY_AGENT,
|
||||
scope=AgentScope.ROSTER,
|
||||
source=AgentSource.AGENT_APP,
|
||||
status=AgentStatus.ACTIVE,
|
||||
active_config_snapshot_id=None,
|
||||
)
|
||||
service = AgentRosterService(FakeSession(scalars=[["agent-1"], ["agent-1"]]))
|
||||
|
||||
flags = service.load_active_config_is_published_by_agent_id(tenant_id="tenant-1", agents=[agent, draft_agent])
|
||||
|
||||
assert flags == {"agent-1": True, "agent-2": False}
|
||||
assert service.active_config_is_published(tenant_id="tenant-1", agent=agent) is True
|
||||
assert AgentRosterService(FakeSession()).load_active_config_is_published_by_agent_id(
|
||||
tenant_id="tenant-1",
|
||||
agents=[draft_agent],
|
||||
) == {"agent-2": False}
|
||||
|
||||
|
||||
def test_published_references_include_app_display_fields_and_sort_by_updated_at():
|
||||
recent_updated_at = datetime(2026, 1, 7, 3, 4, 5, tzinfo=UTC)
|
||||
stale_updated_at = datetime(2026, 1, 6, 3, 4, 5, tzinfo=UTC)
|
||||
@ -1042,7 +1083,7 @@ def test_composer_validator_accepts_valid_shell_env_and_cli():
|
||||
{
|
||||
"env": {
|
||||
"variables": [{"name": "MY_VAR", "value": "v"}],
|
||||
"secret_refs": [{"name": "API_TOKEN", "id": "credential-1"}],
|
||||
"secret_refs": [{"name": "API_TOKEN", "value": "credential-1"}],
|
||||
},
|
||||
"tools": {
|
||||
"cli_tools": [
|
||||
@ -1051,7 +1092,7 @@ def test_composer_validator_accepts_valid_shell_env_and_cli():
|
||||
"command": "apt-get install -y jq",
|
||||
"env": {
|
||||
"variables": [{"name": "JQ_COLOR", "value": "1"}],
|
||||
"secret_refs": [{"name": "JQ_TOKEN", "id": "credential-2"}],
|
||||
"secret_refs": [{"name": "JQ_TOKEN", "value": "credential-2"}],
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -1067,8 +1108,10 @@ def test_composer_validator_accepts_valid_shell_env_and_cli():
|
||||
)
|
||||
assert {variable.name for variable in config.env.variables} == {"MY_VAR"}
|
||||
assert {secret.name for secret in config.env.secret_refs} == {"API_TOKEN"}
|
||||
assert config.env.secret_refs[0].value == "credential-1"
|
||||
assert config.tools.cli_tools[0].env.variables[0].name == "JQ_COLOR"
|
||||
assert config.tools.cli_tools[0].env.secret_refs[0].name == "JQ_TOKEN"
|
||||
assert config.tools.cli_tools[0].env.secret_refs[0].value == "credential-2"
|
||||
|
||||
|
||||
class TestAgentAppBackingAgent:
|
||||
@ -1263,7 +1306,22 @@ class TestWorkflowAgentDraftBindingSync:
|
||||
node_job_config=WorkflowNodeJobConfig(
|
||||
workflow_prompt="Summarize the upstream result.",
|
||||
declared_outputs=[
|
||||
DeclaredOutputConfig(name="summary", type=DeclaredOutputType.STRING, description="Short summary")
|
||||
DeclaredOutputConfig(name="summary", type=DeclaredOutputType.STRING, description="Short summary"),
|
||||
DeclaredOutputConfig(
|
||||
name="profile",
|
||||
type=DeclaredOutputType.OBJECT,
|
||||
children=[
|
||||
DeclaredOutputChildConfig(name="email", type=DeclaredOutputType.STRING),
|
||||
DeclaredOutputChildConfig(
|
||||
name="addresses",
|
||||
type=DeclaredOutputType.ARRAY,
|
||||
array_item=DeclaredArrayItem(
|
||||
type=DeclaredOutputType.OBJECT,
|
||||
children=[DeclaredOutputChildConfig(name="city", type=DeclaredOutputType.STRING)],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
@ -1279,6 +1337,9 @@ class TestWorkflowAgentDraftBindingSync:
|
||||
assert node_data["agent_declared_outputs"][0]["name"] == "summary"
|
||||
assert node_data["agent_declared_outputs"][0]["type"] == "string"
|
||||
assert node_data["agent_declared_outputs"][0]["description"] == "Short summary"
|
||||
profile_output = node_data["agent_declared_outputs"][1]
|
||||
assert profile_output["children"][0]["name"] == "email"
|
||||
assert profile_output["children"][1]["array_item"]["children"][0]["name"] == "city"
|
||||
assert "agent_declared_outputs" not in workflow.graph_dict["nodes"][0]["data"]
|
||||
|
||||
def test_creates_roster_binding_from_agent_node_graph(self):
|
||||
|
||||
@ -23,6 +23,7 @@ export type AgentAppCreatePayload = {
|
||||
|
||||
export type AppDetailWithSite = {
|
||||
access_mode?: string | null
|
||||
active_config_is_published?: boolean
|
||||
api_base_url?: string | null
|
||||
app_id?: string | null
|
||||
bound_agent_id?: string | null
|
||||
@ -259,6 +260,7 @@ export type AgentConfigSnapshotDetailResponse = {
|
||||
|
||||
export type AppPartial = {
|
||||
access_mode?: string | null
|
||||
active_config_is_published?: boolean
|
||||
app_id?: string | null
|
||||
author_name?: string | null
|
||||
bound_agent_id?: string | null
|
||||
@ -345,6 +347,7 @@ export type WorkflowPartial = {
|
||||
}
|
||||
|
||||
export type AgentInviteOptionResponse = {
|
||||
active_config_is_published?: boolean
|
||||
active_config_snapshot?: AgentConfigSnapshotSummaryResponse | null
|
||||
active_config_snapshot_id?: string | null
|
||||
agent_kind: AgentKind
|
||||
@ -757,6 +760,26 @@ export type AgentSoulToolsConfig = {
|
||||
export type DeclaredOutputConfig = {
|
||||
array_item?: DeclaredArrayItem | null
|
||||
check?: DeclaredOutputCheckConfig | null
|
||||
children?: Array<{
|
||||
array_item?: {
|
||||
children?: Array<{
|
||||
[key: string]: unknown
|
||||
}>
|
||||
description?: string | null
|
||||
type?: 'array' | 'boolean' | 'file' | 'number' | 'object' | 'string'
|
||||
[key: string]: unknown
|
||||
}
|
||||
children?: Array<{
|
||||
[key: string]: unknown
|
||||
}>
|
||||
description?: string | null
|
||||
file?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
name: string
|
||||
required?: boolean
|
||||
type: 'array' | 'boolean' | 'file' | 'number' | 'object' | 'string'
|
||||
}>
|
||||
description?: string | null
|
||||
failure_strategy?: DeclaredOutputFailureStrategy
|
||||
file?: DeclaredOutputFileConfig | null
|
||||
@ -949,6 +972,7 @@ export type AgentSecretRefConfig = {
|
||||
provider_credential_id?: string | null
|
||||
ref?: string | null
|
||||
type?: string | null
|
||||
value?: string | null
|
||||
variable?: string | null
|
||||
[key: string]: unknown
|
||||
}
|
||||
@ -1073,6 +1097,26 @@ export type AgentSoulDifyToolConfig = {
|
||||
}
|
||||
|
||||
export type DeclaredArrayItem = {
|
||||
children?: Array<{
|
||||
array_item?: {
|
||||
children?: Array<{
|
||||
[key: string]: unknown
|
||||
}>
|
||||
description?: string | null
|
||||
type?: 'array' | 'boolean' | 'file' | 'number' | 'object' | 'string'
|
||||
[key: string]: unknown
|
||||
}
|
||||
children?: Array<{
|
||||
[key: string]: unknown
|
||||
}>
|
||||
description?: string | null
|
||||
file?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
name: string
|
||||
required?: boolean
|
||||
type: 'array' | 'boolean' | 'file' | 'number' | 'object' | 'string'
|
||||
}>
|
||||
description?: string | null
|
||||
type: DeclaredOutputType
|
||||
}
|
||||
@ -1224,6 +1268,7 @@ export type AppPaginationWritable = {
|
||||
|
||||
export type AppDetailWithSiteWritable = {
|
||||
access_mode?: string | null
|
||||
active_config_is_published?: boolean
|
||||
api_base_url?: string | null
|
||||
app_id?: string | null
|
||||
bound_agent_id?: string | null
|
||||
@ -1253,6 +1298,7 @@ export type AppDetailWithSiteWritable = {
|
||||
|
||||
export type AppPartialWritable = {
|
||||
access_mode?: string | null
|
||||
active_config_is_published?: boolean
|
||||
app_id?: string | null
|
||||
author_name?: string | null
|
||||
bound_agent_id?: string | null
|
||||
|
||||
@ -475,6 +475,7 @@ export const zModelConfigPartial = z.object({
|
||||
*/
|
||||
export const zAppPartial = z.object({
|
||||
access_mode: z.string().nullish(),
|
||||
active_config_is_published: z.boolean().optional().default(false),
|
||||
app_id: z.string().nullish(),
|
||||
author_name: z.string().nullish(),
|
||||
bound_agent_id: z.string().nullish(),
|
||||
@ -534,6 +535,7 @@ export const zModelConfig = z.object({
|
||||
*/
|
||||
export const zAppDetailWithSite = z.object({
|
||||
access_mode: z.string().nullish(),
|
||||
active_config_is_published: z.boolean().optional().default(false),
|
||||
api_base_url: z.string().nullish(),
|
||||
app_id: z.string().nullish(),
|
||||
bound_agent_id: z.string().nullish(),
|
||||
@ -620,6 +622,7 @@ export const zAgentStatus = z.enum(['active', 'archived'])
|
||||
* AgentInviteOptionResponse
|
||||
*/
|
||||
export const zAgentInviteOptionResponse = z.object({
|
||||
active_config_is_published: z.boolean().optional().default(false),
|
||||
active_config_snapshot: zAgentConfigSnapshotSummaryResponse.nullish(),
|
||||
active_config_snapshot_id: z.string().nullish(),
|
||||
agent_kind: zAgentKind,
|
||||
@ -1078,6 +1081,25 @@ export const zWorkflowNodeJobMetadata = z.object({
|
||||
* about. Stage 4 §4.2.
|
||||
*/
|
||||
export const zDeclaredArrayItem = z.object({
|
||||
children: z
|
||||
.array(
|
||||
z.object({
|
||||
array_item: z
|
||||
.object({
|
||||
children: z.array(z.record(z.string(), z.unknown())).optional(),
|
||||
description: z.string().nullish(),
|
||||
type: z.enum(['array', 'boolean', 'file', 'number', 'object', 'string']).optional(),
|
||||
})
|
||||
.optional(),
|
||||
children: z.array(z.record(z.string(), z.unknown())).optional(),
|
||||
description: z.string().nullish(),
|
||||
file: z.record(z.string(), z.unknown()).optional(),
|
||||
name: z.string(),
|
||||
required: z.boolean().optional(),
|
||||
type: z.enum(['array', 'boolean', 'file', 'number', 'object', 'string']),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
description: z.string().nullish(),
|
||||
type: zDeclaredOutputType,
|
||||
})
|
||||
@ -1136,6 +1158,7 @@ export const zAgentSecretRefConfig = z.object({
|
||||
provider_credential_id: z.string().max(255).nullish(),
|
||||
ref: z.string().max(255).nullish(),
|
||||
type: z.string().max(64).nullish(),
|
||||
value: z.string().max(255).nullish(),
|
||||
variable: z.string().max(255).nullish(),
|
||||
})
|
||||
|
||||
@ -1515,6 +1538,25 @@ export const zDeclaredOutputFailureStrategy = z.object({
|
||||
export const zDeclaredOutputConfig = z.object({
|
||||
array_item: zDeclaredArrayItem.nullish(),
|
||||
check: zDeclaredOutputCheckConfig.nullish(),
|
||||
children: z
|
||||
.array(
|
||||
z.object({
|
||||
array_item: z
|
||||
.object({
|
||||
children: z.array(z.record(z.string(), z.unknown())).optional(),
|
||||
description: z.string().nullish(),
|
||||
type: z.enum(['array', 'boolean', 'file', 'number', 'object', 'string']).optional(),
|
||||
})
|
||||
.optional(),
|
||||
children: z.array(z.record(z.string(), z.unknown())).optional(),
|
||||
description: z.string().nullish(),
|
||||
file: z.record(z.string(), z.unknown()).optional(),
|
||||
name: z.string(),
|
||||
required: z.boolean().optional(),
|
||||
type: z.enum(['array', 'boolean', 'file', 'number', 'object', 'string']),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
description: z.string().nullish(),
|
||||
failure_strategy: zDeclaredOutputFailureStrategy.optional(),
|
||||
file: zDeclaredOutputFileConfig.nullish(),
|
||||
@ -1735,6 +1777,7 @@ export const zMessageInfiniteScrollPaginationResponse = z.object({
|
||||
*/
|
||||
export const zAppPartialWritable = z.object({
|
||||
access_mode: z.string().nullish(),
|
||||
active_config_is_published: z.boolean().optional().default(false),
|
||||
app_id: z.string().nullish(),
|
||||
author_name: z.string().nullish(),
|
||||
bound_agent_id: z.string().nullish(),
|
||||
@ -1795,6 +1838,7 @@ export const zSiteWritable = z.object({
|
||||
*/
|
||||
export const zAppDetailWithSiteWritable = z.object({
|
||||
access_mode: z.string().nullish(),
|
||||
active_config_is_published: z.boolean().optional().default(false),
|
||||
api_base_url: z.string().nullish(),
|
||||
app_id: z.string().nullish(),
|
||||
bound_agent_id: z.string().nullish(),
|
||||
|
||||
@ -23,6 +23,7 @@ export type CreateAppPayload = {
|
||||
|
||||
export type AppDetailWithSite = {
|
||||
access_mode?: string | null
|
||||
active_config_is_published?: boolean
|
||||
api_base_url?: string | null
|
||||
app_id?: string | null
|
||||
bound_agent_id?: string | null
|
||||
@ -1155,6 +1156,7 @@ export type ApiKeyItem = {
|
||||
|
||||
export type AppPartial = {
|
||||
access_mode?: string | null
|
||||
active_config_is_published?: boolean
|
||||
app_id?: string | null
|
||||
author_name?: string | null
|
||||
bound_agent_id?: string | null
|
||||
@ -1760,6 +1762,26 @@ export type AgentComposerBindingResponse = {
|
||||
export type DeclaredOutputConfig = {
|
||||
array_item?: DeclaredArrayItem | null
|
||||
check?: DeclaredOutputCheckConfig | null
|
||||
children?: Array<{
|
||||
array_item?: {
|
||||
children?: Array<{
|
||||
[key: string]: unknown
|
||||
}>
|
||||
description?: string | null
|
||||
type?: 'array' | 'boolean' | 'file' | 'number' | 'object' | 'string'
|
||||
[key: string]: unknown
|
||||
}
|
||||
children?: Array<{
|
||||
[key: string]: unknown
|
||||
}>
|
||||
description?: string | null
|
||||
file?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
name: string
|
||||
required?: boolean
|
||||
type: 'array' | 'boolean' | 'file' | 'number' | 'object' | 'string'
|
||||
}>
|
||||
description?: string | null
|
||||
failure_strategy?: DeclaredOutputFailureStrategy
|
||||
file?: DeclaredOutputFileConfig | null
|
||||
@ -2103,6 +2125,26 @@ export type AgentSoulToolsConfig = {
|
||||
export type WorkflowAgentBindingType = 'inline_agent' | 'roster_agent'
|
||||
|
||||
export type DeclaredArrayItem = {
|
||||
children?: Array<{
|
||||
array_item?: {
|
||||
children?: Array<{
|
||||
[key: string]: unknown
|
||||
}>
|
||||
description?: string | null
|
||||
type?: 'array' | 'boolean' | 'file' | 'number' | 'object' | 'string'
|
||||
[key: string]: unknown
|
||||
}
|
||||
children?: Array<{
|
||||
[key: string]: unknown
|
||||
}>
|
||||
description?: string | null
|
||||
file?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
name: string
|
||||
required?: boolean
|
||||
type: 'array' | 'boolean' | 'file' | 'number' | 'object' | 'string'
|
||||
}>
|
||||
description?: string | null
|
||||
type: DeclaredOutputType
|
||||
}
|
||||
@ -2305,6 +2347,7 @@ export type AgentSecretRefConfig = {
|
||||
provider_credential_id?: string | null
|
||||
ref?: string | null
|
||||
type?: string | null
|
||||
value?: string | null
|
||||
variable?: string | null
|
||||
[key: string]: unknown
|
||||
}
|
||||
@ -2543,6 +2586,7 @@ export type AppPaginationWritable = {
|
||||
|
||||
export type AppDetailWithSiteWritable = {
|
||||
access_mode?: string | null
|
||||
active_config_is_published?: boolean
|
||||
api_base_url?: string | null
|
||||
app_id?: string | null
|
||||
bound_agent_id?: string | null
|
||||
@ -2595,6 +2639,7 @@ export type WorkflowCommentDetailWritable = {
|
||||
|
||||
export type AppPartialWritable = {
|
||||
access_mode?: string | null
|
||||
active_config_is_published?: boolean
|
||||
app_id?: string | null
|
||||
author_name?: string | null
|
||||
bound_agent_id?: string | null
|
||||
|
||||
@ -1946,6 +1946,7 @@ export const zModelConfigPartial = z.object({
|
||||
*/
|
||||
export const zAppPartial = z.object({
|
||||
access_mode: z.string().nullish(),
|
||||
active_config_is_published: z.boolean().optional().default(false),
|
||||
app_id: z.string().nullish(),
|
||||
author_name: z.string().nullish(),
|
||||
bound_agent_id: z.string().nullish(),
|
||||
@ -2005,6 +2006,7 @@ export const zModelConfig = z.object({
|
||||
*/
|
||||
export const zAppDetailWithSite = z.object({
|
||||
access_mode: z.string().nullish(),
|
||||
active_config_is_published: z.boolean().optional().default(false),
|
||||
api_base_url: z.string().nullish(),
|
||||
app_id: z.string().nullish(),
|
||||
bound_agent_id: z.string().nullish(),
|
||||
@ -2475,6 +2477,25 @@ export const zAgentComposerBindingResponse = z.object({
|
||||
* about. Stage 4 §4.2.
|
||||
*/
|
||||
export const zDeclaredArrayItem = z.object({
|
||||
children: z
|
||||
.array(
|
||||
z.object({
|
||||
array_item: z
|
||||
.object({
|
||||
children: z.array(z.record(z.string(), z.unknown())).optional(),
|
||||
description: z.string().nullish(),
|
||||
type: z.enum(['array', 'boolean', 'file', 'number', 'object', 'string']).optional(),
|
||||
})
|
||||
.optional(),
|
||||
children: z.array(z.record(z.string(), z.unknown())).optional(),
|
||||
description: z.string().nullish(),
|
||||
file: z.record(z.string(), z.unknown()).optional(),
|
||||
name: z.string(),
|
||||
required: z.boolean().optional(),
|
||||
type: z.enum(['array', 'boolean', 'file', 'number', 'object', 'string']),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
description: z.string().nullish(),
|
||||
type: zDeclaredOutputType,
|
||||
})
|
||||
@ -2908,6 +2929,7 @@ export const zAgentSecretRefConfig = z.object({
|
||||
provider_credential_id: z.string().max(255).nullish(),
|
||||
ref: z.string().max(255).nullish(),
|
||||
type: z.string().max(64).nullish(),
|
||||
value: z.string().max(255).nullish(),
|
||||
variable: z.string().max(255).nullish(),
|
||||
})
|
||||
|
||||
@ -3079,6 +3101,25 @@ export const zDeclaredOutputCheckConfig = z.object({
|
||||
export const zDeclaredOutputConfig = z.object({
|
||||
array_item: zDeclaredArrayItem.nullish(),
|
||||
check: zDeclaredOutputCheckConfig.nullish(),
|
||||
children: z
|
||||
.array(
|
||||
z.object({
|
||||
array_item: z
|
||||
.object({
|
||||
children: z.array(z.record(z.string(), z.unknown())).optional(),
|
||||
description: z.string().nullish(),
|
||||
type: z.enum(['array', 'boolean', 'file', 'number', 'object', 'string']).optional(),
|
||||
})
|
||||
.optional(),
|
||||
children: z.array(z.record(z.string(), z.unknown())).optional(),
|
||||
description: z.string().nullish(),
|
||||
file: z.record(z.string(), z.unknown()).optional(),
|
||||
name: z.string(),
|
||||
required: z.boolean().optional(),
|
||||
type: z.enum(['array', 'boolean', 'file', 'number', 'object', 'string']),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
description: z.string().nullish(),
|
||||
failure_strategy: zDeclaredOutputFailureStrategy.optional(),
|
||||
file: zDeclaredOutputFileConfig.nullish(),
|
||||
@ -3437,6 +3478,7 @@ export const zGeneratedAppResponseWritable = zJsonValue
|
||||
*/
|
||||
export const zAppPartialWritable = z.object({
|
||||
access_mode: z.string().nullish(),
|
||||
active_config_is_published: z.boolean().optional().default(false),
|
||||
app_id: z.string().nullish(),
|
||||
author_name: z.string().nullish(),
|
||||
bound_agent_id: z.string().nullish(),
|
||||
@ -3497,6 +3539,7 @@ export const zSiteWritable = z.object({
|
||||
*/
|
||||
export const zAppDetailWithSiteWritable = z.object({
|
||||
access_mode: z.string().nullish(),
|
||||
active_config_is_published: z.boolean().optional().default(false),
|
||||
api_base_url: z.string().nullish(),
|
||||
app_id: z.string().nullish(),
|
||||
bound_agent_id: z.string().nullish(),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user