"""Inner API for Agent Soul-backed config assets. These endpoints are called by the dify-agent server with the inner API key. They resolve the requested Agent config version directly from Agent Soul JSON and authorize downloads with the existing Config target, tenant, and source ownership semantics. Download requests return metadata plus a short-lived, origin-free ``/files/*`` URI, never file bytes; the Sandbox fetches those bytes directly from the Dify API data plane. """ from __future__ import annotations from typing import Literal from flask import request from flask_restx import Resource from pydantic import BaseModel, ConfigDict, ValidationError, model_validator from controllers.console.wraps import setup_required from controllers.inner_api import inner_api_ns from controllers.inner_api.wraps import plugin_inner_api_only from models.agent_config_entities import validate_config_name, validate_config_skill_name from services.agent_config_service import ( AgentConfigService, AgentConfigServiceError, AgentConfigVersionKind, ConfigPushPayload, ) class _ConfigTargetQuery(BaseModel): tenant_id: str user_id: str | None = None config_version_id: str config_version_kind: AgentConfigVersionKind class _ConfigMutationRequest(BaseModel): tenant_id: str user_id: str config_version_id: str config_version_kind: AgentConfigVersionKind class _ConfigDownloadSource(BaseModel): model_config = ConfigDict(extra="forbid") kind: Literal["file", "skill"] name: str @model_validator(mode="after") def validate_name(self) -> _ConfigDownloadSource: self.name = validate_config_skill_name(self.name) if self.kind == "skill" else validate_config_name(self.name) return self class _ConfigDownloadRequest(_ConfigTargetQuery): model_config = ConfigDict(extra="forbid") config: _ConfigDownloadSource class _ConfigPushRequest(_ConfigMutationRequest): files: list[dict] = [] skills: list[dict] = [] env_text: str | None = None note: str | None = None def to_payload(self) -> ConfigPushPayload: return ConfigPushPayload.model_validate( { "files": self.files, "skills": self.skills, "env_text": self.env_text, "note": self.note, } ) class _ConfigEnvUpdateRequest(_ConfigMutationRequest): env_text: str class _ConfigNoteUpdateRequest(_ConfigMutationRequest): note: str def _target_query_from_request() -> _ConfigTargetQuery: return _ConfigTargetQuery.model_validate( { "tenant_id": request.args.get("tenant_id"), "user_id": request.args.get("user_id"), "config_version_id": request.args.get("config_version_id"), "config_version_kind": request.args.get("config_version_kind"), } ) def _error_response(exc: AgentConfigServiceError) -> tuple[dict[str, str], int]: return {"code": exc.code, "message": exc.message}, exc.status_code @inner_api_ns.route("/agent-config//manifest") class AgentConfigManifestApi(Resource): @setup_required @plugin_inner_api_only @inner_api_ns.doc("agent_config_manifest") def get(self, agent_id: str): try: query = _target_query_from_request() return AgentConfigService().manifest( tenant_id=query.tenant_id, agent_id=agent_id, user_id=query.user_id, config_version_id=query.config_version_id, config_version_kind=query.config_version_kind, ) except ValidationError as exc: return {"code": "invalid_request", "message": str(exc)}, 400 except AgentConfigServiceError as exc: return _error_response(exc) @inner_api_ns.route("/agent-config//download-request") class AgentConfigDownloadRequestApi(Resource): @setup_required @plugin_inner_api_only @inner_api_ns.doc("agent_config_download_request") def post(self, agent_id: str): try: body = _ConfigDownloadRequest.model_validate(request.get_json(silent=True) or {}) result = AgentConfigService().request_download( tenant_id=body.tenant_id, agent_id=agent_id, user_id=body.user_id, config_version_id=body.config_version_id, config_version_kind=body.config_version_kind, kind=body.config.kind, name=body.config.name, ) return { "filename": result.filename, "mime_type": result.mime_type, "size": result.size, "download_uri": result.download_uri, } except ValidationError as exc: return {"code": "invalid_request", "message": str(exc)}, 400 except AgentConfigServiceError as exc: return _error_response(exc) @inner_api_ns.route("/agent-config//skills//inspect") class AgentConfigSkillInspectApi(Resource): @setup_required @plugin_inner_api_only @inner_api_ns.doc("agent_config_skill_inspect") def get(self, agent_id: str, name: str): try: query = _target_query_from_request() return AgentConfigService().inspect_skill( tenant_id=query.tenant_id, agent_id=agent_id, user_id=query.user_id, config_version_id=query.config_version_id, config_version_kind=query.config_version_kind, name=name, ) except ValidationError as exc: return {"code": "invalid_request", "message": str(exc)}, 400 except AgentConfigServiceError as exc: return _error_response(exc) @inner_api_ns.route("/agent-config//push") class AgentConfigPushApi(Resource): @setup_required @plugin_inner_api_only @inner_api_ns.doc("agent_config_push") def post(self, agent_id: str): try: body = _ConfigPushRequest.model_validate(request.get_json(silent=True) or {}) return AgentConfigService().push( tenant_id=body.tenant_id, agent_id=agent_id, user_id=body.user_id, config_version_id=body.config_version_id, config_version_kind=body.config_version_kind, payload=body.to_payload(), ) except ValidationError as exc: return {"code": "invalid_request", "message": str(exc)}, 400 except AgentConfigServiceError as exc: return _error_response(exc) @inner_api_ns.route("/agent-config//env") class AgentConfigEnvApi(Resource): @setup_required @plugin_inner_api_only @inner_api_ns.doc("agent_config_env") def patch(self, agent_id: str): try: body = _ConfigEnvUpdateRequest.model_validate(request.get_json(silent=True) or {}) return AgentConfigService().update_env( tenant_id=body.tenant_id, agent_id=agent_id, user_id=body.user_id, config_version_id=body.config_version_id, config_version_kind=body.config_version_kind, env_text=body.env_text, ) except ValidationError as exc: return {"code": "invalid_request", "message": str(exc)}, 400 except AgentConfigServiceError as exc: return _error_response(exc) @inner_api_ns.route("/agent-config//note") class AgentConfigNoteApi(Resource): @setup_required @plugin_inner_api_only @inner_api_ns.doc("agent_config_note") def put(self, agent_id: str): try: body = _ConfigNoteUpdateRequest.model_validate(request.get_json(silent=True) or {}) return AgentConfigService().update_note( tenant_id=body.tenant_id, agent_id=agent_id, user_id=body.user_id, config_version_id=body.config_version_id, config_version_kind=body.config_version_kind, note=body.note, ) except ValidationError as exc: return {"code": "invalid_request", "message": str(exc)}, 400 except AgentConfigServiceError as exc: return _error_response(exc)