"""Console API for Agent Soul-backed config assets.""" from __future__ import annotations import inspect import io import json from collections.abc import Callable, Mapping from dataclasses import dataclass from typing import Any, Literal from uuid import UUID from flask import Response, request, send_file, url_for from flask_restx import Resource from pydantic import BaseModel, Field from sqlalchemy.orm import Session from controllers.common.schema import ( query_params_from_model, query_params_from_request, register_response_schema_models, register_schema_models, ) from controllers.common.session import with_session from controllers.console import console_ns from controllers.console.agent.app_helpers import resolve_agent_runtime_app_model from controllers.console.app.wraps import get_app_model from controllers.console.wraps import ( RBACPermission, RBACResourceScope, account_initialization_required, edit_permission_required, rbac_permission_required, setup_required, with_current_tenant_id, with_current_user, ) from fields.base import ResponseModel from libs.login import login_required from models.account import Account from models.model import App, AppMode from services.agent.composer_service import AgentComposerService from services.agent.errors import AgentVersionNotFoundError from services.agent_config_service import ( AgentConfigService, AgentConfigServiceError, AgentConfigVersionKind, ConfigPushFileItem, ConfigPushPayload, ConfigPushSkillItem, ) class AgentConfigQuery(BaseModel): node_id: str | None = Field(default=None, description="Workflow node ID (workflow composer variant)") version_id: str | None = Field(default=None, description="Published snapshot ID for read-only version view") draft_type: Literal["draft", "debug_build"] | None = Field( default=None, description="Editable draft surface: omit or 'draft' for normal draft, 'debug_build' for build draft", ) class AgentConfigByAgentQuery(BaseModel): version_id: str | None = Field(default=None, description="Published snapshot ID for read-only version view") draft_type: Literal["draft", "debug_build"] | None = Field( default=None, description="Editable draft surface: omit or 'draft' for normal draft, 'debug_build' for build draft", ) class AgentConfigFileUploadPayload(BaseModel): upload_file_id: str = Field(..., description="UploadFile UUID from POST /console/api/files/upload") _AGENT_CONFIG_SKILL_UPLOAD_PARAMS = { "file": { "in": "formData", "type": "file", "required": True, "description": "Skill package (.zip or .skill).", } } class AgentConfigSkillFileQuery(AgentConfigQuery): path: str = Field(..., description="Normalized zip member path inside the skill package") class AgentConfigSkillFileByAgentQuery(AgentConfigByAgentQuery): path: str = Field(..., description="Normalized zip member path inside the skill package") class AgentConfigVersionResponse(ResponseModel): id: str kind: Literal["snapshot", "draft", "build_draft"] writable: bool class AgentConfigSkillItemResponse(ResponseModel): id: str name: str file_id: str | None = None is_missing: bool = False description: str = "" size: int | None = None mime_type: str | None = None hash: str | None = None class AgentConfigFileItemResponse(ResponseModel): id: str name: str file_id: str | None = None is_missing: bool = False size: int | None = None mime_type: str | None = None hash: str | None = None class AgentConfigSkillItemsResponse(ResponseModel): items: list[AgentConfigSkillItemResponse] = Field(default_factory=list) class AgentConfigFileItemsResponse(ResponseModel): items: list[AgentConfigFileItemResponse] = Field(default_factory=list) class AgentConfigSkillUploadResponse(ResponseModel): skill: AgentConfigSkillItemResponse config_version: AgentConfigVersionResponse class AgentConfigFileUploadResponse(ResponseModel): file: AgentConfigFileItemResponse config_version: AgentConfigVersionResponse class AgentConfigManifestResponse(ResponseModel): agent_id: str config_version: AgentConfigVersionResponse skills: AgentConfigSkillItemsResponse = Field(default_factory=AgentConfigSkillItemsResponse) files: AgentConfigFileItemsResponse = Field(default_factory=AgentConfigFileItemsResponse) env_keys: list[str] = Field(default_factory=list) note: str = "" class AgentConfigSkillListResponse(ResponseModel): agent_id: str config_version: AgentConfigVersionResponse items: list[AgentConfigSkillItemResponse] = Field(default_factory=list) class AgentConfigFileListResponse(ResponseModel): agent_id: str config_version: AgentConfigVersionResponse items: list[AgentConfigFileItemResponse] = Field(default_factory=list) class AgentConfigSkillFileResponse(ResponseModel): path: str name: str type: Literal["file", "directory"] previewable: bool downloadable: bool class AgentConfigSkillMarkdownResponse(ResponseModel): path: Literal["SKILL.md"] size: int | None = None truncated: bool binary: Literal[False] text: str class AgentConfigSkillInspectResponse(ResponseModel): id: str name: str description: str = "" size: int | None = None mime_type: str | None = None hash: str | None = None source: Literal["config_skill_zip"] files: list[AgentConfigSkillFileResponse] = Field(default_factory=list) file_tree: list[dict[str, Any]] | None = None skill_md: AgentConfigSkillMarkdownResponse warnings: list[str] = Field(default_factory=list) class AgentConfigFilePreviewResponse(ResponseModel): name: str size: int | None = None truncated: bool binary: bool text: str | None = None class AgentConfigSkillFilePreviewResponse(ResponseModel): path: str size: int | None = None truncated: bool binary: bool text: str | None = None class AgentConfigDownloadResponse(ResponseModel): url: str class AgentConfigDeleteResponse(ResponseModel): result: Literal["success"] removed_names: list[str] = Field(default_factory=list) register_schema_models(console_ns, AgentConfigFileUploadPayload) register_response_schema_models( console_ns, AgentConfigDeleteResponse, AgentConfigDownloadResponse, AgentConfigFileItemResponse, AgentConfigFileItemsResponse, AgentConfigFileListResponse, AgentConfigFilePreviewResponse, AgentConfigFileUploadResponse, AgentConfigManifestResponse, AgentConfigSkillFilePreviewResponse, AgentConfigSkillFileResponse, AgentConfigSkillInspectResponse, AgentConfigSkillItemResponse, AgentConfigSkillItemsResponse, AgentConfigSkillListResponse, AgentConfigSkillMarkdownResponse, AgentConfigSkillUploadResponse, AgentConfigVersionResponse, ) @dataclass(frozen=True, slots=True) class _ResolvedConsoleTarget: tenant_id: str agent_id: str account_id: str version_id: str version_kind: AgentConfigVersionKind _WORKFLOW_APP_MODES = [AppMode.WORKFLOW, AppMode.ADVANCED_CHAT] def _service() -> AgentConfigService: return AgentConfigService() def _resolve_agent_id(session: Session, app_model: App, node_id: str | None) -> str | None: if node_id: return AgentComposerService.resolve_workflow_node_agent_id( session=session, tenant_id=app_model.tenant_id, app_id=app_model.id, node_id=node_id, ) return app_model.bound_agent_id_with_session(session=session) def _agent_not_bound() -> tuple[dict[str, object], int]: return {"code": "agent_not_bound", "message": "no agent is bound for this app/node"}, 400 def _handle(exc: AgentConfigServiceError) -> tuple[dict[str, object], int]: return {"code": exc.code, "message": exc.message}, exc.status_code def _json_response(data: Mapping[str, Any]) -> Response: return Response( response=json.dumps(data, ensure_ascii=False, separators=(",", ":")), content_type="application/json; charset=utf-8", ) def _resolve_console_version( *, session: Session, tenant_id: str, agent_id: str, account_id: str, version_id: str | None, draft_type: str | None, ) -> tuple[str, AgentConfigVersionKind]: if version_id: return version_id, AgentConfigVersionKind.SNAPSHOT try: if draft_type == "debug_build": state = AgentComposerService.load_agent_app_build_draft( session=session, tenant_id=tenant_id, agent_id=agent_id, account_id=account_id, ) draft = state.get("draft") or {} draft_id = draft.get("id") if isinstance(draft_id, str) and draft_id: return draft_id, AgentConfigVersionKind.BUILD_DRAFT else: state = AgentComposerService.load_agent_composer(session=session, tenant_id=tenant_id, agent_id=agent_id) draft = state.get("draft") or {} draft_id = draft.get("id") if isinstance(draft_id, str) and draft_id: # load_agent_composer creates the normal draft on first access. # Config asset services use their own SQLAlchemy session, so the # draft must be visible before we hand its id across that boundary. session.commit() return draft_id, AgentConfigVersionKind.DRAFT except AgentVersionNotFoundError as exc: raise AgentConfigServiceError( "config_version_not_found", "agent config version was not found", status_code=404, ) from exc raise AgentConfigServiceError( "config_version_not_found", "agent config version was not found", status_code=404, ) def _resolve_target( *, session: Session, tenant_id: str, agent_id: str, account_id: str, version_id: str | None, draft_type: str | None, ) -> _ResolvedConsoleTarget: resolved_version_id, version_kind = _resolve_console_version( session=session, tenant_id=tenant_id, agent_id=agent_id, account_id=account_id, version_id=version_id, draft_type=draft_type, ) return _ResolvedConsoleTarget( tenant_id=tenant_id, agent_id=agent_id, account_id=account_id, version_id=resolved_version_id, version_kind=version_kind, ) def _resolve_agent_route_target( *, session: Session, tenant_id: str, agent_id: UUID, current_user: Account, query: AgentConfigByAgentQuery, ) -> _ResolvedConsoleTarget: resolve_agent_runtime_app_model(session=session, tenant_id=tenant_id, agent_id=agent_id) return _resolve_target( session=session, tenant_id=tenant_id, agent_id=str(agent_id), account_id=current_user.id, version_id=query.version_id, draft_type=query.draft_type, ) def _resolve_app_route_target( *, session: Session, app_model: App, current_user: Account, query: AgentConfigQuery, ) -> _ResolvedConsoleTarget | tuple[dict[str, object], int]: agent_id = _resolve_agent_id(session, app_model, query.node_id) if not agent_id: return _agent_not_bound() return _resolve_target( session=session, tenant_id=app_model.tenant_id, agent_id=agent_id, account_id=current_user.id, version_id=query.version_id, draft_type=query.draft_type, ) def _with_agent_route_target( *, session: Session, tenant_id: str, agent_id: UUID, current_user: Account, action: Callable[[_ResolvedConsoleTarget], Any], ) -> Any: query = query_params_from_request(AgentConfigByAgentQuery) try: target = _resolve_agent_route_target( session=session, tenant_id=tenant_id, agent_id=agent_id, current_user=current_user, query=query, ) return action(target) except AgentConfigServiceError as exc: return _handle(exc) def _with_app_route_target( *, session: Session, app_model: App, current_user: Account, action: Callable[[_ResolvedConsoleTarget], Any], ) -> Any: query = query_params_from_request(AgentConfigQuery) try: target = _resolve_app_route_target(session=session, app_model=app_model, current_user=current_user, query=query) if isinstance(target, tuple): return target return action(target) except AgentConfigServiceError as exc: return _handle(exc) def _manifest_response(target: _ResolvedConsoleTarget) -> dict[str, object]: return _service().manifest( tenant_id=target.tenant_id, agent_id=target.agent_id, config_version_id=target.version_id, config_version_kind=target.version_kind, user_id=target.account_id, ) def _skill_list_response(target: _ResolvedConsoleTarget) -> dict[str, object]: return _service().list_skills( tenant_id=target.tenant_id, agent_id=target.agent_id, config_version_id=target.version_id, config_version_kind=target.version_kind, user_id=target.account_id, ) def _file_list_response(target: _ResolvedConsoleTarget) -> dict[str, object]: return _service().list_files( tenant_id=target.tenant_id, agent_id=target.agent_id, config_version_id=target.version_id, config_version_kind=target.version_kind, user_id=target.account_id, ) def _read_single_upload() -> tuple[bytes, str]: if "file" not in request.files: raise AgentConfigServiceError("no_file", "no skill file uploaded", status_code=400) if len(request.files) > 1: raise AgentConfigServiceError("too_many_files", "only one skill file is allowed", status_code=400) upload = request.files["file"] return upload.stream.read(), upload.filename or "" def _skill_upload_response(target: _ResolvedConsoleTarget) -> tuple[dict[str, object], int]: return _upload_skill_for_target( tenant_id=target.tenant_id, agent_id=target.agent_id, user_id=target.account_id, version_id=target.version_id, version_kind=target.version_kind, ) def _upload_skill_for_target( *, tenant_id: str, agent_id: str, user_id: str, version_id: str, version_kind: AgentConfigVersionKind, content: bytes | None = None, filename: str = "", ) -> tuple[dict[str, object], int]: if content is None: content, filename = _read_single_upload() manifest = _service().upload_skill_for_console( tenant_id=tenant_id, agent_id=agent_id, user_id=user_id, config_version_id=version_id, config_version_kind=version_kind, content=content, filename=filename, ) return manifest, 201 def _file_upload_response( target: _ResolvedConsoleTarget, payload: AgentConfigFileUploadPayload ) -> tuple[dict[str, object], int]: manifest = _service().push_file_for_console( tenant_id=target.tenant_id, agent_id=target.agent_id, user_id=target.account_id, config_version_id=target.version_id, config_version_kind=target.version_kind, upload_file_id=payload.upload_file_id, ) return manifest, 201 def _skill_inspect_response(target: _ResolvedConsoleTarget, name: str) -> Response: return _json_response( _service().inspect_skill( tenant_id=target.tenant_id, agent_id=target.agent_id, config_version_id=target.version_id, config_version_kind=target.version_kind, name=name, user_id=target.account_id, ) ) def _skill_download_response(target: _ResolvedConsoleTarget, name: str) -> Response: return _json_response( { "url": _service().download_skill_url( tenant_id=target.tenant_id, agent_id=target.agent_id, config_version_id=target.version_id, config_version_kind=target.version_kind, name=name, user_id=target.account_id, ) } ) def _skill_file_preview_response(target: _ResolvedConsoleTarget, name: str, path: str) -> dict[str, object]: return _service().preview_skill_file( tenant_id=target.tenant_id, agent_id=target.agent_id, config_version_id=target.version_id, config_version_kind=target.version_kind, name=name, path=path, user_id=target.account_id, ) def _skill_file_download_response( target: _ResolvedConsoleTarget, *, name: str, path: str, raw_endpoint: str, route_params: dict[str, str], extra_query_params: dict[str, str] | None = None, ) -> Response: member_path = _service().resolve_skill_file_member_path( tenant_id=target.tenant_id, agent_id=target.agent_id, config_version_id=target.version_id, config_version_kind=target.version_kind, name=name, path=path, user_id=target.account_id, ) query_args: dict[str, str] = {"path": member_path} if extra_query_params: query_args.update(extra_query_params) if target.version_kind == AgentConfigVersionKind.SNAPSHOT: query_args["version_id"] = target.version_id elif target.version_kind == AgentConfigVersionKind.BUILD_DRAFT: query_args["draft_type"] = "debug_build" url = url_for(raw_endpoint, _external=False, name=name, **route_params, **query_args) return _json_response({"url": url}) def _skill_file_raw_download_response(target: _ResolvedConsoleTarget, name: str, path: str) -> Response: download = _service().pull_skill_file( tenant_id=target.tenant_id, agent_id=target.agent_id, config_version_id=target.version_id, config_version_kind=target.version_kind, name=name, path=path, user_id=target.account_id, ) return send_file( io.BytesIO(download.payload), mimetype=download.mime_type, as_attachment=True, download_name=download.filename, ) def _skill_delete_response(target: _ResolvedConsoleTarget, name: str) -> dict[str, object]: _service().push_for_console( tenant_id=target.tenant_id, agent_id=target.agent_id, user_id=target.account_id, config_version_id=target.version_id, config_version_kind=target.version_kind, payload=ConfigPushPayload(skills=[ConfigPushSkillItem(name=name)]), ) return {"result": "success", "removed_names": [name]} def _file_preview_response(target: _ResolvedConsoleTarget, name: str) -> dict[str, object]: return _service().preview_file( tenant_id=target.tenant_id, agent_id=target.agent_id, config_version_id=target.version_id, config_version_kind=target.version_kind, name=name, user_id=target.account_id, ) def _file_download_response(target: _ResolvedConsoleTarget, name: str) -> Response: return _json_response( { "url": _service().download_file_url( tenant_id=target.tenant_id, agent_id=target.agent_id, config_version_id=target.version_id, config_version_kind=target.version_kind, name=name, user_id=target.account_id, ) } ) def _file_delete_response(target: _ResolvedConsoleTarget, name: str) -> dict[str, object]: _service().push_for_console( tenant_id=target.tenant_id, agent_id=target.agent_id, user_id=target.account_id, config_version_id=target.version_id, config_version_kind=target.version_kind, payload=ConfigPushPayload(files=[ConfigPushFileItem(name=name)]), ) return {"result": "success", "removed_names": [name]} @console_ns.route("/agent//config/manifest") class AgentConfigManifestByAgentApi(Resource): @console_ns.doc("get_agent_config_manifest_by_agent") @console_ns.doc(params={"agent_id": "Agent ID", **query_params_from_model(AgentConfigByAgentQuery)}) @console_ns.response(200, "Agent config manifest", console_ns.models[AgentConfigManifestResponse.__name__]) @setup_required @login_required @account_initialization_required @with_current_user @with_current_tenant_id @with_session def get(self, session: Session, tenant_id: str, current_user: Account, agent_id: UUID): return _with_agent_route_target( session=session, tenant_id=tenant_id, agent_id=agent_id, current_user=current_user, action=_manifest_response, ) @console_ns.route("/apps//agent/config/manifest") class AgentConfigManifestApi(Resource): @console_ns.doc("get_agent_config_manifest") @console_ns.doc(params={"app_id": "Application ID", **query_params_from_model(AgentConfigQuery)}) @console_ns.response(200, "Agent config manifest", console_ns.models[AgentConfigManifestResponse.__name__]) @setup_required @login_required @account_initialization_required @with_current_user @with_session @get_app_model(mode=_WORKFLOW_APP_MODES) def get(self, session: Session, current_user: Account, app_model: App): return _with_app_route_target( session=session, app_model=app_model, current_user=current_user, action=_manifest_response ) @console_ns.route("/agent//config/skills/upload") class AgentConfigSkillUploadByAgentApi(Resource): @console_ns.doc("upload_agent_config_skill_by_agent") @console_ns.doc( consumes=["multipart/form-data"], params={ "agent_id": "Agent ID", **query_params_from_model(AgentConfigByAgentQuery), **_AGENT_CONFIG_SKILL_UPLOAD_PARAMS, }, ) @console_ns.response(201, "Uploaded config skill", console_ns.models[AgentConfigSkillUploadResponse.__name__]) @setup_required @login_required @account_initialization_required @with_current_user @with_current_tenant_id @with_session def post(self, session: Session, tenant_id: str, current_user: Account, agent_id: UUID): return _with_agent_route_target( session=session, tenant_id=tenant_id, agent_id=agent_id, current_user=current_user, action=_skill_upload_response, ) @console_ns.route("/apps//agent/config/skills/upload") class AgentConfigSkillUploadApi(Resource): @console_ns.doc("upload_agent_config_skill") @console_ns.doc( consumes=["multipart/form-data"], params={ "app_id": "Application ID", **query_params_from_model(AgentConfigQuery), **_AGENT_CONFIG_SKILL_UPLOAD_PARAMS, }, ) @console_ns.response(201, "Uploaded config skill", console_ns.models[AgentConfigSkillUploadResponse.__name__]) @setup_required @login_required @account_initialization_required @edit_permission_required @rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT) @with_current_user @with_session @get_app_model(mode=_WORKFLOW_APP_MODES) def post(self, session: Session, current_user: Account, app_model: App): return _with_app_route_target( session=session, app_model=app_model, current_user=current_user, action=_skill_upload_response ) @console_ns.route("/agent//config/skills") class AgentConfigSkillsByAgentApi(Resource): @console_ns.doc("get_agent_config_skills_by_agent") @console_ns.doc(params={"agent_id": "Agent ID", **query_params_from_model(AgentConfigByAgentQuery)}) @console_ns.response(200, "Config skills", console_ns.models[AgentConfigSkillListResponse.__name__]) @setup_required @login_required @account_initialization_required @with_current_user @with_current_tenant_id @with_session def get(self, session: Session, tenant_id: str, current_user: Account, agent_id: UUID): return _with_agent_route_target( session=session, tenant_id=tenant_id, agent_id=agent_id, current_user=current_user, action=_skill_list_response, ) @console_ns.route("/apps//agent/config/skills") class AgentConfigSkillsApi(Resource): @console_ns.doc("get_agent_config_skills") @console_ns.doc(params={"app_id": "Application ID", **query_params_from_model(AgentConfigQuery)}) @console_ns.response(200, "Config skills", console_ns.models[AgentConfigSkillListResponse.__name__]) @setup_required @login_required @account_initialization_required @with_current_user @with_session @get_app_model(mode=_WORKFLOW_APP_MODES) def get(self, session: Session, current_user: Account, app_model: App): return _with_app_route_target( session=session, app_model=app_model, current_user=current_user, action=_skill_list_response ) @console_ns.route("/agent//config/files") class AgentConfigFilesByAgentApi(Resource): @console_ns.doc("get_agent_config_files_by_agent") @console_ns.doc(params={"agent_id": "Agent ID", **query_params_from_model(AgentConfigByAgentQuery)}) @console_ns.response(200, "Config files", console_ns.models[AgentConfigFileListResponse.__name__]) @setup_required @login_required @account_initialization_required @with_current_user @with_current_tenant_id @with_session def get(self, session: Session, tenant_id: str, current_user: Account, agent_id: UUID): return _with_agent_route_target( session=session, tenant_id=tenant_id, agent_id=agent_id, current_user=current_user, action=_file_list_response, ) @console_ns.doc("upload_agent_config_file_by_agent") @console_ns.doc(params={"agent_id": "Agent ID", **query_params_from_model(AgentConfigByAgentQuery)}) @console_ns.expect(console_ns.models[AgentConfigFileUploadPayload.__name__]) @console_ns.response(201, "Uploaded config file", console_ns.models[AgentConfigFileUploadResponse.__name__]) @setup_required @login_required @account_initialization_required @with_current_user @with_current_tenant_id @with_session def post(self, session: Session, tenant_id: str, current_user: Account, agent_id: UUID): payload = AgentConfigFileUploadPayload.model_validate(console_ns.payload or {}) return _with_agent_route_target( session=session, tenant_id=tenant_id, agent_id=agent_id, current_user=current_user, action=lambda target: _file_upload_response(target, payload), ) @console_ns.route("/apps//agent/config/files") class AgentConfigFilesApi(Resource): @console_ns.doc("get_agent_config_files") @console_ns.doc(params={"app_id": "Application ID", **query_params_from_model(AgentConfigQuery)}) @console_ns.response(200, "Config files", console_ns.models[AgentConfigFileListResponse.__name__]) @setup_required @login_required @account_initialization_required @with_current_user @with_session @get_app_model(mode=_WORKFLOW_APP_MODES) def get(self, session: Session, current_user: Account, app_model: App): return _with_app_route_target( session=session, app_model=app_model, current_user=current_user, action=_file_list_response ) @console_ns.doc("upload_agent_config_file") @console_ns.doc(params={"app_id": "Application ID", **query_params_from_model(AgentConfigQuery)}) @console_ns.expect(console_ns.models[AgentConfigFileUploadPayload.__name__]) @console_ns.response(201, "Uploaded config file", console_ns.models[AgentConfigFileUploadResponse.__name__]) @setup_required @login_required @account_initialization_required @edit_permission_required @rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT) @with_current_user @with_session @get_app_model(mode=_WORKFLOW_APP_MODES) def post(self, session: Session, current_user: Account, app_model: App): payload = AgentConfigFileUploadPayload.model_validate(console_ns.payload or {}) return _with_app_route_target( session=session, app_model=app_model, current_user=current_user, action=lambda target: _file_upload_response(target, payload), ) @console_ns.route("/agent//config/skills//inspect") class AgentConfigSkillInspectByAgentApi(Resource): @console_ns.doc("inspect_agent_config_skill_by_agent") @console_ns.doc( params={"agent_id": "Agent ID", "name": "Config skill name", **query_params_from_model(AgentConfigByAgentQuery)} ) @console_ns.response(200, "Config skill inspect view", console_ns.models[AgentConfigSkillInspectResponse.__name__]) @setup_required @login_required @account_initialization_required @with_current_user @with_current_tenant_id @with_session def get(self, session: Session, tenant_id: str, current_user: Account, agent_id: UUID, name: str): return _with_agent_route_target( session=session, tenant_id=tenant_id, agent_id=agent_id, current_user=current_user, action=lambda target: _skill_inspect_response(target, name), ) @console_ns.route("/apps//agent/config/skills//inspect") class AgentConfigSkillInspectApi(Resource): @console_ns.doc("inspect_agent_config_skill") @console_ns.doc( params={"app_id": "Application ID", "name": "Config skill name", **query_params_from_model(AgentConfigQuery)} ) @console_ns.response(200, "Config skill inspect view", console_ns.models[AgentConfigSkillInspectResponse.__name__]) @setup_required @login_required @account_initialization_required @with_current_user @with_session @get_app_model(mode=_WORKFLOW_APP_MODES) def get(self, session: Session, current_user: Account, app_model: App, name: str): return _with_app_route_target( session=session, app_model=app_model, current_user=current_user, action=lambda target: _skill_inspect_response(target, name), ) @console_ns.route("/agent//config/skills//files/preview") class AgentConfigSkillFilePreviewByAgentApi(Resource): @console_ns.doc("preview_agent_config_skill_file_by_agent") @console_ns.doc( params={ "agent_id": "Agent ID", "name": "Config skill name", **query_params_from_model(AgentConfigSkillFileByAgentQuery), } ) @console_ns.response( 200, "Config skill file preview", console_ns.models[AgentConfigSkillFilePreviewResponse.__name__] ) @setup_required @login_required @account_initialization_required @with_current_user @with_current_tenant_id @with_session def get(self, session: Session, tenant_id: str, current_user: Account, agent_id: UUID, name: str): query = query_params_from_request(AgentConfigSkillFileByAgentQuery) try: target = _resolve_agent_route_target( session=session, tenant_id=tenant_id, agent_id=agent_id, current_user=current_user, query=query, ) return _skill_file_preview_response(target, name, query.path) except AgentConfigServiceError as exc: return _handle(exc) @console_ns.route("/apps//agent/config/skills//files/preview") class AgentConfigSkillFilePreviewApi(Resource): @console_ns.doc("preview_agent_config_skill_file") @console_ns.doc( params={ "app_id": "Application ID", "name": "Config skill name", **query_params_from_model(AgentConfigSkillFileQuery), } ) @console_ns.response( 200, "Config skill file preview", console_ns.models[AgentConfigSkillFilePreviewResponse.__name__] ) @setup_required @login_required @account_initialization_required @with_current_user @with_session @get_app_model(mode=_WORKFLOW_APP_MODES) def get(self, session: Session, current_user: Account, app_model: App, name: str): query = query_params_from_request(AgentConfigSkillFileQuery) try: target = _resolve_app_route_target( session=session, app_model=app_model, current_user=current_user, query=query ) if isinstance(target, tuple): return target return _skill_file_preview_response(target, name, query.path) except AgentConfigServiceError as exc: return _handle(exc) @console_ns.route("/agent//config/skills//download") class AgentConfigSkillDownloadByAgentApi(Resource): @console_ns.doc("download_agent_config_skill_by_agent") @console_ns.doc( params={"agent_id": "Agent ID", "name": "Config skill name", **query_params_from_model(AgentConfigByAgentQuery)} ) @console_ns.response(200, "Config skill download URL", console_ns.models[AgentConfigDownloadResponse.__name__]) @setup_required @login_required @account_initialization_required @with_current_user @with_current_tenant_id @with_session def get(self, session: Session, tenant_id: str, current_user: Account, agent_id: UUID, name: str): return _with_agent_route_target( session=session, tenant_id=tenant_id, agent_id=agent_id, current_user=current_user, action=lambda target: _skill_download_response(target, name), ) @console_ns.route("/apps//agent/config/skills//download") class AgentConfigSkillDownloadApi(Resource): @console_ns.doc("download_agent_config_skill") @console_ns.doc( params={"app_id": "Application ID", "name": "Config skill name", **query_params_from_model(AgentConfigQuery)} ) @console_ns.response(200, "Config skill download URL", console_ns.models[AgentConfigDownloadResponse.__name__]) @setup_required @login_required @account_initialization_required @with_current_user @with_session @get_app_model(mode=_WORKFLOW_APP_MODES) def get(self, session: Session, current_user: Account, app_model: App, name: str): return _with_app_route_target( session=session, app_model=app_model, current_user=current_user, action=lambda target: _skill_download_response(target, name), ) @console_ns.route("/agent//config/skills//files/download") class AgentConfigSkillFileDownloadByAgentApi(Resource): @console_ns.doc("download_agent_config_skill_file_by_agent") @console_ns.doc( params={ "agent_id": "Agent ID", "name": "Config skill name", **query_params_from_model(AgentConfigSkillFileByAgentQuery), } ) @console_ns.response(200, "Config skill file download URL", console_ns.models[AgentConfigDownloadResponse.__name__]) @setup_required @login_required @account_initialization_required @with_current_user @with_current_tenant_id @with_session def get(self, session: Session, tenant_id: str, current_user: Account, agent_id: UUID, name: str): query = query_params_from_request(AgentConfigSkillFileByAgentQuery) try: target = _resolve_agent_route_target( session=session, tenant_id=tenant_id, agent_id=agent_id, current_user=current_user, query=query, ) return _skill_file_download_response( target, name=name, path=query.path, raw_endpoint="console.agent_config_skill_file_download_content_by_agent_api", route_params={"agent_id": str(agent_id)}, ) except AgentConfigServiceError as exc: return _handle(exc) @console_ns.route("/apps//agent/config/skills//files/download") class AgentConfigSkillFileDownloadApi(Resource): @console_ns.doc("download_agent_config_skill_file") @console_ns.doc( params={ "app_id": "Application ID", "name": "Config skill name", **query_params_from_model(AgentConfigSkillFileQuery), } ) @console_ns.response(200, "Config skill file download URL", console_ns.models[AgentConfigDownloadResponse.__name__]) @setup_required @login_required @account_initialization_required @with_current_user @with_session @get_app_model(mode=_WORKFLOW_APP_MODES) def get(self, session: Session, current_user: Account, app_model: App, name: str): query = query_params_from_request(AgentConfigSkillFileQuery) try: target = _resolve_app_route_target( session=session, app_model=app_model, current_user=current_user, query=query ) if isinstance(target, tuple): return target return _skill_file_download_response( target, name=name, path=query.path, raw_endpoint="console.agent_config_skill_file_download_content_api", route_params={"app_id": str(app_model.id)}, extra_query_params={"node_id": query.node_id} if query.node_id else None, ) except AgentConfigServiceError as exc: return _handle(exc) @console_ns.route( "/agent//config/skills//files/content", endpoint="agent_config_skill_file_download_content_by_agent_api", ) class AgentConfigSkillFileDownloadContentByAgentApi(Resource): @setup_required @login_required @account_initialization_required @with_current_user @with_current_tenant_id @with_session def get(self, session: Session, tenant_id: str, current_user: Account, agent_id: UUID, name: str): query = query_params_from_request(AgentConfigSkillFileByAgentQuery) try: target = _resolve_agent_route_target( session=session, tenant_id=tenant_id, agent_id=agent_id, current_user=current_user, query=query, ) return _skill_file_raw_download_response(target, name, query.path) except AgentConfigServiceError as exc: return _handle(exc) @console_ns.route( "/apps//agent/config/skills//files/content", endpoint="agent_config_skill_file_download_content_api", ) class AgentConfigSkillFileDownloadContentApi(Resource): @setup_required @login_required @account_initialization_required @with_current_user @with_session @get_app_model(mode=_WORKFLOW_APP_MODES) def get(self, session: Session, current_user: Account, app_model: App, name: str): query = query_params_from_request(AgentConfigSkillFileQuery) try: target = _resolve_app_route_target( session=session, app_model=app_model, current_user=current_user, query=query ) if isinstance(target, tuple): return target return _skill_file_raw_download_response(target, name, query.path) except AgentConfigServiceError as exc: return _handle(exc) @console_ns.route("/agent//config/skills/") class AgentConfigSkillByAgentApi(Resource): @console_ns.doc("delete_agent_config_skill_by_agent") @console_ns.doc( params={"agent_id": "Agent ID", "name": "Config skill name", **query_params_from_model(AgentConfigByAgentQuery)} ) @console_ns.response(200, "Config skill deleted", console_ns.models[AgentConfigDeleteResponse.__name__]) @setup_required @login_required @account_initialization_required @with_current_user @with_current_tenant_id @with_session def delete(self, session: Session, tenant_id: str, current_user: Account, agent_id: UUID, name: str): return _with_agent_route_target( session=session, tenant_id=tenant_id, agent_id=agent_id, current_user=current_user, action=lambda target: _skill_delete_response(target, name), ) @console_ns.route("/apps//agent/config/skills/") class AgentConfigSkillApi(Resource): @console_ns.doc("delete_agent_config_skill") @console_ns.doc( params={"app_id": "Application ID", "name": "Config skill name", **query_params_from_model(AgentConfigQuery)} ) @console_ns.response(200, "Config skill deleted", console_ns.models[AgentConfigDeleteResponse.__name__]) @setup_required @login_required @account_initialization_required @edit_permission_required @rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT) @with_current_user @with_session @get_app_model(mode=_WORKFLOW_APP_MODES) def delete(self, session: Session, current_user: Account, app_model: App, name: str): return _with_app_route_target( session=session, app_model=app_model, current_user=current_user, action=lambda target: _skill_delete_response(target, name), ) @console_ns.route("/agent//config/files//preview") class AgentConfigFilePreviewByAgentApi(Resource): @console_ns.doc("preview_agent_config_file_by_agent") @console_ns.doc( params={"agent_id": "Agent ID", "name": "Config file name", **query_params_from_model(AgentConfigByAgentQuery)} ) @console_ns.response(200, "Preview", console_ns.models[AgentConfigFilePreviewResponse.__name__]) @setup_required @login_required @account_initialization_required @with_current_user @with_current_tenant_id @with_session def get(self, session: Session, tenant_id: str, current_user: Account, agent_id: UUID, name: str): return _with_agent_route_target( session=session, tenant_id=tenant_id, agent_id=agent_id, current_user=current_user, action=lambda target: _file_preview_response(target, name), ) @console_ns.route("/apps//agent/config/files//preview") class AgentConfigFilePreviewApi(Resource): @console_ns.doc("preview_agent_config_file") @console_ns.doc( params={"app_id": "Application ID", "name": "Config file name", **query_params_from_model(AgentConfigQuery)} ) @console_ns.response(200, "Preview", console_ns.models[AgentConfigFilePreviewResponse.__name__]) @setup_required @login_required @account_initialization_required @with_current_user @with_session @get_app_model(mode=_WORKFLOW_APP_MODES) def get(self, session: Session, current_user: Account, app_model: App, name: str): return _with_app_route_target( session=session, app_model=app_model, current_user=current_user, action=lambda target: _file_preview_response(target, name), ) @console_ns.route("/agent//config/files//download") class AgentConfigFileDownloadByAgentApi(Resource): @console_ns.doc("download_agent_config_file_by_agent") @console_ns.doc( params={"agent_id": "Agent ID", "name": "Config file name", **query_params_from_model(AgentConfigByAgentQuery)} ) @console_ns.response(200, "Config file download URL", console_ns.models[AgentConfigDownloadResponse.__name__]) @setup_required @login_required @account_initialization_required @with_current_user @with_current_tenant_id @with_session def get(self, session: Session, tenant_id: str, current_user: Account, agent_id: UUID, name: str): return _with_agent_route_target( session=session, tenant_id=tenant_id, agent_id=agent_id, current_user=current_user, action=lambda target: _file_download_response(target, name), ) @console_ns.route("/apps//agent/config/files//download") class AgentConfigFileDownloadApi(Resource): @console_ns.doc("download_agent_config_file") @console_ns.doc( params={"app_id": "Application ID", "name": "Config file name", **query_params_from_model(AgentConfigQuery)} ) @console_ns.response(200, "Config file download URL", console_ns.models[AgentConfigDownloadResponse.__name__]) @setup_required @login_required @account_initialization_required @with_current_user @with_session @get_app_model(mode=_WORKFLOW_APP_MODES) def get(self, session: Session, current_user: Account, app_model: App, name: str): return _with_app_route_target( session=session, app_model=app_model, current_user=current_user, action=lambda target: _file_download_response(target, name), ) @console_ns.route("/agent//config/files/") class AgentConfigFileByAgentApi(Resource): @console_ns.doc("delete_agent_config_file_by_agent") @console_ns.doc( params={"agent_id": "Agent ID", "name": "Config file name", **query_params_from_model(AgentConfigByAgentQuery)} ) @console_ns.response(200, "Config file deleted", console_ns.models[AgentConfigDeleteResponse.__name__]) @setup_required @login_required @account_initialization_required @with_current_user @with_current_tenant_id @with_session def delete(self, session: Session, tenant_id: str, current_user: Account, agent_id: UUID, name: str): return _with_agent_route_target( session=session, tenant_id=tenant_id, agent_id=agent_id, current_user=current_user, action=lambda target: _file_delete_response(target, name), ) @console_ns.route("/apps//agent/config/files/") class AgentConfigFileApi(Resource): @console_ns.doc("delete_agent_config_file") @console_ns.doc( params={"app_id": "Application ID", "name": "Config file name", **query_params_from_model(AgentConfigQuery)} ) @console_ns.response(200, "Config file deleted", console_ns.models[AgentConfigDeleteResponse.__name__]) @setup_required @login_required @account_initialization_required @edit_permission_required @rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT) @with_current_user @with_session @get_app_model(mode=_WORKFLOW_APP_MODES) def delete(self, session: Session, current_user: Account, app_model: App, name: str): return _with_app_route_target( session=session, app_model=app_model, current_user=current_user, action=lambda target: _file_delete_response(target, name), ) __all__ = [name for name, value in globals().items() if inspect.isclass(value) and issubclass(value, Resource)]