"""Agent App presentation-feature configuration endpoint. The new Agent App type keeps model / prompt / tools in its bound Agent Soul, so the legacy ``/model-config`` surface (which writes model, prompt and agent tool config) is the wrong place to configure its app-level presentation features. This endpoint exposes only the PRD "Misc Legacy" feature subset — conversation opener, follow-up suggestions, citations, content moderation and speech — and persists them onto the app's ``app_model_config`` without touching anything the Soul owns. """ from typing import Any from flask_restx import Resource from pydantic import BaseModel, Field from controllers.common.fields import SimpleResultResponse from controllers.common.schema import register_response_schema_models, register_schema_models from controllers.console import console_ns from controllers.console.app.wraps import get_app_model from controllers.console.wraps import ( account_initialization_required, edit_permission_required, setup_required, with_current_user, ) from events.app_event import app_model_config_was_updated from libs.login import login_required from models import Account from models.model import App, AppMode from services.agent_app_feature_service import AgentAppFeatureConfigService class AgentAppFeaturesRequest(BaseModel): """Presentation features configurable on an Agent App. All fields are optional; an omitted field is reset to its disabled/empty default (the config form sends the full desired feature state on save). """ opening_statement: str | None = Field(default=None, description="Conversation opener shown before the first turn") suggested_questions: list[str] | None = Field( default=None, description="Preset questions shown alongside the opener" ) suggested_questions_after_answer: dict[str, Any] | None = Field( default=None, description="Follow-up suggestions config, e.g. {'enabled': true}" ) speech_to_text: dict[str, Any] | None = Field(default=None, description="Speech-to-text config") text_to_speech: dict[str, Any] | None = Field(default=None, description="Text-to-speech config") retriever_resource: dict[str, Any] | None = Field( default=None, description="Citations / attributions config, e.g. {'enabled': true}" ) sensitive_word_avoidance: dict[str, Any] | None = Field(default=None, description="Content moderation config") register_schema_models(console_ns, AgentAppFeaturesRequest) register_response_schema_models(console_ns, SimpleResultResponse) @console_ns.route("/apps//agent-features") class AgentAppFeatureConfigResource(Resource): @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(params={"app_id": "Application ID"}) @console_ns.expect(console_ns.models[AgentAppFeaturesRequest.__name__]) @console_ns.response(200, "Features updated successfully", console_ns.models[SimpleResultResponse.__name__]) @console_ns.response(400, "Invalid configuration") @console_ns.response(404, "App not found") @setup_required @login_required @edit_permission_required @account_initialization_required @get_app_model(mode=[AppMode.AGENT]) @with_current_user def post(self, current_user: Account, app_model: App): args = AgentAppFeaturesRequest.model_validate(console_ns.payload) new_app_model_config = AgentAppFeatureConfigService.update_features( app_model=app_model, account=current_user, config=args.model_dump(exclude_none=True), ) app_model_config_was_updated.send(app_model, app_model_config=new_app_model_config) return {"result": "success"}