feat: included service center in playground feature to replace old services

This commit is contained in:
Ida Dittrich 2026-03-06 19:21:34 +01:00
parent 1e678a8897
commit 13322e7cf8
3 changed files with 142 additions and 20 deletions

View file

@ -6,7 +6,7 @@ Handles feature initialization and RBAC catalog registration.
""" """
import logging import logging
from typing import Dict, List, Any from typing import Dict, List, Any, Optional
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -48,6 +48,16 @@ RESOURCE_OBJECTS = [
}, },
] ]
# Service requirements - services this feature needs from the service center
# Same as automation: chatplayground runs the same WorkflowManager and workflow methods
REQUIRED_SERVICES = [
{"serviceKey": "chat", "meta": {"usage": "Workflow CRUD, messages, logs"}},
{"serviceKey": "ai", "meta": {"usage": "AI planning for workflow execution"}},
{"serviceKey": "utils", "meta": {"usage": "Timestamps, utilities"}},
{"serviceKey": "billing", "meta": {"usage": "AI call billing"}},
{"serviceKey": "extraction", "meta": {"usage": "Workflow method actions"}},
{"serviceKey": "sharepoint", "meta": {"usage": "SharePoint actions (listDocuments, uploadDocument, etc.)"}},
]
# Template roles for this feature # Template roles for this feature
# Role names MUST follow convention: {featureCode}-{roleName} # Role names MUST follow convention: {featureCode}-{roleName}
TEMPLATE_ROLES = [ TEMPLATE_ROLES = [
@ -104,6 +114,88 @@ TEMPLATE_ROLES = [
] ]
def getRequiredServiceKeys() -> List[str]:
"""Return list of service keys this feature requires."""
return [s["serviceKey"] for s in REQUIRED_SERVICES]
def getChatplaygroundServices(
user,
mandateId: Optional[str] = None,
featureInstanceId: Optional[str] = None,
workflow=None,
) -> "_ChatplaygroundServiceHub":
"""
Get a service hub for the chatplayground feature using the service center.
Resolves only the services declared in REQUIRED_SERVICES.
No legacy fallback - service center only.
Returns a hub-like object with: chat, ai, utils, billing, extraction,
sharepoint, rbac, interfaceDbApp, interfaceDbComponent, interfaceDbChat.
"""
from modules.serviceCenter import getService
from modules.serviceCenter.context import ServiceCenterContext
_workflow = workflow
if _workflow is None:
_workflow = type("_Placeholder", (), {"featureCode": FEATURE_CODE})()
ctx = ServiceCenterContext(
user=user,
mandate_id=mandateId,
feature_instance_id=featureInstanceId,
workflow=_workflow,
)
hub = _ChatplaygroundServiceHub()
hub.user = user
hub.mandateId = mandateId
hub.featureInstanceId = featureInstanceId
hub.workflow = workflow
hub.featureCode = FEATURE_CODE
hub.allowedProviders = None
for spec in REQUIRED_SERVICES:
key = spec["serviceKey"]
try:
svc = getService(key, ctx, legacy_hub=None)
setattr(hub, key, svc)
except Exception as e:
logger.warning(f"Could not resolve service '{key}' for chatplayground: {e}")
setattr(hub, key, None)
# Copy interfaces from chat service for WorkflowManager compatibility
if hub.chat:
hub.interfaceDbApp = getattr(hub.chat, "interfaceDbApp", None)
hub.interfaceDbComponent = getattr(hub.chat, "interfaceDbComponent", None)
hub.interfaceDbChat = getattr(hub.chat, "interfaceDbChat", None)
# RBAC for MethodBase action permission checks (workflow methods)
hub.rbac = getattr(hub.interfaceDbApp, "rbac", None) if hub.interfaceDbApp else None
return hub
class _ChatplaygroundServiceHub:
"""Lightweight hub exposing only services required by the chatplayground feature."""
user = None
mandateId = None
featureInstanceId = None
workflow = None
featureCode = "chatplayground"
allowedProviders = None
interfaceDbApp = None
interfaceDbComponent = None
interfaceDbChat = None
rbac = None
chat = None
ai = None
utils = None
billing = None
extraction = None
sharepoint = None
def getFeatureDefinition() -> Dict[str, Any]: def getFeatureDefinition() -> Dict[str, Any]:
"""Return the feature definition for registration.""" """Return the feature definition for registration."""
return { return {

View file

@ -20,6 +20,7 @@ from modules.datamodels.datamodelChat import ChatWorkflow, UserInputRequest, Wor
# Import workflow control functions # Import workflow control functions
from modules.workflows.automation import chatStart, chatStop from modules.workflows.automation import chatStart, chatStop
from modules.features.chatplayground.mainChatplayground import getChatplaygroundServices
# Configure logger # Configure logger
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -95,15 +96,26 @@ async def start_workflow(
# Validate access and get mandate ID # Validate access and get mandate ID
mandateId = _validateInstanceAccess(instanceId, context) mandateId = _validateInstanceAccess(instanceId, context)
# Start or continue workflow # Get chatplayground services from service center (not automation)
workflow = await chatStart( services = getChatplaygroundServices(
context.user, context.user,
userInput,
workflowMode,
workflowId,
mandateId=mandateId, mandateId=mandateId,
featureInstanceId=instanceId, featureInstanceId=instanceId,
featureCode='chatplayground' )
services.featureCode = 'chatplayground'
if hasattr(userInput, 'allowedProviders') and userInput.allowedProviders:
services.allowedProviders = userInput.allowedProviders
# Start or continue workflow
workflow = await chatStart(
context.user,
userInput,
workflowMode,
workflowId,
mandateId=mandateId,
featureInstanceId=instanceId,
featureCode='chatplayground',
services=services,
) )
return workflow return workflow
@ -132,12 +144,22 @@ async def stop_workflow(
# Validate access and get mandate ID # Validate access and get mandate ID
mandateId = _validateInstanceAccess(instanceId, context) mandateId = _validateInstanceAccess(instanceId, context)
# Get chatplayground services from service center (not automation)
services = getChatplaygroundServices(
context.user,
mandateId=mandateId,
featureInstanceId=instanceId,
)
services.featureCode = 'chatplayground'
# Stop workflow (pass featureInstanceId for proper RBAC filtering) # Stop workflow (pass featureInstanceId for proper RBAC filtering)
workflow = await chatStop( workflow = await chatStop(
context.user, context.user,
workflowId, workflowId,
mandateId=mandateId, mandateId=mandateId,
featureInstanceId=instanceId featureInstanceId=instanceId,
featureCode='chatplayground',
services=services,
) )
return workflow return workflow

View file

@ -24,7 +24,7 @@ from .subAutomationUtils import parseScheduleToCron, planToPrompt, replacePlaceh
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
async def chatStart(currentUser: User, userInput: UserInputRequest, workflowMode: WorkflowModeEnum, workflowId: Optional[str] = None, mandateId: Optional[str] = None, featureInstanceId: Optional[str] = None, featureCode: Optional[str] = None) -> ChatWorkflow: async def chatStart(currentUser: User, userInput: UserInputRequest, workflowMode: WorkflowModeEnum, workflowId: Optional[str] = None, mandateId: Optional[str] = None, featureInstanceId: Optional[str] = None, featureCode: Optional[str] = None, services=None) -> ChatWorkflow:
""" """
Starts a new chat or continues an existing one, then launches processing asynchronously. Starts a new chat or continues an existing one, then launches processing asynchronously.
@ -36,9 +36,11 @@ async def chatStart(currentUser: User, userInput: UserInputRequest, workflowMode
mandateId: Mandate ID (required for billing) mandateId: Mandate ID (required for billing)
featureInstanceId: Feature instance ID (required for billing) featureInstanceId: Feature instance ID (required for billing)
featureCode: Feature code (e.g., 'chatplayground', 'automation') featureCode: Feature code (e.g., 'chatplayground', 'automation')
services: Pre-built service hub from the calling feature (required). Each feature must pass its own services.
""" """
if services is None:
raise ValueError("services is required: each feature must pass its own service hub (e.g. getChatplaygroundServices, getAutomationServices)")
try: try:
services = getAutomationServices(currentUser, mandateId=mandateId, featureInstanceId=featureInstanceId)
# Store allowedProviders in services context for model selection # Store allowedProviders in services context for model selection
if hasattr(userInput, 'allowedProviders') and userInput.allowedProviders: if hasattr(userInput, 'allowedProviders') and userInput.allowedProviders:
@ -56,10 +58,11 @@ async def chatStart(currentUser: User, userInput: UserInputRequest, workflowMode
logger.error(f"Error starting chat: {str(e)}") logger.error(f"Error starting chat: {str(e)}")
raise raise
async def chatStop(currentUser: User, workflowId: str, mandateId: Optional[str] = None, featureInstanceId: Optional[str] = None, featureCode: Optional[str] = None) -> ChatWorkflow: async def chatStop(currentUser: User, workflowId: str, mandateId: Optional[str] = None, featureInstanceId: Optional[str] = None, featureCode: Optional[str] = None, services=None) -> ChatWorkflow:
"""Stops a running chat.""" """Stops a running chat. Caller must pass services from the owning feature."""
if services is None:
raise ValueError("services is required: each feature must pass its own service hub (e.g. getChatplaygroundServices, getAutomationServices)")
try: try:
services = getAutomationServices(currentUser, mandateId=mandateId, featureInstanceId=featureInstanceId)
if featureCode: if featureCode:
services.featureCode = featureCode services.featureCode = featureCode
workflowManager = WorkflowManager(services) workflowManager = WorkflowManager(services)
@ -146,6 +149,12 @@ async def executeAutomation(automationId: str, automation, creatorUser: User, se
# 3. Start workflow using chatStart with creator's context # 3. Start workflow using chatStart with creator's context
# mandateId and featureInstanceId come from the automation definition # mandateId and featureInstanceId come from the automation definition
# Each feature must pass its own services - no fallback
creatorServices = getAutomationServices(
creatorUser,
mandateId=automationMandateId,
featureInstanceId=automationFeatureInstanceId,
)
workflow = await chatStart( workflow = await chatStart(
currentUser=creatorUser, currentUser=creatorUser,
userInput=userInput, userInput=userInput,
@ -153,7 +162,8 @@ async def executeAutomation(automationId: str, automation, creatorUser: User, se
workflowId=None, workflowId=None,
mandateId=automationMandateId, mandateId=automationMandateId,
featureInstanceId=automationFeatureInstanceId, featureInstanceId=automationFeatureInstanceId,
featureCode='automation' featureCode='automation',
services=creatorServices,
) )
executionLog["workflowId"] = workflow.id executionLog["workflowId"] = workflow.id
@ -161,9 +171,7 @@ async def executeAutomation(automationId: str, automation, creatorUser: User, se
executionLog["messages"].append(f"Workflow {workflow.id} started successfully") executionLog["messages"].append(f"Workflow {workflow.id} started successfully")
logger.info(f"Started workflow {workflow.id} with plan containing {len(plan.get('tasks', []))} tasks (plan embedded in userInput)") logger.info(f"Started workflow {workflow.id} with plan containing {len(plan.get('tasks', []))} tasks (plan embedded in userInput)")
# Set workflow name with "automated" prefix — use creatorUser's Services # Set workflow name with "automated" prefix — use creatorServices from chatStart
# (services parameter is eventServices with eventUser context, must use creatorUser context)
creatorServices = getAutomationServices(creatorUser, mandateId=automationMandateId, featureInstanceId=automationFeatureInstanceId)
automationLabel = automation.label or "Unknown Automation" automationLabel = automation.label or "Unknown Automation"
workflowName = f"automated: {automationLabel}" workflowName = f"automated: {automationLabel}"
creatorServices.interfaceDbChat.updateWorkflow(workflow.id, {"name": workflowName}) creatorServices.interfaceDbChat.updateWorkflow(workflow.id, {"name": workflowName})