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
from typing import Dict, List, Any
from typing import Dict, List, Any, Optional
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
# Role names MUST follow convention: {featureCode}-{roleName}
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]:
"""Return the feature definition for registration."""
return {

View file

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

View file

@ -24,7 +24,7 @@ from .subAutomationUtils import parseScheduleToCron, planToPrompt, replacePlaceh
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.
@ -36,9 +36,11 @@ async def chatStart(currentUser: User, userInput: UserInputRequest, workflowMode
mandateId: Mandate ID (required for billing)
featureInstanceId: Feature instance ID (required for billing)
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:
services = getAutomationServices(currentUser, mandateId=mandateId, featureInstanceId=featureInstanceId)
# Store allowedProviders in services context for model selection
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)}")
raise
async def chatStop(currentUser: User, workflowId: str, mandateId: Optional[str] = None, featureInstanceId: Optional[str] = None, featureCode: Optional[str] = None) -> ChatWorkflow:
"""Stops a running chat."""
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. 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:
services = getAutomationServices(currentUser, mandateId=mandateId, featureInstanceId=featureInstanceId)
if featureCode:
services.featureCode = featureCode
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
# 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(
currentUser=creatorUser,
userInput=userInput,
@ -153,7 +162,8 @@ async def executeAutomation(automationId: str, automation, creatorUser: User, se
workflowId=None,
mandateId=automationMandateId,
featureInstanceId=automationFeatureInstanceId,
featureCode='automation'
featureCode='automation',
services=creatorServices,
)
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")
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
# (services parameter is eventServices with eventUser context, must use creatorUser context)
creatorServices = getAutomationServices(creatorUser, mandateId=automationMandateId, featureInstanceId=automationFeatureInstanceId)
# Set workflow name with "automated" prefix — use creatorServices from chatStart
automationLabel = automation.label or "Unknown Automation"
workflowName = f"automated: {automationLabel}"
creatorServices.interfaceDbChat.updateWorkflow(workflow.id, {"name": workflowName})