mirror of
https://github.com/langgenius/dify.git
synced 2026-06-17 06:21:07 +08:00
feat: Unify Agent v2 console routes (#37465)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
a18635e566
commit
1e8329f02c
10
api/controllers/console/agent/app_helpers.py
Normal file
10
api/controllers/console/agent/app_helpers.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from extensions.ext_database import db
|
||||||
|
from models.model import App
|
||||||
|
from services.agent.roster_service import AgentRosterService
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_agent_app_model(*, tenant_id: str, agent_id: UUID) -> App:
|
||||||
|
"""Resolve the hidden Agent App backing an Agent Console resource."""
|
||||||
|
return AgentRosterService(db.session).get_agent_app_model(tenant_id=tenant_id, agent_id=str(agent_id))
|
||||||
@ -1,7 +1,10 @@
|
|||||||
|
from uuid import UUID
|
||||||
|
|
||||||
from flask_restx import Resource
|
from flask_restx import Resource
|
||||||
|
|
||||||
from controllers.common.schema import register_response_schema_models, register_schema_models
|
from controllers.common.schema import register_response_schema_models, register_schema_models
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
|
from controllers.console.agent.app_helpers import resolve_agent_app_model
|
||||||
from controllers.console.app.wraps import get_app_model
|
from controllers.console.app.wraps import get_app_model
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
@ -35,6 +38,10 @@ register_response_schema_models(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_agent_app_id(*, tenant_id: str, agent_id: UUID) -> str:
|
||||||
|
return resolve_agent_app_model(tenant_id=tenant_id, agent_id=agent_id).id
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/apps/<uuid:app_id>/workflows/draft/nodes/<string:node_id>/agent-composer")
|
@console_ns.route("/apps/<uuid:app_id>/workflows/draft/nodes/<string:node_id>/agent-composer")
|
||||||
class WorkflowAgentComposerApi(Resource):
|
class WorkflowAgentComposerApi(Resource):
|
||||||
@console_ns.response(
|
@console_ns.response(
|
||||||
@ -176,18 +183,18 @@ class WorkflowAgentComposerSaveToRosterApi(Resource):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/apps/<uuid:app_id>/agent-composer")
|
@console_ns.route("/agent/<uuid:agent_id>/composer")
|
||||||
class AgentAppComposerApi(Resource):
|
class AgentComposerApi(Resource):
|
||||||
@console_ns.response(200, "Agent app composer state", console_ns.models[AgentAppComposerResponse.__name__])
|
@console_ns.response(200, "Agent app composer state", console_ns.models[AgentAppComposerResponse.__name__])
|
||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@get_app_model(mode=[AppMode.AGENT])
|
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
def get(self, tenant_id: str, app_model: App):
|
def get(self, tenant_id: str, agent_id: UUID):
|
||||||
|
app_id = _resolve_agent_app_id(tenant_id=tenant_id, agent_id=agent_id)
|
||||||
return dump_response(
|
return dump_response(
|
||||||
AgentAppComposerResponse,
|
AgentAppComposerResponse,
|
||||||
AgentComposerService.load_agent_app_composer(tenant_id=tenant_id, app_id=app_model.id),
|
AgentComposerService.load_agent_app_composer(tenant_id=tenant_id, app_id=app_id),
|
||||||
)
|
)
|
||||||
|
|
||||||
@console_ns.expect(console_ns.models[ComposerSavePayload.__name__])
|
@console_ns.expect(console_ns.models[ComposerSavePayload.__name__])
|
||||||
@ -196,24 +203,24 @@ class AgentAppComposerApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@get_app_model(mode=[AppMode.AGENT])
|
|
||||||
@with_current_user_id
|
@with_current_user_id
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
def put(self, tenant_id: str, account_id: str, app_model: App):
|
def put(self, tenant_id: str, account_id: str, agent_id: UUID):
|
||||||
|
app_id = _resolve_agent_app_id(tenant_id=tenant_id, agent_id=agent_id)
|
||||||
payload = ComposerSavePayload.model_validate(console_ns.payload or {})
|
payload = ComposerSavePayload.model_validate(console_ns.payload or {})
|
||||||
return dump_response(
|
return dump_response(
|
||||||
AgentAppComposerResponse,
|
AgentAppComposerResponse,
|
||||||
AgentComposerService.save_agent_app_composer(
|
AgentComposerService.save_agent_app_composer(
|
||||||
tenant_id=tenant_id,
|
tenant_id=tenant_id,
|
||||||
app_id=app_model.id,
|
app_id=app_id,
|
||||||
account_id=account_id,
|
account_id=account_id,
|
||||||
payload=payload,
|
payload=payload,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/apps/<uuid:app_id>/agent-composer/validate")
|
@console_ns.route("/agent/<uuid:agent_id>/composer/validate")
|
||||||
class AgentAppComposerValidateApi(Resource):
|
class AgentComposerValidateApi(Resource):
|
||||||
@console_ns.expect(console_ns.models[ComposerSavePayload.__name__])
|
@console_ns.expect(console_ns.models[ComposerSavePayload.__name__])
|
||||||
@console_ns.response(
|
@console_ns.response(
|
||||||
200, "Agent app composer validation result", console_ns.models[AgentComposerValidateResponse.__name__]
|
200, "Agent app composer validation result", console_ns.models[AgentComposerValidateResponse.__name__]
|
||||||
@ -221,36 +228,36 @@ class AgentAppComposerValidateApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@get_app_model(mode=[AppMode.AGENT])
|
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
def post(self, tenant_id: str, app_model: App):
|
def post(self, tenant_id: str, agent_id: UUID):
|
||||||
|
_resolve_agent_app_id(tenant_id=tenant_id, agent_id=agent_id)
|
||||||
payload = ComposerSavePayload.model_validate(console_ns.payload or {})
|
payload = ComposerSavePayload.model_validate(console_ns.payload or {})
|
||||||
ComposerConfigValidator.validate_save_payload(payload)
|
ComposerConfigValidator.validate_save_payload(payload)
|
||||||
findings = AgentComposerService.collect_validation_findings(
|
findings = AgentComposerService.collect_validation_findings(
|
||||||
tenant_id=tenant_id,
|
tenant_id=tenant_id,
|
||||||
payload=payload,
|
payload=payload,
|
||||||
agent_id=AgentComposerService.resolve_bound_agent_id(tenant_id=tenant_id, app_id=app_model.id),
|
agent_id=str(agent_id),
|
||||||
)
|
)
|
||||||
return dump_response(AgentComposerValidateResponse, {"result": "success", "errors": [], **findings})
|
return dump_response(AgentComposerValidateResponse, {"result": "success", "errors": [], **findings})
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/apps/<uuid:app_id>/agent-composer/candidates")
|
@console_ns.route("/agent/<uuid:agent_id>/composer/candidates")
|
||||||
class AgentAppComposerCandidatesApi(Resource):
|
class AgentComposerCandidatesApi(Resource):
|
||||||
@console_ns.response(
|
@console_ns.response(
|
||||||
200, "Agent app composer candidates", console_ns.models[AgentComposerCandidatesResponse.__name__]
|
200, "Agent app composer candidates", console_ns.models[AgentComposerCandidatesResponse.__name__]
|
||||||
)
|
)
|
||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@get_app_model(mode=[AppMode.AGENT])
|
|
||||||
@with_current_user_id
|
@with_current_user_id
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
def get(self, tenant_id: str, current_user_id: str, app_model: App):
|
def get(self, tenant_id: str, current_user_id: str, agent_id: UUID):
|
||||||
|
app_id = _resolve_agent_app_id(tenant_id=tenant_id, agent_id=agent_id)
|
||||||
return dump_response(
|
return dump_response(
|
||||||
AgentComposerCandidatesResponse,
|
AgentComposerCandidatesResponse,
|
||||||
AgentComposerService.get_agent_app_candidates(
|
AgentComposerService.get_agent_app_candidates(
|
||||||
tenant_id=tenant_id,
|
tenant_id=tenant_id,
|
||||||
app_id=app_model.id,
|
app_id=app_id,
|
||||||
user_id=current_user_id,
|
user_id=current_user_id,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -6,10 +6,21 @@ from pydantic import BaseModel, Field
|
|||||||
|
|
||||||
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
|
from controllers.console.app.app import (
|
||||||
|
AppDetailWithSite,
|
||||||
|
AppListQuery,
|
||||||
|
AppPagination,
|
||||||
|
UpdateAppPayload,
|
||||||
|
_normalize_app_list_query_args,
|
||||||
|
)
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
|
cloud_edition_billing_resource_check,
|
||||||
|
edit_permission_required,
|
||||||
|
enterprise_license_required,
|
||||||
setup_required,
|
setup_required,
|
||||||
with_current_tenant_id,
|
with_current_tenant_id,
|
||||||
|
with_current_user,
|
||||||
)
|
)
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from fields.agent_fields import (
|
from fields.agent_fields import (
|
||||||
@ -18,12 +29,17 @@ from fields.agent_fields import (
|
|||||||
AgentInviteOptionsResponse,
|
AgentInviteOptionsResponse,
|
||||||
AgentPublishedReferenceResponse,
|
AgentPublishedReferenceResponse,
|
||||||
AgentRosterListResponse,
|
AgentRosterListResponse,
|
||||||
AgentRosterResponse,
|
|
||||||
)
|
)
|
||||||
from libs.helper import dump_response
|
from libs.helper import dump_response
|
||||||
from libs.login import login_required
|
from libs.login import login_required
|
||||||
|
from models import Account
|
||||||
|
from models.model import IconType
|
||||||
|
from services.agent.errors import AgentNotFoundError
|
||||||
from services.agent.roster_service import AgentRosterService
|
from services.agent.roster_service import AgentRosterService
|
||||||
|
from services.app_service import AppListParams, AppService, CreateAppParams
|
||||||
|
from services.enterprise.enterprise_service import EnterpriseService
|
||||||
from services.entities.agent_entities import RosterListQuery
|
from services.entities.agent_entities import RosterListQuery
|
||||||
|
from services.feature_service import FeatureService
|
||||||
|
|
||||||
|
|
||||||
class AgentInviteOptionsQuery(RosterListQuery):
|
class AgentInviteOptionsQuery(RosterListQuery):
|
||||||
@ -34,20 +50,32 @@ class AgentIdPath(BaseModel):
|
|||||||
agent_id: str
|
agent_id: str
|
||||||
|
|
||||||
|
|
||||||
|
class AgentAppCreatePayload(BaseModel):
|
||||||
|
name: str = Field(..., min_length=1, description="Agent name")
|
||||||
|
description: str | None = Field(default=None, description="Agent description (max 400 chars)", max_length=400)
|
||||||
|
icon_type: IconType | None = Field(default=None, description="Icon type")
|
||||||
|
icon: str | None = Field(default=None, description="Icon")
|
||||||
|
icon_background: str | None = Field(default=None, description="Icon background color")
|
||||||
|
|
||||||
|
|
||||||
register_schema_models(
|
register_schema_models(
|
||||||
console_ns,
|
console_ns,
|
||||||
|
AgentAppCreatePayload,
|
||||||
AgentInviteOptionsQuery,
|
AgentInviteOptionsQuery,
|
||||||
AgentIdPath,
|
AgentIdPath,
|
||||||
|
AppListQuery,
|
||||||
|
UpdateAppPayload,
|
||||||
RosterListQuery,
|
RosterListQuery,
|
||||||
)
|
)
|
||||||
register_response_schema_models(
|
register_response_schema_models(
|
||||||
console_ns,
|
console_ns,
|
||||||
|
AppDetailWithSite,
|
||||||
|
AppPagination,
|
||||||
AgentConfigSnapshotDetailResponse,
|
AgentConfigSnapshotDetailResponse,
|
||||||
AgentConfigSnapshotListResponse,
|
AgentConfigSnapshotListResponse,
|
||||||
AgentInviteOptionsResponse,
|
AgentInviteOptionsResponse,
|
||||||
AgentPublishedReferenceResponse,
|
AgentPublishedReferenceResponse,
|
||||||
AgentRosterListResponse,
|
AgentRosterListResponse,
|
||||||
AgentRosterResponse,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -55,25 +83,138 @@ def _agent_roster_service() -> AgentRosterService:
|
|||||||
return AgentRosterService(db.session)
|
return AgentRosterService(db.session)
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/agents")
|
def _serialize_agent_app_detail(app_model) -> dict:
|
||||||
class AgentRosterListApi(Resource):
|
app_model = AppService().get_app(app_model)
|
||||||
@console_ns.doc(params=query_params_from_model(RosterListQuery))
|
if FeatureService.get_system_features().webapp_auth.enabled:
|
||||||
@console_ns.response(200, "Agent roster list", console_ns.models[AgentRosterListResponse.__name__])
|
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]
|
||||||
|
|
||||||
|
payload = AppDetailWithSite.model_validate(app_model, from_attributes=True).model_dump(mode="json")
|
||||||
|
agent_id = payload.pop("bound_agent_id", None)
|
||||||
|
if not agent_id:
|
||||||
|
raise AgentNotFoundError()
|
||||||
|
payload["id"] = agent_id
|
||||||
|
return payload
|
||||||
|
|
||||||
|
|
||||||
|
def _serialize_agent_app_pagination(app_pagination) -> dict:
|
||||||
|
payload = AppPagination.model_validate(app_pagination, from_attributes=True).model_dump(mode="json")
|
||||||
|
for item in payload["data"]:
|
||||||
|
agent_id = item.pop("bound_agent_id", None)
|
||||||
|
if agent_id:
|
||||||
|
item["id"] = agent_id
|
||||||
|
return payload
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_agent_app_model(*, tenant_id: str, agent_id: UUID):
|
||||||
|
return _agent_roster_service().get_agent_app_model(tenant_id=tenant_id, agent_id=str(agent_id))
|
||||||
|
|
||||||
|
|
||||||
|
@console_ns.route("/agent")
|
||||||
|
class AgentAppListApi(Resource):
|
||||||
|
@console_ns.doc(params=query_params_from_model(AppListQuery))
|
||||||
|
@console_ns.response(200, "Agent app list", console_ns.models[AppPagination.__name__])
|
||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
|
@with_current_user
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
def get(self, tenant_id: str):
|
def get(self, current_tenant_id: str, current_user: Account):
|
||||||
query = RosterListQuery.model_validate(request.args.to_dict(flat=True))
|
args = AppListQuery.model_validate(_normalize_app_list_query_args(request.args))
|
||||||
return dump_response(
|
params = AppListParams(
|
||||||
AgentRosterListResponse,
|
page=args.page,
|
||||||
_agent_roster_service().list_roster_agents(
|
limit=args.limit,
|
||||||
tenant_id=tenant_id, page=query.page, limit=query.limit, keyword=query.keyword
|
mode="agent",
|
||||||
),
|
name=args.name,
|
||||||
|
tag_ids=args.tag_ids,
|
||||||
|
creator_ids=args.creator_ids,
|
||||||
|
is_created_by_me=args.is_created_by_me,
|
||||||
|
status="normal",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
app_pagination = AppService().get_paginate_apps(current_user.id, current_tenant_id, params)
|
||||||
|
if app_pagination is None:
|
||||||
|
empty = AppPagination(page=args.page, limit=args.limit, total=0, has_more=False, data=[])
|
||||||
|
return empty.model_dump(mode="json")
|
||||||
|
|
||||||
@console_ns.route("/agents/invite-options")
|
return _serialize_agent_app_pagination(app_pagination)
|
||||||
|
|
||||||
|
@console_ns.expect(console_ns.models[AgentAppCreatePayload.__name__])
|
||||||
|
@console_ns.response(201, "Agent app created successfully", console_ns.models[AppDetailWithSite.__name__])
|
||||||
|
@console_ns.response(403, "Insufficient permissions")
|
||||||
|
@console_ns.response(400, "Invalid request parameters")
|
||||||
|
@setup_required
|
||||||
|
@login_required
|
||||||
|
@account_initialization_required
|
||||||
|
@cloud_edition_billing_resource_check("apps")
|
||||||
|
@edit_permission_required
|
||||||
|
@with_current_user
|
||||||
|
@with_current_tenant_id
|
||||||
|
def post(self, current_tenant_id: str, current_user: Account):
|
||||||
|
args = AgentAppCreatePayload.model_validate(console_ns.payload)
|
||||||
|
params = CreateAppParams(
|
||||||
|
name=args.name,
|
||||||
|
description=args.description,
|
||||||
|
mode="agent",
|
||||||
|
icon_type=args.icon_type,
|
||||||
|
icon=args.icon,
|
||||||
|
icon_background=args.icon_background,
|
||||||
|
)
|
||||||
|
|
||||||
|
app = AppService().create_app(current_tenant_id, params, current_user)
|
||||||
|
return _serialize_agent_app_detail(app), 201
|
||||||
|
|
||||||
|
|
||||||
|
@console_ns.route("/agent/<uuid:agent_id>")
|
||||||
|
class AgentAppApi(Resource):
|
||||||
|
@console_ns.response(200, "Agent app detail", console_ns.models[AppDetailWithSite.__name__])
|
||||||
|
@setup_required
|
||||||
|
@login_required
|
||||||
|
@account_initialization_required
|
||||||
|
@enterprise_license_required
|
||||||
|
@with_current_tenant_id
|
||||||
|
def get(self, tenant_id: str, agent_id: UUID):
|
||||||
|
app_model = _resolve_agent_app_model(tenant_id=tenant_id, agent_id=agent_id)
|
||||||
|
return _serialize_agent_app_detail(app_model)
|
||||||
|
|
||||||
|
@console_ns.expect(console_ns.models[UpdateAppPayload.__name__])
|
||||||
|
@console_ns.response(200, "Agent app updated successfully", console_ns.models[AppDetailWithSite.__name__])
|
||||||
|
@console_ns.response(403, "Insufficient permissions")
|
||||||
|
@console_ns.response(400, "Invalid request parameters")
|
||||||
|
@setup_required
|
||||||
|
@login_required
|
||||||
|
@account_initialization_required
|
||||||
|
@edit_permission_required
|
||||||
|
@with_current_tenant_id
|
||||||
|
def put(self, tenant_id: str, agent_id: UUID):
|
||||||
|
app_model = _resolve_agent_app_model(tenant_id=tenant_id, agent_id=agent_id)
|
||||||
|
args = UpdateAppPayload.model_validate(console_ns.payload)
|
||||||
|
args_dict: AppService.ArgsDict = {
|
||||||
|
"name": args.name,
|
||||||
|
"description": args.description or "",
|
||||||
|
"icon_type": args.icon_type,
|
||||||
|
"icon": args.icon or "",
|
||||||
|
"icon_background": args.icon_background or "",
|
||||||
|
"use_icon_as_answer_icon": args.use_icon_as_answer_icon or False,
|
||||||
|
"max_active_requests": args.max_active_requests or 0,
|
||||||
|
}
|
||||||
|
updated = AppService().update_app(app_model, args_dict)
|
||||||
|
return _serialize_agent_app_detail(updated)
|
||||||
|
|
||||||
|
@console_ns.response(204, "Agent app deleted successfully")
|
||||||
|
@console_ns.response(403, "Insufficient permissions")
|
||||||
|
@setup_required
|
||||||
|
@login_required
|
||||||
|
@account_initialization_required
|
||||||
|
@edit_permission_required
|
||||||
|
@with_current_tenant_id
|
||||||
|
def delete(self, tenant_id: str, agent_id: UUID):
|
||||||
|
app_model = _resolve_agent_app_model(tenant_id=tenant_id, agent_id=agent_id)
|
||||||
|
AppService().delete_app(app_model)
|
||||||
|
return "", 204
|
||||||
|
|
||||||
|
|
||||||
|
@console_ns.route("/agent/invite-options")
|
||||||
class AgentInviteOptionsApi(Resource):
|
class AgentInviteOptionsApi(Resource):
|
||||||
@console_ns.doc(params=query_params_from_model(AgentInviteOptionsQuery))
|
@console_ns.doc(params=query_params_from_model(AgentInviteOptionsQuery))
|
||||||
@console_ns.response(200, "Agent invite options", console_ns.models[AgentInviteOptionsResponse.__name__])
|
@console_ns.response(200, "Agent invite options", console_ns.models[AgentInviteOptionsResponse.__name__])
|
||||||
@ -95,21 +236,7 @@ class AgentInviteOptionsApi(Resource):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/agents/<uuid:agent_id>")
|
@console_ns.route("/agent/<uuid:agent_id>/versions")
|
||||||
class AgentRosterDetailApi(Resource):
|
|
||||||
@console_ns.response(200, "Agent detail", console_ns.models[AgentRosterResponse.__name__])
|
|
||||||
@setup_required
|
|
||||||
@login_required
|
|
||||||
@account_initialization_required
|
|
||||||
@with_current_tenant_id
|
|
||||||
def get(self, tenant_id: str, agent_id: UUID):
|
|
||||||
return dump_response(
|
|
||||||
AgentRosterResponse,
|
|
||||||
_agent_roster_service().get_roster_agent_detail(tenant_id=tenant_id, agent_id=str(agent_id)),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/agents/<uuid:agent_id>/versions")
|
|
||||||
class AgentRosterVersionsApi(Resource):
|
class AgentRosterVersionsApi(Resource):
|
||||||
@console_ns.response(200, "Agent versions", console_ns.models[AgentConfigSnapshotListResponse.__name__])
|
@console_ns.response(200, "Agent versions", console_ns.models[AgentConfigSnapshotListResponse.__name__])
|
||||||
@setup_required
|
@setup_required
|
||||||
@ -123,7 +250,7 @@ class AgentRosterVersionsApi(Resource):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/agents/<uuid:agent_id>/versions/<uuid:version_id>")
|
@console_ns.route("/agent/<uuid:agent_id>/versions/<uuid:version_id>")
|
||||||
class AgentRosterVersionDetailApi(Resource):
|
class AgentRosterVersionDetailApi(Resource):
|
||||||
@console_ns.response(200, "Agent version detail", console_ns.models[AgentConfigSnapshotDetailResponse.__name__])
|
@console_ns.response(200, "Agent version detail", console_ns.models[AgentConfigSnapshotDetailResponse.__name__])
|
||||||
@setup_required
|
@setup_required
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask_restx import Resource
|
from flask_restx import Resource
|
||||||
@ -13,8 +14,14 @@ from controllers.common.schema import (
|
|||||||
register_schema_models,
|
register_schema_models,
|
||||||
)
|
)
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
|
from controllers.console.agent.app_helpers import resolve_agent_app_model
|
||||||
from controllers.console.app.wraps import get_app_model
|
from controllers.console.app.wraps import get_app_model
|
||||||
from controllers.console.wraps import account_initialization_required, setup_required, with_current_user
|
from controllers.console.wraps import (
|
||||||
|
account_initialization_required,
|
||||||
|
setup_required,
|
||||||
|
with_current_tenant_id,
|
||||||
|
with_current_user,
|
||||||
|
)
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from fields.base import ResponseModel
|
from fields.base import ResponseModel
|
||||||
from libs.helper import uuid_value
|
from libs.helper import uuid_value
|
||||||
@ -42,7 +49,7 @@ from services.file_service import FileService
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
_AGENT_DRIVE_APP_MODES = [AppMode.AGENT, AppMode.WORKFLOW, AppMode.ADVANCED_CHAT]
|
_WORKFLOW_AGENT_DRIVE_APP_MODES = [AppMode.WORKFLOW, AppMode.ADVANCED_CHAT]
|
||||||
|
|
||||||
|
|
||||||
class AgentLogQuery(BaseModel):
|
class AgentLogQuery(BaseModel):
|
||||||
@ -72,6 +79,10 @@ class AgentDriveDeleteFileQuery(AgentDriveMutationQuery):
|
|||||||
key: str = Field(min_length=1, description="Drive key, e.g. files/sample.pdf")
|
key: str = Field(min_length=1, description="Drive key, e.g. files/sample.pdf")
|
||||||
|
|
||||||
|
|
||||||
|
class AgentDriveDeleteFileByAgentQuery(BaseModel):
|
||||||
|
key: str = Field(min_length=1, description="Drive key, e.g. files/sample.pdf")
|
||||||
|
|
||||||
|
|
||||||
class AgentLogMetaResponse(ResponseModel):
|
class AgentLogMetaResponse(ResponseModel):
|
||||||
status: str
|
status: str
|
||||||
executor: str
|
executor: str
|
||||||
@ -138,7 +149,7 @@ class AgentDriveDeleteResponse(ResponseModel):
|
|||||||
config_version_id: str | None = None
|
config_version_id: str | None = None
|
||||||
|
|
||||||
|
|
||||||
register_schema_models(console_ns, AgentLogQuery, AgentDriveFilePayload)
|
register_schema_models(console_ns, AgentLogQuery, AgentDriveFilePayload, AgentDriveDeleteFileByAgentQuery)
|
||||||
register_response_schema_models(
|
register_response_schema_models(
|
||||||
console_ns,
|
console_ns,
|
||||||
AgentDriveDeleteResponse,
|
AgentDriveDeleteResponse,
|
||||||
@ -152,7 +163,7 @@ register_response_schema_models(
|
|||||||
|
|
||||||
|
|
||||||
def _resolve_agent_id(app_model: App, node_id: str | None) -> str | None:
|
def _resolve_agent_id(app_model: App, node_id: str | None) -> str | None:
|
||||||
if node_id:
|
if node_id and app_model.mode != AppMode.AGENT:
|
||||||
return AgentComposerService.resolve_workflow_node_agent_id(
|
return AgentComposerService.resolve_workflow_node_agent_id(
|
||||||
tenant_id=app_model.tenant_id, app_id=app_model.id, node_id=node_id
|
tenant_id=app_model.tenant_id, app_id=app_model.id, node_id=node_id
|
||||||
)
|
)
|
||||||
@ -163,6 +174,192 @@ def _agent_not_bound() -> tuple[dict[str, str], int]:
|
|||||||
return {"code": "agent_not_bound", "message": "no agent is bound for this app/node"}, 400
|
return {"code": "agent_not_bound", "message": "no agent is bound for this app/node"}, 400
|
||||||
|
|
||||||
|
|
||||||
|
def _upload_skill_for_app(*, current_user: Account):
|
||||||
|
if "file" not in request.files:
|
||||||
|
return {"code": "no_file", "message": "no skill file uploaded"}, 400
|
||||||
|
if len(request.files) > 1:
|
||||||
|
return {"code": "too_many_files", "message": "only one skill file is allowed"}, 400
|
||||||
|
|
||||||
|
upload = request.files["file"]
|
||||||
|
content = upload.stream.read()
|
||||||
|
try:
|
||||||
|
manifest = SkillPackageService().validate_and_extract(content=content, filename=upload.filename or "")
|
||||||
|
except SkillPackageError as exc:
|
||||||
|
return {"code": exc.code, "message": exc.message}, exc.status_code
|
||||||
|
|
||||||
|
upload_file = FileService(db.engine).upload_file(
|
||||||
|
filename=upload.filename or "skill.zip",
|
||||||
|
content=content,
|
||||||
|
mimetype=upload.mimetype or "application/zip",
|
||||||
|
user=current_user,
|
||||||
|
)
|
||||||
|
skill_ref = manifest.to_skill_ref(file_id=upload_file.id)
|
||||||
|
return {"skill": skill_ref.model_dump(exclude_none=True), "manifest": manifest.model_dump()}, 201
|
||||||
|
|
||||||
|
|
||||||
|
def _standardize_skill_for_app(*, current_user: Account, app_model: App):
|
||||||
|
query = query_params_from_request(AgentDriveMutationQuery)
|
||||||
|
agent_id = _resolve_agent_id(app_model, query.node_id)
|
||||||
|
if not agent_id:
|
||||||
|
return _agent_not_bound()
|
||||||
|
if "file" not in request.files:
|
||||||
|
return {"code": "no_file", "message": "no skill file uploaded"}, 400
|
||||||
|
if len(request.files) > 1:
|
||||||
|
return {"code": "too_many_files", "message": "only one skill file is allowed"}, 400
|
||||||
|
|
||||||
|
upload = request.files["file"]
|
||||||
|
content = upload.stream.read()
|
||||||
|
try:
|
||||||
|
result = SkillStandardizeService().standardize(
|
||||||
|
content=content,
|
||||||
|
filename=upload.filename or "",
|
||||||
|
tenant_id=app_model.tenant_id,
|
||||||
|
user_id=current_user.id,
|
||||||
|
agent_id=agent_id,
|
||||||
|
)
|
||||||
|
except (SkillPackageError, AgentDriveError) as exc:
|
||||||
|
return {"code": exc.code, "message": exc.message}, exc.status_code
|
||||||
|
return result, 201
|
||||||
|
|
||||||
|
|
||||||
|
def _commit_drive_file_for_app(*, current_user: Account, app_model: App, allow_node_id: bool = True):
|
||||||
|
query = query_params_from_request(AgentDriveMutationQuery)
|
||||||
|
node_id = query.node_id if allow_node_id else None
|
||||||
|
agent_id = _resolve_agent_id(app_model, node_id)
|
||||||
|
if not agent_id:
|
||||||
|
return _agent_not_bound()
|
||||||
|
payload = AgentDriveFilePayload.model_validate(console_ns.payload or {})
|
||||||
|
|
||||||
|
upload_file = db.session.scalar(
|
||||||
|
select(UploadFile).where(
|
||||||
|
UploadFile.id == payload.upload_file_id,
|
||||||
|
UploadFile.tenant_id == app_model.tenant_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if upload_file is None:
|
||||||
|
return {"code": "upload_file_not_found", "message": "upload file not found in this workspace"}, 404
|
||||||
|
|
||||||
|
try:
|
||||||
|
key = normalize_drive_key(f"files/{upload_file.name}")
|
||||||
|
committed = AgentDriveService().commit(
|
||||||
|
tenant_id=app_model.tenant_id,
|
||||||
|
user_id=current_user.id,
|
||||||
|
agent_id=agent_id,
|
||||||
|
items=[
|
||||||
|
DriveCommitItem(
|
||||||
|
key=key,
|
||||||
|
file_ref=DriveFileRef(kind="upload_file", id=upload_file.id),
|
||||||
|
# ADD FILE uploads exist solely to live in the drive, so the
|
||||||
|
# drive owns (and physically cleans) the value on delete.
|
||||||
|
value_owned_by_drive=True,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
except AgentDriveError as exc:
|
||||||
|
return {"code": exc.code, "message": exc.message}, exc.status_code
|
||||||
|
|
||||||
|
row = committed[0]
|
||||||
|
file_ref = AgentFileRefConfig.model_validate(
|
||||||
|
{
|
||||||
|
"id": row["key"],
|
||||||
|
"name": upload_file.name,
|
||||||
|
"file_id": upload_file.id,
|
||||||
|
"drive_key": row["key"],
|
||||||
|
"type": row.get("mime_type"),
|
||||||
|
"size": row.get("size"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
config_version_id = AgentComposerService.add_drive_file_ref(
|
||||||
|
tenant_id=app_model.tenant_id,
|
||||||
|
agent_id=agent_id,
|
||||||
|
account_id=current_user.id,
|
||||||
|
file_ref=file_ref,
|
||||||
|
app_id=app_model.id,
|
||||||
|
node_id=node_id,
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"file": {
|
||||||
|
"name": upload_file.name,
|
||||||
|
"drive_key": row["key"],
|
||||||
|
"file_id": upload_file.id,
|
||||||
|
"size": row.get("size"),
|
||||||
|
"mime_type": row.get("mime_type"),
|
||||||
|
},
|
||||||
|
"config_version_id": config_version_id,
|
||||||
|
}, 201
|
||||||
|
|
||||||
|
|
||||||
|
def _delete_drive_file_for_app(*, current_user: Account, app_model: App, allow_node_id: bool = True):
|
||||||
|
query = query_params_from_request(AgentDriveDeleteFileQuery)
|
||||||
|
node_id = query.node_id if allow_node_id else None
|
||||||
|
agent_id = _resolve_agent_id(app_model, node_id)
|
||||||
|
if not agent_id:
|
||||||
|
return _agent_not_bound()
|
||||||
|
try:
|
||||||
|
key = normalize_drive_key(query.key)
|
||||||
|
except AgentDriveError as exc:
|
||||||
|
return {"code": exc.code, "message": exc.message}, exc.status_code
|
||||||
|
|
||||||
|
config_version_id = AgentComposerService.remove_drive_refs(
|
||||||
|
tenant_id=app_model.tenant_id,
|
||||||
|
agent_id=agent_id,
|
||||||
|
account_id=current_user.id,
|
||||||
|
file_key=key,
|
||||||
|
app_id=app_model.id,
|
||||||
|
node_id=node_id,
|
||||||
|
)
|
||||||
|
removed_keys: list[str] = []
|
||||||
|
try:
|
||||||
|
removed_keys = AgentDriveService().delete(tenant_id=app_model.tenant_id, agent_id=agent_id, key=key)
|
||||||
|
except AgentDriveError as exc:
|
||||||
|
return {"code": exc.code, "message": exc.message}, exc.status_code
|
||||||
|
except Exception:
|
||||||
|
# Soul-first ordering: the ref is already gone; orphan KV rows are
|
||||||
|
# harmless and an idempotent DELETE retry cleans them.
|
||||||
|
logger.exception("agent drive delete failed for key %s (soul already updated)", key)
|
||||||
|
return {"result": "success", "removed_keys": removed_keys, "config_version_id": config_version_id}
|
||||||
|
|
||||||
|
|
||||||
|
def _delete_skill_for_app(*, current_user: Account, app_model: App, slug: str, allow_node_id: bool = True):
|
||||||
|
query = query_params_from_request(AgentDriveMutationQuery)
|
||||||
|
node_id = query.node_id if allow_node_id else None
|
||||||
|
agent_id = _resolve_agent_id(app_model, node_id)
|
||||||
|
if not agent_id:
|
||||||
|
return _agent_not_bound()
|
||||||
|
if "/" in slug or not slug.strip():
|
||||||
|
return {"code": "drive_key_invalid", "message": "skill slug must be a single path segment"}, 400
|
||||||
|
|
||||||
|
config_version_id = AgentComposerService.remove_drive_refs(
|
||||||
|
tenant_id=app_model.tenant_id,
|
||||||
|
agent_id=agent_id,
|
||||||
|
account_id=current_user.id,
|
||||||
|
skill_slug=slug,
|
||||||
|
app_id=app_model.id,
|
||||||
|
node_id=node_id,
|
||||||
|
)
|
||||||
|
removed_keys: list[str] = []
|
||||||
|
try:
|
||||||
|
removed_keys = AgentDriveService().delete(tenant_id=app_model.tenant_id, agent_id=agent_id, prefix=f"{slug}/")
|
||||||
|
except AgentDriveError as exc:
|
||||||
|
return {"code": exc.code, "message": exc.message}, exc.status_code
|
||||||
|
except Exception:
|
||||||
|
logger.exception("agent drive delete failed for skill %s (soul already updated)", slug)
|
||||||
|
return {"result": "success", "removed_keys": removed_keys, "config_version_id": config_version_id}
|
||||||
|
|
||||||
|
|
||||||
|
def _infer_skill_tools_for_app(*, app_model: App, slug: str):
|
||||||
|
query = query_params_from_request(AgentDriveMutationQuery)
|
||||||
|
agent_id = _resolve_agent_id(app_model, query.node_id)
|
||||||
|
if not agent_id:
|
||||||
|
return _agent_not_bound()
|
||||||
|
if "/" in slug or not slug.strip():
|
||||||
|
return {"code": "drive_key_invalid", "message": "skill slug must be a single path segment"}, 400
|
||||||
|
try:
|
||||||
|
return SkillToolInferenceService().infer(tenant_id=app_model.tenant_id, agent_id=agent_id, slug=slug)
|
||||||
|
except SkillToolInferenceError as exc:
|
||||||
|
return {"code": exc.code, "message": exc.message}, exc.status_code
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/apps/<uuid:app_id>/agent/logs")
|
@console_ns.route("/apps/<uuid:app_id>/agent/logs")
|
||||||
class AgentLogApi(Resource):
|
class AgentLogApi(Resource):
|
||||||
@console_ns.doc("get_agent_logs")
|
@console_ns.doc("get_agent_logs")
|
||||||
@ -182,6 +379,23 @@ class AgentLogApi(Resource):
|
|||||||
return AgentService.get_agent_logs(app_model, args.conversation_id, args.message_id)
|
return AgentService.get_agent_logs(app_model, args.conversation_id, args.message_id)
|
||||||
|
|
||||||
|
|
||||||
|
@console_ns.route("/agent/<uuid:agent_id>/skills/upload")
|
||||||
|
class AgentSkillUploadByAgentApi(Resource):
|
||||||
|
@console_ns.doc("upload_agent_skill_by_agent")
|
||||||
|
@console_ns.doc(description="Upload + validate a Skill package for an Agent App")
|
||||||
|
@console_ns.doc(params={"agent_id": "Agent ID"})
|
||||||
|
@console_ns.response(201, "Skill validated", console_ns.models[AgentSkillUploadResponse.__name__])
|
||||||
|
@console_ns.response(400, "Invalid skill package")
|
||||||
|
@setup_required
|
||||||
|
@login_required
|
||||||
|
@account_initialization_required
|
||||||
|
@with_current_user
|
||||||
|
@with_current_tenant_id
|
||||||
|
def post(self, tenant_id: str, current_user: Account, agent_id: UUID):
|
||||||
|
resolve_agent_app_model(tenant_id=tenant_id, agent_id=agent_id)
|
||||||
|
return _upload_skill_for_app(current_user=current_user)
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/apps/<uuid:app_id>/agent/skills/upload")
|
@console_ns.route("/apps/<uuid:app_id>/agent/skills/upload")
|
||||||
class AgentSkillUploadApi(Resource):
|
class AgentSkillUploadApi(Resource):
|
||||||
@console_ns.doc("upload_agent_skill")
|
@console_ns.doc("upload_agent_skill")
|
||||||
@ -192,7 +406,7 @@ class AgentSkillUploadApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@get_app_model(mode=_AGENT_DRIVE_APP_MODES)
|
@get_app_model(mode=_WORKFLOW_AGENT_DRIVE_APP_MODES)
|
||||||
@with_current_user
|
@with_current_user
|
||||||
def post(self, current_user: Account, app_model: App):
|
def post(self, current_user: Account, app_model: App):
|
||||||
"""Validate an uploaded Skill package and persist the archive.
|
"""Validate an uploaded Skill package and persist the archive.
|
||||||
@ -200,26 +414,28 @@ class AgentSkillUploadApi(Resource):
|
|||||||
Returns a validated skill ref (to bind into the Agent soul config on save)
|
Returns a validated skill ref (to bind into the Agent soul config on save)
|
||||||
plus its manifest. Standardizing into the agent drive is ENG-594.
|
plus its manifest. Standardizing into the agent drive is ENG-594.
|
||||||
"""
|
"""
|
||||||
if "file" not in request.files:
|
return _upload_skill_for_app(current_user=current_user)
|
||||||
return {"code": "no_file", "message": "no skill file uploaded"}, 400
|
|
||||||
if len(request.files) > 1:
|
|
||||||
return {"code": "too_many_files", "message": "only one skill file is allowed"}, 400
|
|
||||||
|
|
||||||
upload = request.files["file"]
|
|
||||||
content = upload.stream.read()
|
|
||||||
try:
|
|
||||||
manifest = SkillPackageService().validate_and_extract(content=content, filename=upload.filename or "")
|
|
||||||
except SkillPackageError as exc:
|
|
||||||
return {"code": exc.code, "message": exc.message}, exc.status_code
|
|
||||||
|
|
||||||
upload_file = FileService(db.engine).upload_file(
|
@console_ns.route("/agent/<uuid:agent_id>/skills/standardize")
|
||||||
filename=upload.filename or "skill.zip",
|
class AgentSkillStandardizeByAgentApi(Resource):
|
||||||
content=content,
|
@console_ns.doc("standardize_agent_skill_by_agent")
|
||||||
mimetype=upload.mimetype or "application/zip",
|
@console_ns.doc(description="Validate + standardize a Skill into an Agent App drive")
|
||||||
user=current_user,
|
@console_ns.doc(params={"agent_id": "Agent ID"})
|
||||||
)
|
@console_ns.response(
|
||||||
skill_ref = manifest.to_skill_ref(file_id=upload_file.id)
|
201,
|
||||||
return {"skill": skill_ref.model_dump(exclude_none=True), "manifest": manifest.model_dump()}, 201
|
"Skill standardized into drive",
|
||||||
|
console_ns.models[AgentSkillStandardizeResponse.__name__],
|
||||||
|
)
|
||||||
|
@console_ns.response(400, "Invalid skill package or no bound agent")
|
||||||
|
@setup_required
|
||||||
|
@login_required
|
||||||
|
@account_initialization_required
|
||||||
|
@with_current_user
|
||||||
|
@with_current_tenant_id
|
||||||
|
def post(self, tenant_id: str, current_user: Account, agent_id: UUID):
|
||||||
|
app_model = resolve_agent_app_model(tenant_id=tenant_id, agent_id=agent_id)
|
||||||
|
return _standardize_skill_for_app(current_user=current_user, app_model=app_model)
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/apps/<uuid:app_id>/agent/skills/standardize")
|
@console_ns.route("/apps/<uuid:app_id>/agent/skills/standardize")
|
||||||
@ -236,32 +452,43 @@ class AgentSkillStandardizeApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@get_app_model(mode=_AGENT_DRIVE_APP_MODES)
|
@get_app_model(mode=_WORKFLOW_AGENT_DRIVE_APP_MODES)
|
||||||
@with_current_user
|
@with_current_user
|
||||||
def post(self, current_user: Account, app_model: App):
|
def post(self, current_user: Account, app_model: App):
|
||||||
"""Upload a Skill, validate it, and standardize it into the app agent's drive."""
|
"""Upload a Skill, validate it, and standardize it into the app agent's drive."""
|
||||||
query = query_params_from_request(AgentDriveMutationQuery)
|
return _standardize_skill_for_app(current_user=current_user, app_model=app_model)
|
||||||
agent_id = _resolve_agent_id(app_model, query.node_id)
|
|
||||||
if not agent_id:
|
|
||||||
return _agent_not_bound()
|
|
||||||
if "file" not in request.files:
|
|
||||||
return {"code": "no_file", "message": "no skill file uploaded"}, 400
|
|
||||||
if len(request.files) > 1:
|
|
||||||
return {"code": "too_many_files", "message": "only one skill file is allowed"}, 400
|
|
||||||
|
|
||||||
upload = request.files["file"]
|
|
||||||
content = upload.stream.read()
|
@console_ns.route("/agent/<uuid:agent_id>/files")
|
||||||
try:
|
class AgentDriveFilesByAgentApi(Resource):
|
||||||
result = SkillStandardizeService().standardize(
|
@console_ns.doc("commit_agent_drive_file_by_agent")
|
||||||
content=content,
|
@console_ns.doc(description="Commit an uploaded file into the Agent App drive under files/<name>")
|
||||||
filename=upload.filename or "",
|
@console_ns.doc(params={"agent_id": "Agent ID"})
|
||||||
tenant_id=app_model.tenant_id,
|
@console_ns.expect(console_ns.models[AgentDriveFilePayload.__name__])
|
||||||
user_id=current_user.id,
|
@console_ns.response(
|
||||||
agent_id=agent_id,
|
201, "File committed into the agent drive", console_ns.models[AgentDriveFileCommitResponse.__name__]
|
||||||
)
|
)
|
||||||
except (SkillPackageError, AgentDriveError) as exc:
|
@setup_required
|
||||||
return {"code": exc.code, "message": exc.message}, exc.status_code
|
@login_required
|
||||||
return result, 201
|
@account_initialization_required
|
||||||
|
@with_current_user
|
||||||
|
@with_current_tenant_id
|
||||||
|
def post(self, tenant_id: str, current_user: Account, agent_id: UUID):
|
||||||
|
app_model = resolve_agent_app_model(tenant_id=tenant_id, agent_id=agent_id)
|
||||||
|
return _commit_drive_file_for_app(current_user=current_user, app_model=app_model, allow_node_id=False)
|
||||||
|
|
||||||
|
@console_ns.doc("delete_agent_drive_file_by_agent")
|
||||||
|
@console_ns.doc(description="Delete one Agent App drive file by key")
|
||||||
|
@console_ns.doc(params={"agent_id": "Agent ID", **query_params_from_model(AgentDriveDeleteFileByAgentQuery)})
|
||||||
|
@console_ns.response(200, "File removed", console_ns.models[AgentDriveDeleteResponse.__name__])
|
||||||
|
@setup_required
|
||||||
|
@login_required
|
||||||
|
@account_initialization_required
|
||||||
|
@with_current_user
|
||||||
|
@with_current_tenant_id
|
||||||
|
def delete(self, tenant_id: str, current_user: Account, agent_id: UUID):
|
||||||
|
app_model = resolve_agent_app_model(tenant_id=tenant_id, agent_id=agent_id)
|
||||||
|
return _delete_drive_file_for_app(current_user=current_user, app_model=app_model, allow_node_id=False)
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/apps/<uuid:app_id>/agent/files")
|
@console_ns.route("/apps/<uuid:app_id>/agent/files")
|
||||||
@ -276,73 +503,11 @@ class AgentDriveFilesApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@get_app_model(mode=_AGENT_DRIVE_APP_MODES)
|
@get_app_model(mode=_WORKFLOW_AGENT_DRIVE_APP_MODES)
|
||||||
@with_current_user
|
@with_current_user
|
||||||
def post(self, current_user: Account, app_model: App):
|
def post(self, current_user: Account, app_model: App):
|
||||||
"""ADD FILE: commit one uploaded file into the bound agent's drive."""
|
"""ADD FILE: commit one uploaded file into the bound agent's drive."""
|
||||||
query = query_params_from_request(AgentDriveMutationQuery)
|
return _commit_drive_file_for_app(current_user=current_user, app_model=app_model)
|
||||||
agent_id = _resolve_agent_id(app_model, query.node_id)
|
|
||||||
if not agent_id:
|
|
||||||
return _agent_not_bound()
|
|
||||||
payload = AgentDriveFilePayload.model_validate(console_ns.payload or {})
|
|
||||||
|
|
||||||
upload_file = db.session.scalar(
|
|
||||||
select(UploadFile).where(
|
|
||||||
UploadFile.id == payload.upload_file_id,
|
|
||||||
UploadFile.tenant_id == app_model.tenant_id,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if upload_file is None:
|
|
||||||
return {"code": "upload_file_not_found", "message": "upload file not found in this workspace"}, 404
|
|
||||||
|
|
||||||
try:
|
|
||||||
key = normalize_drive_key(f"files/{upload_file.name}")
|
|
||||||
committed = AgentDriveService().commit(
|
|
||||||
tenant_id=app_model.tenant_id,
|
|
||||||
user_id=current_user.id,
|
|
||||||
agent_id=agent_id,
|
|
||||||
items=[
|
|
||||||
DriveCommitItem(
|
|
||||||
key=key,
|
|
||||||
file_ref=DriveFileRef(kind="upload_file", id=upload_file.id),
|
|
||||||
# ADD FILE uploads exist solely to live in the drive, so the
|
|
||||||
# drive owns (and physically cleans) the value on delete.
|
|
||||||
value_owned_by_drive=True,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
except AgentDriveError as exc:
|
|
||||||
return {"code": exc.code, "message": exc.message}, exc.status_code
|
|
||||||
|
|
||||||
row = committed[0]
|
|
||||||
file_ref = AgentFileRefConfig.model_validate(
|
|
||||||
{
|
|
||||||
"id": row["key"],
|
|
||||||
"name": upload_file.name,
|
|
||||||
"file_id": upload_file.id,
|
|
||||||
"drive_key": row["key"],
|
|
||||||
"type": row.get("mime_type"),
|
|
||||||
"size": row.get("size"),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
config_version_id = AgentComposerService.add_drive_file_ref(
|
|
||||||
tenant_id=app_model.tenant_id,
|
|
||||||
agent_id=agent_id,
|
|
||||||
account_id=current_user.id,
|
|
||||||
file_ref=file_ref,
|
|
||||||
app_id=app_model.id,
|
|
||||||
node_id=query.node_id,
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
"file": {
|
|
||||||
"name": upload_file.name,
|
|
||||||
"drive_key": row["key"],
|
|
||||||
"file_id": upload_file.id,
|
|
||||||
"size": row.get("size"),
|
|
||||||
"mime_type": row.get("mime_type"),
|
|
||||||
},
|
|
||||||
"config_version_id": config_version_id,
|
|
||||||
}, 201
|
|
||||||
|
|
||||||
@console_ns.doc("delete_agent_drive_file")
|
@console_ns.doc("delete_agent_drive_file")
|
||||||
@console_ns.doc(description="Delete one drive file by key; soul ref first, then the KV row (ENG-625 D5)")
|
@console_ns.doc(description="Delete one drive file by key; soul ref first, then the KV row (ENG-625 D5)")
|
||||||
@ -351,36 +516,26 @@ class AgentDriveFilesApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@get_app_model(mode=_AGENT_DRIVE_APP_MODES)
|
@get_app_model(mode=_WORKFLOW_AGENT_DRIVE_APP_MODES)
|
||||||
@with_current_user
|
@with_current_user
|
||||||
def delete(self, current_user: Account, app_model: App):
|
def delete(self, current_user: Account, app_model: App):
|
||||||
query = query_params_from_request(AgentDriveDeleteFileQuery)
|
return _delete_drive_file_for_app(current_user=current_user, app_model=app_model)
|
||||||
agent_id = _resolve_agent_id(app_model, query.node_id)
|
|
||||||
if not agent_id:
|
|
||||||
return _agent_not_bound()
|
|
||||||
try:
|
|
||||||
key = normalize_drive_key(query.key)
|
|
||||||
except AgentDriveError as exc:
|
|
||||||
return {"code": exc.code, "message": exc.message}, exc.status_code
|
|
||||||
|
|
||||||
config_version_id = AgentComposerService.remove_drive_refs(
|
|
||||||
tenant_id=app_model.tenant_id,
|
@console_ns.route("/agent/<uuid:agent_id>/skills/<string:slug>")
|
||||||
agent_id=agent_id,
|
class AgentSkillByAgentApi(Resource):
|
||||||
account_id=current_user.id,
|
@console_ns.doc("delete_agent_skill_by_agent")
|
||||||
file_key=key,
|
@console_ns.doc(description="Delete a standardized skill from an Agent App drive")
|
||||||
app_id=app_model.id,
|
@console_ns.doc(params={"agent_id": "Agent ID", "slug": "Skill slug (single path segment)"})
|
||||||
node_id=query.node_id,
|
@console_ns.response(200, "Skill removed", console_ns.models[AgentDriveDeleteResponse.__name__])
|
||||||
)
|
@setup_required
|
||||||
removed_keys: list[str] = []
|
@login_required
|
||||||
try:
|
@account_initialization_required
|
||||||
removed_keys = AgentDriveService().delete(tenant_id=app_model.tenant_id, agent_id=agent_id, key=key)
|
@with_current_user
|
||||||
except AgentDriveError as exc:
|
@with_current_tenant_id
|
||||||
return {"code": exc.code, "message": exc.message}, exc.status_code
|
def delete(self, tenant_id: str, current_user: Account, agent_id: UUID, slug: str):
|
||||||
except Exception:
|
app_model = resolve_agent_app_model(tenant_id=tenant_id, agent_id=agent_id)
|
||||||
# Soul-first ordering: the ref is already gone; orphan KV rows are
|
return _delete_skill_for_app(current_user=current_user, app_model=app_model, slug=slug, allow_node_id=False)
|
||||||
# harmless and an idempotent DELETE retry cleans them.
|
|
||||||
logger.exception("agent drive delete failed for key %s (soul already updated)", key)
|
|
||||||
return {"result": "success", "removed_keys": removed_keys, "config_version_id": config_version_id}
|
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/apps/<uuid:app_id>/agent/skills/<string:slug>")
|
@console_ns.route("/apps/<uuid:app_id>/agent/skills/<string:slug>")
|
||||||
@ -400,34 +555,29 @@ class AgentSkillApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@get_app_model(mode=_AGENT_DRIVE_APP_MODES)
|
@get_app_model(mode=_WORKFLOW_AGENT_DRIVE_APP_MODES)
|
||||||
@with_current_user
|
@with_current_user
|
||||||
def delete(self, current_user: Account, app_model: App, slug: str):
|
def delete(self, current_user: Account, app_model: App, slug: str):
|
||||||
query = query_params_from_request(AgentDriveMutationQuery)
|
return _delete_skill_for_app(current_user=current_user, app_model=app_model, slug=slug)
|
||||||
agent_id = _resolve_agent_id(app_model, query.node_id)
|
|
||||||
if not agent_id:
|
|
||||||
return _agent_not_bound()
|
|
||||||
if "/" in slug or not slug.strip():
|
|
||||||
return {"code": "drive_key_invalid", "message": "skill slug must be a single path segment"}, 400
|
|
||||||
|
|
||||||
config_version_id = AgentComposerService.remove_drive_refs(
|
|
||||||
tenant_id=app_model.tenant_id,
|
@console_ns.route("/agent/<uuid:agent_id>/skills/<string:slug>/infer-tools")
|
||||||
agent_id=agent_id,
|
class AgentSkillInferToolsByAgentApi(Resource):
|
||||||
account_id=current_user.id,
|
@console_ns.doc("infer_agent_skill_tools_by_agent")
|
||||||
skill_slug=slug,
|
@console_ns.doc(description="Infer CLI tool + ENV suggestions from a standardized Agent App skill")
|
||||||
app_id=app_model.id,
|
@console_ns.doc(params={"agent_id": "Agent ID", "slug": "Skill slug (single path segment)"})
|
||||||
node_id=query.node_id,
|
@console_ns.response(
|
||||||
)
|
200,
|
||||||
removed_keys: list[str] = []
|
"Inference result (draft suggestions, nothing persisted)",
|
||||||
try:
|
console_ns.models[SkillToolInferenceResult.__name__],
|
||||||
removed_keys = AgentDriveService().delete(
|
)
|
||||||
tenant_id=app_model.tenant_id, agent_id=agent_id, prefix=f"{slug}/"
|
@setup_required
|
||||||
)
|
@login_required
|
||||||
except AgentDriveError as exc:
|
@account_initialization_required
|
||||||
return {"code": exc.code, "message": exc.message}, exc.status_code
|
@with_current_tenant_id
|
||||||
except Exception:
|
def post(self, tenant_id: str, agent_id: UUID, slug: str):
|
||||||
logger.exception("agent drive delete failed for skill %s (soul already updated)", slug)
|
app_model = resolve_agent_app_model(tenant_id=tenant_id, agent_id=agent_id)
|
||||||
return {"result": "success", "removed_keys": removed_keys, "config_version_id": config_version_id}
|
return _infer_skill_tools_for_app(app_model=app_model, slug=slug)
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/apps/<uuid:app_id>/agent/skills/<string:slug>/infer-tools")
|
@console_ns.route("/apps/<uuid:app_id>/agent/skills/<string:slug>/infer-tools")
|
||||||
@ -451,16 +601,7 @@ class AgentSkillInferToolsApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@get_app_model(mode=_AGENT_DRIVE_APP_MODES)
|
@get_app_model(mode=_WORKFLOW_AGENT_DRIVE_APP_MODES)
|
||||||
def post(self, app_model: App, slug: str):
|
def post(self, app_model: App, slug: str):
|
||||||
"""Suggest CLI tools/env for a skill. Saving still goes through composer validation."""
|
"""Suggest CLI tools/env for a skill. Saving still goes through composer validation."""
|
||||||
query = query_params_from_request(AgentDriveMutationQuery)
|
return _infer_skill_tools_for_app(app_model=app_model, slug=slug)
|
||||||
agent_id = _resolve_agent_id(app_model, query.node_id)
|
|
||||||
if not agent_id:
|
|
||||||
return _agent_not_bound()
|
|
||||||
if "/" in slug or not slug.strip():
|
|
||||||
return {"code": "drive_key_invalid", "message": "skill slug must be a single path segment"}, 400
|
|
||||||
try:
|
|
||||||
return SkillToolInferenceService().infer(tenant_id=app_model.tenant_id, agent_id=agent_id, slug=slug)
|
|
||||||
except SkillToolInferenceError as exc:
|
|
||||||
return {"code": exc.code, "message": exc.message}, exc.status_code
|
|
||||||
|
|||||||
@ -5,17 +5,18 @@ reference. This exposes the read-only "Workflow access" surface from the PRD:
|
|||||||
which workflow apps use this Agent, without leaking the workflows' internals.
|
which workflow apps use this Agent, without leaking the workflows' internals.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
from flask_restx import Resource
|
from flask_restx import Resource
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
|
||||||
from controllers.common.schema import register_response_schema_models
|
from controllers.common.schema import register_response_schema_models
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
from controllers.console.app.wraps import get_app_model
|
from controllers.console.agent.app_helpers import resolve_agent_app_model
|
||||||
from controllers.console.wraps import account_initialization_required, setup_required, with_current_tenant_id
|
from controllers.console.wraps import account_initialization_required, setup_required, with_current_tenant_id
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from fields.base import ResponseModel
|
from fields.base import ResponseModel
|
||||||
from libs.login import login_required
|
from libs.login import login_required
|
||||||
from models.model import App, AppMode
|
|
||||||
from services.agent.roster_service import AgentRosterService
|
from services.agent.roster_service import AgentRosterService
|
||||||
|
|
||||||
|
|
||||||
@ -34,23 +35,23 @@ class AgentReferencingWorkflowsResponse(ResponseModel):
|
|||||||
register_response_schema_models(console_ns, AgentReferencingWorkflowsResponse)
|
register_response_schema_models(console_ns, AgentReferencingWorkflowsResponse)
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/apps/<uuid:app_id>/agent-referencing-workflows")
|
@console_ns.route("/agent/<uuid:agent_id>/referencing-workflows")
|
||||||
class AgentAppReferencingWorkflowsResource(Resource):
|
class AgentAppReferencingWorkflowsResource(Resource):
|
||||||
@console_ns.doc("list_agent_app_referencing_workflows")
|
@console_ns.doc("list_agent_app_referencing_workflows")
|
||||||
@console_ns.doc(description="List workflow apps that reference this Agent App's bound Agent (read-only)")
|
@console_ns.doc(description="List workflow apps that reference this Agent App's bound Agent (read-only)")
|
||||||
@console_ns.doc(params={"app_id": "Application ID"})
|
@console_ns.doc(params={"agent_id": "Agent ID"})
|
||||||
@console_ns.response(
|
@console_ns.response(
|
||||||
200,
|
200,
|
||||||
"Referencing workflows listed successfully",
|
"Referencing workflows listed successfully",
|
||||||
console_ns.models[AgentReferencingWorkflowsResponse.__name__],
|
console_ns.models[AgentReferencingWorkflowsResponse.__name__],
|
||||||
)
|
)
|
||||||
@console_ns.response(404, "App not found")
|
@console_ns.response(404, "Agent not found")
|
||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@get_app_model(mode=[AppMode.AGENT])
|
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
def get(self, tenant_id: str, app_model: App):
|
def get(self, tenant_id: str, agent_id: UUID):
|
||||||
|
app_model = resolve_agent_app_model(tenant_id=tenant_id, agent_id=agent_id)
|
||||||
workflows = AgentRosterService(db.session).list_workflows_referencing_app_agent(
|
workflows = AgentRosterService(db.session).list_workflows_referencing_app_agent(
|
||||||
tenant_id=tenant_id, app_id=app_model.id
|
tenant_id=tenant_id, app_id=app_model.id
|
||||||
)
|
)
|
||||||
|
|||||||
@ -9,17 +9,20 @@ persists them onto the app's ``app_model_config`` without touching anything the
|
|||||||
Soul owns.
|
Soul owns.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
from flask_restx import Resource
|
from flask_restx import Resource
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from controllers.common.fields import SimpleResultResponse
|
from controllers.common.fields import SimpleResultResponse
|
||||||
from controllers.common.schema import register_response_schema_models, register_schema_models
|
from controllers.common.schema import register_response_schema_models, register_schema_models
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
from controllers.console.app.wraps import get_app_model
|
from controllers.console.agent.app_helpers import resolve_agent_app_model
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
edit_permission_required,
|
edit_permission_required,
|
||||||
setup_required,
|
setup_required,
|
||||||
|
with_current_tenant_id,
|
||||||
with_current_user,
|
with_current_user,
|
||||||
)
|
)
|
||||||
from events.app_event import app_model_config_was_updated
|
from events.app_event import app_model_config_was_updated
|
||||||
@ -32,7 +35,6 @@ from models.agent_config_entities import (
|
|||||||
AgentSuggestedQuestionsAfterAnswerFeatureConfig,
|
AgentSuggestedQuestionsAfterAnswerFeatureConfig,
|
||||||
AgentTextToSpeechFeatureConfig,
|
AgentTextToSpeechFeatureConfig,
|
||||||
)
|
)
|
||||||
from models.model import App, AppMode
|
|
||||||
from services.agent_app_feature_service import AgentAppFeatureConfigService
|
from services.agent_app_feature_service import AgentAppFeatureConfigService
|
||||||
|
|
||||||
|
|
||||||
@ -64,22 +66,23 @@ register_schema_models(console_ns, AgentAppFeaturesPayload)
|
|||||||
register_response_schema_models(console_ns, SimpleResultResponse)
|
register_response_schema_models(console_ns, SimpleResultResponse)
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/apps/<uuid:app_id>/agent-features")
|
@console_ns.route("/agent/<uuid:agent_id>/features")
|
||||||
class AgentAppFeatureConfigResource(Resource):
|
class AgentAppFeatureConfigResource(Resource):
|
||||||
@console_ns.doc("update_agent_app_features")
|
@console_ns.doc("update_agent_app_features")
|
||||||
@console_ns.doc(description="Update an Agent App's presentation features (opener, follow-up, citations, ...)")
|
@console_ns.doc(description="Update an Agent App's presentation features (opener, follow-up, citations, ...)")
|
||||||
@console_ns.doc(params={"app_id": "Application ID"})
|
@console_ns.doc(params={"agent_id": "Agent ID"})
|
||||||
@console_ns.expect(console_ns.models[AgentAppFeaturesPayload.__name__])
|
@console_ns.expect(console_ns.models[AgentAppFeaturesPayload.__name__])
|
||||||
@console_ns.response(200, "Features updated successfully", console_ns.models[SimpleResultResponse.__name__])
|
@console_ns.response(200, "Features updated successfully", console_ns.models[SimpleResultResponse.__name__])
|
||||||
@console_ns.response(400, "Invalid configuration")
|
@console_ns.response(400, "Invalid configuration")
|
||||||
@console_ns.response(404, "App not found")
|
@console_ns.response(404, "Agent not found")
|
||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@get_app_model(mode=[AppMode.AGENT])
|
|
||||||
@with_current_user
|
@with_current_user
|
||||||
def post(self, current_user: Account, app_model: App):
|
@with_current_tenant_id
|
||||||
|
def post(self, tenant_id: str, current_user: Account, agent_id: UUID):
|
||||||
|
app_model = resolve_agent_app_model(tenant_id=tenant_id, agent_id=agent_id)
|
||||||
args = AgentAppFeaturesPayload.model_validate(console_ns.payload or {})
|
args = AgentAppFeaturesPayload.model_validate(console_ns.payload or {})
|
||||||
|
|
||||||
new_app_model_config = AgentAppFeatureConfigService.update_features(
|
new_app_model_config = AgentAppFeatureConfigService.update_features(
|
||||||
|
|||||||
@ -22,6 +22,7 @@ from controllers.common.schema import (
|
|||||||
register_schema_models,
|
register_schema_models,
|
||||||
)
|
)
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
|
from controllers.console.agent.app_helpers import resolve_agent_app_model
|
||||||
from controllers.console.app.wraps import get_app_model
|
from controllers.console.app.wraps import get_app_model
|
||||||
from controllers.console.wraps import account_initialization_required, setup_required, with_current_tenant_id
|
from controllers.console.wraps import account_initialization_required, setup_required, with_current_tenant_id
|
||||||
from fields.base import ResponseModel
|
from fields.base import ResponseModel
|
||||||
@ -132,18 +133,18 @@ def _handle(exc: Exception) -> tuple[dict[str, object], int]:
|
|||||||
raise exc
|
raise exc
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/apps/<uuid:app_id>/agent-sandbox/files")
|
@console_ns.route("/agent/<uuid:agent_id>/sandbox/files")
|
||||||
class AgentAppSandboxListResource(Resource):
|
class AgentAppSandboxListResource(Resource):
|
||||||
@console_ns.doc("list_agent_app_sandbox_files")
|
@console_ns.doc("list_agent_app_sandbox_files")
|
||||||
@console_ns.doc(description="List a directory in an Agent App conversation sandbox")
|
@console_ns.doc(description="List a directory in an Agent App conversation sandbox")
|
||||||
@console_ns.doc(params={"app_id": "Application ID", **query_params_from_model(AgentSandboxListQuery)})
|
@console_ns.doc(params={"agent_id": "Agent ID", **query_params_from_model(AgentSandboxListQuery)})
|
||||||
@console_ns.response(200, "Listing returned", console_ns.models[SandboxListResponse.__name__])
|
@console_ns.response(200, "Listing returned", console_ns.models[SandboxListResponse.__name__])
|
||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@get_app_model(mode=[AppMode.AGENT])
|
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
def get(self, tenant_id: str, app_model: App):
|
def get(self, tenant_id: str, agent_id: UUID):
|
||||||
|
app_model = resolve_agent_app_model(tenant_id=tenant_id, agent_id=agent_id)
|
||||||
query = query_params_from_request(AgentSandboxListQuery)
|
query = query_params_from_request(AgentSandboxListQuery)
|
||||||
try:
|
try:
|
||||||
result = AgentAppSandboxService().list_files(
|
result = AgentAppSandboxService().list_files(
|
||||||
@ -157,18 +158,18 @@ class AgentAppSandboxListResource(Resource):
|
|||||||
return result.model_dump()
|
return result.model_dump()
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/apps/<uuid:app_id>/agent-sandbox/files/read")
|
@console_ns.route("/agent/<uuid:agent_id>/sandbox/files/read")
|
||||||
class AgentAppSandboxReadResource(Resource):
|
class AgentAppSandboxReadResource(Resource):
|
||||||
@console_ns.doc("read_agent_app_sandbox_file")
|
@console_ns.doc("read_agent_app_sandbox_file")
|
||||||
@console_ns.doc(description="Read a text/binary preview file in an Agent App conversation sandbox")
|
@console_ns.doc(description="Read a text/binary preview file in an Agent App conversation sandbox")
|
||||||
@console_ns.doc(params={"app_id": "Application ID", **query_params_from_model(AgentSandboxFileQuery)})
|
@console_ns.doc(params={"agent_id": "Agent ID", **query_params_from_model(AgentSandboxFileQuery)})
|
||||||
@console_ns.response(200, "Preview returned", console_ns.models[SandboxReadResponse.__name__])
|
@console_ns.response(200, "Preview returned", console_ns.models[SandboxReadResponse.__name__])
|
||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@get_app_model(mode=[AppMode.AGENT])
|
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
def get(self, tenant_id: str, app_model: App):
|
def get(self, tenant_id: str, agent_id: UUID):
|
||||||
|
app_model = resolve_agent_app_model(tenant_id=tenant_id, agent_id=agent_id)
|
||||||
query = query_params_from_request(AgentSandboxFileQuery)
|
query = query_params_from_request(AgentSandboxFileQuery)
|
||||||
try:
|
try:
|
||||||
result = AgentAppSandboxService().read_file(
|
result = AgentAppSandboxService().read_file(
|
||||||
@ -182,7 +183,7 @@ class AgentAppSandboxReadResource(Resource):
|
|||||||
return result.model_dump()
|
return result.model_dump()
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/apps/<uuid:app_id>/agent-sandbox/files/upload")
|
@console_ns.route("/agent/<uuid:agent_id>/sandbox/files/upload")
|
||||||
class AgentAppSandboxUploadResource(Resource):
|
class AgentAppSandboxUploadResource(Resource):
|
||||||
@console_ns.doc("upload_agent_app_sandbox_file")
|
@console_ns.doc("upload_agent_app_sandbox_file")
|
||||||
@console_ns.doc(description="Upload one Agent App sandbox file as a Dify ToolFile mapping")
|
@console_ns.doc(description="Upload one Agent App sandbox file as a Dify ToolFile mapping")
|
||||||
@ -191,9 +192,9 @@ class AgentAppSandboxUploadResource(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@get_app_model(mode=[AppMode.AGENT])
|
|
||||||
@with_current_tenant_id
|
@with_current_tenant_id
|
||||||
def post(self, tenant_id: str, app_model: App):
|
def post(self, tenant_id: str, agent_id: UUID):
|
||||||
|
app_model = resolve_agent_app_model(tenant_id=tenant_id, agent_id=agent_id)
|
||||||
payload = AgentSandboxUploadPayload.model_validate(request.get_json(silent=True) or {})
|
payload = AgentSandboxUploadPayload.model_validate(request.get_json(silent=True) or {})
|
||||||
try:
|
try:
|
||||||
result = AgentAppSandboxService().upload_file(
|
result = AgentAppSandboxService().upload_file(
|
||||||
|
|||||||
@ -10,6 +10,8 @@ backend — drive data lives in the API's own DB/storage, served straight from
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
from flask_restx import Resource
|
from flask_restx import Resource
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
@ -19,8 +21,9 @@ from controllers.common.schema import (
|
|||||||
register_response_schema_models,
|
register_response_schema_models,
|
||||||
)
|
)
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
|
from controllers.console.agent.app_helpers import resolve_agent_app_model
|
||||||
from controllers.console.app.wraps import get_app_model
|
from controllers.console.app.wraps import get_app_model
|
||||||
from controllers.console.wraps import account_initialization_required, setup_required
|
from controllers.console.wraps import account_initialization_required, setup_required, with_current_tenant_id
|
||||||
from fields.base import ResponseModel
|
from fields.base import ResponseModel
|
||||||
from libs.login import login_required
|
from libs.login import login_required
|
||||||
from models.model import App, AppMode
|
from models.model import App, AppMode
|
||||||
@ -33,11 +36,19 @@ class AgentDriveListQuery(BaseModel):
|
|||||||
node_id: str | None = Field(default=None, description="Workflow node ID (workflow composer variant)")
|
node_id: str | None = Field(default=None, description="Workflow node ID (workflow composer variant)")
|
||||||
|
|
||||||
|
|
||||||
|
class AgentDriveListByAgentQuery(BaseModel):
|
||||||
|
prefix: str = Field(default="", description="Key prefix filter: '<slug>/' for one skill, 'files/' for files")
|
||||||
|
|
||||||
|
|
||||||
class AgentDriveFileQuery(BaseModel):
|
class AgentDriveFileQuery(BaseModel):
|
||||||
key: str = Field(min_length=1, description="Drive key, e.g. tender-analyzer/SKILL.md")
|
key: str = Field(min_length=1, description="Drive key, e.g. tender-analyzer/SKILL.md")
|
||||||
node_id: str | None = Field(default=None, description="Workflow node ID (workflow composer variant)")
|
node_id: str | None = Field(default=None, description="Workflow node ID (workflow composer variant)")
|
||||||
|
|
||||||
|
|
||||||
|
class AgentDriveFileByAgentQuery(BaseModel):
|
||||||
|
key: str = Field(min_length=1, description="Drive key, e.g. tender-analyzer/SKILL.md")
|
||||||
|
|
||||||
|
|
||||||
class AgentDriveItemResponse(ResponseModel):
|
class AgentDriveItemResponse(ResponseModel):
|
||||||
key: str
|
key: str
|
||||||
size: int | None = None
|
size: int | None = None
|
||||||
@ -85,7 +96,66 @@ def _handle(exc: AgentDriveError) -> tuple[dict[str, object], int]:
|
|||||||
return {"code": exc.code, "message": exc.message}, exc.status_code
|
return {"code": exc.code, "message": exc.message}, exc.status_code
|
||||||
|
|
||||||
|
|
||||||
_APP_MODES = [AppMode.AGENT, AppMode.WORKFLOW, AppMode.ADVANCED_CHAT]
|
_WORKFLOW_APP_MODES = [AppMode.WORKFLOW, AppMode.ADVANCED_CHAT]
|
||||||
|
|
||||||
|
|
||||||
|
@console_ns.route("/agent/<uuid:agent_id>/drive/files")
|
||||||
|
class AgentDriveListByAgentApi(Resource):
|
||||||
|
@console_ns.doc("list_agent_drive_files_by_agent")
|
||||||
|
@console_ns.doc(description="List agent drive entries for an Agent App")
|
||||||
|
@console_ns.doc(params={"agent_id": "Agent ID", **query_params_from_model(AgentDriveListByAgentQuery)})
|
||||||
|
@console_ns.response(200, "Drive entries", console_ns.models[AgentDriveListResponse.__name__])
|
||||||
|
@setup_required
|
||||||
|
@login_required
|
||||||
|
@account_initialization_required
|
||||||
|
@with_current_tenant_id
|
||||||
|
def get(self, tenant_id: str, agent_id: UUID):
|
||||||
|
query = query_params_from_request(AgentDriveListByAgentQuery)
|
||||||
|
resolve_agent_app_model(tenant_id=tenant_id, agent_id=agent_id)
|
||||||
|
try:
|
||||||
|
items = AgentDriveService().manifest(tenant_id=tenant_id, agent_id=str(agent_id), prefix=query.prefix)
|
||||||
|
except AgentDriveError as exc:
|
||||||
|
return _handle(exc)
|
||||||
|
return {"items": [{k: v for k, v in item.items() if k != "file_id"} for item in items]}
|
||||||
|
|
||||||
|
|
||||||
|
@console_ns.route("/agent/<uuid:agent_id>/drive/files/preview")
|
||||||
|
class AgentDrivePreviewByAgentApi(Resource):
|
||||||
|
@console_ns.doc("preview_agent_drive_file_by_agent")
|
||||||
|
@console_ns.doc(description="Truncated text preview of one Agent App drive value")
|
||||||
|
@console_ns.doc(params={"agent_id": "Agent ID", **query_params_from_model(AgentDriveFileByAgentQuery)})
|
||||||
|
@console_ns.response(200, "Preview", console_ns.models[AgentDrivePreviewResponse.__name__])
|
||||||
|
@setup_required
|
||||||
|
@login_required
|
||||||
|
@account_initialization_required
|
||||||
|
@with_current_tenant_id
|
||||||
|
def get(self, tenant_id: str, agent_id: UUID):
|
||||||
|
query = query_params_from_request(AgentDriveFileByAgentQuery)
|
||||||
|
resolve_agent_app_model(tenant_id=tenant_id, agent_id=agent_id)
|
||||||
|
try:
|
||||||
|
return AgentDriveService().preview(tenant_id=tenant_id, agent_id=str(agent_id), key=query.key)
|
||||||
|
except AgentDriveError as exc:
|
||||||
|
return _handle(exc)
|
||||||
|
|
||||||
|
|
||||||
|
@console_ns.route("/agent/<uuid:agent_id>/drive/files/download")
|
||||||
|
class AgentDriveDownloadByAgentApi(Resource):
|
||||||
|
@console_ns.doc("download_agent_drive_file_by_agent")
|
||||||
|
@console_ns.doc(description="Time-limited external signed URL for one Agent App drive value")
|
||||||
|
@console_ns.doc(params={"agent_id": "Agent ID", **query_params_from_model(AgentDriveFileByAgentQuery)})
|
||||||
|
@console_ns.response(200, "Signed URL", console_ns.models[AgentDriveDownloadResponse.__name__])
|
||||||
|
@setup_required
|
||||||
|
@login_required
|
||||||
|
@account_initialization_required
|
||||||
|
@with_current_tenant_id
|
||||||
|
def get(self, tenant_id: str, agent_id: UUID):
|
||||||
|
query = query_params_from_request(AgentDriveFileByAgentQuery)
|
||||||
|
resolve_agent_app_model(tenant_id=tenant_id, agent_id=agent_id)
|
||||||
|
try:
|
||||||
|
url = AgentDriveService().download_url(tenant_id=tenant_id, agent_id=str(agent_id), key=query.key)
|
||||||
|
except AgentDriveError as exc:
|
||||||
|
return _handle(exc)
|
||||||
|
return {"url": url}
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/apps/<uuid:app_id>/agent/drive/files")
|
@console_ns.route("/apps/<uuid:app_id>/agent/drive/files")
|
||||||
@ -97,7 +167,7 @@ class AgentDriveListApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@get_app_model(mode=_APP_MODES)
|
@get_app_model(mode=_WORKFLOW_APP_MODES)
|
||||||
def get(self, app_model: App):
|
def get(self, app_model: App):
|
||||||
query = query_params_from_request(AgentDriveListQuery)
|
query = query_params_from_request(AgentDriveListQuery)
|
||||||
agent_id = _resolve_agent_id(app_model, query.node_id)
|
agent_id = _resolve_agent_id(app_model, query.node_id)
|
||||||
@ -121,7 +191,7 @@ class AgentDrivePreviewApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@get_app_model(mode=_APP_MODES)
|
@get_app_model(mode=_WORKFLOW_APP_MODES)
|
||||||
def get(self, app_model: App):
|
def get(self, app_model: App):
|
||||||
query = query_params_from_request(AgentDriveFileQuery)
|
query = query_params_from_request(AgentDriveFileQuery)
|
||||||
agent_id = _resolve_agent_id(app_model, query.node_id)
|
agent_id = _resolve_agent_id(app_model, query.node_id)
|
||||||
@ -142,7 +212,7 @@ class AgentDriveDownloadApi(Resource):
|
|||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@get_app_model(mode=_APP_MODES)
|
@get_app_model(mode=_WORKFLOW_APP_MODES)
|
||||||
def get(self, app_model: App):
|
def get(self, app_model: App):
|
||||||
query = query_params_from_request(AgentDriveFileQuery)
|
query = query_params_from_request(AgentDriveFileQuery)
|
||||||
agent_id = _resolve_agent_id(app_model, query.node_id)
|
agent_id = _resolve_agent_id(app_model, query.node_id)
|
||||||
@ -157,6 +227,9 @@ class AgentDriveDownloadApi(Resource):
|
|||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"AgentDriveDownloadApi",
|
"AgentDriveDownloadApi",
|
||||||
|
"AgentDriveDownloadByAgentApi",
|
||||||
"AgentDriveListApi",
|
"AgentDriveListApi",
|
||||||
|
"AgentDriveListByAgentApi",
|
||||||
"AgentDrivePreviewApi",
|
"AgentDrivePreviewApi",
|
||||||
|
"AgentDrivePreviewByAgentApi",
|
||||||
]
|
]
|
||||||
|
|||||||
@ -64,7 +64,7 @@ from services.entities.knowledge_entities.knowledge_entities import (
|
|||||||
)
|
)
|
||||||
from services.feature_service import FeatureService
|
from services.feature_service import FeatureService
|
||||||
|
|
||||||
ALLOW_CREATE_APP_MODES = ["chat", "agent-chat", "agent", "advanced-chat", "workflow", "completion"]
|
ALLOW_CREATE_APP_MODES = ["chat", "agent-chat", "advanced-chat", "workflow", "completion"]
|
||||||
|
|
||||||
register_enum_models(console_ns, IconType)
|
register_enum_models(console_ns, IconType)
|
||||||
|
|
||||||
@ -163,9 +163,7 @@ def _normalize_app_list_query_args(query_args: MultiDict[str, str]) -> dict[str,
|
|||||||
class CreateAppPayload(BaseModel):
|
class CreateAppPayload(BaseModel):
|
||||||
name: str = Field(..., min_length=1, description="App name")
|
name: str = Field(..., min_length=1, description="App name")
|
||||||
description: str | None = Field(default=None, description="App description (max 400 chars)", max_length=400)
|
description: str | None = Field(default=None, description="App description (max 400 chars)", max_length=400)
|
||||||
mode: Literal["chat", "agent-chat", "agent", "advanced-chat", "workflow", "completion"] = Field(
|
mode: Literal["chat", "agent-chat", "advanced-chat", "workflow", "completion"] = Field(..., description="App mode")
|
||||||
..., description="App mode"
|
|
||||||
)
|
|
||||||
icon_type: IconType | None = Field(default=None, description="Icon type")
|
icon_type: IconType | None = Field(default=None, description="Icon type")
|
||||||
icon: str | None = Field(default=None, description="Icon")
|
icon: str | None = Field(default=None, description="Icon")
|
||||||
icon_background: str | None = Field(default=None, description="Icon background color")
|
icon_background: str | None = Field(default=None, description="Icon background color")
|
||||||
@ -400,6 +398,8 @@ class AppPartial(ResponseModel):
|
|||||||
create_user_name: str | None = None
|
create_user_name: str | None = None
|
||||||
author_name: str | None = None
|
author_name: str | None = None
|
||||||
has_draft_trigger: bool | None = None
|
has_draft_trigger: bool | None = None
|
||||||
|
# For Agent App type: the roster Agent backing this app (None otherwise).
|
||||||
|
bound_agent_id: str | None = None
|
||||||
is_starred: bool = False
|
is_starred: bool = False
|
||||||
|
|
||||||
@computed_field(return_type=str | None) # type: ignore
|
@computed_field(return_type=str | None) # type: ignore
|
||||||
|
|||||||
@ -293,22 +293,42 @@ Check if activation token is valid
|
|||||||
| ---- | ----------- | ------ |
|
| ---- | ----------- | ------ |
|
||||||
| 200 | Success | **application/json**: [ActivationCheckResponse](#activationcheckresponse)<br> |
|
| 200 | Success | **application/json**: [ActivationCheckResponse](#activationcheckresponse)<br> |
|
||||||
|
|
||||||
### [GET] /agents
|
### [GET] /agent
|
||||||
#### Parameters
|
#### Parameters
|
||||||
|
|
||||||
| Name | Located in | Description | Required | Schema |
|
| Name | Located in | Description | Required | Schema |
|
||||||
| ---- | ---------- | ----------- | -------- | ------ |
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
| keyword | query | | No | string |
|
| creator_ids | query | Filter by creator account IDs | No | [ string ] |
|
||||||
| limit | query | | No | integer, <br>**Default:** 20 |
|
| is_created_by_me | query | Filter by creator | No | boolean |
|
||||||
| page | query | | No | integer, <br>**Default:** 1 |
|
| limit | query | Page size (1-100) | No | integer, <br>**Default:** 20 |
|
||||||
|
| mode | query | App mode filter | No | string, <br>**Available values:** "advanced-chat", "agent", "agent-chat", "all", "channel", "chat", "completion", "workflow", <br>**Default:** all |
|
||||||
|
| name | query | Filter by app name | No | string |
|
||||||
|
| page | query | Page number (1-99999) | No | integer, <br>**Default:** 1 |
|
||||||
|
| sort_by | query | Sort apps by last modified, recently created, or earliest created | No | string, <br>**Available values:** "earliest_created", "last_modified", "recently_created", <br>**Default:** last_modified |
|
||||||
|
| tag_ids | query | Filter by tag IDs | No | [ string ] |
|
||||||
|
|
||||||
#### Responses
|
#### Responses
|
||||||
|
|
||||||
| Code | Description | Schema |
|
| Code | Description | Schema |
|
||||||
| ---- | ----------- | ------ |
|
| ---- | ----------- | ------ |
|
||||||
| 200 | Agent roster list | **application/json**: [AgentRosterListResponse](#agentrosterlistresponse)<br> |
|
| 200 | Agent app list | **application/json**: [AppPagination](#apppagination)<br> |
|
||||||
|
|
||||||
### [GET] /agents/invite-options
|
### [POST] /agent
|
||||||
|
#### Request Body
|
||||||
|
|
||||||
|
| Required | Schema |
|
||||||
|
| -------- | ------ |
|
||||||
|
| Yes | **application/json**: [AgentAppCreatePayload](#agentappcreatepayload)<br> |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 201 | Agent app created successfully | **application/json**: [AppDetailWithSite](#appdetailwithsite)<br> |
|
||||||
|
| 400 | Invalid request parameters | |
|
||||||
|
| 403 | Insufficient permissions | |
|
||||||
|
|
||||||
|
### [GET] /agent/invite-options
|
||||||
#### Parameters
|
#### Parameters
|
||||||
|
|
||||||
| Name | Located in | Description | Required | Schema |
|
| Name | Located in | Description | Required | Schema |
|
||||||
@ -324,7 +344,21 @@ Check if activation token is valid
|
|||||||
| ---- | ----------- | ------ |
|
| ---- | ----------- | ------ |
|
||||||
| 200 | Agent invite options | **application/json**: [AgentInviteOptionsResponse](#agentinviteoptionsresponse)<br> |
|
| 200 | Agent invite options | **application/json**: [AgentInviteOptionsResponse](#agentinviteoptionsresponse)<br> |
|
||||||
|
|
||||||
### [GET] /agents/{agent_id}
|
### [DELETE] /agent/{agent_id}
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| agent_id | path | | Yes | string |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description |
|
||||||
|
| ---- | ----------- |
|
||||||
|
| 204 | Agent app deleted successfully |
|
||||||
|
| 403 | Insufficient permissions |
|
||||||
|
|
||||||
|
### [GET] /agent/{agent_id}
|
||||||
#### Parameters
|
#### Parameters
|
||||||
|
|
||||||
| Name | Located in | Description | Required | Schema |
|
| Name | Located in | Description | Required | Schema |
|
||||||
@ -335,9 +369,337 @@ Check if activation token is valid
|
|||||||
|
|
||||||
| Code | Description | Schema |
|
| Code | Description | Schema |
|
||||||
| ---- | ----------- | ------ |
|
| ---- | ----------- | ------ |
|
||||||
| 200 | Agent detail | **application/json**: [AgentRosterResponse](#agentrosterresponse)<br> |
|
| 200 | Agent app detail | **application/json**: [AppDetailWithSite](#appdetailwithsite)<br> |
|
||||||
|
|
||||||
### [GET] /agents/{agent_id}/versions
|
### [PUT] /agent/{agent_id}
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| agent_id | path | | Yes | string |
|
||||||
|
|
||||||
|
#### Request Body
|
||||||
|
|
||||||
|
| Required | Schema |
|
||||||
|
| -------- | ------ |
|
||||||
|
| Yes | **application/json**: [UpdateAppPayload](#updateapppayload)<br> |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 200 | Agent app updated successfully | **application/json**: [AppDetailWithSite](#appdetailwithsite)<br> |
|
||||||
|
| 400 | Invalid request parameters | |
|
||||||
|
| 403 | Insufficient permissions | |
|
||||||
|
|
||||||
|
### [GET] /agent/{agent_id}/composer
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| agent_id | path | | Yes | string |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 200 | Agent app composer state | **application/json**: [AgentAppComposerResponse](#agentappcomposerresponse)<br> |
|
||||||
|
|
||||||
|
### [PUT] /agent/{agent_id}/composer
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| agent_id | path | | Yes | string |
|
||||||
|
|
||||||
|
#### Request Body
|
||||||
|
|
||||||
|
| Required | Schema |
|
||||||
|
| -------- | ------ |
|
||||||
|
| Yes | **application/json**: [ComposerSavePayload](#composersavepayload)<br> |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 200 | Agent app composer saved | **application/json**: [AgentAppComposerResponse](#agentappcomposerresponse)<br> |
|
||||||
|
|
||||||
|
### [GET] /agent/{agent_id}/composer/candidates
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| agent_id | path | | Yes | string |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 200 | Agent app composer candidates | **application/json**: [AgentComposerCandidatesResponse](#agentcomposercandidatesresponse)<br> |
|
||||||
|
|
||||||
|
### [POST] /agent/{agent_id}/composer/validate
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| agent_id | path | | Yes | string |
|
||||||
|
|
||||||
|
#### Request Body
|
||||||
|
|
||||||
|
| Required | Schema |
|
||||||
|
| -------- | ------ |
|
||||||
|
| Yes | **application/json**: [ComposerSavePayload](#composersavepayload)<br> |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 200 | Agent app composer validation result | **application/json**: [AgentComposerValidateResponse](#agentcomposervalidateresponse)<br> |
|
||||||
|
|
||||||
|
### [GET] /agent/{agent_id}/drive/files
|
||||||
|
List agent drive entries for an Agent App
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| agent_id | path | Agent ID | Yes | string |
|
||||||
|
| prefix | query | Key prefix filter: '<slug>/' for one skill, 'files/' for files | No | string |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 200 | Drive entries | **application/json**: [AgentDriveListResponse](#agentdrivelistresponse)<br> |
|
||||||
|
|
||||||
|
### [GET] /agent/{agent_id}/drive/files/download
|
||||||
|
Time-limited external signed URL for one Agent App drive value
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| agent_id | path | Agent ID | Yes | string |
|
||||||
|
| key | query | Drive key, e.g. tender-analyzer/SKILL.md | Yes | string |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 200 | Signed URL | **application/json**: [AgentDriveDownloadResponse](#agentdrivedownloadresponse)<br> |
|
||||||
|
|
||||||
|
### [GET] /agent/{agent_id}/drive/files/preview
|
||||||
|
Truncated text preview of one Agent App drive value
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| agent_id | path | Agent ID | Yes | string |
|
||||||
|
| key | query | Drive key, e.g. tender-analyzer/SKILL.md | Yes | string |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 200 | Preview | **application/json**: [AgentDrivePreviewResponse](#agentdrivepreviewresponse)<br> |
|
||||||
|
|
||||||
|
### [POST] /agent/{agent_id}/features
|
||||||
|
Update an Agent App's presentation features (opener, follow-up, citations, ...)
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| agent_id | path | Agent ID | Yes | string |
|
||||||
|
|
||||||
|
#### Request Body
|
||||||
|
|
||||||
|
| Required | Schema |
|
||||||
|
| -------- | ------ |
|
||||||
|
| Yes | **application/json**: [AgentAppFeaturesPayload](#agentappfeaturespayload)<br> |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 200 | Features updated successfully | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
|
||||||
|
| 400 | Invalid configuration | |
|
||||||
|
| 404 | Agent not found | |
|
||||||
|
|
||||||
|
### [DELETE] /agent/{agent_id}/files
|
||||||
|
Delete one Agent App drive file by key
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| agent_id | path | Agent ID | Yes | string |
|
||||||
|
| key | query | Drive key, e.g. files/sample.pdf | Yes | string |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 200 | File removed | **application/json**: [AgentDriveDeleteResponse](#agentdrivedeleteresponse)<br> |
|
||||||
|
|
||||||
|
### [POST] /agent/{agent_id}/files
|
||||||
|
Commit an uploaded file into the Agent App drive under files/<name>
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| agent_id | path | Agent ID | Yes | string |
|
||||||
|
|
||||||
|
#### Request Body
|
||||||
|
|
||||||
|
| Required | Schema |
|
||||||
|
| -------- | ------ |
|
||||||
|
| Yes | **application/json**: [AgentDriveFilePayload](#agentdrivefilepayload)<br> |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 201 | File committed into the agent drive | **application/json**: [AgentDriveFileCommitResponse](#agentdrivefilecommitresponse)<br> |
|
||||||
|
|
||||||
|
### [GET] /agent/{agent_id}/referencing-workflows
|
||||||
|
List workflow apps that reference this Agent App's bound Agent (read-only)
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| agent_id | path | Agent ID | Yes | string |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 200 | Referencing workflows listed successfully | **application/json**: [AgentReferencingWorkflowsResponse](#agentreferencingworkflowsresponse)<br> |
|
||||||
|
| 404 | Agent not found | |
|
||||||
|
|
||||||
|
### [GET] /agent/{agent_id}/sandbox/files
|
||||||
|
List a directory in an Agent App conversation sandbox
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| agent_id | path | Agent ID | Yes | string |
|
||||||
|
| conversation_id | query | Agent App conversation ID | Yes | string |
|
||||||
|
| path | query | Directory path relative to the sandbox workspace | No | string, <br>**Default:** . |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 200 | Listing returned | **application/json**: [SandboxListResponse](#sandboxlistresponse)<br> |
|
||||||
|
|
||||||
|
### [GET] /agent/{agent_id}/sandbox/files/read
|
||||||
|
Read a text/binary preview file in an Agent App conversation sandbox
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| agent_id | path | Agent ID | Yes | string |
|
||||||
|
| conversation_id | query | Agent App conversation ID | Yes | string |
|
||||||
|
| path | query | File path relative to the sandbox workspace | Yes | string |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 200 | Preview returned | **application/json**: [SandboxReadResponse](#sandboxreadresponse)<br> |
|
||||||
|
|
||||||
|
### [POST] /agent/{agent_id}/sandbox/files/upload
|
||||||
|
Upload one Agent App sandbox file as a Dify ToolFile mapping
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| agent_id | path | | Yes | string |
|
||||||
|
|
||||||
|
#### Request Body
|
||||||
|
|
||||||
|
| Required | Schema |
|
||||||
|
| -------- | ------ |
|
||||||
|
| Yes | **application/json**: [AgentSandboxUploadPayload](#agentsandboxuploadpayload)<br> |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 200 | Uploaded | **application/json**: [SandboxUploadResponse](#sandboxuploadresponse)<br> |
|
||||||
|
|
||||||
|
### [POST] /agent/{agent_id}/skills/standardize
|
||||||
|
Validate + standardize a Skill into an Agent App drive
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| agent_id | path | Agent ID | Yes | string |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 201 | Skill standardized into drive | **application/json**: [AgentSkillStandardizeResponse](#agentskillstandardizeresponse)<br> |
|
||||||
|
| 400 | Invalid skill package or no bound agent | |
|
||||||
|
|
||||||
|
### [POST] /agent/{agent_id}/skills/upload
|
||||||
|
Upload + validate a Skill package for an Agent App
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| agent_id | path | Agent ID | Yes | string |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 201 | Skill validated | **application/json**: [AgentSkillUploadResponse](#agentskilluploadresponse)<br> |
|
||||||
|
| 400 | Invalid skill package | |
|
||||||
|
|
||||||
|
### [DELETE] /agent/{agent_id}/skills/{slug}
|
||||||
|
Delete a standardized skill from an Agent App drive
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| agent_id | path | Agent ID | Yes | string |
|
||||||
|
| slug | path | Skill slug (single path segment) | Yes | string |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 200 | Skill removed | **application/json**: [AgentDriveDeleteResponse](#agentdrivedeleteresponse)<br> |
|
||||||
|
|
||||||
|
### [POST] /agent/{agent_id}/skills/{slug}/infer-tools
|
||||||
|
Infer CLI tool + ENV suggestions from a standardized Agent App skill
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Located in | Description | Required | Schema |
|
||||||
|
| ---- | ---------- | ----------- | -------- | ------ |
|
||||||
|
| agent_id | path | Agent ID | Yes | string |
|
||||||
|
| slug | path | Skill slug (single path segment) | Yes | string |
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| Code | Description | Schema |
|
||||||
|
| ---- | ----------- | ------ |
|
||||||
|
| 200 | Inference result (draft suggestions, nothing persisted) | **application/json**: [SkillToolInferenceResult](#skilltoolinferenceresult)<br> |
|
||||||
|
|
||||||
|
### [GET] /agent/{agent_id}/versions
|
||||||
#### Parameters
|
#### Parameters
|
||||||
|
|
||||||
| Name | Located in | Description | Required | Schema |
|
| Name | Located in | Description | Required | Schema |
|
||||||
@ -350,7 +712,7 @@ Check if activation token is valid
|
|||||||
| ---- | ----------- | ------ |
|
| ---- | ----------- | ------ |
|
||||||
| 200 | Agent versions | **application/json**: [AgentConfigSnapshotListResponse](#agentconfigsnapshotlistresponse)<br> |
|
| 200 | Agent versions | **application/json**: [AgentConfigSnapshotListResponse](#agentconfigsnapshotlistresponse)<br> |
|
||||||
|
|
||||||
### [GET] /agents/{agent_id}/versions/{version_id}
|
### [GET] /agent/{agent_id}/versions/{version_id}
|
||||||
#### Parameters
|
#### Parameters
|
||||||
|
|
||||||
| Name | Located in | Description | Required | Schema |
|
| Name | Located in | Description | Required | Schema |
|
||||||
@ -862,164 +1224,6 @@ Run draft workflow for advanced chat application
|
|||||||
| 400 | Invalid request parameters | |
|
| 400 | Invalid request parameters | |
|
||||||
| 403 | Permission denied | |
|
| 403 | Permission denied | |
|
||||||
|
|
||||||
### [GET] /apps/{app_id}/agent-composer
|
|
||||||
#### Parameters
|
|
||||||
|
|
||||||
| Name | Located in | Description | Required | Schema |
|
|
||||||
| ---- | ---------- | ----------- | -------- | ------ |
|
|
||||||
| app_id | path | | Yes | string |
|
|
||||||
|
|
||||||
#### Responses
|
|
||||||
|
|
||||||
| Code | Description | Schema |
|
|
||||||
| ---- | ----------- | ------ |
|
|
||||||
| 200 | Agent app composer state | **application/json**: [AgentAppComposerResponse](#agentappcomposerresponse)<br> |
|
|
||||||
|
|
||||||
### [PUT] /apps/{app_id}/agent-composer
|
|
||||||
#### Parameters
|
|
||||||
|
|
||||||
| Name | Located in | Description | Required | Schema |
|
|
||||||
| ---- | ---------- | ----------- | -------- | ------ |
|
|
||||||
| app_id | path | | Yes | string |
|
|
||||||
|
|
||||||
#### Request Body
|
|
||||||
|
|
||||||
| Required | Schema |
|
|
||||||
| -------- | ------ |
|
|
||||||
| Yes | **application/json**: [ComposerSavePayload](#composersavepayload)<br> |
|
|
||||||
|
|
||||||
#### Responses
|
|
||||||
|
|
||||||
| Code | Description | Schema |
|
|
||||||
| ---- | ----------- | ------ |
|
|
||||||
| 200 | Agent app composer saved | **application/json**: [AgentAppComposerResponse](#agentappcomposerresponse)<br> |
|
|
||||||
|
|
||||||
### [GET] /apps/{app_id}/agent-composer/candidates
|
|
||||||
#### Parameters
|
|
||||||
|
|
||||||
| Name | Located in | Description | Required | Schema |
|
|
||||||
| ---- | ---------- | ----------- | -------- | ------ |
|
|
||||||
| app_id | path | | Yes | string |
|
|
||||||
|
|
||||||
#### Responses
|
|
||||||
|
|
||||||
| Code | Description | Schema |
|
|
||||||
| ---- | ----------- | ------ |
|
|
||||||
| 200 | Agent app composer candidates | **application/json**: [AgentComposerCandidatesResponse](#agentcomposercandidatesresponse)<br> |
|
|
||||||
|
|
||||||
### [POST] /apps/{app_id}/agent-composer/validate
|
|
||||||
#### Parameters
|
|
||||||
|
|
||||||
| Name | Located in | Description | Required | Schema |
|
|
||||||
| ---- | ---------- | ----------- | -------- | ------ |
|
|
||||||
| app_id | path | | Yes | string |
|
|
||||||
|
|
||||||
#### Request Body
|
|
||||||
|
|
||||||
| Required | Schema |
|
|
||||||
| -------- | ------ |
|
|
||||||
| Yes | **application/json**: [ComposerSavePayload](#composersavepayload)<br> |
|
|
||||||
|
|
||||||
#### Responses
|
|
||||||
|
|
||||||
| Code | Description | Schema |
|
|
||||||
| ---- | ----------- | ------ |
|
|
||||||
| 200 | Agent app composer validation result | **application/json**: [AgentComposerValidateResponse](#agentcomposervalidateresponse)<br> |
|
|
||||||
|
|
||||||
### [POST] /apps/{app_id}/agent-features
|
|
||||||
Update an Agent App's presentation features (opener, follow-up, citations, ...)
|
|
||||||
|
|
||||||
#### Parameters
|
|
||||||
|
|
||||||
| Name | Located in | Description | Required | Schema |
|
|
||||||
| ---- | ---------- | ----------- | -------- | ------ |
|
|
||||||
| app_id | path | Application ID | Yes | string |
|
|
||||||
|
|
||||||
#### Request Body
|
|
||||||
|
|
||||||
| Required | Schema |
|
|
||||||
| -------- | ------ |
|
|
||||||
| Yes | **application/json**: [AgentAppFeaturesPayload](#agentappfeaturespayload)<br> |
|
|
||||||
|
|
||||||
#### Responses
|
|
||||||
|
|
||||||
| Code | Description | Schema |
|
|
||||||
| ---- | ----------- | ------ |
|
|
||||||
| 200 | Features updated successfully | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
|
|
||||||
| 400 | Invalid configuration | |
|
|
||||||
| 404 | App not found | |
|
|
||||||
|
|
||||||
### [GET] /apps/{app_id}/agent-referencing-workflows
|
|
||||||
List workflow apps that reference this Agent App's bound Agent (read-only)
|
|
||||||
|
|
||||||
#### Parameters
|
|
||||||
|
|
||||||
| Name | Located in | Description | Required | Schema |
|
|
||||||
| ---- | ---------- | ----------- | -------- | ------ |
|
|
||||||
| app_id | path | Application ID | Yes | string |
|
|
||||||
|
|
||||||
#### Responses
|
|
||||||
|
|
||||||
| Code | Description | Schema |
|
|
||||||
| ---- | ----------- | ------ |
|
|
||||||
| 200 | Referencing workflows listed successfully | **application/json**: [AgentReferencingWorkflowsResponse](#agentreferencingworkflowsresponse)<br> |
|
|
||||||
| 404 | App not found | |
|
|
||||||
|
|
||||||
### [GET] /apps/{app_id}/agent-sandbox/files
|
|
||||||
List a directory in an Agent App conversation sandbox
|
|
||||||
|
|
||||||
#### Parameters
|
|
||||||
|
|
||||||
| Name | Located in | Description | Required | Schema |
|
|
||||||
| ---- | ---------- | ----------- | -------- | ------ |
|
|
||||||
| app_id | path | Application ID | Yes | string |
|
|
||||||
| conversation_id | query | Agent App conversation ID | Yes | string |
|
|
||||||
| path | query | Directory path relative to the sandbox workspace | No | string, <br>**Default:** . |
|
|
||||||
|
|
||||||
#### Responses
|
|
||||||
|
|
||||||
| Code | Description | Schema |
|
|
||||||
| ---- | ----------- | ------ |
|
|
||||||
| 200 | Listing returned | **application/json**: [SandboxListResponse](#sandboxlistresponse)<br> |
|
|
||||||
|
|
||||||
### [GET] /apps/{app_id}/agent-sandbox/files/read
|
|
||||||
Read a text/binary preview file in an Agent App conversation sandbox
|
|
||||||
|
|
||||||
#### Parameters
|
|
||||||
|
|
||||||
| Name | Located in | Description | Required | Schema |
|
|
||||||
| ---- | ---------- | ----------- | -------- | ------ |
|
|
||||||
| app_id | path | Application ID | Yes | string |
|
|
||||||
| conversation_id | query | Agent App conversation ID | Yes | string |
|
|
||||||
| path | query | File path relative to the sandbox workspace | Yes | string |
|
|
||||||
|
|
||||||
#### Responses
|
|
||||||
|
|
||||||
| Code | Description | Schema |
|
|
||||||
| ---- | ----------- | ------ |
|
|
||||||
| 200 | Preview returned | **application/json**: [SandboxReadResponse](#sandboxreadresponse)<br> |
|
|
||||||
|
|
||||||
### [POST] /apps/{app_id}/agent-sandbox/files/upload
|
|
||||||
Upload one Agent App sandbox file as a Dify ToolFile mapping
|
|
||||||
|
|
||||||
#### Parameters
|
|
||||||
|
|
||||||
| Name | Located in | Description | Required | Schema |
|
|
||||||
| ---- | ---------- | ----------- | -------- | ------ |
|
|
||||||
| app_id | path | | Yes | string |
|
|
||||||
|
|
||||||
#### Request Body
|
|
||||||
|
|
||||||
| Required | Schema |
|
|
||||||
| -------- | ------ |
|
|
||||||
| Yes | **application/json**: [AgentSandboxUploadPayload](#agentsandboxuploadpayload)<br> |
|
|
||||||
|
|
||||||
#### Responses
|
|
||||||
|
|
||||||
| Code | Description | Schema |
|
|
||||||
| ---- | ----------- | ------ |
|
|
||||||
| 200 | Uploaded | **application/json**: [SandboxUploadResponse](#sandboxuploadresponse)<br> |
|
|
||||||
|
|
||||||
### [GET] /apps/{app_id}/agent/drive/files
|
### [GET] /apps/{app_id}/agent/drive/files
|
||||||
List agent drive entries (read-only inspector; one endpoint for both tabs)
|
List agent drive entries (read-only inspector; one endpoint for both tabs)
|
||||||
|
|
||||||
@ -10982,6 +11186,16 @@ Default namespace
|
|||||||
| validation | [ComposerValidationFindingsResponse](#composervalidationfindingsresponse) | | No |
|
| validation | [ComposerValidationFindingsResponse](#composervalidationfindingsresponse) | | No |
|
||||||
| variant | string | | Yes |
|
| variant | string | | Yes |
|
||||||
|
|
||||||
|
#### AgentAppCreatePayload
|
||||||
|
|
||||||
|
| Name | Type | Description | Required |
|
||||||
|
| ---- | ---- | ----------- | -------- |
|
||||||
|
| description | string | Agent description (max 400 chars) | No |
|
||||||
|
| icon | string | Icon | No |
|
||||||
|
| icon_background | string | Icon background color | No |
|
||||||
|
| icon_type | [IconType](#icontype) | Icon type | No |
|
||||||
|
| name | string | Agent name | Yes |
|
||||||
|
|
||||||
#### AgentAppFeaturesPayload
|
#### AgentAppFeaturesPayload
|
||||||
|
|
||||||
Presentation features configurable on an Agent App.
|
Presentation features configurable on an Agent App.
|
||||||
@ -11239,6 +11453,12 @@ Audit operation recorded for Agent Soul version/revision changes.
|
|||||||
| version | integer | | Yes |
|
| version | integer | | Yes |
|
||||||
| version_note | string | | No |
|
| version_note | string | | No |
|
||||||
|
|
||||||
|
#### AgentDriveDeleteFileByAgentQuery
|
||||||
|
|
||||||
|
| Name | Type | Description | Required |
|
||||||
|
| ---- | ---- | ----------- | -------- |
|
||||||
|
| key | string | Drive key, e.g. files/sample.pdf | Yes |
|
||||||
|
|
||||||
#### AgentDriveDeleteResponse
|
#### AgentDriveDeleteResponse
|
||||||
|
|
||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
@ -12182,7 +12402,6 @@ Enum class for api provider schema type.
|
|||||||
| ---- | ---- | ----------- | -------- |
|
| ---- | ---- | ----------- | -------- |
|
||||||
| access_mode | string | | No |
|
| access_mode | string | | No |
|
||||||
| api_base_url | string | | No |
|
| api_base_url | string | | No |
|
||||||
| app_model_config | [ModelConfig](#modelconfig) | | No |
|
|
||||||
| bound_agent_id | string | | No |
|
| bound_agent_id | string | | No |
|
||||||
| created_at | integer | | No |
|
| created_at | integer | | No |
|
||||||
| created_by | string | | No |
|
| created_by | string | | No |
|
||||||
@ -12193,9 +12412,11 @@ Enum class for api provider schema type.
|
|||||||
| icon | string | | No |
|
| icon | string | | No |
|
||||||
| icon_background | string | | No |
|
| icon_background | string | | No |
|
||||||
| icon_type | string | | No |
|
| icon_type | string | | No |
|
||||||
|
| icon_url | string | | Yes |
|
||||||
| id | string | | Yes |
|
| id | string | | Yes |
|
||||||
| max_active_requests | integer | | No |
|
| max_active_requests | integer | | No |
|
||||||
| mode_compatible_with_agent | string | | Yes |
|
| mode | string | | Yes |
|
||||||
|
| model_config | [ModelConfig](#modelconfig) | | No |
|
||||||
| name | string | | Yes |
|
| name | string | | Yes |
|
||||||
| site | [Site](#site) | | No |
|
| site | [Site](#site) | | No |
|
||||||
| tags | [ [Tag](#tag) ] | | No |
|
| tags | [ [Tag](#tag) ] | | No |
|
||||||
@ -12290,10 +12511,10 @@ AppMCPServer Status Enum
|
|||||||
|
|
||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
| ---- | ---- | ----------- | -------- |
|
| ---- | ---- | ----------- | -------- |
|
||||||
| has_next | boolean | | Yes |
|
| data | [ [AppPartial](#apppartial) ] | | Yes |
|
||||||
| items | [ [AppPartial](#apppartial) ] | | Yes |
|
| has_more | boolean | | Yes |
|
||||||
|
| limit | integer | | Yes |
|
||||||
| page | integer | | Yes |
|
| page | integer | | Yes |
|
||||||
| per_page | integer | | Yes |
|
|
||||||
| total | integer | | Yes |
|
| total | integer | | Yes |
|
||||||
|
|
||||||
#### AppPartial
|
#### AppPartial
|
||||||
@ -12301,20 +12522,22 @@ AppMCPServer Status Enum
|
|||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
| ---- | ---- | ----------- | -------- |
|
| ---- | ---- | ----------- | -------- |
|
||||||
| access_mode | string | | No |
|
| access_mode | string | | No |
|
||||||
| app_model_config | [ModelConfigPartial](#modelconfigpartial) | | No |
|
|
||||||
| author_name | string | | No |
|
| author_name | string | | No |
|
||||||
|
| bound_agent_id | string | | No |
|
||||||
| create_user_name | string | | No |
|
| create_user_name | string | | No |
|
||||||
| created_at | integer | | No |
|
| created_at | integer | | No |
|
||||||
| created_by | string | | No |
|
| created_by | string | | No |
|
||||||
| desc_or_prompt | string | | No |
|
| description | string | | No |
|
||||||
| has_draft_trigger | boolean | | No |
|
| has_draft_trigger | boolean | | No |
|
||||||
| icon | string | | No |
|
| icon | string | | No |
|
||||||
| icon_background | string | | No |
|
| icon_background | string | | No |
|
||||||
| icon_type | string | | No |
|
| icon_type | string | | No |
|
||||||
|
| icon_url | string | | Yes |
|
||||||
| id | string | | Yes |
|
| id | string | | Yes |
|
||||||
| is_starred | boolean | | No |
|
| is_starred | boolean | | No |
|
||||||
| max_active_requests | integer | | No |
|
| max_active_requests | integer | | No |
|
||||||
| mode_compatible_with_agent | string | | Yes |
|
| mode | string | | Yes |
|
||||||
|
| model_config | [ModelConfigPartial](#modelconfigpartial) | | No |
|
||||||
| name | string | | Yes |
|
| name | string | | Yes |
|
||||||
| tags | [ [Tag](#tag) ] | | No |
|
| tags | [ [Tag](#tag) ] | | No |
|
||||||
| updated_at | integer | | No |
|
| updated_at | integer | | No |
|
||||||
@ -13109,7 +13332,7 @@ Enum class for configurate method of provider model.
|
|||||||
| icon | string | Icon | No |
|
| icon | string | Icon | No |
|
||||||
| icon_background | string | Icon background color | No |
|
| icon_background | string | Icon background color | No |
|
||||||
| icon_type | [IconType](#icontype) | Icon type | No |
|
| icon_type | [IconType](#icontype) | Icon type | No |
|
||||||
| mode | string, <br>**Available values:** "advanced-chat", "agent", "agent-chat", "chat", "completion", "workflow" | App mode<br>*Enum:* `"advanced-chat"`, `"agent"`, `"agent-chat"`, `"chat"`, `"completion"`, `"workflow"` | Yes |
|
| mode | string, <br>**Available values:** "advanced-chat", "agent-chat", "chat", "completion", "workflow" | App mode<br>*Enum:* `"advanced-chat"`, `"agent-chat"`, `"chat"`, `"completion"`, `"workflow"` | Yes |
|
||||||
| name | string | App name | Yes |
|
| name | string | App name | Yes |
|
||||||
|
|
||||||
#### CreateSnippetPayload
|
#### CreateSnippetPayload
|
||||||
@ -15562,7 +15785,7 @@ Metadata operation data
|
|||||||
| ---- | ---- | ----------- | -------- |
|
| ---- | ---- | ----------- | -------- |
|
||||||
| created_at | integer | | No |
|
| created_at | integer | | No |
|
||||||
| created_by | string | | No |
|
| created_by | string | | No |
|
||||||
| model_dict | [JSONValue](#jsonvalue) | | No |
|
| model | [JSONValue](#jsonvalue) | | No |
|
||||||
| pre_prompt | string | | No |
|
| pre_prompt | string | | No |
|
||||||
| updated_at | integer | | No |
|
| updated_at | integer | | No |
|
||||||
| updated_by | string | | No |
|
| updated_by | string | | No |
|
||||||
|
|||||||
@ -19,7 +19,7 @@ from models.agent import (
|
|||||||
)
|
)
|
||||||
from models.agent_config_entities import AgentSoulConfig
|
from models.agent_config_entities import AgentSoulConfig
|
||||||
from models.enums import AppStatus
|
from models.enums import AppStatus
|
||||||
from models.model import App
|
from models.model import App, AppMode
|
||||||
from models.workflow import Workflow
|
from models.workflow import Workflow
|
||||||
from services.agent.agent_soul_state import agent_soul_has_model
|
from services.agent.agent_soul_state import agent_soul_has_model
|
||||||
from services.agent.composer_validator import ComposerConfigValidator
|
from services.agent.composer_validator import ComposerConfigValidator
|
||||||
@ -349,6 +349,43 @@ class AgentRosterService:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_agent_app_model(self, *, tenant_id: str, agent_id: str) -> App:
|
||||||
|
"""Resolve the Agent App hidden behind an app-backed Agent id.
|
||||||
|
|
||||||
|
The public /agent route uses Agent ids, while the runtime and legacy app
|
||||||
|
APIs still operate on App ids internally. Only app-backed roster Agents
|
||||||
|
are accepted here; workflow-only Agents and historical standalone roster
|
||||||
|
Agents are not Agent App resources.
|
||||||
|
"""
|
||||||
|
agent = self._session.scalar(
|
||||||
|
select(Agent)
|
||||||
|
.where(
|
||||||
|
Agent.tenant_id == tenant_id,
|
||||||
|
Agent.id == agent_id,
|
||||||
|
Agent.scope == AgentScope.ROSTER,
|
||||||
|
Agent.source == AgentSource.AGENT_APP,
|
||||||
|
Agent.app_id.is_not(None),
|
||||||
|
Agent.status == AgentStatus.ACTIVE,
|
||||||
|
)
|
||||||
|
.limit(1)
|
||||||
|
)
|
||||||
|
if agent is None or agent.app_id is None:
|
||||||
|
raise AgentNotFoundError()
|
||||||
|
|
||||||
|
app = self._session.scalar(
|
||||||
|
select(App)
|
||||||
|
.where(
|
||||||
|
App.tenant_id == tenant_id,
|
||||||
|
App.id == agent.app_id,
|
||||||
|
App.mode == AppMode.AGENT,
|
||||||
|
App.status == AppStatus.NORMAL,
|
||||||
|
)
|
||||||
|
.limit(1)
|
||||||
|
)
|
||||||
|
if app is None:
|
||||||
|
raise AgentNotFoundError()
|
||||||
|
return app
|
||||||
|
|
||||||
def list_workflows_referencing_app_agent(self, *, tenant_id: str, app_id: str) -> list[AgentReferencingWorkflow]:
|
def list_workflows_referencing_app_agent(self, *, tenant_id: str, app_id: str) -> list[AgentReferencingWorkflow]:
|
||||||
"""List the workflow apps that reference this Agent App's bound Agent.
|
"""List the workflow apps that reference this Agent App's bound Agent.
|
||||||
|
|
||||||
|
|||||||
@ -1,16 +1,17 @@
|
|||||||
from inspect import unwrap
|
from inspect import unwrap
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
from typing import cast
|
from typing import Any, cast
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
|
|
||||||
|
from controllers.console import console_ns
|
||||||
from controllers.console.agent import composer as composer_controller
|
from controllers.console.agent import composer as composer_controller
|
||||||
from controllers.console.agent import roster as roster_controller
|
from controllers.console.agent import roster as roster_controller
|
||||||
from controllers.console.agent.composer import (
|
from controllers.console.agent.composer import (
|
||||||
AgentAppComposerApi,
|
AgentComposerApi,
|
||||||
AgentAppComposerCandidatesApi,
|
AgentComposerCandidatesApi,
|
||||||
AgentAppComposerValidateApi,
|
AgentComposerValidateApi,
|
||||||
WorkflowAgentComposerApi,
|
WorkflowAgentComposerApi,
|
||||||
WorkflowAgentComposerCandidatesApi,
|
WorkflowAgentComposerCandidatesApi,
|
||||||
WorkflowAgentComposerImpactApi,
|
WorkflowAgentComposerImpactApi,
|
||||||
@ -18,42 +19,15 @@ from controllers.console.agent.composer import (
|
|||||||
WorkflowAgentComposerValidateApi,
|
WorkflowAgentComposerValidateApi,
|
||||||
)
|
)
|
||||||
from controllers.console.agent.roster import (
|
from controllers.console.agent.roster import (
|
||||||
|
AgentAppApi,
|
||||||
|
AgentAppListApi,
|
||||||
AgentInviteOptionsApi,
|
AgentInviteOptionsApi,
|
||||||
AgentRosterDetailApi,
|
|
||||||
AgentRosterListApi,
|
|
||||||
AgentRosterVersionDetailApi,
|
AgentRosterVersionDetailApi,
|
||||||
AgentRosterVersionsApi,
|
AgentRosterVersionsApi,
|
||||||
)
|
)
|
||||||
from models.model import AppMode
|
|
||||||
from services.entities.agent_entities import ComposerSaveStrategy, ComposerVariant
|
from services.entities.agent_entities import ComposerSaveStrategy, ComposerVariant
|
||||||
|
|
||||||
|
|
||||||
def _agent_response(agent_id: str = "agent-1") -> dict:
|
|
||||||
return {
|
|
||||||
"id": agent_id,
|
|
||||||
"name": "Analyst",
|
|
||||||
"description": "",
|
|
||||||
"icon_type": None,
|
|
||||||
"icon": None,
|
|
||||||
"icon_background": None,
|
|
||||||
"agent_kind": "dify_agent",
|
|
||||||
"scope": "roster",
|
|
||||||
"source": "agent_app",
|
|
||||||
"app_id": None,
|
|
||||||
"workflow_id": None,
|
|
||||||
"workflow_node_id": None,
|
|
||||||
"active_config_snapshot_id": "version-1",
|
|
||||||
"active_config_snapshot": _version_response(),
|
|
||||||
"status": "active",
|
|
||||||
"created_by": "account-1",
|
|
||||||
"updated_by": "account-1",
|
|
||||||
"archived_by": None,
|
|
||||||
"archived_at": None,
|
|
||||||
"created_at": None,
|
|
||||||
"updated_at": None,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _version_response(version_id: str = "version-1") -> dict:
|
def _version_response(version_id: str = "version-1") -> dict:
|
||||||
return {
|
return {
|
||||||
"id": version_id,
|
"id": version_id,
|
||||||
@ -103,6 +77,37 @@ def _agent_app_composer_response() -> dict:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _app_detail_obj(**overrides):
|
||||||
|
data = {
|
||||||
|
"id": "app-1",
|
||||||
|
"name": "Iris",
|
||||||
|
"description": "Agent app",
|
||||||
|
"mode_compatible_with_agent": "agent",
|
||||||
|
"icon_type": "emoji",
|
||||||
|
"icon": "robot",
|
||||||
|
"icon_background": "#fff",
|
||||||
|
"enable_site": False,
|
||||||
|
"enable_api": False,
|
||||||
|
"app_model_config": None,
|
||||||
|
"workflow": None,
|
||||||
|
"tracing": None,
|
||||||
|
"use_icon_as_answer_icon": False,
|
||||||
|
"created_by": "account-1",
|
||||||
|
"created_at": None,
|
||||||
|
"updated_by": "account-1",
|
||||||
|
"updated_at": None,
|
||||||
|
"access_mode": None,
|
||||||
|
"tags": [],
|
||||||
|
"api_base_url": None,
|
||||||
|
"max_active_requests": 0,
|
||||||
|
"deleted_tools": [],
|
||||||
|
"site": None,
|
||||||
|
"bound_agent_id": "00000000-0000-0000-0000-000000000001",
|
||||||
|
}
|
||||||
|
data.update(overrides)
|
||||||
|
return SimpleNamespace(**data)
|
||||||
|
|
||||||
|
|
||||||
def _candidates_response(variant: str) -> dict:
|
def _candidates_response(variant: str) -> dict:
|
||||||
return {
|
return {
|
||||||
"variant": variant,
|
"variant": variant,
|
||||||
@ -112,20 +117,38 @@ def _candidates_response(variant: str) -> dict:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _get_app_model_modes(view) -> list[AppMode]:
|
def test_agent_v2_console_routes_are_agent_id_first() -> None:
|
||||||
current = view
|
paths = {route for item in console_ns.resources for route in item.urls}
|
||||||
while current is not None:
|
|
||||||
closure = getattr(current, "__closure__", None)
|
for route in (
|
||||||
if closure is not None:
|
"/agent",
|
||||||
for cell in closure:
|
"/agent/<uuid:agent_id>",
|
||||||
try:
|
"/agent/<uuid:agent_id>/composer",
|
||||||
value = cell.cell_contents
|
"/agent/<uuid:agent_id>/composer/validate",
|
||||||
except ValueError:
|
"/agent/<uuid:agent_id>/composer/candidates",
|
||||||
continue
|
"/agent/<uuid:agent_id>/features",
|
||||||
if isinstance(value, list) and all(isinstance(item, AppMode) for item in value):
|
"/agent/<uuid:agent_id>/referencing-workflows",
|
||||||
return value
|
"/agent/<uuid:agent_id>/drive/files",
|
||||||
current = getattr(current, "__wrapped__", None)
|
"/agent/<uuid:agent_id>/sandbox/files",
|
||||||
return []
|
"/agent/<uuid:agent_id>/skills/upload",
|
||||||
|
"/agent/<uuid:agent_id>/files",
|
||||||
|
"/agent/invite-options",
|
||||||
|
):
|
||||||
|
assert route in paths
|
||||||
|
|
||||||
|
for route in (
|
||||||
|
"/agents",
|
||||||
|
"/agents/invite-options",
|
||||||
|
"/agents/<uuid:agent_id>",
|
||||||
|
"/agents/<uuid:agent_id>/versions",
|
||||||
|
"/apps/<uuid:app_id>/agent-composer",
|
||||||
|
"/apps/<uuid:app_id>/agent-composer/validate",
|
||||||
|
"/apps/<uuid:app_id>/agent-composer/candidates",
|
||||||
|
"/apps/<uuid:app_id>/agent-features",
|
||||||
|
"/apps/<uuid:app_id>/agent-referencing-workflows",
|
||||||
|
"/apps/<uuid:app_id>/agent-sandbox/files",
|
||||||
|
):
|
||||||
|
assert route not in paths
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@ -133,26 +156,114 @@ def account_id() -> str:
|
|||||||
return "account-1"
|
return "account-1"
|
||||||
|
|
||||||
|
|
||||||
def test_roster_list_get_parses_query_and_calls_service(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
def test_agent_app_list_and_create_use_agent_route(
|
||||||
|
app: Flask, monkeypatch: pytest.MonkeyPatch, account_id: str
|
||||||
|
) -> None:
|
||||||
captured: dict[str, object] = {}
|
captured: dict[str, object] = {}
|
||||||
|
|
||||||
def list_roster_agents(_self: object, **kwargs: object) -> dict[str, object]:
|
class FakeAppService:
|
||||||
captured.update(kwargs)
|
def get_app(self, app_obj: object) -> object:
|
||||||
return {"data": [], "page": kwargs["page"], "limit": kwargs["limit"], "total": 0, "has_more": False}
|
return app_obj
|
||||||
|
|
||||||
monkeypatch.setattr(roster_controller.AgentRosterService, "list_roster_agents", list_roster_agents)
|
def get_paginate_apps(self, user_id: str, tenant_id: str, params) -> object:
|
||||||
|
captured["list"] = {"user_id": user_id, "tenant_id": tenant_id, "params": params}
|
||||||
|
return SimpleNamespace(
|
||||||
|
page=1,
|
||||||
|
per_page=10,
|
||||||
|
total=1,
|
||||||
|
has_next=False,
|
||||||
|
items=[_app_detail_obj(id="app-list", bound_agent_id="agent-list")],
|
||||||
|
)
|
||||||
|
|
||||||
with app.test_request_context("/console/api/agents?page=2&limit=5&keyword=analyst"):
|
def create_app(self, tenant_id: str, params, current_user: object) -> object:
|
||||||
result = unwrap(AgentRosterListApi.get)(AgentRosterListApi(), "tenant-1")
|
captured["create"] = {"tenant_id": tenant_id, "params": params, "current_user": current_user}
|
||||||
|
return _app_detail_obj(id="app-created", bound_agent_id="agent-created")
|
||||||
|
|
||||||
assert result["page"] == 2
|
monkeypatch.setattr(roster_controller, "AppService", FakeAppService)
|
||||||
assert captured == {"tenant_id": "tenant-1", "page": 2, "limit": 5, "keyword": "analyst"}
|
monkeypatch.setattr(
|
||||||
|
roster_controller.FeatureService,
|
||||||
|
"get_system_features",
|
||||||
|
lambda: SimpleNamespace(webapp_auth=SimpleNamespace(enabled=False)),
|
||||||
|
)
|
||||||
|
|
||||||
|
with app.test_request_context("/console/api/agent?page=1&limit=10&mode=workflow"):
|
||||||
|
listed = unwrap(AgentAppListApi.get)(AgentAppListApi(), "tenant-1", SimpleNamespace(id=account_id))
|
||||||
|
|
||||||
|
assert listed["page"] == 1
|
||||||
|
assert listed["limit"] == 10
|
||||||
|
assert listed["total"] == 1
|
||||||
|
assert listed["data"][0]["id"] == "agent-list"
|
||||||
|
assert "bound_agent_id" not in listed["data"][0]
|
||||||
|
list_call = cast(dict[str, object], captured["list"])
|
||||||
|
list_params = cast(Any, list_call["params"])
|
||||||
|
assert list_params.mode == "agent"
|
||||||
|
assert list_params.status == "normal"
|
||||||
|
|
||||||
|
with app.test_request_context(
|
||||||
|
"/console/api/agent",
|
||||||
|
json={"name": "Iris", "description": "Agent app", "icon_type": "emoji", "icon": "robot"},
|
||||||
|
):
|
||||||
|
created, status = unwrap(AgentAppListApi.post)(AgentAppListApi(), "tenant-1", SimpleNamespace(id=account_id))
|
||||||
|
|
||||||
|
assert status == 201
|
||||||
|
assert created["id"] == "agent-created"
|
||||||
|
assert "bound_agent_id" not in created
|
||||||
|
create_call = cast(dict[str, object], captured["create"])
|
||||||
|
create_params = cast(Any, create_call["params"])
|
||||||
|
assert create_params.mode == "agent"
|
||||||
|
|
||||||
|
|
||||||
def test_roster_direct_mutation_endpoints_are_not_exposed() -> None:
|
def test_agent_app_detail_update_delete_resolve_app_from_agent_id(
|
||||||
assert not hasattr(AgentRosterListApi, "post")
|
app: Flask, monkeypatch: pytest.MonkeyPatch, account_id: str
|
||||||
assert not hasattr(AgentRosterDetailApi, "patch")
|
) -> None:
|
||||||
assert not hasattr(AgentRosterDetailApi, "delete")
|
agent_id = "00000000-0000-0000-0000-000000000001"
|
||||||
|
app_model = _app_detail_obj(id="app-1", bound_agent_id=agent_id)
|
||||||
|
captured: dict[str, object] = {}
|
||||||
|
|
||||||
|
monkeypatch.setattr(
|
||||||
|
roster_controller.AgentRosterService,
|
||||||
|
"get_agent_app_model",
|
||||||
|
lambda _self, **kwargs: app_model,
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
roster_controller.FeatureService,
|
||||||
|
"get_system_features",
|
||||||
|
lambda: SimpleNamespace(webapp_auth=SimpleNamespace(enabled=False)),
|
||||||
|
)
|
||||||
|
|
||||||
|
class FakeAppService:
|
||||||
|
def get_app(self, app_obj: object) -> object:
|
||||||
|
captured["get_app"] = app_obj
|
||||||
|
return app_obj
|
||||||
|
|
||||||
|
def update_app(self, app_obj: object, args: dict[str, object]) -> object:
|
||||||
|
captured["update"] = {"app": app_obj, "args": args}
|
||||||
|
return _app_detail_obj(id="app-1", name=args["name"], bound_agent_id=agent_id)
|
||||||
|
|
||||||
|
def delete_app(self, app_obj: object) -> None:
|
||||||
|
captured["delete"] = app_obj
|
||||||
|
|
||||||
|
monkeypatch.setattr(roster_controller, "AppService", FakeAppService)
|
||||||
|
|
||||||
|
detail = unwrap(AgentAppApi.get)(AgentAppApi(), "tenant-1", agent_id)
|
||||||
|
assert detail["id"] == agent_id
|
||||||
|
assert "bound_agent_id" not in detail
|
||||||
|
|
||||||
|
with app.test_request_context(
|
||||||
|
"/console/api/agent/00000000-0000-0000-0000-000000000001",
|
||||||
|
json={"name": "Renamed", "description": "", "icon_type": "emoji", "icon": "R"},
|
||||||
|
):
|
||||||
|
updated = unwrap(AgentAppApi.put)(AgentAppApi(), "tenant-1", agent_id)
|
||||||
|
|
||||||
|
assert updated["name"] == "Renamed"
|
||||||
|
assert updated["id"] == agent_id
|
||||||
|
assert "bound_agent_id" not in updated
|
||||||
|
update_call = cast(dict[str, object], captured["update"])
|
||||||
|
assert update_call["app"] is app_model
|
||||||
|
|
||||||
|
deleted, status = unwrap(AgentAppApi.delete)(AgentAppApi(), "tenant-1", agent_id)
|
||||||
|
assert (deleted, status) == ("", 204)
|
||||||
|
assert captured["delete"] is app_model
|
||||||
|
|
||||||
|
|
||||||
def test_invite_options_get_parses_app_id(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
def test_invite_options_get_parses_app_id(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
@ -164,21 +275,16 @@ def test_invite_options_get_parses_app_id(app: Flask, monkeypatch: pytest.Monkey
|
|||||||
|
|
||||||
monkeypatch.setattr(roster_controller.AgentRosterService, "list_invite_options", list_invite_options)
|
monkeypatch.setattr(roster_controller.AgentRosterService, "list_invite_options", list_invite_options)
|
||||||
|
|
||||||
with app.test_request_context("/console/api/agents/invite-options?page=1&limit=10&app_id=app-1"):
|
with app.test_request_context("/console/api/agent/invite-options?page=1&limit=10&app_id=app-1"):
|
||||||
result = unwrap(AgentInviteOptionsApi.get)(AgentInviteOptionsApi(), "tenant-1")
|
result = unwrap(AgentInviteOptionsApi.get)(AgentInviteOptionsApi(), "tenant-1")
|
||||||
|
|
||||||
assert result == {"data": [], "page": 1, "limit": 10, "total": 0, "has_more": False}
|
assert result == {"data": [], "page": 1, "limit": 10, "total": 0, "has_more": False}
|
||||||
assert captured == {"tenant_id": "tenant-1", "page": 1, "limit": 10, "keyword": None, "app_id": "app-1"}
|
assert captured == {"tenant_id": "tenant-1", "page": 1, "limit": 10, "keyword": None, "app_id": "app-1"}
|
||||||
|
|
||||||
|
|
||||||
def test_roster_detail_and_versions_call_services(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
def test_agent_versions_call_services(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
agent_id = "00000000-0000-0000-0000-000000000001"
|
agent_id = "00000000-0000-0000-0000-000000000001"
|
||||||
version_id = "00000000-0000-0000-0000-000000000002"
|
version_id = "00000000-0000-0000-0000-000000000002"
|
||||||
monkeypatch.setattr(
|
|
||||||
roster_controller.AgentRosterService,
|
|
||||||
"get_roster_agent_detail",
|
|
||||||
lambda _self, **kwargs: _agent_response(cast(str, kwargs["agent_id"])),
|
|
||||||
)
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
roster_controller.AgentRosterService,
|
roster_controller.AgentRosterService,
|
||||||
"list_agent_versions",
|
"list_agent_versions",
|
||||||
@ -207,7 +313,6 @@ def test_roster_detail_and_versions_call_services(app: Flask, monkeypatch: pytes
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert unwrap(AgentRosterDetailApi.get)(AgentRosterDetailApi(), "tenant-1", agent_id)["id"] == agent_id
|
|
||||||
assert (
|
assert (
|
||||||
unwrap(AgentRosterVersionsApi.get)(AgentRosterVersionsApi(), "tenant-1", agent_id)["data"][0]["id"]
|
unwrap(AgentRosterVersionsApi.get)(AgentRosterVersionsApi(), "tenant-1", agent_id)["data"][0]["id"]
|
||||||
== "version-1"
|
== "version-1"
|
||||||
@ -294,59 +399,76 @@ def test_workflow_impact_returns_empty_without_version(app: Flask) -> None:
|
|||||||
assert result == {"current_snapshot_id": None, "workflow_node_count": 0, "bindings": []}
|
assert result == {"current_snapshot_id": None, "workflow_node_count": 0, "bindings": []}
|
||||||
|
|
||||||
|
|
||||||
def test_agent_app_composer_get_put_validate_and_candidates(
|
def test_agent_composer_routes_resolve_app_from_agent_id(
|
||||||
app: Flask, monkeypatch: pytest.MonkeyPatch, account_id: str
|
app: Flask, monkeypatch: pytest.MonkeyPatch, account_id: str
|
||||||
) -> None:
|
) -> None:
|
||||||
app_model = SimpleNamespace(id="app-1")
|
agent_id = "00000000-0000-0000-0000-000000000001"
|
||||||
|
captured: dict[str, object] = {}
|
||||||
payload = {
|
payload = {
|
||||||
"variant": ComposerVariant.AGENT_APP.value,
|
"variant": ComposerVariant.AGENT_APP.value,
|
||||||
"save_strategy": ComposerSaveStrategy.SAVE_TO_CURRENT_VERSION.value,
|
"save_strategy": ComposerSaveStrategy.SAVE_TO_CURRENT_VERSION.value,
|
||||||
"agent_soul": {"prompt": {"system_prompt": "x"}},
|
"agent_soul": {"prompt": {"system_prompt": "x"}},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
monkeypatch.setattr(composer_controller, "resolve_agent_app_model", lambda **kwargs: SimpleNamespace(id="app-1"))
|
||||||
|
|
||||||
|
def load_agent_app_composer(**kwargs: object) -> dict:
|
||||||
|
captured["load"] = kwargs
|
||||||
|
return _agent_app_composer_response()
|
||||||
|
|
||||||
|
def save_agent_app_composer(**kwargs: object) -> dict:
|
||||||
|
captured["save"] = kwargs
|
||||||
|
return _agent_app_composer_response()
|
||||||
|
|
||||||
|
def collect_validation_findings(**kwargs: object) -> dict:
|
||||||
|
captured["validate"] = kwargs
|
||||||
|
return {"warnings": [], "knowledge_retrieval_placeholder": []}
|
||||||
|
|
||||||
|
def get_agent_app_candidates(**kwargs: object) -> dict:
|
||||||
|
captured["candidates"] = kwargs
|
||||||
|
return _candidates_response("agent_app")
|
||||||
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
composer_controller.AgentComposerService,
|
composer_controller.AgentComposerService,
|
||||||
"load_agent_app_composer",
|
"load_agent_app_composer",
|
||||||
lambda **kwargs: _agent_app_composer_response(),
|
load_agent_app_composer,
|
||||||
)
|
)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
composer_controller.AgentComposerService,
|
composer_controller.AgentComposerService,
|
||||||
"save_agent_app_composer",
|
"save_agent_app_composer",
|
||||||
lambda **kwargs: _agent_app_composer_response(),
|
save_agent_app_composer,
|
||||||
)
|
)
|
||||||
monkeypatch.setattr(composer_controller.ComposerConfigValidator, "validate_save_payload", lambda payload: None)
|
monkeypatch.setattr(composer_controller.ComposerConfigValidator, "validate_save_payload", lambda payload: None)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
composer_controller.AgentComposerService, "resolve_workflow_node_agent_id", lambda **kwargs: None
|
composer_controller.AgentComposerService,
|
||||||
|
"collect_validation_findings",
|
||||||
|
collect_validation_findings,
|
||||||
)
|
)
|
||||||
monkeypatch.setattr(composer_controller.AgentComposerService, "resolve_bound_agent_id", lambda **kwargs: None)
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
composer_controller.AgentComposerService,
|
composer_controller.AgentComposerService,
|
||||||
"get_agent_app_candidates",
|
"get_agent_app_candidates",
|
||||||
lambda **kwargs: _candidates_response("agent_app"),
|
get_agent_app_candidates,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert unwrap(AgentAppComposerApi.get)(AgentAppComposerApi(), "tenant-1", app_model)["variant"] == "agent_app"
|
assert unwrap(AgentComposerApi.get)(AgentComposerApi(), "tenant-1", agent_id)["variant"] == "agent_app"
|
||||||
|
assert cast(dict[str, object], captured["load"])["app_id"] == "app-1"
|
||||||
|
|
||||||
with app.test_request_context(json=payload):
|
with app.test_request_context(json=payload):
|
||||||
assert (
|
assert (
|
||||||
unwrap(AgentAppComposerApi.put)(AgentAppComposerApi(), "tenant-1", account_id, app_model)["variant"]
|
unwrap(AgentComposerApi.put)(AgentComposerApi(), "tenant-1", account_id, agent_id)["variant"] == "agent_app"
|
||||||
== "agent_app"
|
|
||||||
)
|
)
|
||||||
assert unwrap(AgentAppComposerValidateApi.post)(AgentAppComposerValidateApi(), "tenant-1", app_model) == {
|
assert cast(dict[str, object], captured["save"])["app_id"] == "app-1"
|
||||||
|
assert unwrap(AgentComposerValidateApi.post)(AgentComposerValidateApi(), "tenant-1", agent_id) == {
|
||||||
"result": "success",
|
"result": "success",
|
||||||
"errors": [],
|
"errors": [],
|
||||||
"warnings": [],
|
"warnings": [],
|
||||||
"knowledge_retrieval_placeholder": [],
|
"knowledge_retrieval_placeholder": [],
|
||||||
}
|
}
|
||||||
agent_app_candidates = unwrap(AgentAppComposerCandidatesApi.get)(
|
assert cast(dict[str, object], captured["validate"])["agent_id"] == agent_id
|
||||||
AgentAppComposerCandidatesApi(), "tenant-1", account_id, app_model
|
|
||||||
)
|
|
||||||
assert agent_app_candidates["variant"] == "agent_app"
|
|
||||||
|
|
||||||
|
candidates = unwrap(AgentComposerCandidatesApi.get)(AgentComposerCandidatesApi(), "tenant-1", account_id, agent_id)
|
||||||
def test_agent_app_composer_routes_are_agent_mode_only() -> None:
|
assert candidates["variant"] == "agent_app"
|
||||||
assert _get_app_model_modes(AgentAppComposerApi.get) == [AppMode.AGENT]
|
assert cast(dict[str, object], captured["candidates"])["app_id"] == "app-1"
|
||||||
assert _get_app_model_modes(AgentAppComposerApi.put) == [AppMode.AGENT]
|
|
||||||
assert _get_app_model_modes(AgentAppComposerValidateApi.post) == [AppMode.AGENT]
|
|
||||||
assert _get_app_model_modes(AgentAppComposerCandidatesApi.get) == [AppMode.AGENT]
|
|
||||||
|
|
||||||
|
|
||||||
def test_dify_tool_candidate_response_keeps_granularity_fields():
|
def test_dify_tool_candidate_response_keeps_granularity_fields():
|
||||||
|
|||||||
@ -119,6 +119,7 @@ def test_handle_maps_sandbox_and_agent_backend_errors() -> None:
|
|||||||
def test_agent_app_sandbox_resources_proxy_service(monkeypatch: pytest.MonkeyPatch) -> None:
|
def test_agent_app_sandbox_resources_proxy_service(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
service = _AgentAppService()
|
service = _AgentAppService()
|
||||||
monkeypatch.setattr(module, "AgentAppSandboxService", lambda: service)
|
monkeypatch.setattr(module, "AgentAppSandboxService", lambda: service)
|
||||||
|
monkeypatch.setattr(module, "resolve_agent_app_model", lambda *, tenant_id, agent_id: _app_model())
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
module,
|
module,
|
||||||
"query_params_from_request",
|
"query_params_from_request",
|
||||||
@ -129,11 +130,10 @@ def test_agent_app_sandbox_resources_proxy_service(monkeypatch: pytest.MonkeyPat
|
|||||||
"request",
|
"request",
|
||||||
SimpleNamespace(get_json=lambda silent=True: {"conversation_id": "conv-1", "path": "report.txt"}),
|
SimpleNamespace(get_json=lambda silent=True: {"conversation_id": "conv-1", "path": "report.txt"}),
|
||||||
)
|
)
|
||||||
app_model = _app_model()
|
|
||||||
|
|
||||||
listing = unwrap(module.AgentAppSandboxListResource.get)(object(), "tenant-1", app_model)
|
listing = unwrap(module.AgentAppSandboxListResource.get)(object(), "tenant-1", "agent-1")
|
||||||
preview = unwrap(module.AgentAppSandboxReadResource.get)(object(), "tenant-1", app_model)
|
preview = unwrap(module.AgentAppSandboxReadResource.get)(object(), "tenant-1", "agent-1")
|
||||||
upload = unwrap(module.AgentAppSandboxUploadResource.post)(object(), "tenant-1", app_model)
|
upload = unwrap(module.AgentAppSandboxUploadResource.post)(object(), "tenant-1", "agent-1")
|
||||||
|
|
||||||
assert listing["path"] == "sub/report.txt"
|
assert listing["path"] == "sub/report.txt"
|
||||||
assert preview["text"] == "hello"
|
assert preview["text"] == "hello"
|
||||||
@ -151,11 +151,12 @@ def test_agent_app_sandbox_resource_returns_normalized_errors(monkeypatch: pytes
|
|||||||
raise AgentSandboxInspectorError("no_active_session", "no active session", status_code=404)
|
raise AgentSandboxInspectorError("no_active_session", "no active session", status_code=404)
|
||||||
|
|
||||||
monkeypatch.setattr(module, "AgentAppSandboxService", FailingService)
|
monkeypatch.setattr(module, "AgentAppSandboxService", FailingService)
|
||||||
|
monkeypatch.setattr(module, "resolve_agent_app_model", lambda *, tenant_id, agent_id: _app_model())
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
module, "query_params_from_request", lambda model: SimpleNamespace(conversation_id="conv-1", path=".")
|
module, "query_params_from_request", lambda model: SimpleNamespace(conversation_id="conv-1", path=".")
|
||||||
)
|
)
|
||||||
|
|
||||||
assert unwrap(module.AgentAppSandboxListResource.get)(object(), "tenant-1", _app_model()) == (
|
assert unwrap(module.AgentAppSandboxListResource.get)(object(), "tenant-1", "agent-1") == (
|
||||||
{"code": "no_active_session", "message": "no active session"},
|
{"code": "no_active_session", "message": "no active session"},
|
||||||
404,
|
404,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -15,8 +15,11 @@ from flask import Flask
|
|||||||
|
|
||||||
from controllers.console.app.agent_drive_inspector import (
|
from controllers.console.app.agent_drive_inspector import (
|
||||||
AgentDriveDownloadApi,
|
AgentDriveDownloadApi,
|
||||||
|
AgentDriveDownloadByAgentApi,
|
||||||
AgentDriveListApi,
|
AgentDriveListApi,
|
||||||
|
AgentDriveListByAgentApi,
|
||||||
AgentDrivePreviewApi,
|
AgentDrivePreviewApi,
|
||||||
|
AgentDrivePreviewByAgentApi,
|
||||||
)
|
)
|
||||||
from services.agent_drive_service import AgentDriveError
|
from services.agent_drive_service import AgentDriveError
|
||||||
|
|
||||||
@ -53,6 +56,32 @@ def test_list_filters_value_pointers_out_of_console_payload():
|
|||||||
assert drive.return_value.manifest.call_args.kwargs["prefix"] == "pdf-toolkit/"
|
assert drive.return_value.manifest.call_args.kwargs["prefix"] == "pdf-toolkit/"
|
||||||
|
|
||||||
|
|
||||||
|
def test_list_by_agent_filters_value_pointers_out_of_console_payload():
|
||||||
|
raw = _raw(AgentDriveListByAgentApi.get)
|
||||||
|
with app.test_request_context("/?prefix=pdf-toolkit/"):
|
||||||
|
with (
|
||||||
|
patch(f"{_MOD}.resolve_agent_app_model", return_value=_APP) as resolve_app,
|
||||||
|
patch(f"{_MOD}.AgentDriveService") as drive,
|
||||||
|
):
|
||||||
|
drive.return_value.manifest.return_value = [
|
||||||
|
{
|
||||||
|
"key": "pdf-toolkit/SKILL.md",
|
||||||
|
"size": 5,
|
||||||
|
"hash": "h",
|
||||||
|
"mime_type": "text/markdown",
|
||||||
|
"file_kind": "tool_file",
|
||||||
|
"file_id": "tf-1",
|
||||||
|
"created_at": 1718000000,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
body = raw(AgentDriveListByAgentApi(), "tenant-1", "agent-1")
|
||||||
|
|
||||||
|
assert body["items"][0]["key"] == "pdf-toolkit/SKILL.md"
|
||||||
|
assert "file_id" not in body["items"][0]
|
||||||
|
resolve_app.assert_called_once_with(tenant_id="tenant-1", agent_id="agent-1")
|
||||||
|
assert drive.return_value.manifest.call_args.kwargs["agent_id"] == "agent-1"
|
||||||
|
|
||||||
|
|
||||||
def test_list_resolves_workflow_node_binding_agent():
|
def test_list_resolves_workflow_node_binding_agent():
|
||||||
raw = _raw(AgentDriveListApi.get)
|
raw = _raw(AgentDriveListApi.get)
|
||||||
with app.test_request_context("/?node_id=agent-node-1"):
|
with app.test_request_context("/?node_id=agent-node-1"):
|
||||||
@ -101,6 +130,37 @@ def test_preview_passes_through_and_maps_errors():
|
|||||||
assert body["code"] == "drive_key_not_found"
|
assert body["code"] == "drive_key_not_found"
|
||||||
|
|
||||||
|
|
||||||
|
def test_preview_by_agent_passes_through_and_maps_errors():
|
||||||
|
raw = _raw(AgentDrivePreviewByAgentApi.get)
|
||||||
|
with app.test_request_context("/?key=pdf-toolkit/SKILL.md"):
|
||||||
|
with (
|
||||||
|
patch(f"{_MOD}.resolve_agent_app_model", return_value=_APP) as resolve_app,
|
||||||
|
patch(f"{_MOD}.AgentDriveService") as drive,
|
||||||
|
):
|
||||||
|
drive.return_value.preview.return_value = {
|
||||||
|
"key": "pdf-toolkit/SKILL.md",
|
||||||
|
"size": 5,
|
||||||
|
"truncated": False,
|
||||||
|
"binary": False,
|
||||||
|
"text": "# hi",
|
||||||
|
}
|
||||||
|
body = raw(AgentDrivePreviewByAgentApi(), "tenant-1", "agent-1")
|
||||||
|
assert body["text"] == "# hi"
|
||||||
|
resolve_app.assert_called_once_with(tenant_id="tenant-1", agent_id="agent-1")
|
||||||
|
|
||||||
|
with app.test_request_context("/?key=ghost/SKILL.md"):
|
||||||
|
with (
|
||||||
|
patch(f"{_MOD}.resolve_agent_app_model", return_value=_APP),
|
||||||
|
patch(f"{_MOD}.AgentDriveService") as drive,
|
||||||
|
):
|
||||||
|
drive.return_value.preview.side_effect = AgentDriveError(
|
||||||
|
"drive_key_not_found", "no drive entry", status_code=404
|
||||||
|
)
|
||||||
|
body, status = raw(AgentDrivePreviewByAgentApi(), "tenant-1", "agent-1")
|
||||||
|
assert status == 404
|
||||||
|
assert body["code"] == "drive_key_not_found"
|
||||||
|
|
||||||
|
|
||||||
def test_download_returns_signed_url_json():
|
def test_download_returns_signed_url_json():
|
||||||
raw = _raw(AgentDriveDownloadApi.get)
|
raw = _raw(AgentDriveDownloadApi.get)
|
||||||
with app.test_request_context("/?key=pdf-toolkit/.DIFY-SKILL-FULL.zip"):
|
with app.test_request_context("/?key=pdf-toolkit/.DIFY-SKILL-FULL.zip"):
|
||||||
@ -108,3 +168,16 @@ def test_download_returns_signed_url_json():
|
|||||||
drive.return_value.download_url.return_value = "https://signed.example/zip"
|
drive.return_value.download_url.return_value = "https://signed.example/zip"
|
||||||
body = raw(AgentDriveDownloadApi(), _APP)
|
body = raw(AgentDriveDownloadApi(), _APP)
|
||||||
assert body == {"url": "https://signed.example/zip"}
|
assert body == {"url": "https://signed.example/zip"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_download_by_agent_returns_signed_url_json():
|
||||||
|
raw = _raw(AgentDriveDownloadByAgentApi.get)
|
||||||
|
with app.test_request_context("/?key=pdf-toolkit/.DIFY-SKILL-FULL.zip"):
|
||||||
|
with (
|
||||||
|
patch(f"{_MOD}.resolve_agent_app_model", return_value=_APP) as resolve_app,
|
||||||
|
patch(f"{_MOD}.AgentDriveService") as drive,
|
||||||
|
):
|
||||||
|
drive.return_value.download_url.return_value = "https://signed.example/zip"
|
||||||
|
body = raw(AgentDriveDownloadByAgentApi(), "tenant-1", "agent-1")
|
||||||
|
assert body == {"url": "https://signed.example/zip"}
|
||||||
|
resolve_app.assert_called_once_with(tenant_id="tenant-1", agent_id="agent-1")
|
||||||
|
|||||||
@ -14,7 +14,16 @@ from unittest.mock import MagicMock, patch
|
|||||||
|
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
|
|
||||||
from controllers.console.app.agent import AgentSkillStandardizeApi, AgentSkillUploadApi
|
from controllers.console.app.agent import (
|
||||||
|
AgentDriveFilesByAgentApi,
|
||||||
|
AgentSkillByAgentApi,
|
||||||
|
AgentSkillInferToolsByAgentApi,
|
||||||
|
AgentSkillStandardizeApi,
|
||||||
|
AgentSkillStandardizeByAgentApi,
|
||||||
|
AgentSkillUploadApi,
|
||||||
|
AgentSkillUploadByAgentApi,
|
||||||
|
)
|
||||||
|
from models.model import AppMode
|
||||||
from services.agent.skill_package_service import SkillPackageError
|
from services.agent.skill_package_service import SkillPackageError
|
||||||
from services.agent_drive_service import AgentDriveError
|
from services.agent_drive_service import AgentDriveError
|
||||||
|
|
||||||
@ -32,7 +41,8 @@ def _file_ctx(*, files: dict[str, bytes] | None = None):
|
|||||||
|
|
||||||
|
|
||||||
_USER = SimpleNamespace(id="user-1")
|
_USER = SimpleNamespace(id="user-1")
|
||||||
_APP = SimpleNamespace(id="app-1", tenant_id="tenant-1", bound_agent_id="agent-1")
|
_APP = SimpleNamespace(id="app-1", tenant_id="tenant-1", mode=AppMode.AGENT, bound_agent_id="agent-1")
|
||||||
|
_WORKFLOW_APP = SimpleNamespace(id="app-1", tenant_id="tenant-1", mode=AppMode.WORKFLOW, bound_agent_id=None)
|
||||||
|
|
||||||
|
|
||||||
def test_upload_validates_and_returns_skill_ref():
|
def test_upload_validates_and_returns_skill_ref():
|
||||||
@ -56,6 +66,28 @@ def test_upload_validates_and_returns_skill_ref():
|
|||||||
manifest.to_skill_ref.assert_called_once_with(file_id="uf-1")
|
manifest.to_skill_ref.assert_called_once_with(file_id="uf-1")
|
||||||
|
|
||||||
|
|
||||||
|
def test_upload_by_agent_resolves_app_and_returns_skill_ref():
|
||||||
|
raw = _raw(AgentSkillUploadByAgentApi.post)
|
||||||
|
manifest = MagicMock()
|
||||||
|
manifest.to_skill_ref.return_value.model_dump.return_value = {"name": "S", "file_id": "uf-1"}
|
||||||
|
manifest.model_dump.return_value = {"name": "S"}
|
||||||
|
|
||||||
|
with _file_ctx(files={"file": b"zip-bytes"}):
|
||||||
|
with (
|
||||||
|
patch(f"{_MOD}.resolve_agent_app_model", return_value=_APP) as resolve_app,
|
||||||
|
patch(f"{_MOD}.SkillPackageService") as pkg,
|
||||||
|
patch(f"{_MOD}.FileService") as fs,
|
||||||
|
patch(f"{_MOD}.db"),
|
||||||
|
):
|
||||||
|
pkg.return_value.validate_and_extract.return_value = manifest
|
||||||
|
fs.return_value.upload_file.return_value = SimpleNamespace(id="uf-1")
|
||||||
|
body, status = raw(AgentSkillUploadByAgentApi(), "tenant-1", _USER, "agent-1")
|
||||||
|
|
||||||
|
assert status == 201
|
||||||
|
assert body["skill"] == {"name": "S", "file_id": "uf-1"}
|
||||||
|
resolve_app.assert_called_once_with(tenant_id="tenant-1", agent_id="agent-1")
|
||||||
|
|
||||||
|
|
||||||
def test_upload_no_file_is_400():
|
def test_upload_no_file_is_400():
|
||||||
raw = _raw(AgentSkillUploadApi.post)
|
raw = _raw(AgentSkillUploadApi.post)
|
||||||
with _file_ctx(files={}):
|
with _file_ctx(files={}):
|
||||||
@ -87,9 +119,25 @@ def test_standardize_returns_result():
|
|||||||
assert svc.return_value.standardize.call_args.kwargs["agent_id"] == "agent-1"
|
assert svc.return_value.standardize.call_args.kwargs["agent_id"] == "agent-1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_standardize_by_agent_resolves_app():
|
||||||
|
raw = _raw(AgentSkillStandardizeByAgentApi.post)
|
||||||
|
with _file_ctx(files={"file": b"zip"}):
|
||||||
|
with (
|
||||||
|
patch(f"{_MOD}.resolve_agent_app_model", return_value=_APP) as resolve_app,
|
||||||
|
patch(f"{_MOD}.SkillStandardizeService") as svc,
|
||||||
|
):
|
||||||
|
svc.return_value.standardize.return_value = {"skill": {"path": "s"}, "manifest": {}}
|
||||||
|
body, status = raw(AgentSkillStandardizeByAgentApi(), "tenant-1", _USER, "agent-1")
|
||||||
|
|
||||||
|
assert status == 201
|
||||||
|
assert body["skill"] == {"path": "s"}
|
||||||
|
resolve_app.assert_called_once_with(tenant_id="tenant-1", agent_id="agent-1")
|
||||||
|
assert svc.return_value.standardize.call_args.kwargs["agent_id"] == "agent-1"
|
||||||
|
|
||||||
|
|
||||||
def test_standardize_no_bound_agent_is_400():
|
def test_standardize_no_bound_agent_is_400():
|
||||||
raw = _raw(AgentSkillStandardizeApi.post)
|
raw = _raw(AgentSkillStandardizeApi.post)
|
||||||
app_without_agent = SimpleNamespace(id="app-1", tenant_id="tenant-1", bound_agent_id=None)
|
app_without_agent = SimpleNamespace(id="app-1", tenant_id="tenant-1", mode=AppMode.AGENT, bound_agent_id=None)
|
||||||
with _file_ctx(files={"file": b"zip"}):
|
with _file_ctx(files={"file": b"zip"}):
|
||||||
body, status = raw(AgentSkillStandardizeApi(), _USER, app_without_agent)
|
body, status = raw(AgentSkillStandardizeApi(), _USER, app_without_agent)
|
||||||
assert status == 400
|
assert status == 400
|
||||||
@ -98,7 +146,6 @@ def test_standardize_no_bound_agent_is_400():
|
|||||||
|
|
||||||
def test_standardize_resolves_workflow_node_agent():
|
def test_standardize_resolves_workflow_node_agent():
|
||||||
raw = _raw(AgentSkillStandardizeApi.post)
|
raw = _raw(AgentSkillStandardizeApi.post)
|
||||||
workflow_app = SimpleNamespace(id="app-1", tenant_id="tenant-1", bound_agent_id=None)
|
|
||||||
with app.test_request_context(
|
with app.test_request_context(
|
||||||
"/?node_id=agent-node-1", method="POST", data={"file": (io.BytesIO(b"zip"), "skill.zip")}
|
"/?node_id=agent-node-1", method="POST", data={"file": (io.BytesIO(b"zip"), "skill.zip")}
|
||||||
):
|
):
|
||||||
@ -108,7 +155,7 @@ def test_standardize_resolves_workflow_node_agent():
|
|||||||
):
|
):
|
||||||
composer.resolve_workflow_node_agent_id.return_value = "wf-agent-1"
|
composer.resolve_workflow_node_agent_id.return_value = "wf-agent-1"
|
||||||
svc.return_value.standardize.return_value = {"skill": {"path": "s"}, "manifest": {}}
|
svc.return_value.standardize.return_value = {"skill": {"path": "s"}, "manifest": {}}
|
||||||
body, status = raw(AgentSkillStandardizeApi(), _USER, workflow_app)
|
body, status = raw(AgentSkillStandardizeApi(), _USER, _WORKFLOW_APP)
|
||||||
|
|
||||||
assert status == 201
|
assert status == 201
|
||||||
assert body["skill"] == {"path": "s"}
|
assert body["skill"] == {"path": "s"}
|
||||||
@ -165,6 +212,31 @@ def test_files_commit_validates_upload_and_returns_drive_ref():
|
|||||||
assert composer.add_drive_file_ref.call_args.kwargs["app_id"] == "app-1"
|
assert composer.add_drive_file_ref.call_args.kwargs["app_id"] == "app-1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_files_by_agent_commit_uses_agent_route_and_ignores_node_id():
|
||||||
|
raw = _raw(AgentDriveFilesByAgentApi.post)
|
||||||
|
upload = SimpleNamespace(id="uf-1", name="sample.pdf")
|
||||||
|
with _json_ctx({"upload_file_id": "0fa6f9bc-3416-4476-8857-a13129704dd9"}, query_string="node_id=ignored"):
|
||||||
|
with (
|
||||||
|
patch(f"{_MOD}.resolve_agent_app_model", return_value=_APP) as resolve_app,
|
||||||
|
patch(f"{_MOD}.console_ns") as ns,
|
||||||
|
patch(f"{_MOD}.db") as db_mock,
|
||||||
|
patch(f"{_MOD}.AgentDriveService") as drive,
|
||||||
|
patch(f"{_MOD}.AgentComposerService") as composer,
|
||||||
|
):
|
||||||
|
ns.payload = {"upload_file_id": "0fa6f9bc-3416-4476-8857-a13129704dd9"}
|
||||||
|
db_mock.session.scalar.return_value = upload
|
||||||
|
drive.return_value.commit.return_value = [
|
||||||
|
{"key": "files/sample.pdf", "size": 5, "mime_type": "application/pdf"}
|
||||||
|
]
|
||||||
|
composer.add_drive_file_ref.return_value = "ver-2"
|
||||||
|
body, status = raw(AgentDriveFilesByAgentApi(), "tenant-1", _USER, "agent-1")
|
||||||
|
|
||||||
|
assert status == 201
|
||||||
|
assert body["config_version_id"] == "ver-2"
|
||||||
|
resolve_app.assert_called_once_with(tenant_id="tenant-1", agent_id="agent-1")
|
||||||
|
assert composer.add_drive_file_ref.call_args.kwargs["node_id"] is None
|
||||||
|
|
||||||
|
|
||||||
def test_files_commit_404_when_upload_not_in_tenant():
|
def test_files_commit_404_when_upload_not_in_tenant():
|
||||||
from controllers.console.app.agent import AgentDriveFilesApi
|
from controllers.console.app.agent import AgentDriveFilesApi
|
||||||
|
|
||||||
@ -186,7 +258,6 @@ def test_files_commit_resolves_workflow_node_agent():
|
|||||||
|
|
||||||
raw = _raw(AgentDriveFilesApi.post)
|
raw = _raw(AgentDriveFilesApi.post)
|
||||||
upload = SimpleNamespace(id="uf-1", name="sample.pdf")
|
upload = SimpleNamespace(id="uf-1", name="sample.pdf")
|
||||||
workflow_app = SimpleNamespace(id="app-1", tenant_id="tenant-1", bound_agent_id=None)
|
|
||||||
with _json_ctx({"upload_file_id": "0fa6f9bc-3416-4476-8857-a13129704dd9"}, query_string="node_id=agent-node-1"):
|
with _json_ctx({"upload_file_id": "0fa6f9bc-3416-4476-8857-a13129704dd9"}, query_string="node_id=agent-node-1"):
|
||||||
with (
|
with (
|
||||||
patch(f"{_MOD}.console_ns") as ns,
|
patch(f"{_MOD}.console_ns") as ns,
|
||||||
@ -201,7 +272,7 @@ def test_files_commit_resolves_workflow_node_agent():
|
|||||||
{"key": "files/sample.pdf", "size": 5, "mime_type": "application/pdf"}
|
{"key": "files/sample.pdf", "size": 5, "mime_type": "application/pdf"}
|
||||||
]
|
]
|
||||||
composer.add_drive_file_ref.return_value = "ver-2"
|
composer.add_drive_file_ref.return_value = "ver-2"
|
||||||
body, status = raw(AgentDriveFilesApi(), _USER, workflow_app)
|
body, status = raw(AgentDriveFilesApi(), _USER, _WORKFLOW_APP)
|
||||||
|
|
||||||
assert status == 201
|
assert status == 201
|
||||||
assert body["config_version_id"] == "ver-2"
|
assert body["config_version_id"] == "ver-2"
|
||||||
@ -229,11 +300,27 @@ def test_files_delete_updates_soul_then_drive():
|
|||||||
assert composer.remove_drive_refs.call_args.kwargs["app_id"] == "app-1"
|
assert composer.remove_drive_refs.call_args.kwargs["app_id"] == "app-1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_files_by_agent_delete_uses_agent_route_and_ignores_node_id():
|
||||||
|
raw = _raw(AgentDriveFilesByAgentApi.delete)
|
||||||
|
with _json_ctx(method="DELETE", query_string="key=files/sample.pdf&node_id=ignored"):
|
||||||
|
with (
|
||||||
|
patch(f"{_MOD}.resolve_agent_app_model", return_value=_APP) as resolve_app,
|
||||||
|
patch(f"{_MOD}.AgentComposerService") as composer,
|
||||||
|
patch(f"{_MOD}.AgentDriveService") as drive,
|
||||||
|
):
|
||||||
|
composer.remove_drive_refs.return_value = "ver-2"
|
||||||
|
drive.return_value.delete.return_value = ["files/sample.pdf"]
|
||||||
|
body = raw(AgentDriveFilesByAgentApi(), "tenant-1", _USER, "agent-1")
|
||||||
|
|
||||||
|
assert body["config_version_id"] == "ver-2"
|
||||||
|
resolve_app.assert_called_once_with(tenant_id="tenant-1", agent_id="agent-1")
|
||||||
|
assert composer.remove_drive_refs.call_args.kwargs["node_id"] is None
|
||||||
|
|
||||||
|
|
||||||
def test_files_delete_resolves_workflow_node_agent():
|
def test_files_delete_resolves_workflow_node_agent():
|
||||||
from controllers.console.app.agent import AgentDriveFilesApi
|
from controllers.console.app.agent import AgentDriveFilesApi
|
||||||
|
|
||||||
raw = _raw(AgentDriveFilesApi.delete)
|
raw = _raw(AgentDriveFilesApi.delete)
|
||||||
workflow_app = SimpleNamespace(id="app-1", tenant_id="tenant-1", bound_agent_id=None)
|
|
||||||
with _json_ctx(method="DELETE", query_string="key=files/sample.pdf&node_id=agent-node-1"):
|
with _json_ctx(method="DELETE", query_string="key=files/sample.pdf&node_id=agent-node-1"):
|
||||||
with (
|
with (
|
||||||
patch(f"{_MOD}.AgentComposerService") as composer,
|
patch(f"{_MOD}.AgentComposerService") as composer,
|
||||||
@ -242,7 +329,7 @@ def test_files_delete_resolves_workflow_node_agent():
|
|||||||
composer.resolve_workflow_node_agent_id.return_value = "wf-agent-1"
|
composer.resolve_workflow_node_agent_id.return_value = "wf-agent-1"
|
||||||
composer.remove_drive_refs.return_value = "ver-2"
|
composer.remove_drive_refs.return_value = "ver-2"
|
||||||
drive.return_value.delete.return_value = ["files/sample.pdf"]
|
drive.return_value.delete.return_value = ["files/sample.pdf"]
|
||||||
body = raw(AgentDriveFilesApi(), _USER, workflow_app)
|
body = raw(AgentDriveFilesApi(), _USER, _WORKFLOW_APP)
|
||||||
|
|
||||||
assert body["config_version_id"] == "ver-2"
|
assert body["config_version_id"] == "ver-2"
|
||||||
assert drive.return_value.delete.call_args.kwargs["agent_id"] == "wf-agent-1"
|
assert drive.return_value.delete.call_args.kwargs["agent_id"] == "wf-agent-1"
|
||||||
@ -284,6 +371,23 @@ def test_skill_delete_uses_slug_prefix_and_is_idempotent():
|
|||||||
assert composer.remove_drive_refs.call_args.kwargs["app_id"] == "app-1"
|
assert composer.remove_drive_refs.call_args.kwargs["app_id"] == "app-1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_skill_delete_by_agent_uses_agent_route():
|
||||||
|
raw = _raw(AgentSkillByAgentApi.delete)
|
||||||
|
with _json_ctx(method="DELETE", query_string="node_id=ignored"):
|
||||||
|
with (
|
||||||
|
patch(f"{_MOD}.resolve_agent_app_model", return_value=_APP) as resolve_app,
|
||||||
|
patch(f"{_MOD}.AgentComposerService") as composer,
|
||||||
|
patch(f"{_MOD}.AgentDriveService") as drive,
|
||||||
|
):
|
||||||
|
composer.remove_drive_refs.return_value = "ver-2"
|
||||||
|
drive.return_value.delete.return_value = ["tender-analyzer/SKILL.md"]
|
||||||
|
body = raw(AgentSkillByAgentApi(), "tenant-1", _USER, "agent-1", "tender-analyzer")
|
||||||
|
|
||||||
|
assert body["config_version_id"] == "ver-2"
|
||||||
|
resolve_app.assert_called_once_with(tenant_id="tenant-1", agent_id="agent-1")
|
||||||
|
assert composer.remove_drive_refs.call_args.kwargs["node_id"] is None
|
||||||
|
|
||||||
|
|
||||||
def test_skill_delete_rejects_path_like_slug():
|
def test_skill_delete_rejects_path_like_slug():
|
||||||
from controllers.console.app.agent import AgentSkillApi
|
from controllers.console.app.agent import AgentSkillApi
|
||||||
|
|
||||||
@ -314,11 +418,25 @@ def test_infer_tools_returns_draft_suggestions():
|
|||||||
assert svc.return_value.infer.call_args.kwargs["slug"] == "audio-transcribe"
|
assert svc.return_value.infer.call_args.kwargs["slug"] == "audio-transcribe"
|
||||||
|
|
||||||
|
|
||||||
|
def test_infer_tools_by_agent_uses_agent_route():
|
||||||
|
raw = _raw(AgentSkillInferToolsByAgentApi.post)
|
||||||
|
with _json_ctx(query_string="node_id=ignored"):
|
||||||
|
with (
|
||||||
|
patch(f"{_MOD}.resolve_agent_app_model", return_value=_APP) as resolve_app,
|
||||||
|
patch(f"{_MOD}.SkillToolInferenceService") as svc,
|
||||||
|
):
|
||||||
|
svc.return_value.infer.return_value = {"inferable": True, "cli_tools": [], "reason": None}
|
||||||
|
body = raw(AgentSkillInferToolsByAgentApi(), "tenant-1", "agent-1", "audio-transcribe")
|
||||||
|
|
||||||
|
assert body["inferable"] is True
|
||||||
|
resolve_app.assert_called_once_with(tenant_id="tenant-1", agent_id="agent-1")
|
||||||
|
assert svc.return_value.infer.call_args.kwargs["agent_id"] == "agent-1"
|
||||||
|
|
||||||
|
|
||||||
def test_infer_tools_resolves_workflow_node_agent():
|
def test_infer_tools_resolves_workflow_node_agent():
|
||||||
from controllers.console.app.agent import AgentSkillInferToolsApi
|
from controllers.console.app.agent import AgentSkillInferToolsApi
|
||||||
|
|
||||||
raw = _raw(AgentSkillInferToolsApi.post)
|
raw = _raw(AgentSkillInferToolsApi.post)
|
||||||
workflow_app = SimpleNamespace(id="app-1", tenant_id="tenant-1", bound_agent_id=None)
|
|
||||||
with _json_ctx(query_string="node_id=agent-node-1"):
|
with _json_ctx(query_string="node_id=agent-node-1"):
|
||||||
with (
|
with (
|
||||||
patch(f"{_MOD}.AgentComposerService") as composer,
|
patch(f"{_MOD}.AgentComposerService") as composer,
|
||||||
@ -326,7 +444,7 @@ def test_infer_tools_resolves_workflow_node_agent():
|
|||||||
):
|
):
|
||||||
composer.resolve_workflow_node_agent_id.return_value = "wf-agent-1"
|
composer.resolve_workflow_node_agent_id.return_value = "wf-agent-1"
|
||||||
svc.return_value.infer.return_value = {"inferable": False, "cli_tools": [], "reason": "none"}
|
svc.return_value.infer.return_value = {"inferable": False, "cli_tools": [], "reason": "none"}
|
||||||
body = raw(AgentSkillInferToolsApi(), workflow_app, "audio-transcribe")
|
body = raw(AgentSkillInferToolsApi(), _WORKFLOW_APP, "audio-transcribe")
|
||||||
|
|
||||||
assert body["inferable"] is False
|
assert body["inferable"] is False
|
||||||
assert svc.return_value.infer.call_args.kwargs["agent_id"] == "wf-agent-1"
|
assert svc.return_value.infer.call_args.kwargs["agent_id"] == "wf-agent-1"
|
||||||
@ -355,7 +473,7 @@ def test_infer_tools_rejects_path_like_slug_and_unbound_app():
|
|||||||
body, status = raw(AgentSkillInferToolsApi(), _APP, "a/b")
|
body, status = raw(AgentSkillInferToolsApi(), _APP, "a/b")
|
||||||
assert (status, body["code"]) == (400, "drive_key_invalid")
|
assert (status, body["code"]) == (400, "drive_key_invalid")
|
||||||
|
|
||||||
app_without_agent = SimpleNamespace(id="app-1", tenant_id="tenant-1", bound_agent_id=None)
|
app_without_agent = SimpleNamespace(id="app-1", tenant_id="tenant-1", mode=AppMode.AGENT, bound_agent_id=None)
|
||||||
with _json_ctx():
|
with _json_ctx():
|
||||||
body, status = raw(AgentSkillInferToolsApi(), app_without_agent, "x")
|
body, status = raw(AgentSkillInferToolsApi(), app_without_agent, "x")
|
||||||
assert (status, body["code"]) == (400, "agent_not_bound")
|
assert (status, body["code"]) == (400, "agent_not_bound")
|
||||||
|
|||||||
@ -314,37 +314,19 @@ def test_app_list_query_rejects_flat_tag_ids(app_module):
|
|||||||
app_module.AppListQuery.model_validate(normalized)
|
app_module.AppListQuery.model_validate(normalized)
|
||||||
|
|
||||||
|
|
||||||
def test_create_agent_app_response_includes_bound_agent_id(app_module, monkeypatch: pytest.MonkeyPatch):
|
def test_create_app_endpoint_rejects_agent_mode(app_module, monkeypatch: pytest.MonkeyPatch):
|
||||||
payload = {"name": "Iris", "mode": "agent", "description": "Agent app"}
|
payload = {"name": "Iris", "mode": "agent", "description": "Agent app"}
|
||||||
app_obj = SimpleNamespace(
|
|
||||||
id="app-1",
|
|
||||||
name="Iris",
|
|
||||||
description="Agent app",
|
|
||||||
mode_compatible_with_agent="agent",
|
|
||||||
icon_type="emoji",
|
|
||||||
icon="robot",
|
|
||||||
icon_background="#fff",
|
|
||||||
enable_site=False,
|
|
||||||
enable_api=False,
|
|
||||||
created_at=_ts(),
|
|
||||||
updated_at=_ts(),
|
|
||||||
bound_agent_id="agent-1",
|
|
||||||
)
|
|
||||||
app_service = MagicMock()
|
app_service = MagicMock()
|
||||||
app_service.create_app.return_value = app_obj
|
|
||||||
monkeypatch.setattr(app_module, "AppService", lambda: app_service)
|
monkeypatch.setattr(app_module, "AppService", lambda: app_service)
|
||||||
|
|
||||||
app_module.console_ns.payload = payload
|
app_module.console_ns.payload = payload
|
||||||
try:
|
try:
|
||||||
response, status = _unwrap(app_module.AppListApi().post)("tenant-1", SimpleNamespace(id="account-1"))
|
with pytest.raises(ValidationError):
|
||||||
|
_unwrap(app_module.AppListApi().post)("tenant-1", SimpleNamespace(id="account-1"))
|
||||||
finally:
|
finally:
|
||||||
app_module.console_ns.payload = None
|
app_module.console_ns.payload = None
|
||||||
|
|
||||||
assert status == 201
|
app_service.create_app.assert_not_called()
|
||||||
assert response["id"] == "app-1"
|
|
||||||
assert response["bound_agent_id"] == "agent-1"
|
|
||||||
created_params = app_service.create_app.call_args.args[1]
|
|
||||||
assert created_params.mode == "agent"
|
|
||||||
|
|
||||||
|
|
||||||
def test_app_partial_serialization_uses_aliases(app_models):
|
def test_app_partial_serialization_uses_aliases(app_models):
|
||||||
|
|||||||
@ -1,9 +1,4 @@
|
|||||||
"""Regression tests for CreateAppPayload mode validation.
|
"""Regression tests for CreateAppPayload mode validation."""
|
||||||
|
|
||||||
The HTTP create-app payload must accept the new "agent" app mode; without it a
|
|
||||||
user cannot create an Agent App through POST /console/api/apps even though the
|
|
||||||
service layer (CreateAppParams) supports it.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
@ -14,12 +9,16 @@ from controllers.console.app.app import CreateAppPayload
|
|||||||
class TestCreateAppPayloadMode:
|
class TestCreateAppPayloadMode:
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"mode",
|
"mode",
|
||||||
["chat", "agent-chat", "agent", "advanced-chat", "workflow", "completion"],
|
["chat", "agent-chat", "advanced-chat", "workflow", "completion"],
|
||||||
)
|
)
|
||||||
def test_accepts_supported_modes(self, mode: str):
|
def test_accepts_supported_modes(self, mode: str):
|
||||||
payload = CreateAppPayload.model_validate({"name": "X", "mode": mode})
|
payload = CreateAppPayload.model_validate({"name": "X", "mode": mode})
|
||||||
assert payload.mode == mode
|
assert payload.mode == mode
|
||||||
|
|
||||||
|
def test_rejects_agent_mode(self):
|
||||||
|
with pytest.raises(ValidationError):
|
||||||
|
CreateAppPayload.model_validate({"name": "X", "mode": "agent"})
|
||||||
|
|
||||||
def test_rejects_unknown_mode(self):
|
def test_rejects_unknown_mode(self):
|
||||||
with pytest.raises(ValidationError):
|
with pytest.raises(ValidationError):
|
||||||
CreateAppPayload.model_validate({"name": "X", "mode": "not-a-mode"})
|
CreateAppPayload.model_validate({"name": "X", "mode": "not-a-mode"})
|
||||||
|
|||||||
@ -1037,6 +1037,31 @@ class TestAgentAppBackingAgent:
|
|||||||
|
|
||||||
assert service.get_app_backing_agent(tenant_id="tenant-1", app_id="app-x") is None
|
assert service.get_app_backing_agent(tenant_id="tenant-1", app_id="app-x") is None
|
||||||
|
|
||||||
|
def test_get_agent_app_model_resolves_app_backing_agent(self):
|
||||||
|
agent = Agent(
|
||||||
|
id="agent-1",
|
||||||
|
tenant_id="tenant-1",
|
||||||
|
name="Iris",
|
||||||
|
description="",
|
||||||
|
agent_kind=AgentKind.DIFY_AGENT,
|
||||||
|
scope=AgentScope.ROSTER,
|
||||||
|
source=AgentSource.AGENT_APP,
|
||||||
|
status=AgentStatus.ACTIVE,
|
||||||
|
app_id="app-1",
|
||||||
|
)
|
||||||
|
app = SimpleNamespace(id="app-1", mode="agent", status="normal")
|
||||||
|
session = FakeSession(scalar=[agent, app])
|
||||||
|
service = AgentRosterService(session)
|
||||||
|
|
||||||
|
assert service.get_agent_app_model(tenant_id="tenant-1", agent_id="agent-1") is app
|
||||||
|
|
||||||
|
def test_get_agent_app_model_rejects_unbound_agent(self):
|
||||||
|
session = FakeSession()
|
||||||
|
service = AgentRosterService(session)
|
||||||
|
|
||||||
|
with pytest.raises(roster_service.AgentNotFoundError):
|
||||||
|
service.get_agent_app_model(tenant_id="tenant-1", agent_id="agent-x")
|
||||||
|
|
||||||
|
|
||||||
class TestListWorkflowsReferencingAppAgent:
|
class TestListWorkflowsReferencingAppAgent:
|
||||||
def test_groups_bindings_by_workflow_app_and_sorts_by_name(self):
|
def test_groups_bindings_by_workflow_app_and_sorts_by_name(self):
|
||||||
|
|||||||
582
packages/contracts/generated/api/console/agent/orpc.gen.ts
Normal file
582
packages/contracts/generated/api/console/agent/orpc.gen.ts
Normal file
@ -0,0 +1,582 @@
|
|||||||
|
// This file is auto-generated by @hey-api/openapi-ts
|
||||||
|
|
||||||
|
import { oc } from '@orpc/contract'
|
||||||
|
import * as z from 'zod'
|
||||||
|
|
||||||
|
import {
|
||||||
|
zDeleteAgentByAgentIdFilesPath,
|
||||||
|
zDeleteAgentByAgentIdFilesQuery,
|
||||||
|
zDeleteAgentByAgentIdFilesResponse,
|
||||||
|
zDeleteAgentByAgentIdPath,
|
||||||
|
zDeleteAgentByAgentIdResponse,
|
||||||
|
zDeleteAgentByAgentIdSkillsBySlugPath,
|
||||||
|
zDeleteAgentByAgentIdSkillsBySlugResponse,
|
||||||
|
zGetAgentByAgentIdComposerCandidatesPath,
|
||||||
|
zGetAgentByAgentIdComposerCandidatesResponse,
|
||||||
|
zGetAgentByAgentIdComposerPath,
|
||||||
|
zGetAgentByAgentIdComposerResponse,
|
||||||
|
zGetAgentByAgentIdDriveFilesDownloadPath,
|
||||||
|
zGetAgentByAgentIdDriveFilesDownloadQuery,
|
||||||
|
zGetAgentByAgentIdDriveFilesDownloadResponse,
|
||||||
|
zGetAgentByAgentIdDriveFilesPath,
|
||||||
|
zGetAgentByAgentIdDriveFilesPreviewPath,
|
||||||
|
zGetAgentByAgentIdDriveFilesPreviewQuery,
|
||||||
|
zGetAgentByAgentIdDriveFilesPreviewResponse,
|
||||||
|
zGetAgentByAgentIdDriveFilesQuery,
|
||||||
|
zGetAgentByAgentIdDriveFilesResponse,
|
||||||
|
zGetAgentByAgentIdPath,
|
||||||
|
zGetAgentByAgentIdReferencingWorkflowsPath,
|
||||||
|
zGetAgentByAgentIdReferencingWorkflowsResponse,
|
||||||
|
zGetAgentByAgentIdResponse,
|
||||||
|
zGetAgentByAgentIdSandboxFilesPath,
|
||||||
|
zGetAgentByAgentIdSandboxFilesQuery,
|
||||||
|
zGetAgentByAgentIdSandboxFilesReadPath,
|
||||||
|
zGetAgentByAgentIdSandboxFilesReadQuery,
|
||||||
|
zGetAgentByAgentIdSandboxFilesReadResponse,
|
||||||
|
zGetAgentByAgentIdSandboxFilesResponse,
|
||||||
|
zGetAgentByAgentIdVersionsByVersionIdPath,
|
||||||
|
zGetAgentByAgentIdVersionsByVersionIdResponse,
|
||||||
|
zGetAgentByAgentIdVersionsPath,
|
||||||
|
zGetAgentByAgentIdVersionsResponse,
|
||||||
|
zGetAgentInviteOptionsQuery,
|
||||||
|
zGetAgentInviteOptionsResponse,
|
||||||
|
zGetAgentQuery,
|
||||||
|
zGetAgentResponse,
|
||||||
|
zPostAgentBody,
|
||||||
|
zPostAgentByAgentIdComposerValidateBody,
|
||||||
|
zPostAgentByAgentIdComposerValidatePath,
|
||||||
|
zPostAgentByAgentIdComposerValidateResponse,
|
||||||
|
zPostAgentByAgentIdFeaturesBody,
|
||||||
|
zPostAgentByAgentIdFeaturesPath,
|
||||||
|
zPostAgentByAgentIdFeaturesResponse,
|
||||||
|
zPostAgentByAgentIdFilesBody,
|
||||||
|
zPostAgentByAgentIdFilesPath,
|
||||||
|
zPostAgentByAgentIdFilesResponse,
|
||||||
|
zPostAgentByAgentIdSandboxFilesUploadBody,
|
||||||
|
zPostAgentByAgentIdSandboxFilesUploadPath,
|
||||||
|
zPostAgentByAgentIdSandboxFilesUploadResponse,
|
||||||
|
zPostAgentByAgentIdSkillsBySlugInferToolsPath,
|
||||||
|
zPostAgentByAgentIdSkillsBySlugInferToolsResponse,
|
||||||
|
zPostAgentByAgentIdSkillsStandardizePath,
|
||||||
|
zPostAgentByAgentIdSkillsStandardizeResponse,
|
||||||
|
zPostAgentByAgentIdSkillsUploadPath,
|
||||||
|
zPostAgentByAgentIdSkillsUploadResponse,
|
||||||
|
zPostAgentResponse,
|
||||||
|
zPutAgentByAgentIdBody,
|
||||||
|
zPutAgentByAgentIdComposerBody,
|
||||||
|
zPutAgentByAgentIdComposerPath,
|
||||||
|
zPutAgentByAgentIdComposerResponse,
|
||||||
|
zPutAgentByAgentIdPath,
|
||||||
|
zPutAgentByAgentIdResponse,
|
||||||
|
} from './zod.gen'
|
||||||
|
|
||||||
|
export const get = oc
|
||||||
|
.route({
|
||||||
|
inputStructure: 'detailed',
|
||||||
|
method: 'GET',
|
||||||
|
operationId: 'getAgentInviteOptions',
|
||||||
|
path: '/agent/invite-options',
|
||||||
|
tags: ['console'],
|
||||||
|
})
|
||||||
|
.input(z.object({ query: zGetAgentInviteOptionsQuery.optional() }))
|
||||||
|
.output(zGetAgentInviteOptionsResponse)
|
||||||
|
|
||||||
|
export const inviteOptions = {
|
||||||
|
get,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const get2 = oc
|
||||||
|
.route({
|
||||||
|
inputStructure: 'detailed',
|
||||||
|
method: 'GET',
|
||||||
|
operationId: 'getAgentByAgentIdComposerCandidates',
|
||||||
|
path: '/agent/{agent_id}/composer/candidates',
|
||||||
|
tags: ['console'],
|
||||||
|
})
|
||||||
|
.input(z.object({ params: zGetAgentByAgentIdComposerCandidatesPath }))
|
||||||
|
.output(zGetAgentByAgentIdComposerCandidatesResponse)
|
||||||
|
|
||||||
|
export const candidates = {
|
||||||
|
get: get2,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const post = oc
|
||||||
|
.route({
|
||||||
|
inputStructure: 'detailed',
|
||||||
|
method: 'POST',
|
||||||
|
operationId: 'postAgentByAgentIdComposerValidate',
|
||||||
|
path: '/agent/{agent_id}/composer/validate',
|
||||||
|
tags: ['console'],
|
||||||
|
})
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
body: zPostAgentByAgentIdComposerValidateBody,
|
||||||
|
params: zPostAgentByAgentIdComposerValidatePath,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.output(zPostAgentByAgentIdComposerValidateResponse)
|
||||||
|
|
||||||
|
export const validate = {
|
||||||
|
post,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const get3 = oc
|
||||||
|
.route({
|
||||||
|
inputStructure: 'detailed',
|
||||||
|
method: 'GET',
|
||||||
|
operationId: 'getAgentByAgentIdComposer',
|
||||||
|
path: '/agent/{agent_id}/composer',
|
||||||
|
tags: ['console'],
|
||||||
|
})
|
||||||
|
.input(z.object({ params: zGetAgentByAgentIdComposerPath }))
|
||||||
|
.output(zGetAgentByAgentIdComposerResponse)
|
||||||
|
|
||||||
|
export const put = oc
|
||||||
|
.route({
|
||||||
|
inputStructure: 'detailed',
|
||||||
|
method: 'PUT',
|
||||||
|
operationId: 'putAgentByAgentIdComposer',
|
||||||
|
path: '/agent/{agent_id}/composer',
|
||||||
|
tags: ['console'],
|
||||||
|
})
|
||||||
|
.input(z.object({ body: zPutAgentByAgentIdComposerBody, params: zPutAgentByAgentIdComposerPath }))
|
||||||
|
.output(zPutAgentByAgentIdComposerResponse)
|
||||||
|
|
||||||
|
export const composer = {
|
||||||
|
get: get3,
|
||||||
|
put,
|
||||||
|
candidates,
|
||||||
|
validate,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time-limited external signed URL for one Agent App drive value
|
||||||
|
*/
|
||||||
|
export const get4 = oc
|
||||||
|
.route({
|
||||||
|
description: 'Time-limited external signed URL for one Agent App drive value',
|
||||||
|
inputStructure: 'detailed',
|
||||||
|
method: 'GET',
|
||||||
|
operationId: 'getAgentByAgentIdDriveFilesDownload',
|
||||||
|
path: '/agent/{agent_id}/drive/files/download',
|
||||||
|
tags: ['console'],
|
||||||
|
})
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
params: zGetAgentByAgentIdDriveFilesDownloadPath,
|
||||||
|
query: zGetAgentByAgentIdDriveFilesDownloadQuery,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.output(zGetAgentByAgentIdDriveFilesDownloadResponse)
|
||||||
|
|
||||||
|
export const download = {
|
||||||
|
get: get4,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Truncated text preview of one Agent App drive value
|
||||||
|
*/
|
||||||
|
export const get5 = oc
|
||||||
|
.route({
|
||||||
|
description: 'Truncated text preview of one Agent App drive value',
|
||||||
|
inputStructure: 'detailed',
|
||||||
|
method: 'GET',
|
||||||
|
operationId: 'getAgentByAgentIdDriveFilesPreview',
|
||||||
|
path: '/agent/{agent_id}/drive/files/preview',
|
||||||
|
tags: ['console'],
|
||||||
|
})
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
params: zGetAgentByAgentIdDriveFilesPreviewPath,
|
||||||
|
query: zGetAgentByAgentIdDriveFilesPreviewQuery,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.output(zGetAgentByAgentIdDriveFilesPreviewResponse)
|
||||||
|
|
||||||
|
export const preview = {
|
||||||
|
get: get5,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List agent drive entries for an Agent App
|
||||||
|
*/
|
||||||
|
export const get6 = oc
|
||||||
|
.route({
|
||||||
|
description: 'List agent drive entries for an Agent App',
|
||||||
|
inputStructure: 'detailed',
|
||||||
|
method: 'GET',
|
||||||
|
operationId: 'getAgentByAgentIdDriveFiles',
|
||||||
|
path: '/agent/{agent_id}/drive/files',
|
||||||
|
tags: ['console'],
|
||||||
|
})
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
params: zGetAgentByAgentIdDriveFilesPath,
|
||||||
|
query: zGetAgentByAgentIdDriveFilesQuery.optional(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.output(zGetAgentByAgentIdDriveFilesResponse)
|
||||||
|
|
||||||
|
export const files = {
|
||||||
|
get: get6,
|
||||||
|
download,
|
||||||
|
preview,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const drive = {
|
||||||
|
files,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an Agent App's presentation features (opener, follow-up, citations, ...)
|
||||||
|
*/
|
||||||
|
export const post2 = oc
|
||||||
|
.route({
|
||||||
|
description: 'Update an Agent App\'s presentation features (opener, follow-up, citations, ...)',
|
||||||
|
inputStructure: 'detailed',
|
||||||
|
method: 'POST',
|
||||||
|
operationId: 'postAgentByAgentIdFeatures',
|
||||||
|
path: '/agent/{agent_id}/features',
|
||||||
|
tags: ['console'],
|
||||||
|
})
|
||||||
|
.input(
|
||||||
|
z.object({ body: zPostAgentByAgentIdFeaturesBody, params: zPostAgentByAgentIdFeaturesPath }),
|
||||||
|
)
|
||||||
|
.output(zPostAgentByAgentIdFeaturesResponse)
|
||||||
|
|
||||||
|
export const features = {
|
||||||
|
post: post2,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete one Agent App drive file by key
|
||||||
|
*/
|
||||||
|
export const delete_ = oc
|
||||||
|
.route({
|
||||||
|
description: 'Delete one Agent App drive file by key',
|
||||||
|
inputStructure: 'detailed',
|
||||||
|
method: 'DELETE',
|
||||||
|
operationId: 'deleteAgentByAgentIdFiles',
|
||||||
|
path: '/agent/{agent_id}/files',
|
||||||
|
tags: ['console'],
|
||||||
|
})
|
||||||
|
.input(
|
||||||
|
z.object({ params: zDeleteAgentByAgentIdFilesPath, query: zDeleteAgentByAgentIdFilesQuery }),
|
||||||
|
)
|
||||||
|
.output(zDeleteAgentByAgentIdFilesResponse)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Commit an uploaded file into the Agent App drive under files/<name>
|
||||||
|
*/
|
||||||
|
export const post3 = oc
|
||||||
|
.route({
|
||||||
|
description: 'Commit an uploaded file into the Agent App drive under files/<name>',
|
||||||
|
inputStructure: 'detailed',
|
||||||
|
method: 'POST',
|
||||||
|
operationId: 'postAgentByAgentIdFiles',
|
||||||
|
path: '/agent/{agent_id}/files',
|
||||||
|
successStatus: 201,
|
||||||
|
tags: ['console'],
|
||||||
|
})
|
||||||
|
.input(z.object({ body: zPostAgentByAgentIdFilesBody, params: zPostAgentByAgentIdFilesPath }))
|
||||||
|
.output(zPostAgentByAgentIdFilesResponse)
|
||||||
|
|
||||||
|
export const files2 = {
|
||||||
|
delete: delete_,
|
||||||
|
post: post3,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List workflow apps that reference this Agent App's bound Agent (read-only)
|
||||||
|
*/
|
||||||
|
export const get7 = oc
|
||||||
|
.route({
|
||||||
|
description: 'List workflow apps that reference this Agent App\'s bound Agent (read-only)',
|
||||||
|
inputStructure: 'detailed',
|
||||||
|
method: 'GET',
|
||||||
|
operationId: 'getAgentByAgentIdReferencingWorkflows',
|
||||||
|
path: '/agent/{agent_id}/referencing-workflows',
|
||||||
|
tags: ['console'],
|
||||||
|
})
|
||||||
|
.input(z.object({ params: zGetAgentByAgentIdReferencingWorkflowsPath }))
|
||||||
|
.output(zGetAgentByAgentIdReferencingWorkflowsResponse)
|
||||||
|
|
||||||
|
export const referencingWorkflows = {
|
||||||
|
get: get7,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a text/binary preview file in an Agent App conversation sandbox
|
||||||
|
*/
|
||||||
|
export const get8 = oc
|
||||||
|
.route({
|
||||||
|
description: 'Read a text/binary preview file in an Agent App conversation sandbox',
|
||||||
|
inputStructure: 'detailed',
|
||||||
|
method: 'GET',
|
||||||
|
operationId: 'getAgentByAgentIdSandboxFilesRead',
|
||||||
|
path: '/agent/{agent_id}/sandbox/files/read',
|
||||||
|
tags: ['console'],
|
||||||
|
})
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
params: zGetAgentByAgentIdSandboxFilesReadPath,
|
||||||
|
query: zGetAgentByAgentIdSandboxFilesReadQuery,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.output(zGetAgentByAgentIdSandboxFilesReadResponse)
|
||||||
|
|
||||||
|
export const read = {
|
||||||
|
get: get8,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload one Agent App sandbox file as a Dify ToolFile mapping
|
||||||
|
*/
|
||||||
|
export const post4 = oc
|
||||||
|
.route({
|
||||||
|
description: 'Upload one Agent App sandbox file as a Dify ToolFile mapping',
|
||||||
|
inputStructure: 'detailed',
|
||||||
|
method: 'POST',
|
||||||
|
operationId: 'postAgentByAgentIdSandboxFilesUpload',
|
||||||
|
path: '/agent/{agent_id}/sandbox/files/upload',
|
||||||
|
tags: ['console'],
|
||||||
|
})
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
body: zPostAgentByAgentIdSandboxFilesUploadBody,
|
||||||
|
params: zPostAgentByAgentIdSandboxFilesUploadPath,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.output(zPostAgentByAgentIdSandboxFilesUploadResponse)
|
||||||
|
|
||||||
|
export const upload = {
|
||||||
|
post: post4,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List a directory in an Agent App conversation sandbox
|
||||||
|
*/
|
||||||
|
export const get9 = oc
|
||||||
|
.route({
|
||||||
|
description: 'List a directory in an Agent App conversation sandbox',
|
||||||
|
inputStructure: 'detailed',
|
||||||
|
method: 'GET',
|
||||||
|
operationId: 'getAgentByAgentIdSandboxFiles',
|
||||||
|
path: '/agent/{agent_id}/sandbox/files',
|
||||||
|
tags: ['console'],
|
||||||
|
})
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
params: zGetAgentByAgentIdSandboxFilesPath,
|
||||||
|
query: zGetAgentByAgentIdSandboxFilesQuery,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.output(zGetAgentByAgentIdSandboxFilesResponse)
|
||||||
|
|
||||||
|
export const files3 = {
|
||||||
|
get: get9,
|
||||||
|
read,
|
||||||
|
upload,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sandbox = {
|
||||||
|
files: files3,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate + standardize a Skill into an Agent App drive
|
||||||
|
*/
|
||||||
|
export const post5 = oc
|
||||||
|
.route({
|
||||||
|
description: 'Validate + standardize a Skill into an Agent App drive',
|
||||||
|
inputStructure: 'detailed',
|
||||||
|
method: 'POST',
|
||||||
|
operationId: 'postAgentByAgentIdSkillsStandardize',
|
||||||
|
path: '/agent/{agent_id}/skills/standardize',
|
||||||
|
successStatus: 201,
|
||||||
|
tags: ['console'],
|
||||||
|
})
|
||||||
|
.input(z.object({ params: zPostAgentByAgentIdSkillsStandardizePath }))
|
||||||
|
.output(zPostAgentByAgentIdSkillsStandardizeResponse)
|
||||||
|
|
||||||
|
export const standardize = {
|
||||||
|
post: post5,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload + validate a Skill package for an Agent App
|
||||||
|
*/
|
||||||
|
export const post6 = oc
|
||||||
|
.route({
|
||||||
|
description: 'Upload + validate a Skill package for an Agent App',
|
||||||
|
inputStructure: 'detailed',
|
||||||
|
method: 'POST',
|
||||||
|
operationId: 'postAgentByAgentIdSkillsUpload',
|
||||||
|
path: '/agent/{agent_id}/skills/upload',
|
||||||
|
successStatus: 201,
|
||||||
|
tags: ['console'],
|
||||||
|
})
|
||||||
|
.input(z.object({ params: zPostAgentByAgentIdSkillsUploadPath }))
|
||||||
|
.output(zPostAgentByAgentIdSkillsUploadResponse)
|
||||||
|
|
||||||
|
export const upload2 = {
|
||||||
|
post: post6,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Infer CLI tool + ENV suggestions from a standardized Agent App skill
|
||||||
|
*/
|
||||||
|
export const post7 = oc
|
||||||
|
.route({
|
||||||
|
description: 'Infer CLI tool + ENV suggestions from a standardized Agent App skill',
|
||||||
|
inputStructure: 'detailed',
|
||||||
|
method: 'POST',
|
||||||
|
operationId: 'postAgentByAgentIdSkillsBySlugInferTools',
|
||||||
|
path: '/agent/{agent_id}/skills/{slug}/infer-tools',
|
||||||
|
tags: ['console'],
|
||||||
|
})
|
||||||
|
.input(z.object({ params: zPostAgentByAgentIdSkillsBySlugInferToolsPath }))
|
||||||
|
.output(zPostAgentByAgentIdSkillsBySlugInferToolsResponse)
|
||||||
|
|
||||||
|
export const inferTools = {
|
||||||
|
post: post7,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a standardized skill from an Agent App drive
|
||||||
|
*/
|
||||||
|
export const delete2 = oc
|
||||||
|
.route({
|
||||||
|
description: 'Delete a standardized skill from an Agent App drive',
|
||||||
|
inputStructure: 'detailed',
|
||||||
|
method: 'DELETE',
|
||||||
|
operationId: 'deleteAgentByAgentIdSkillsBySlug',
|
||||||
|
path: '/agent/{agent_id}/skills/{slug}',
|
||||||
|
tags: ['console'],
|
||||||
|
})
|
||||||
|
.input(z.object({ params: zDeleteAgentByAgentIdSkillsBySlugPath }))
|
||||||
|
.output(zDeleteAgentByAgentIdSkillsBySlugResponse)
|
||||||
|
|
||||||
|
export const bySlug = {
|
||||||
|
delete: delete2,
|
||||||
|
inferTools,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const skills = {
|
||||||
|
standardize,
|
||||||
|
upload: upload2,
|
||||||
|
bySlug,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const get10 = oc
|
||||||
|
.route({
|
||||||
|
inputStructure: 'detailed',
|
||||||
|
method: 'GET',
|
||||||
|
operationId: 'getAgentByAgentIdVersionsByVersionId',
|
||||||
|
path: '/agent/{agent_id}/versions/{version_id}',
|
||||||
|
tags: ['console'],
|
||||||
|
})
|
||||||
|
.input(z.object({ params: zGetAgentByAgentIdVersionsByVersionIdPath }))
|
||||||
|
.output(zGetAgentByAgentIdVersionsByVersionIdResponse)
|
||||||
|
|
||||||
|
export const byVersionId = {
|
||||||
|
get: get10,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const get11 = oc
|
||||||
|
.route({
|
||||||
|
inputStructure: 'detailed',
|
||||||
|
method: 'GET',
|
||||||
|
operationId: 'getAgentByAgentIdVersions',
|
||||||
|
path: '/agent/{agent_id}/versions',
|
||||||
|
tags: ['console'],
|
||||||
|
})
|
||||||
|
.input(z.object({ params: zGetAgentByAgentIdVersionsPath }))
|
||||||
|
.output(zGetAgentByAgentIdVersionsResponse)
|
||||||
|
|
||||||
|
export const versions = {
|
||||||
|
get: get11,
|
||||||
|
byVersionId,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const delete3 = oc
|
||||||
|
.route({
|
||||||
|
inputStructure: 'detailed',
|
||||||
|
method: 'DELETE',
|
||||||
|
operationId: 'deleteAgentByAgentId',
|
||||||
|
path: '/agent/{agent_id}',
|
||||||
|
successStatus: 204,
|
||||||
|
tags: ['console'],
|
||||||
|
})
|
||||||
|
.input(z.object({ params: zDeleteAgentByAgentIdPath }))
|
||||||
|
.output(zDeleteAgentByAgentIdResponse)
|
||||||
|
|
||||||
|
export const get12 = oc
|
||||||
|
.route({
|
||||||
|
inputStructure: 'detailed',
|
||||||
|
method: 'GET',
|
||||||
|
operationId: 'getAgentByAgentId',
|
||||||
|
path: '/agent/{agent_id}',
|
||||||
|
tags: ['console'],
|
||||||
|
})
|
||||||
|
.input(z.object({ params: zGetAgentByAgentIdPath }))
|
||||||
|
.output(zGetAgentByAgentIdResponse)
|
||||||
|
|
||||||
|
export const put2 = oc
|
||||||
|
.route({
|
||||||
|
inputStructure: 'detailed',
|
||||||
|
method: 'PUT',
|
||||||
|
operationId: 'putAgentByAgentId',
|
||||||
|
path: '/agent/{agent_id}',
|
||||||
|
tags: ['console'],
|
||||||
|
})
|
||||||
|
.input(z.object({ body: zPutAgentByAgentIdBody, params: zPutAgentByAgentIdPath }))
|
||||||
|
.output(zPutAgentByAgentIdResponse)
|
||||||
|
|
||||||
|
export const byAgentId = {
|
||||||
|
delete: delete3,
|
||||||
|
get: get12,
|
||||||
|
put: put2,
|
||||||
|
composer,
|
||||||
|
drive,
|
||||||
|
features,
|
||||||
|
files: files2,
|
||||||
|
referencingWorkflows,
|
||||||
|
sandbox,
|
||||||
|
skills,
|
||||||
|
versions,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const get13 = oc
|
||||||
|
.route({
|
||||||
|
inputStructure: 'detailed',
|
||||||
|
method: 'GET',
|
||||||
|
operationId: 'getAgent',
|
||||||
|
path: '/agent',
|
||||||
|
tags: ['console'],
|
||||||
|
})
|
||||||
|
.input(z.object({ query: zGetAgentQuery.optional() }))
|
||||||
|
.output(zGetAgentResponse)
|
||||||
|
|
||||||
|
export const post8 = oc
|
||||||
|
.route({
|
||||||
|
inputStructure: 'detailed',
|
||||||
|
method: 'POST',
|
||||||
|
operationId: 'postAgent',
|
||||||
|
path: '/agent',
|
||||||
|
successStatus: 201,
|
||||||
|
tags: ['console'],
|
||||||
|
})
|
||||||
|
.input(z.object({ body: zPostAgentBody }))
|
||||||
|
.output(zPostAgentResponse)
|
||||||
|
|
||||||
|
export const agent = {
|
||||||
|
get: get13,
|
||||||
|
post: post8,
|
||||||
|
inviteOptions,
|
||||||
|
byAgentId,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const contract = {
|
||||||
|
agent,
|
||||||
|
}
|
||||||
1545
packages/contracts/generated/api/console/agent/types.gen.ts
Normal file
1545
packages/contracts/generated/api/console/agent/types.gen.ts
Normal file
File diff suppressed because it is too large
Load Diff
1817
packages/contracts/generated/api/console/agent/zod.gen.ts
Normal file
1817
packages/contracts/generated/api/console/agent/zod.gen.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,100 +0,0 @@
|
|||||||
// This file is auto-generated by @hey-api/openapi-ts
|
|
||||||
|
|
||||||
import { oc } from '@orpc/contract'
|
|
||||||
import * as z from 'zod'
|
|
||||||
|
|
||||||
import {
|
|
||||||
zGetAgentsByAgentIdPath,
|
|
||||||
zGetAgentsByAgentIdResponse,
|
|
||||||
zGetAgentsByAgentIdVersionsByVersionIdPath,
|
|
||||||
zGetAgentsByAgentIdVersionsByVersionIdResponse,
|
|
||||||
zGetAgentsByAgentIdVersionsPath,
|
|
||||||
zGetAgentsByAgentIdVersionsResponse,
|
|
||||||
zGetAgentsInviteOptionsQuery,
|
|
||||||
zGetAgentsInviteOptionsResponse,
|
|
||||||
zGetAgentsQuery,
|
|
||||||
zGetAgentsResponse,
|
|
||||||
} from './zod.gen'
|
|
||||||
|
|
||||||
export const get = oc
|
|
||||||
.route({
|
|
||||||
inputStructure: 'detailed',
|
|
||||||
method: 'GET',
|
|
||||||
operationId: 'getAgentsInviteOptions',
|
|
||||||
path: '/agents/invite-options',
|
|
||||||
tags: ['console'],
|
|
||||||
})
|
|
||||||
.input(z.object({ query: zGetAgentsInviteOptionsQuery.optional() }))
|
|
||||||
.output(zGetAgentsInviteOptionsResponse)
|
|
||||||
|
|
||||||
export const inviteOptions = {
|
|
||||||
get,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const get2 = oc
|
|
||||||
.route({
|
|
||||||
inputStructure: 'detailed',
|
|
||||||
method: 'GET',
|
|
||||||
operationId: 'getAgentsByAgentIdVersionsByVersionId',
|
|
||||||
path: '/agents/{agent_id}/versions/{version_id}',
|
|
||||||
tags: ['console'],
|
|
||||||
})
|
|
||||||
.input(z.object({ params: zGetAgentsByAgentIdVersionsByVersionIdPath }))
|
|
||||||
.output(zGetAgentsByAgentIdVersionsByVersionIdResponse)
|
|
||||||
|
|
||||||
export const byVersionId = {
|
|
||||||
get: get2,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const get3 = oc
|
|
||||||
.route({
|
|
||||||
inputStructure: 'detailed',
|
|
||||||
method: 'GET',
|
|
||||||
operationId: 'getAgentsByAgentIdVersions',
|
|
||||||
path: '/agents/{agent_id}/versions',
|
|
||||||
tags: ['console'],
|
|
||||||
})
|
|
||||||
.input(z.object({ params: zGetAgentsByAgentIdVersionsPath }))
|
|
||||||
.output(zGetAgentsByAgentIdVersionsResponse)
|
|
||||||
|
|
||||||
export const versions = {
|
|
||||||
get: get3,
|
|
||||||
byVersionId,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const get4 = oc
|
|
||||||
.route({
|
|
||||||
inputStructure: 'detailed',
|
|
||||||
method: 'GET',
|
|
||||||
operationId: 'getAgentsByAgentId',
|
|
||||||
path: '/agents/{agent_id}',
|
|
||||||
tags: ['console'],
|
|
||||||
})
|
|
||||||
.input(z.object({ params: zGetAgentsByAgentIdPath }))
|
|
||||||
.output(zGetAgentsByAgentIdResponse)
|
|
||||||
|
|
||||||
export const byAgentId = {
|
|
||||||
get: get4,
|
|
||||||
versions,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const get5 = oc
|
|
||||||
.route({
|
|
||||||
inputStructure: 'detailed',
|
|
||||||
method: 'GET',
|
|
||||||
operationId: 'getAgents',
|
|
||||||
path: '/agents',
|
|
||||||
tags: ['console'],
|
|
||||||
})
|
|
||||||
.input(z.object({ query: zGetAgentsQuery.optional() }))
|
|
||||||
.output(zGetAgentsResponse)
|
|
||||||
|
|
||||||
export const agents = {
|
|
||||||
get: get5,
|
|
||||||
inviteOptions,
|
|
||||||
byAgentId,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const contract = {
|
|
||||||
agents,
|
|
||||||
}
|
|
||||||
@ -1,587 +0,0 @@
|
|||||||
// This file is auto-generated by @hey-api/openapi-ts
|
|
||||||
|
|
||||||
export type ClientOptions = {
|
|
||||||
baseUrl: `${string}://${string}/console/api` | (string & {})
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentRosterListResponse = {
|
|
||||||
data: Array<AgentRosterResponse>
|
|
||||||
has_more: boolean
|
|
||||||
limit: number
|
|
||||||
page: number
|
|
||||||
total: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentInviteOptionsResponse = {
|
|
||||||
data: Array<AgentInviteOptionResponse>
|
|
||||||
has_more: boolean
|
|
||||||
limit: number
|
|
||||||
page: number
|
|
||||||
total: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentRosterResponse = {
|
|
||||||
active_config_snapshot?: AgentConfigSnapshotSummaryResponse | null
|
|
||||||
active_config_snapshot_id?: string | null
|
|
||||||
agent_kind: AgentKind
|
|
||||||
app_id?: string | null
|
|
||||||
archived_at?: number | null
|
|
||||||
archived_by?: string | null
|
|
||||||
created_at?: number | null
|
|
||||||
created_by?: string | null
|
|
||||||
description: string
|
|
||||||
icon?: string | null
|
|
||||||
icon_background?: string | null
|
|
||||||
icon_type?: AgentIconType | null
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
published_node_reference_count?: number
|
|
||||||
published_reference_count?: number
|
|
||||||
published_references?: Array<AgentPublishedReferenceResponse>
|
|
||||||
role?: string
|
|
||||||
scope: AgentScope
|
|
||||||
source: AgentSource
|
|
||||||
status: AgentStatus
|
|
||||||
updated_at?: number | null
|
|
||||||
updated_by?: string | null
|
|
||||||
workflow_id?: string | null
|
|
||||||
workflow_node_id?: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentConfigSnapshotListResponse = {
|
|
||||||
data: Array<AgentConfigSnapshotSummaryResponse>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentConfigSnapshotDetailResponse = {
|
|
||||||
agent_id?: string | null
|
|
||||||
config_snapshot: AgentSoulConfig
|
|
||||||
created_at?: number | null
|
|
||||||
created_by?: string | null
|
|
||||||
id: string
|
|
||||||
revisions?: Array<AgentConfigRevisionResponse>
|
|
||||||
summary?: string | null
|
|
||||||
version: number
|
|
||||||
version_note?: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentInviteOptionResponse = {
|
|
||||||
active_config_snapshot?: AgentConfigSnapshotSummaryResponse | null
|
|
||||||
active_config_snapshot_id?: string | null
|
|
||||||
agent_kind: AgentKind
|
|
||||||
app_id?: string | null
|
|
||||||
archived_at?: number | null
|
|
||||||
archived_by?: string | null
|
|
||||||
created_at?: number | null
|
|
||||||
created_by?: string | null
|
|
||||||
description: string
|
|
||||||
existing_node_ids?: Array<string>
|
|
||||||
icon?: string | null
|
|
||||||
icon_background?: string | null
|
|
||||||
icon_type?: AgentIconType | null
|
|
||||||
id: string
|
|
||||||
in_current_workflow_count?: number
|
|
||||||
is_in_current_workflow?: boolean
|
|
||||||
name: string
|
|
||||||
published_node_reference_count?: number
|
|
||||||
published_reference_count?: number
|
|
||||||
published_references?: Array<AgentPublishedReferenceResponse>
|
|
||||||
role?: string
|
|
||||||
scope: AgentScope
|
|
||||||
source: AgentSource
|
|
||||||
status: AgentStatus
|
|
||||||
updated_at?: number | null
|
|
||||||
updated_by?: string | null
|
|
||||||
workflow_id?: string | null
|
|
||||||
workflow_node_id?: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentConfigSnapshotSummaryResponse = {
|
|
||||||
agent_id?: string | null
|
|
||||||
created_at?: number | null
|
|
||||||
created_by?: string | null
|
|
||||||
id: string
|
|
||||||
summary?: string | null
|
|
||||||
version: number
|
|
||||||
version_note?: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentKind = 'dify_agent'
|
|
||||||
|
|
||||||
export type AgentIconType = 'emoji' | 'image' | 'link'
|
|
||||||
|
|
||||||
export type AgentPublishedReferenceResponse = {
|
|
||||||
app_id: string
|
|
||||||
app_mode: string
|
|
||||||
app_name: string
|
|
||||||
node_ids?: Array<string>
|
|
||||||
workflow_id: string
|
|
||||||
workflow_version: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentScope = 'roster' | 'workflow_only'
|
|
||||||
|
|
||||||
export type AgentSource = 'agent_app' | 'imported' | 'roster' | 'system' | 'workflow'
|
|
||||||
|
|
||||||
export type AgentStatus = 'active' | 'archived'
|
|
||||||
|
|
||||||
export type AgentSoulConfig = {
|
|
||||||
app_features?: AgentSoulAppFeaturesConfig
|
|
||||||
app_variables?: Array<AppVariableConfig>
|
|
||||||
env?: AgentSoulEnvConfig
|
|
||||||
human?: AgentSoulHumanConfig
|
|
||||||
knowledge?: AgentSoulKnowledgeConfig
|
|
||||||
memory?: AgentSoulMemoryConfig
|
|
||||||
misc_legacy?: AgentSoulAppFeaturesConfig
|
|
||||||
model?: AgentSoulModelConfig | null
|
|
||||||
prompt?: AgentSoulPromptConfig
|
|
||||||
sandbox?: AgentSoulSandboxConfig
|
|
||||||
schema_version?: number
|
|
||||||
skills_files?: AgentSoulSkillsFilesConfig
|
|
||||||
tools?: AgentSoulToolsConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentConfigRevisionResponse = {
|
|
||||||
created_at?: number | null
|
|
||||||
created_by?: string | null
|
|
||||||
current_snapshot_id: string
|
|
||||||
id: string
|
|
||||||
operation: AgentConfigRevisionOperation
|
|
||||||
previous_snapshot_id?: string | null
|
|
||||||
revision: number
|
|
||||||
summary?: string | null
|
|
||||||
version_note?: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentSoulAppFeaturesConfig = {
|
|
||||||
opening_statement?: string | null
|
|
||||||
retriever_resource?: AgentFeatureToggleConfig | null
|
|
||||||
sensitive_word_avoidance?: AgentSensitiveWordAvoidanceFeatureConfig | null
|
|
||||||
speech_to_text?: AgentFeatureToggleConfig | null
|
|
||||||
suggested_questions?: Array<string> | null
|
|
||||||
suggested_questions_after_answer?: AgentSuggestedQuestionsAfterAnswerFeatureConfig | null
|
|
||||||
text_to_speech?: AgentTextToSpeechFeatureConfig | null
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AppVariableConfig = {
|
|
||||||
default?: unknown
|
|
||||||
name: string
|
|
||||||
required?: boolean
|
|
||||||
type: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentSoulEnvConfig = {
|
|
||||||
secret_refs?: Array<AgentSecretRefConfig>
|
|
||||||
variables?: Array<AgentEnvVariableConfig>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentSoulHumanConfig = {
|
|
||||||
contacts?: Array<AgentHumanContactConfig>
|
|
||||||
tools?: Array<AgentHumanToolConfig>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentSoulKnowledgeConfig = {
|
|
||||||
datasets?: Array<AgentKnowledgeDatasetConfig>
|
|
||||||
query_config?: AgentKnowledgeQueryConfig
|
|
||||||
query_mode?: AgentKnowledgeQueryMode | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentSoulMemoryConfig = {
|
|
||||||
artifacts?: Array<AgentMemoryArtifactConfig>
|
|
||||||
budget?: string | null
|
|
||||||
scope?: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentSoulModelConfig = {
|
|
||||||
credential_ref?: AgentSoulModelCredentialRef | null
|
|
||||||
model: string
|
|
||||||
model_provider: string
|
|
||||||
model_settings?: AgentSoulModelSettings
|
|
||||||
plugin_id: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentSoulPromptConfig = {
|
|
||||||
system_prompt?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentSoulSandboxConfig = {
|
|
||||||
config?: AgentSandboxProviderConfig
|
|
||||||
provider?: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentSoulSkillsFilesConfig = {
|
|
||||||
files?: Array<AgentFileRefConfig>
|
|
||||||
skills?: Array<AgentSkillRefConfig>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentSoulToolsConfig = {
|
|
||||||
cli_tools?: Array<AgentCliToolConfig>
|
|
||||||
dify_tools?: Array<AgentSoulDifyToolConfig>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentConfigRevisionOperation
|
|
||||||
= | 'create_version'
|
|
||||||
| 'save_current_version'
|
|
||||||
| 'save_new_agent'
|
|
||||||
| 'save_new_version'
|
|
||||||
| 'save_to_roster'
|
|
||||||
|
|
||||||
export type AgentFeatureToggleConfig = {
|
|
||||||
enabled?: boolean
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentSensitiveWordAvoidanceFeatureConfig = {
|
|
||||||
config?: AgentModerationProviderConfig | null
|
|
||||||
enabled?: boolean
|
|
||||||
type?: string | null
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentSuggestedQuestionsAfterAnswerFeatureConfig = {
|
|
||||||
enabled?: boolean
|
|
||||||
model?: AgentSoulModelConfig | null
|
|
||||||
prompt?: string | null
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentTextToSpeechFeatureConfig = {
|
|
||||||
autoPlay?: string | null
|
|
||||||
enabled?: boolean
|
|
||||||
language?: string | null
|
|
||||||
voice?: string | null
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentSecretRefConfig = {
|
|
||||||
credential_id?: string | null
|
|
||||||
env_name?: string | null
|
|
||||||
id?: string | null
|
|
||||||
key?: string | null
|
|
||||||
name?: string | null
|
|
||||||
permission?: AgentPermissionConfig | null
|
|
||||||
permission_status?: string | null
|
|
||||||
provider?: string | null
|
|
||||||
provider_credential_id?: string | null
|
|
||||||
ref?: string | null
|
|
||||||
type?: string | null
|
|
||||||
variable?: string | null
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentEnvVariableConfig = {
|
|
||||||
default?:
|
|
||||||
| string
|
|
||||||
| number
|
|
||||||
| number
|
|
||||||
| boolean
|
|
||||||
| Array<string>
|
|
||||||
| Array<number>
|
|
||||||
| Array<number>
|
|
||||||
| Array<boolean>
|
|
||||||
| null
|
|
||||||
env_name?: string | null
|
|
||||||
key?: string | null
|
|
||||||
name?: string | null
|
|
||||||
required?: boolean
|
|
||||||
type?: string | null
|
|
||||||
value?:
|
|
||||||
| string
|
|
||||||
| number
|
|
||||||
| number
|
|
||||||
| boolean
|
|
||||||
| Array<string>
|
|
||||||
| Array<number>
|
|
||||||
| Array<number>
|
|
||||||
| Array<boolean>
|
|
||||||
| null
|
|
||||||
variable?: string | null
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentHumanContactConfig = {
|
|
||||||
channel?: string | null
|
|
||||||
contact_id?: string | null
|
|
||||||
contact_method?: string | null
|
|
||||||
email?: string | null
|
|
||||||
human_id?: string | null
|
|
||||||
id?: string | null
|
|
||||||
method?: string | null
|
|
||||||
name?: string | null
|
|
||||||
tenant_id?: string | null
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentHumanToolConfig = {
|
|
||||||
description?: string | null
|
|
||||||
enabled?: boolean
|
|
||||||
name?: string | null
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentKnowledgeDatasetConfig = {
|
|
||||||
description?: string | null
|
|
||||||
id?: string | null
|
|
||||||
name?: string | null
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentKnowledgeQueryConfig = {
|
|
||||||
query?: string | null
|
|
||||||
score_threshold?: number | null
|
|
||||||
score_threshold_enabled?: boolean | null
|
|
||||||
top_k?: number | null
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentKnowledgeQueryMode = 'generated_query' | 'user_query'
|
|
||||||
|
|
||||||
export type AgentMemoryArtifactConfig = {
|
|
||||||
id?: string | null
|
|
||||||
name?: string | null
|
|
||||||
type?: string | null
|
|
||||||
url?: string | null
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentSoulModelCredentialRef = {
|
|
||||||
id?: string | null
|
|
||||||
provider?: string | null
|
|
||||||
type: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentSoulModelSettings = {
|
|
||||||
frequency_penalty?: number | null
|
|
||||||
max_tokens?: number | null
|
|
||||||
presence_penalty?: number | null
|
|
||||||
response_format?: AgentModelResponseFormatConfig | null
|
|
||||||
stop?: Array<string> | null
|
|
||||||
temperature?: number | null
|
|
||||||
top_p?: number | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentSandboxProviderConfig = {
|
|
||||||
cpu?: number | null
|
|
||||||
env?: Array<AgentEnvVariableConfig>
|
|
||||||
image?: string | null
|
|
||||||
working_dir?: string | null
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentFileRefConfig = {
|
|
||||||
drive_key?: string | null
|
|
||||||
file_id?: string | null
|
|
||||||
id?: string | null
|
|
||||||
name?: string | null
|
|
||||||
reference?: string | null
|
|
||||||
remote_url?: string | null
|
|
||||||
tenant_id?: string | null
|
|
||||||
transfer_method?: string | null
|
|
||||||
type?: string | null
|
|
||||||
upload_file_id?: string | null
|
|
||||||
url?: string | null
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentSkillRefConfig = {
|
|
||||||
description?: string | null
|
|
||||||
file_id?: string | null
|
|
||||||
full_archive_file_id?: string | null
|
|
||||||
full_archive_key?: string | null
|
|
||||||
id?: string | null
|
|
||||||
manifest_files?: Array<string> | null
|
|
||||||
name?: string | null
|
|
||||||
path?: string | null
|
|
||||||
skill_md_file_id?: string | null
|
|
||||||
skill_md_key?: string | null
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentCliToolConfig = {
|
|
||||||
approved?: boolean
|
|
||||||
authorization_status?: AgentCliToolAuthorizationStatus | null
|
|
||||||
command?: string | null
|
|
||||||
dangerous?: boolean
|
|
||||||
dangerous_accepted?: boolean
|
|
||||||
dangerous_acknowledged?: boolean
|
|
||||||
dangerous_command?: boolean
|
|
||||||
description?: string | null
|
|
||||||
enabled?: boolean
|
|
||||||
env?: AgentCliToolEnvConfig
|
|
||||||
id?: string | null
|
|
||||||
inferred_from?: string | null
|
|
||||||
install?: string | null
|
|
||||||
install_command?: string | null
|
|
||||||
install_commands?: Array<string>
|
|
||||||
invoke_metadata?: {
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
label?: string | null
|
|
||||||
name?: string | null
|
|
||||||
permission?: AgentPermissionConfig | null
|
|
||||||
pre_authorized?: boolean | null
|
|
||||||
requires_confirmation?: boolean
|
|
||||||
risk_accepted?: boolean
|
|
||||||
risk_level?: AgentCliToolRiskLevel | null
|
|
||||||
setup_command?: string | null
|
|
||||||
tool_name?: string | null
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentSoulDifyToolConfig = {
|
|
||||||
credential_ref?: AgentSoulDifyToolCredentialRef | null
|
|
||||||
credential_type?: 'api-key' | 'oauth2' | 'unauthorized'
|
|
||||||
description?: string | null
|
|
||||||
enabled?: boolean
|
|
||||||
name?: string | null
|
|
||||||
plugin_id?: string | null
|
|
||||||
provider?: string | null
|
|
||||||
provider_id?: string | null
|
|
||||||
provider_type?: string
|
|
||||||
runtime_parameters?: {
|
|
||||||
[key: string]:
|
|
||||||
| string
|
|
||||||
| number
|
|
||||||
| number
|
|
||||||
| boolean
|
|
||||||
| Array<string>
|
|
||||||
| Array<number>
|
|
||||||
| Array<number>
|
|
||||||
| Array<boolean>
|
|
||||||
| null
|
|
||||||
}
|
|
||||||
tool_name?: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentModerationProviderConfig = {
|
|
||||||
api_based_extension_id?: string | null
|
|
||||||
inputs_config?: AgentModerationIoConfig | null
|
|
||||||
keywords?: string | null
|
|
||||||
outputs_config?: AgentModerationIoConfig | null
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentPermissionConfig = {
|
|
||||||
allowed?: boolean | null
|
|
||||||
state?: string | null
|
|
||||||
status?: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentModelResponseFormatConfig = {
|
|
||||||
type?: string | null
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentCliToolAuthorizationStatus
|
|
||||||
= | 'allowed'
|
|
||||||
| 'authorized'
|
|
||||||
| 'denied'
|
|
||||||
| 'forbidden'
|
|
||||||
| 'not_required'
|
|
||||||
| 'pending'
|
|
||||||
| 'pre_authorized'
|
|
||||||
| 'unauthorized'
|
|
||||||
|
|
||||||
export type AgentCliToolEnvConfig = {
|
|
||||||
secret_refs?: Array<AgentSecretRefConfig>
|
|
||||||
variables?: Array<AgentEnvVariableConfig>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentCliToolRiskLevel = 'dangerous' | 'safe' | 'unknown'
|
|
||||||
|
|
||||||
export type AgentSoulDifyToolCredentialRef = {
|
|
||||||
id?: string | null
|
|
||||||
provider?: string | null
|
|
||||||
type?: 'provider' | 'tool'
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AgentModerationIoConfig = {
|
|
||||||
enabled?: boolean
|
|
||||||
preset_response?: string | null
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
export type GetAgentsData = {
|
|
||||||
body?: never
|
|
||||||
path?: never
|
|
||||||
query?: {
|
|
||||||
keyword?: string
|
|
||||||
limit?: number
|
|
||||||
page?: number
|
|
||||||
}
|
|
||||||
url: '/agents'
|
|
||||||
}
|
|
||||||
|
|
||||||
export type GetAgentsResponses = {
|
|
||||||
200: AgentRosterListResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
export type GetAgentsResponse = GetAgentsResponses[keyof GetAgentsResponses]
|
|
||||||
|
|
||||||
export type GetAgentsInviteOptionsData = {
|
|
||||||
body?: never
|
|
||||||
path?: never
|
|
||||||
query?: {
|
|
||||||
app_id?: string
|
|
||||||
keyword?: string
|
|
||||||
limit?: number
|
|
||||||
page?: number
|
|
||||||
}
|
|
||||||
url: '/agents/invite-options'
|
|
||||||
}
|
|
||||||
|
|
||||||
export type GetAgentsInviteOptionsResponses = {
|
|
||||||
200: AgentInviteOptionsResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
export type GetAgentsInviteOptionsResponse
|
|
||||||
= GetAgentsInviteOptionsResponses[keyof GetAgentsInviteOptionsResponses]
|
|
||||||
|
|
||||||
export type GetAgentsByAgentIdData = {
|
|
||||||
body?: never
|
|
||||||
path: {
|
|
||||||
agent_id: string
|
|
||||||
}
|
|
||||||
query?: never
|
|
||||||
url: '/agents/{agent_id}'
|
|
||||||
}
|
|
||||||
|
|
||||||
export type GetAgentsByAgentIdResponses = {
|
|
||||||
200: AgentRosterResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
export type GetAgentsByAgentIdResponse
|
|
||||||
= GetAgentsByAgentIdResponses[keyof GetAgentsByAgentIdResponses]
|
|
||||||
|
|
||||||
export type GetAgentsByAgentIdVersionsData = {
|
|
||||||
body?: never
|
|
||||||
path: {
|
|
||||||
agent_id: string
|
|
||||||
}
|
|
||||||
query?: never
|
|
||||||
url: '/agents/{agent_id}/versions'
|
|
||||||
}
|
|
||||||
|
|
||||||
export type GetAgentsByAgentIdVersionsResponses = {
|
|
||||||
200: AgentConfigSnapshotListResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
export type GetAgentsByAgentIdVersionsResponse
|
|
||||||
= GetAgentsByAgentIdVersionsResponses[keyof GetAgentsByAgentIdVersionsResponses]
|
|
||||||
|
|
||||||
export type GetAgentsByAgentIdVersionsByVersionIdData = {
|
|
||||||
body?: never
|
|
||||||
path: {
|
|
||||||
agent_id: string
|
|
||||||
version_id: string
|
|
||||||
}
|
|
||||||
query?: never
|
|
||||||
url: '/agents/{agent_id}/versions/{version_id}'
|
|
||||||
}
|
|
||||||
|
|
||||||
export type GetAgentsByAgentIdVersionsByVersionIdResponses = {
|
|
||||||
200: AgentConfigSnapshotDetailResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
export type GetAgentsByAgentIdVersionsByVersionIdResponse
|
|
||||||
= GetAgentsByAgentIdVersionsByVersionIdResponses[keyof GetAgentsByAgentIdVersionsByVersionIdResponses]
|
|
||||||
@ -1,743 +0,0 @@
|
|||||||
// This file is auto-generated by @hey-api/openapi-ts
|
|
||||||
|
|
||||||
import * as z from 'zod'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentConfigSnapshotSummaryResponse
|
|
||||||
*/
|
|
||||||
export const zAgentConfigSnapshotSummaryResponse = z.object({
|
|
||||||
agent_id: z.string().nullish(),
|
|
||||||
created_at: z.int().nullish(),
|
|
||||||
created_by: z.string().nullish(),
|
|
||||||
id: z.string(),
|
|
||||||
summary: z.string().nullish(),
|
|
||||||
version: z.int(),
|
|
||||||
version_note: z.string().nullish(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentConfigSnapshotListResponse
|
|
||||||
*/
|
|
||||||
export const zAgentConfigSnapshotListResponse = z.object({
|
|
||||||
data: z.array(zAgentConfigSnapshotSummaryResponse),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentKind
|
|
||||||
*
|
|
||||||
* Agent implementation family.
|
|
||||||
*
|
|
||||||
* This leaves room for future non-Dify agent implementations while keeping
|
|
||||||
* the current roster/workflow APIs scoped to Dify Agent.
|
|
||||||
*/
|
|
||||||
export const zAgentKind = z.enum(['dify_agent'])
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentIconType
|
|
||||||
*
|
|
||||||
* Supported icon storage formats for Agent roster entries.
|
|
||||||
*/
|
|
||||||
export const zAgentIconType = z.enum(['emoji', 'image', 'link'])
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentPublishedReferenceResponse
|
|
||||||
*/
|
|
||||||
export const zAgentPublishedReferenceResponse = z.object({
|
|
||||||
app_id: z.string(),
|
|
||||||
app_mode: z.string(),
|
|
||||||
app_name: z.string(),
|
|
||||||
node_ids: z.array(z.string()).optional(),
|
|
||||||
workflow_id: z.string(),
|
|
||||||
workflow_version: z.string(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentScope
|
|
||||||
*
|
|
||||||
* Visibility and lifecycle scope of an Agent record.
|
|
||||||
*/
|
|
||||||
export const zAgentScope = z.enum(['roster', 'workflow_only'])
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentSource
|
|
||||||
*
|
|
||||||
* Origin that created or imported the Agent.
|
|
||||||
*/
|
|
||||||
export const zAgentSource = z.enum(['agent_app', 'imported', 'roster', 'system', 'workflow'])
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentStatus
|
|
||||||
*
|
|
||||||
* Soft lifecycle state for Agent records.
|
|
||||||
*/
|
|
||||||
export const zAgentStatus = z.enum(['active', 'archived'])
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentRosterResponse
|
|
||||||
*/
|
|
||||||
export const zAgentRosterResponse = z.object({
|
|
||||||
active_config_snapshot: zAgentConfigSnapshotSummaryResponse.nullish(),
|
|
||||||
active_config_snapshot_id: z.string().nullish(),
|
|
||||||
agent_kind: zAgentKind,
|
|
||||||
app_id: z.string().nullish(),
|
|
||||||
archived_at: z.int().nullish(),
|
|
||||||
archived_by: z.string().nullish(),
|
|
||||||
created_at: z.int().nullish(),
|
|
||||||
created_by: z.string().nullish(),
|
|
||||||
description: z.string(),
|
|
||||||
icon: z.string().nullish(),
|
|
||||||
icon_background: z.string().nullish(),
|
|
||||||
icon_type: zAgentIconType.nullish(),
|
|
||||||
id: z.string(),
|
|
||||||
name: z.string(),
|
|
||||||
published_node_reference_count: z.int().optional().default(0),
|
|
||||||
published_reference_count: z.int().optional().default(0),
|
|
||||||
published_references: z.array(zAgentPublishedReferenceResponse).optional(),
|
|
||||||
role: z.string().optional().default(''),
|
|
||||||
scope: zAgentScope,
|
|
||||||
source: zAgentSource,
|
|
||||||
status: zAgentStatus,
|
|
||||||
updated_at: z.int().nullish(),
|
|
||||||
updated_by: z.string().nullish(),
|
|
||||||
workflow_id: z.string().nullish(),
|
|
||||||
workflow_node_id: z.string().nullish(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentRosterListResponse
|
|
||||||
*/
|
|
||||||
export const zAgentRosterListResponse = z.object({
|
|
||||||
data: z.array(zAgentRosterResponse),
|
|
||||||
has_more: z.boolean(),
|
|
||||||
limit: z.int(),
|
|
||||||
page: z.int(),
|
|
||||||
total: z.int(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentInviteOptionResponse
|
|
||||||
*/
|
|
||||||
export const zAgentInviteOptionResponse = z.object({
|
|
||||||
active_config_snapshot: zAgentConfigSnapshotSummaryResponse.nullish(),
|
|
||||||
active_config_snapshot_id: z.string().nullish(),
|
|
||||||
agent_kind: zAgentKind,
|
|
||||||
app_id: z.string().nullish(),
|
|
||||||
archived_at: z.int().nullish(),
|
|
||||||
archived_by: z.string().nullish(),
|
|
||||||
created_at: z.int().nullish(),
|
|
||||||
created_by: z.string().nullish(),
|
|
||||||
description: z.string(),
|
|
||||||
existing_node_ids: z.array(z.string()).optional(),
|
|
||||||
icon: z.string().nullish(),
|
|
||||||
icon_background: z.string().nullish(),
|
|
||||||
icon_type: zAgentIconType.nullish(),
|
|
||||||
id: z.string(),
|
|
||||||
in_current_workflow_count: z.int().optional().default(0),
|
|
||||||
is_in_current_workflow: z.boolean().optional().default(false),
|
|
||||||
name: z.string(),
|
|
||||||
published_node_reference_count: z.int().optional().default(0),
|
|
||||||
published_reference_count: z.int().optional().default(0),
|
|
||||||
published_references: z.array(zAgentPublishedReferenceResponse).optional(),
|
|
||||||
role: z.string().optional().default(''),
|
|
||||||
scope: zAgentScope,
|
|
||||||
source: zAgentSource,
|
|
||||||
status: zAgentStatus,
|
|
||||||
updated_at: z.int().nullish(),
|
|
||||||
updated_by: z.string().nullish(),
|
|
||||||
workflow_id: z.string().nullish(),
|
|
||||||
workflow_node_id: z.string().nullish(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentInviteOptionsResponse
|
|
||||||
*/
|
|
||||||
export const zAgentInviteOptionsResponse = z.object({
|
|
||||||
data: z.array(zAgentInviteOptionResponse),
|
|
||||||
has_more: z.boolean(),
|
|
||||||
limit: z.int(),
|
|
||||||
page: z.int(),
|
|
||||||
total: z.int(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AppVariableConfig
|
|
||||||
*/
|
|
||||||
export const zAppVariableConfig = z.object({
|
|
||||||
default: z.unknown().optional(),
|
|
||||||
name: z.string().min(1).max(255),
|
|
||||||
required: z.boolean().optional().default(false),
|
|
||||||
type: z.string().min(1).max(64),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentSoulPromptConfig
|
|
||||||
*/
|
|
||||||
export const zAgentSoulPromptConfig = z.object({
|
|
||||||
system_prompt: z.string().optional().default(''),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentConfigRevisionOperation
|
|
||||||
*
|
|
||||||
* Audit operation recorded for Agent Soul version/revision changes.
|
|
||||||
*/
|
|
||||||
export const zAgentConfigRevisionOperation = z.enum([
|
|
||||||
'create_version',
|
|
||||||
'save_current_version',
|
|
||||||
'save_new_agent',
|
|
||||||
'save_new_version',
|
|
||||||
'save_to_roster',
|
|
||||||
])
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentConfigRevisionResponse
|
|
||||||
*/
|
|
||||||
export const zAgentConfigRevisionResponse = z.object({
|
|
||||||
created_at: z.int().nullish(),
|
|
||||||
created_by: z.string().nullish(),
|
|
||||||
current_snapshot_id: z.string(),
|
|
||||||
id: z.string(),
|
|
||||||
operation: zAgentConfigRevisionOperation,
|
|
||||||
previous_snapshot_id: z.string().nullish(),
|
|
||||||
revision: z.int(),
|
|
||||||
summary: z.string().nullish(),
|
|
||||||
version_note: z.string().nullish(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentFeatureToggleConfig
|
|
||||||
*/
|
|
||||||
export const zAgentFeatureToggleConfig = z.object({
|
|
||||||
enabled: z.boolean().optional().default(false),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentTextToSpeechFeatureConfig
|
|
||||||
*/
|
|
||||||
export const zAgentTextToSpeechFeatureConfig = z.object({
|
|
||||||
autoPlay: z.string().nullish(),
|
|
||||||
enabled: z.boolean().optional().default(false),
|
|
||||||
language: z.string().nullish(),
|
|
||||||
voice: z.string().nullish(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentEnvVariableConfig
|
|
||||||
*/
|
|
||||||
export const zAgentEnvVariableConfig = z.object({
|
|
||||||
default: z
|
|
||||||
.union([
|
|
||||||
z.string(),
|
|
||||||
z.int(),
|
|
||||||
z.number(),
|
|
||||||
z.boolean(),
|
|
||||||
z.array(z.string()),
|
|
||||||
z.array(z.int()),
|
|
||||||
z.array(z.number()),
|
|
||||||
z.array(z.boolean()),
|
|
||||||
])
|
|
||||||
.nullish(),
|
|
||||||
env_name: z.string().max(255).nullish(),
|
|
||||||
key: z.string().max(255).nullish(),
|
|
||||||
name: z.string().max(255).nullish(),
|
|
||||||
required: z.boolean().optional().default(false),
|
|
||||||
type: z.string().max(64).nullish(),
|
|
||||||
value: z
|
|
||||||
.union([
|
|
||||||
z.string(),
|
|
||||||
z.int(),
|
|
||||||
z.number(),
|
|
||||||
z.boolean(),
|
|
||||||
z.array(z.string()),
|
|
||||||
z.array(z.int()),
|
|
||||||
z.array(z.number()),
|
|
||||||
z.array(z.boolean()),
|
|
||||||
])
|
|
||||||
.nullish(),
|
|
||||||
variable: z.string().max(255).nullish(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentHumanContactConfig
|
|
||||||
*/
|
|
||||||
export const zAgentHumanContactConfig = z.object({
|
|
||||||
channel: z.string().max(64).nullish(),
|
|
||||||
contact_id: z.string().max(255).nullish(),
|
|
||||||
contact_method: z.string().max(64).nullish(),
|
|
||||||
email: z.string().max(255).nullish(),
|
|
||||||
human_id: z.string().max(255).nullish(),
|
|
||||||
id: z.string().max(255).nullish(),
|
|
||||||
method: z.string().max(64).nullish(),
|
|
||||||
name: z.string().max(255).nullish(),
|
|
||||||
tenant_id: z.string().max(255).nullish(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentHumanToolConfig
|
|
||||||
*/
|
|
||||||
export const zAgentHumanToolConfig = z.object({
|
|
||||||
description: z.string().nullish(),
|
|
||||||
enabled: z.boolean().optional().default(true),
|
|
||||||
name: z.string().max(255).nullish(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentSoulHumanConfig
|
|
||||||
*/
|
|
||||||
export const zAgentSoulHumanConfig = z.object({
|
|
||||||
contacts: z.array(zAgentHumanContactConfig).optional(),
|
|
||||||
tools: z.array(zAgentHumanToolConfig).optional(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentKnowledgeDatasetConfig
|
|
||||||
*/
|
|
||||||
export const zAgentKnowledgeDatasetConfig = z.object({
|
|
||||||
description: z.string().nullish(),
|
|
||||||
id: z.string().max(255).nullish(),
|
|
||||||
name: z.string().max(255).nullish(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentKnowledgeQueryConfig
|
|
||||||
*/
|
|
||||||
export const zAgentKnowledgeQueryConfig = z.object({
|
|
||||||
query: z.string().nullish(),
|
|
||||||
score_threshold: z.number().gte(0).lte(1).nullish(),
|
|
||||||
score_threshold_enabled: z.boolean().nullish(),
|
|
||||||
top_k: z.int().gte(1).nullish(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentKnowledgeQueryMode
|
|
||||||
*/
|
|
||||||
export const zAgentKnowledgeQueryMode = z.enum(['generated_query', 'user_query'])
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentSoulKnowledgeConfig
|
|
||||||
*/
|
|
||||||
export const zAgentSoulKnowledgeConfig = z.object({
|
|
||||||
datasets: z.array(zAgentKnowledgeDatasetConfig).optional(),
|
|
||||||
query_config: zAgentKnowledgeQueryConfig.optional(),
|
|
||||||
query_mode: zAgentKnowledgeQueryMode.nullish(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentMemoryArtifactConfig
|
|
||||||
*/
|
|
||||||
export const zAgentMemoryArtifactConfig = z.object({
|
|
||||||
id: z.string().max(255).nullish(),
|
|
||||||
name: z.string().max(255).nullish(),
|
|
||||||
type: z.string().max(64).nullish(),
|
|
||||||
url: z.string().nullish(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentSoulMemoryConfig
|
|
||||||
*/
|
|
||||||
export const zAgentSoulMemoryConfig = z.object({
|
|
||||||
artifacts: z.array(zAgentMemoryArtifactConfig).optional(),
|
|
||||||
budget: z.string().nullish(),
|
|
||||||
scope: z.string().nullish(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentSoulModelCredentialRef
|
|
||||||
*
|
|
||||||
* Reference to model credentials resolved only at runtime.
|
|
||||||
*/
|
|
||||||
export const zAgentSoulModelCredentialRef = z.object({
|
|
||||||
id: z.string().max(255).nullish(),
|
|
||||||
provider: z.string().max(255).nullish(),
|
|
||||||
type: z.string().min(1).max(64),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentSandboxProviderConfig
|
|
||||||
*/
|
|
||||||
export const zAgentSandboxProviderConfig = z.object({
|
|
||||||
cpu: z.int().gte(1).nullish(),
|
|
||||||
env: z.array(zAgentEnvVariableConfig).optional(),
|
|
||||||
image: z.string().nullish(),
|
|
||||||
working_dir: z.string().nullish(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentSoulSandboxConfig
|
|
||||||
*/
|
|
||||||
export const zAgentSoulSandboxConfig = z.object({
|
|
||||||
config: zAgentSandboxProviderConfig.optional(),
|
|
||||||
provider: z.string().nullish(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentFileRefConfig
|
|
||||||
*/
|
|
||||||
export const zAgentFileRefConfig = z.object({
|
|
||||||
drive_key: z.string().max(512).nullish(),
|
|
||||||
file_id: z.string().max(255).nullish(),
|
|
||||||
id: z.string().max(255).nullish(),
|
|
||||||
name: z.string().max(255).nullish(),
|
|
||||||
reference: z.string().max(255).nullish(),
|
|
||||||
remote_url: z.string().nullish(),
|
|
||||||
tenant_id: z.string().max(255).nullish(),
|
|
||||||
transfer_method: z.string().max(64).nullish(),
|
|
||||||
type: z.string().max(64).nullish(),
|
|
||||||
upload_file_id: z.string().max(255).nullish(),
|
|
||||||
url: z.string().nullish(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentSkillRefConfig
|
|
||||||
*/
|
|
||||||
export const zAgentSkillRefConfig = z.object({
|
|
||||||
description: z.string().nullish(),
|
|
||||||
file_id: z.string().max(255).nullish(),
|
|
||||||
full_archive_file_id: z.string().max(255).nullish(),
|
|
||||||
full_archive_key: z.string().max(512).nullish(),
|
|
||||||
id: z.string().max(255).nullish(),
|
|
||||||
manifest_files: z.array(z.string()).nullish(),
|
|
||||||
name: z.string().max(255).nullish(),
|
|
||||||
path: z.string().nullish(),
|
|
||||||
skill_md_file_id: z.string().max(255).nullish(),
|
|
||||||
skill_md_key: z.string().max(512).nullish(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentSoulSkillsFilesConfig
|
|
||||||
*/
|
|
||||||
export const zAgentSoulSkillsFilesConfig = z.object({
|
|
||||||
files: z.array(zAgentFileRefConfig).optional(),
|
|
||||||
skills: z.array(zAgentSkillRefConfig).optional(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentPermissionConfig
|
|
||||||
*/
|
|
||||||
export const zAgentPermissionConfig = z.object({
|
|
||||||
allowed: z.boolean().nullish(),
|
|
||||||
state: z.string().max(64).nullish(),
|
|
||||||
status: z.string().max(64).nullish(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentSecretRefConfig
|
|
||||||
*/
|
|
||||||
export const zAgentSecretRefConfig = z.object({
|
|
||||||
credential_id: z.string().max(255).nullish(),
|
|
||||||
env_name: z.string().max(255).nullish(),
|
|
||||||
id: z.string().max(255).nullish(),
|
|
||||||
key: z.string().max(255).nullish(),
|
|
||||||
name: z.string().max(255).nullish(),
|
|
||||||
permission: zAgentPermissionConfig.nullish(),
|
|
||||||
permission_status: z.string().max(64).nullish(),
|
|
||||||
provider: z.string().max(255).nullish(),
|
|
||||||
provider_credential_id: z.string().max(255).nullish(),
|
|
||||||
ref: z.string().max(255).nullish(),
|
|
||||||
type: z.string().max(64).nullish(),
|
|
||||||
variable: z.string().max(255).nullish(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentSoulEnvConfig
|
|
||||||
*/
|
|
||||||
export const zAgentSoulEnvConfig = z.object({
|
|
||||||
secret_refs: z.array(zAgentSecretRefConfig).optional(),
|
|
||||||
variables: z.array(zAgentEnvVariableConfig).optional(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentModelResponseFormatConfig
|
|
||||||
*/
|
|
||||||
export const zAgentModelResponseFormatConfig = z.object({
|
|
||||||
type: z.string().max(64).nullish(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentSoulModelSettings
|
|
||||||
*/
|
|
||||||
export const zAgentSoulModelSettings = z.object({
|
|
||||||
frequency_penalty: z.number().nullish(),
|
|
||||||
max_tokens: z.int().nullish(),
|
|
||||||
presence_penalty: z.number().nullish(),
|
|
||||||
response_format: zAgentModelResponseFormatConfig.nullish(),
|
|
||||||
stop: z.array(z.string()).nullish(),
|
|
||||||
temperature: z.number().nullish(),
|
|
||||||
top_p: z.number().nullish(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentSoulModelConfig
|
|
||||||
*
|
|
||||||
* Stable model selection for Agent runtime without storing secret values.
|
|
||||||
*/
|
|
||||||
export const zAgentSoulModelConfig = z.object({
|
|
||||||
credential_ref: zAgentSoulModelCredentialRef.nullish(),
|
|
||||||
model: z.string().min(1).max(255),
|
|
||||||
model_provider: z.string().min(1).max(255),
|
|
||||||
model_settings: zAgentSoulModelSettings.optional(),
|
|
||||||
plugin_id: z.string().min(1).max(255),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentSuggestedQuestionsAfterAnswerFeatureConfig
|
|
||||||
*/
|
|
||||||
export const zAgentSuggestedQuestionsAfterAnswerFeatureConfig = z.object({
|
|
||||||
enabled: z.boolean().optional().default(false),
|
|
||||||
model: zAgentSoulModelConfig.nullish(),
|
|
||||||
prompt: z.string().nullish(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentCliToolAuthorizationStatus
|
|
||||||
*
|
|
||||||
* Authorization state for Agent-scoped CLI tools.
|
|
||||||
*
|
|
||||||
* Missing status keeps backward compatibility with draft rows and CLI tools that
|
|
||||||
* do not need pre-authorization. Explicit denied-like states are blocked by the
|
|
||||||
* composer/publish validators and skipped by runtime request builders.
|
|
||||||
*/
|
|
||||||
export const zAgentCliToolAuthorizationStatus = z.enum([
|
|
||||||
'allowed',
|
|
||||||
'authorized',
|
|
||||||
'denied',
|
|
||||||
'forbidden',
|
|
||||||
'not_required',
|
|
||||||
'pending',
|
|
||||||
'pre_authorized',
|
|
||||||
'unauthorized',
|
|
||||||
])
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentCliToolEnvConfig
|
|
||||||
*/
|
|
||||||
export const zAgentCliToolEnvConfig = z.object({
|
|
||||||
secret_refs: z.array(zAgentSecretRefConfig).optional(),
|
|
||||||
variables: z.array(zAgentEnvVariableConfig).optional(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentCliToolRiskLevel
|
|
||||||
*
|
|
||||||
* Risk marker for CLI tool bootstrap commands.
|
|
||||||
*/
|
|
||||||
export const zAgentCliToolRiskLevel = z.enum(['dangerous', 'safe', 'unknown'])
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentCliToolConfig
|
|
||||||
*/
|
|
||||||
export const zAgentCliToolConfig = z.object({
|
|
||||||
approved: z.boolean().optional().default(false),
|
|
||||||
authorization_status: zAgentCliToolAuthorizationStatus.nullish(),
|
|
||||||
command: z.string().nullish(),
|
|
||||||
dangerous: z.boolean().optional().default(false),
|
|
||||||
dangerous_accepted: z.boolean().optional().default(false),
|
|
||||||
dangerous_acknowledged: z.boolean().optional().default(false),
|
|
||||||
dangerous_command: z.boolean().optional().default(false),
|
|
||||||
description: z.string().nullish(),
|
|
||||||
enabled: z.boolean().optional().default(true),
|
|
||||||
env: zAgentCliToolEnvConfig.optional(),
|
|
||||||
id: z.string().max(255).nullish(),
|
|
||||||
inferred_from: z.string().max(255).nullish(),
|
|
||||||
install: z.string().nullish(),
|
|
||||||
install_command: z.string().nullish(),
|
|
||||||
install_commands: z.array(z.string()).optional(),
|
|
||||||
invoke_metadata: z.record(z.string(), z.unknown()).optional(),
|
|
||||||
label: z.string().max(255).nullish(),
|
|
||||||
name: z.string().max(255).nullish(),
|
|
||||||
permission: zAgentPermissionConfig.nullish(),
|
|
||||||
pre_authorized: z.boolean().nullish(),
|
|
||||||
requires_confirmation: z.boolean().optional().default(false),
|
|
||||||
risk_accepted: z.boolean().optional().default(false),
|
|
||||||
risk_level: zAgentCliToolRiskLevel.nullish(),
|
|
||||||
setup_command: z.string().nullish(),
|
|
||||||
tool_name: z.string().max(255).nullish(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentSoulDifyToolCredentialRef
|
|
||||||
*
|
|
||||||
* Reference to a stored Dify Plugin Tool credential.
|
|
||||||
*
|
|
||||||
* Secret values are resolved only at runtime. The legacy ``credential_id``
|
|
||||||
* field is accepted by :class:`AgentSoulDifyToolConfig` and normalized here so
|
|
||||||
* old Agent tool payloads can be read while new payloads stay explicit.
|
|
||||||
*/
|
|
||||||
export const zAgentSoulDifyToolCredentialRef = z.object({
|
|
||||||
id: z.string().max(255).nullish(),
|
|
||||||
provider: z.string().max(255).nullish(),
|
|
||||||
type: z.enum(['provider', 'tool']).optional().default('tool'),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentSoulDifyToolConfig
|
|
||||||
*
|
|
||||||
* One Dify Plugin Tool configured on Agent Soul.
|
|
||||||
*
|
|
||||||
* The API backend prepares this persisted product shape into
|
|
||||||
* ``DifyPluginToolConfig`` before sending a run request to Agent backend.
|
|
||||||
* ``provider_id`` keeps compatibility with existing Agent tool config payloads;
|
|
||||||
* new callers should send ``plugin_id`` + ``provider`` when available.
|
|
||||||
*/
|
|
||||||
export const zAgentSoulDifyToolConfig = z.object({
|
|
||||||
credential_ref: zAgentSoulDifyToolCredentialRef.nullish(),
|
|
||||||
credential_type: z.enum(['api-key', 'oauth2', 'unauthorized']).optional().default('api-key'),
|
|
||||||
description: z.string().nullish(),
|
|
||||||
enabled: z.boolean().optional().default(true),
|
|
||||||
name: z.string().max(255).nullish(),
|
|
||||||
plugin_id: z.string().max(255).nullish(),
|
|
||||||
provider: z.string().max(255).nullish(),
|
|
||||||
provider_id: z.string().max(255).nullish(),
|
|
||||||
provider_type: z.string().optional().default('plugin'),
|
|
||||||
runtime_parameters: z
|
|
||||||
.record(
|
|
||||||
z.string(),
|
|
||||||
z
|
|
||||||
.union([
|
|
||||||
z.string(),
|
|
||||||
z.int(),
|
|
||||||
z.number(),
|
|
||||||
z.boolean(),
|
|
||||||
z.array(z.string()),
|
|
||||||
z.array(z.int()),
|
|
||||||
z.array(z.number()),
|
|
||||||
z.array(z.boolean()),
|
|
||||||
])
|
|
||||||
.nullable(),
|
|
||||||
)
|
|
||||||
.optional(),
|
|
||||||
tool_name: z.string().min(1).max(255).nullish(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentSoulToolsConfig
|
|
||||||
*/
|
|
||||||
export const zAgentSoulToolsConfig = z.object({
|
|
||||||
cli_tools: z.array(zAgentCliToolConfig).optional(),
|
|
||||||
dify_tools: z.array(zAgentSoulDifyToolConfig).optional(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentModerationIOConfig
|
|
||||||
*/
|
|
||||||
export const zAgentModerationIoConfig = z.object({
|
|
||||||
enabled: z.boolean().optional().default(false),
|
|
||||||
preset_response: z.string().nullish(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentModerationProviderConfig
|
|
||||||
*/
|
|
||||||
export const zAgentModerationProviderConfig = z.object({
|
|
||||||
api_based_extension_id: z.string().nullish(),
|
|
||||||
inputs_config: zAgentModerationIoConfig.nullish(),
|
|
||||||
keywords: z.string().nullish(),
|
|
||||||
outputs_config: zAgentModerationIoConfig.nullish(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentSensitiveWordAvoidanceFeatureConfig
|
|
||||||
*/
|
|
||||||
export const zAgentSensitiveWordAvoidanceFeatureConfig = z.object({
|
|
||||||
config: zAgentModerationProviderConfig.nullish(),
|
|
||||||
enabled: z.boolean().optional().default(false),
|
|
||||||
type: z.string().nullish(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentSoulAppFeaturesConfig
|
|
||||||
*/
|
|
||||||
export const zAgentSoulAppFeaturesConfig = z.object({
|
|
||||||
opening_statement: z.string().nullish(),
|
|
||||||
retriever_resource: zAgentFeatureToggleConfig.nullish(),
|
|
||||||
sensitive_word_avoidance: zAgentSensitiveWordAvoidanceFeatureConfig.nullish(),
|
|
||||||
speech_to_text: zAgentFeatureToggleConfig.nullish(),
|
|
||||||
suggested_questions: z.array(z.string()).nullish(),
|
|
||||||
suggested_questions_after_answer: zAgentSuggestedQuestionsAfterAnswerFeatureConfig.nullish(),
|
|
||||||
text_to_speech: zAgentTextToSpeechFeatureConfig.nullish(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentSoulConfig
|
|
||||||
*/
|
|
||||||
export const zAgentSoulConfig = z.object({
|
|
||||||
app_features: zAgentSoulAppFeaturesConfig.optional(),
|
|
||||||
app_variables: z.array(zAppVariableConfig).optional(),
|
|
||||||
env: zAgentSoulEnvConfig.optional(),
|
|
||||||
human: zAgentSoulHumanConfig.optional(),
|
|
||||||
knowledge: zAgentSoulKnowledgeConfig.optional(),
|
|
||||||
memory: zAgentSoulMemoryConfig.optional(),
|
|
||||||
misc_legacy: zAgentSoulAppFeaturesConfig.optional(),
|
|
||||||
model: zAgentSoulModelConfig.nullish(),
|
|
||||||
prompt: zAgentSoulPromptConfig.optional(),
|
|
||||||
sandbox: zAgentSoulSandboxConfig.optional(),
|
|
||||||
schema_version: z.int().optional().default(1),
|
|
||||||
skills_files: zAgentSoulSkillsFilesConfig.optional(),
|
|
||||||
tools: zAgentSoulToolsConfig.optional(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AgentConfigSnapshotDetailResponse
|
|
||||||
*/
|
|
||||||
export const zAgentConfigSnapshotDetailResponse = z.object({
|
|
||||||
agent_id: z.string().nullish(),
|
|
||||||
config_snapshot: zAgentSoulConfig,
|
|
||||||
created_at: z.int().nullish(),
|
|
||||||
created_by: z.string().nullish(),
|
|
||||||
id: z.string(),
|
|
||||||
revisions: z.array(zAgentConfigRevisionResponse).optional(),
|
|
||||||
summary: z.string().nullish(),
|
|
||||||
version: z.int(),
|
|
||||||
version_note: z.string().nullish(),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const zGetAgentsQuery = z.object({
|
|
||||||
keyword: z.string().optional(),
|
|
||||||
limit: z.int().gte(1).lte(100).optional().default(20),
|
|
||||||
page: z.int().gte(1).optional().default(1),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Agent roster list
|
|
||||||
*/
|
|
||||||
export const zGetAgentsResponse = zAgentRosterListResponse
|
|
||||||
|
|
||||||
export const zGetAgentsInviteOptionsQuery = z.object({
|
|
||||||
app_id: z.string().optional(),
|
|
||||||
keyword: z.string().optional(),
|
|
||||||
limit: z.int().gte(1).lte(100).optional().default(20),
|
|
||||||
page: z.int().gte(1).optional().default(1),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Agent invite options
|
|
||||||
*/
|
|
||||||
export const zGetAgentsInviteOptionsResponse = zAgentInviteOptionsResponse
|
|
||||||
|
|
||||||
export const zGetAgentsByAgentIdPath = z.object({
|
|
||||||
agent_id: z.string(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Agent detail
|
|
||||||
*/
|
|
||||||
export const zGetAgentsByAgentIdResponse = zAgentRosterResponse
|
|
||||||
|
|
||||||
export const zGetAgentsByAgentIdVersionsPath = z.object({
|
|
||||||
agent_id: z.string(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Agent versions
|
|
||||||
*/
|
|
||||||
export const zGetAgentsByAgentIdVersionsResponse = zAgentConfigSnapshotListResponse
|
|
||||||
|
|
||||||
export const zGetAgentsByAgentIdVersionsByVersionIdPath = z.object({
|
|
||||||
agent_id: z.string(),
|
|
||||||
version_id: z.string(),
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Agent version detail
|
|
||||||
*/
|
|
||||||
export const zGetAgentsByAgentIdVersionsByVersionIdResponse = zAgentConfigSnapshotDetailResponse
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { account } from './account/orpc.gen'
|
import { account } from './account/orpc.gen'
|
||||||
import { activate } from './activate/orpc.gen'
|
import { activate } from './activate/orpc.gen'
|
||||||
import { agents } from './agents/orpc.gen'
|
import { agent } from './agent/orpc.gen'
|
||||||
import { allWorkspaces } from './all-workspaces/orpc.gen'
|
import { allWorkspaces } from './all-workspaces/orpc.gen'
|
||||||
import { apiBasedExtension } from './api-based-extension/orpc.gen'
|
import { apiBasedExtension } from './api-based-extension/orpc.gen'
|
||||||
import { apiKeyAuth } from './api-key-auth/orpc.gen'
|
import { apiKeyAuth } from './api-key-auth/orpc.gen'
|
||||||
@ -53,7 +53,7 @@ import { workspaces } from './workspaces/orpc.gen'
|
|||||||
export const contract = {
|
export const contract = {
|
||||||
account,
|
account,
|
||||||
activate,
|
activate,
|
||||||
agents,
|
agent,
|
||||||
allWorkspaces,
|
allWorkspaces,
|
||||||
apiBasedExtension,
|
apiBasedExtension,
|
||||||
apiKeyAuth,
|
apiKeyAuth,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user