fix(automation): reorder routes — static paths before dynamic {id} to prevent 400 errors
This commit is contained in:
@@ -139,6 +139,64 @@ async def create_agent(
|
|||||||
return _agent_to_response(agent)
|
return _agent_to_response(agent)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/tools",
|
||||||
|
dependencies=[Depends(require_permission("agents:read"))],
|
||||||
|
)
|
||||||
|
async def list_tools(
|
||||||
|
current_user: dict[str, Any] = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""List available tools from the tool registry."""
|
||||||
|
try:
|
||||||
|
from app.plugins.builtins.ai_assistant.tool_registry import (
|
||||||
|
get_tool_registry,
|
||||||
|
)
|
||||||
|
|
||||||
|
registry = get_tool_registry()
|
||||||
|
tools = registry.list_tools()
|
||||||
|
return {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": t.get("id", t.get("name", "")),
|
||||||
|
"name": t.get("name", ""),
|
||||||
|
"description": t.get("description", ""),
|
||||||
|
"plugin": t.get("plugin", ""),
|
||||||
|
}
|
||||||
|
for t in tools
|
||||||
|
],
|
||||||
|
"total": len(tools),
|
||||||
|
}
|
||||||
|
except ImportError:
|
||||||
|
return {"items": [], "total": 0, "note": "Tool registry not available"}
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Failed to list tools")
|
||||||
|
return {"items": [], "total": 0, "error": str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
# ─── Recent Runs ───
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/runs/recent",
|
||||||
|
dependencies=[Depends(require_permission("agents:read"))],
|
||||||
|
response_model=AgentRunListResponse,
|
||||||
|
)
|
||||||
|
async def list_recent_agent_runs(
|
||||||
|
limit: int = Query(20, ge=1, le=100),
|
||||||
|
status: str | None = Query(None),
|
||||||
|
current_user: dict[str, Any] = Depends(get_current_user),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""List recent agent runs across all agents (for dashboard)."""
|
||||||
|
tenant_id = uuid.UUID(current_user["tenant_id"])
|
||||||
|
items, total = await RunLogService.list_agent_runs(
|
||||||
|
db, tenant_id, agent_id=None, status=status, limit=limit, offset=0
|
||||||
|
)
|
||||||
|
return AgentRunListResponse(
|
||||||
|
items=[_agent_run_to_response(r) for r in items],
|
||||||
|
total=total,
|
||||||
|
)
|
||||||
@router.get(
|
@router.get(
|
||||||
"/{agent_id}",
|
"/{agent_id}",
|
||||||
dependencies=[Depends(require_permission("agents:read"))],
|
dependencies=[Depends(require_permission("agents:read"))],
|
||||||
@@ -419,61 +477,3 @@ async def send_agent_message_endpoint(
|
|||||||
)
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
|
||||||
"/tools",
|
|
||||||
dependencies=[Depends(require_permission("agents:read"))],
|
|
||||||
)
|
|
||||||
async def list_tools(
|
|
||||||
current_user: dict[str, Any] = Depends(get_current_user),
|
|
||||||
):
|
|
||||||
"""List available tools from the tool registry."""
|
|
||||||
try:
|
|
||||||
from app.plugins.builtins.ai_assistant.tool_registry import (
|
|
||||||
get_tool_registry,
|
|
||||||
)
|
|
||||||
|
|
||||||
registry = get_tool_registry()
|
|
||||||
tools = registry.list_tools()
|
|
||||||
return {
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": t.get("id", t.get("name", "")),
|
|
||||||
"name": t.get("name", ""),
|
|
||||||
"description": t.get("description", ""),
|
|
||||||
"plugin": t.get("plugin", ""),
|
|
||||||
}
|
|
||||||
for t in tools
|
|
||||||
],
|
|
||||||
"total": len(tools),
|
|
||||||
}
|
|
||||||
except ImportError:
|
|
||||||
return {"items": [], "total": 0, "note": "Tool registry not available"}
|
|
||||||
except Exception as e:
|
|
||||||
logger.exception("Failed to list tools")
|
|
||||||
return {"items": [], "total": 0, "error": str(e)}
|
|
||||||
|
|
||||||
|
|
||||||
# ─── Recent Runs ───
|
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
|
||||||
"/runs/recent",
|
|
||||||
dependencies=[Depends(require_permission("agents:read"))],
|
|
||||||
response_model=AgentRunListResponse,
|
|
||||||
)
|
|
||||||
async def list_recent_agent_runs(
|
|
||||||
limit: int = Query(20, ge=1, le=100),
|
|
||||||
status: str | None = Query(None),
|
|
||||||
current_user: dict[str, Any] = Depends(get_current_user),
|
|
||||||
db: AsyncSession = Depends(get_db),
|
|
||||||
):
|
|
||||||
"""List recent agent runs across all agents (for dashboard)."""
|
|
||||||
tenant_id = uuid.UUID(current_user["tenant_id"])
|
|
||||||
items, total = await RunLogService.list_agent_runs(
|
|
||||||
db, tenant_id, agent_id=None, status=status, limit=limit, offset=0
|
|
||||||
)
|
|
||||||
return AgentRunListResponse(
|
|
||||||
items=[_agent_run_to_response(r) for r in items],
|
|
||||||
total=total,
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -142,6 +142,134 @@ async def create_automation(
|
|||||||
return _automation_to_response(automation)
|
return _automation_to_response(automation)
|
||||||
|
|
||||||
|
|
||||||
|
# ─── Recent Runs ───
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/runs/recent",
|
||||||
|
dependencies=[Depends(require_permission("automation:read"))],
|
||||||
|
response_model=AutomationRunListResponse,
|
||||||
|
)
|
||||||
|
async def list_recent_automation_runs(
|
||||||
|
limit: int = Query(20, ge=1, le=100),
|
||||||
|
status: str | None = Query(None),
|
||||||
|
current_user: dict[str, Any] = Depends(get_current_user),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""List recent automation runs across all automations (for dashboard)."""
|
||||||
|
tenant_id = uuid.UUID(current_user["tenant_id"])
|
||||||
|
items, total = await RunLogService.list_automation_runs(
|
||||||
|
db, tenant_id, automation_id=None, status=status, limit=limit, offset=0
|
||||||
|
)
|
||||||
|
return AutomationRunListResponse(
|
||||||
|
items=[_run_to_response(r) for r in items],
|
||||||
|
total=total,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ─── MiniApps ───
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/miniapps",
|
||||||
|
dependencies=[Depends(require_permission("automation:read"))],
|
||||||
|
response_model=list[MiniAppResponse],
|
||||||
|
)
|
||||||
|
async def list_miniapps(
|
||||||
|
current_user: dict[str, Any] = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""List custom MiniApps from plugin config."""
|
||||||
|
from app.plugins.builtins.kommunikation.miniapp_registry import MiniAppRegistry
|
||||||
|
registry = MiniAppRegistry()
|
||||||
|
return registry.list_apps()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/miniapps",
|
||||||
|
dependencies=[Depends(require_permission("automation:write"))],
|
||||||
|
response_model=MiniAppResponse,
|
||||||
|
status_code=201,
|
||||||
|
)
|
||||||
|
async def create_miniapp(
|
||||||
|
data: MiniAppCreate,
|
||||||
|
current_user: dict[str, Any] = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""Create a custom MiniApp definition."""
|
||||||
|
from app.plugins.builtins.kommunikation.miniapp_registry import MiniAppRegistry
|
||||||
|
registry = MiniAppRegistry()
|
||||||
|
registry.register(
|
||||||
|
app_id=data.app_id,
|
||||||
|
name=data.name,
|
||||||
|
icon=data.icon,
|
||||||
|
description=data.description,
|
||||||
|
plugin_name="automation",
|
||||||
|
render_schema=data.render_schema,
|
||||||
|
)
|
||||||
|
return MiniAppResponse(
|
||||||
|
app_id=data.app_id,
|
||||||
|
name=data.name,
|
||||||
|
icon=data.icon,
|
||||||
|
description=data.description,
|
||||||
|
plugin_name="automation",
|
||||||
|
render_schema=data.render_schema,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete(
|
||||||
|
"/miniapps/{app_id}",
|
||||||
|
dependencies=[Depends(require_permission("automation:delete"))],
|
||||||
|
)
|
||||||
|
async def delete_miniapp(
|
||||||
|
app_id: str,
|
||||||
|
current_user: dict[str, Any] = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""Delete a custom MiniApp definition."""
|
||||||
|
from app.plugins.builtins.kommunikation.miniapp_registry import MiniAppRegistry
|
||||||
|
registry = MiniAppRegistry()
|
||||||
|
registry.unregister(app_id)
|
||||||
|
return {"status": "ok"}
|
||||||
|
|
||||||
|
|
||||||
|
# ─── Settings ───
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/settings",
|
||||||
|
dependencies=[Depends(require_permission("automation:configure"))],
|
||||||
|
response_model=AutomationSettingsResponse,
|
||||||
|
)
|
||||||
|
async def get_automation_settings(
|
||||||
|
current_user: dict[str, Any] = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""Get automation default settings."""
|
||||||
|
# Return default settings (could be stored in plugin config in the future)
|
||||||
|
return AutomationSettingsResponse(
|
||||||
|
default_llm_model="ollama/deepseek-v4-flash",
|
||||||
|
heartbeat_default_interval=300,
|
||||||
|
max_concurrent_agents=5,
|
||||||
|
log_level="INFO",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch(
|
||||||
|
"/settings",
|
||||||
|
dependencies=[Depends(require_permission("automation:configure"))],
|
||||||
|
response_model=AutomationSettingsResponse,
|
||||||
|
)
|
||||||
|
async def update_automation_settings(
|
||||||
|
data: AutomationSettingsUpdate,
|
||||||
|
current_user: dict[str, Any] = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""Update automation settings (stored in plugin config)."""
|
||||||
|
# TODO: Persist settings to plugin config table
|
||||||
|
# For now, return the updated values as a preview
|
||||||
|
settings = AutomationSettingsResponse(
|
||||||
|
default_llm_model=data.default_llm_model or "ollama/deepseek-v4-flash",
|
||||||
|
heartbeat_default_interval=data.heartbeat_default_interval or 300,
|
||||||
|
max_concurrent_agents=data.max_concurrent_agents or 5,
|
||||||
|
log_level=data.log_level or "INFO",
|
||||||
|
)
|
||||||
|
return settings
|
||||||
@router.get(
|
@router.get(
|
||||||
"/{automation_id}",
|
"/{automation_id}",
|
||||||
dependencies=[Depends(require_permission("automation:read"))],
|
dependencies=[Depends(require_permission("automation:read"))],
|
||||||
@@ -415,131 +543,3 @@ async def restore_automation_version(
|
|||||||
return _automation_to_response(automation)
|
return _automation_to_response(automation)
|
||||||
|
|
||||||
|
|
||||||
# ─── Recent Runs ───
|
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
|
||||||
"/runs/recent",
|
|
||||||
dependencies=[Depends(require_permission("automation:read"))],
|
|
||||||
response_model=AutomationRunListResponse,
|
|
||||||
)
|
|
||||||
async def list_recent_automation_runs(
|
|
||||||
limit: int = Query(20, ge=1, le=100),
|
|
||||||
status: str | None = Query(None),
|
|
||||||
current_user: dict[str, Any] = Depends(get_current_user),
|
|
||||||
db: AsyncSession = Depends(get_db),
|
|
||||||
):
|
|
||||||
"""List recent automation runs across all automations (for dashboard)."""
|
|
||||||
tenant_id = uuid.UUID(current_user["tenant_id"])
|
|
||||||
items, total = await RunLogService.list_automation_runs(
|
|
||||||
db, tenant_id, automation_id=None, status=status, limit=limit, offset=0
|
|
||||||
)
|
|
||||||
return AutomationRunListResponse(
|
|
||||||
items=[_run_to_response(r) for r in items],
|
|
||||||
total=total,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ─── MiniApps ───
|
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
|
||||||
"/miniapps",
|
|
||||||
dependencies=[Depends(require_permission("automation:read"))],
|
|
||||||
response_model=list[MiniAppResponse],
|
|
||||||
)
|
|
||||||
async def list_miniapps(
|
|
||||||
current_user: dict[str, Any] = Depends(get_current_user),
|
|
||||||
):
|
|
||||||
"""List custom MiniApps from plugin config."""
|
|
||||||
from app.plugins.builtins.kommunikation.miniapp_registry import MiniAppRegistry
|
|
||||||
registry = MiniAppRegistry()
|
|
||||||
return registry.list_apps()
|
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
|
||||||
"/miniapps",
|
|
||||||
dependencies=[Depends(require_permission("automation:write"))],
|
|
||||||
response_model=MiniAppResponse,
|
|
||||||
status_code=201,
|
|
||||||
)
|
|
||||||
async def create_miniapp(
|
|
||||||
data: MiniAppCreate,
|
|
||||||
current_user: dict[str, Any] = Depends(get_current_user),
|
|
||||||
):
|
|
||||||
"""Create a custom MiniApp definition."""
|
|
||||||
from app.plugins.builtins.kommunikation.miniapp_registry import MiniAppRegistry
|
|
||||||
registry = MiniAppRegistry()
|
|
||||||
registry.register(
|
|
||||||
app_id=data.app_id,
|
|
||||||
name=data.name,
|
|
||||||
icon=data.icon,
|
|
||||||
description=data.description,
|
|
||||||
plugin_name="automation",
|
|
||||||
render_schema=data.render_schema,
|
|
||||||
)
|
|
||||||
return MiniAppResponse(
|
|
||||||
app_id=data.app_id,
|
|
||||||
name=data.name,
|
|
||||||
icon=data.icon,
|
|
||||||
description=data.description,
|
|
||||||
plugin_name="automation",
|
|
||||||
render_schema=data.render_schema,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.delete(
|
|
||||||
"/miniapps/{app_id}",
|
|
||||||
dependencies=[Depends(require_permission("automation:delete"))],
|
|
||||||
)
|
|
||||||
async def delete_miniapp(
|
|
||||||
app_id: str,
|
|
||||||
current_user: dict[str, Any] = Depends(get_current_user),
|
|
||||||
):
|
|
||||||
"""Delete a custom MiniApp definition."""
|
|
||||||
from app.plugins.builtins.kommunikation.miniapp_registry import MiniAppRegistry
|
|
||||||
registry = MiniAppRegistry()
|
|
||||||
registry.unregister(app_id)
|
|
||||||
return {"status": "ok"}
|
|
||||||
|
|
||||||
|
|
||||||
# ─── Settings ───
|
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
|
||||||
"/settings",
|
|
||||||
dependencies=[Depends(require_permission("automation:configure"))],
|
|
||||||
response_model=AutomationSettingsResponse,
|
|
||||||
)
|
|
||||||
async def get_automation_settings(
|
|
||||||
current_user: dict[str, Any] = Depends(get_current_user),
|
|
||||||
):
|
|
||||||
"""Get automation default settings."""
|
|
||||||
# Return default settings (could be stored in plugin config in the future)
|
|
||||||
return AutomationSettingsResponse(
|
|
||||||
default_llm_model="ollama/deepseek-v4-flash",
|
|
||||||
heartbeat_default_interval=300,
|
|
||||||
max_concurrent_agents=5,
|
|
||||||
log_level="INFO",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.patch(
|
|
||||||
"/settings",
|
|
||||||
dependencies=[Depends(require_permission("automation:configure"))],
|
|
||||||
response_model=AutomationSettingsResponse,
|
|
||||||
)
|
|
||||||
async def update_automation_settings(
|
|
||||||
data: AutomationSettingsUpdate,
|
|
||||||
current_user: dict[str, Any] = Depends(get_current_user),
|
|
||||||
):
|
|
||||||
"""Update automation settings (stored in plugin config)."""
|
|
||||||
# TODO: Persist settings to plugin config table
|
|
||||||
# For now, return the updated values as a preview
|
|
||||||
settings = AutomationSettingsResponse(
|
|
||||||
default_llm_model=data.default_llm_model or "ollama/deepseek-v4-flash",
|
|
||||||
heartbeat_default_interval=data.heartbeat_default_interval or 300,
|
|
||||||
max_concurrent_agents=data.max_concurrent_agents or 5,
|
|
||||||
log_level=data.log_level or "INFO",
|
|
||||||
)
|
|
||||||
return settings
|
|
||||||
|
|||||||
Reference in New Issue
Block a user