Aari ff0a6768c2
feat(subagents): add unified capacity and durable batch execution (#4998)
* feat(subagents): add capacity controls and durable batches

* fix(helm): sync subagent config schema version

* fix(subagents): preserve batch history without worker

* fix(subagents): support explicit factory runtimes

* fix: address durable batch review findings
2026-08-25 07:49:38 +08:00

84 lines
3.6 KiB
Python

"""Read-only feature-flag endpoint for the frontend bootstrap.
Reports which optional features are exposed over HTTP so the frontend can gate
UI and avoid firing requests that the backend would reject. Config-only flags
read through ``get_config`` so edits to ``config.yaml`` take effect on the next
request, while startup-scoped capabilities report the runtime that actually
started.
"""
from fastapi import APIRouter, Depends, Request
from pydantic import BaseModel, Field
from app.gateway.browser_capability import browser_capability
from app.gateway.deps import get_config
from deerflow.config.app_config import AppConfig
from deerflow.subagents.capacity import configured_subagent_max_running
router = APIRouter(prefix="/api", tags=["features"])
class AgentsApiFeature(BaseModel):
"""Availability of the custom-agent management API."""
enabled: bool = Field(..., description="Whether the agents_api routes are exposed over HTTP")
class BrowserControlFeature(BaseModel):
"""Availability of live agentic browser control."""
enabled: bool = Field(..., description="Whether the live browser routes and UI are available")
class McpTasksFeature(BaseModel):
"""Availability of the durable MCP task runtime."""
enabled: bool = Field(..., description="Whether durable MCP task APIs and UI are available")
class SubagentBatchesFeature(BaseModel):
"""Persistence, worker, and process capacity for native-subagent batches."""
enabled: bool = Field(..., description="Compatibility alias for worker_running")
repository_available: bool = Field(..., description="Whether durable batch history APIs are available")
worker_running: bool = Field(..., description="Whether this Gateway process is executing durable batch work")
max_running: int = Field(..., description="Native subagent execution slots in this Gateway process")
class FeaturesResponse(BaseModel):
"""Frontend-facing feature availability flags."""
agents_api: AgentsApiFeature
browser_control: BrowserControlFeature
mcp_tasks: McpTasksFeature
subagent_batches: SubagentBatchesFeature
@router.get(
"/features",
response_model=FeaturesResponse,
summary="List Feature Flags",
description="Report which optional features are available, so the frontend can gate UI before issuing requests.",
)
async def list_features(request: Request, config: AppConfig = Depends(get_config)) -> FeaturesResponse:
"""Return availability of optional frontend features."""
browser = browser_capability(config)
subagent_batch_worker_running = bool(getattr(request.app.state, "subagent_batches_available", False))
return FeaturesResponse(
agents_api=AgentsApiFeature(enabled=config.agents_api.enabled),
browser_control=BrowserControlFeature(enabled=browser.available),
# MCP task bindings and the submitter are startup-scoped. Report the
# capability that actually started rather than a hot-reloaded config
# value that would require a Gateway restart to take effect.
mcp_tasks=McpTasksFeature(enabled=bool(getattr(request.app.state, "mcp_tasks_available", False))),
subagent_batches=SubagentBatchesFeature(
# Keep the historical `enabled` field as a compatibility alias
# while exposing read persistence independently from execution.
# A stopped/disabled worker must not hide durable history/export.
enabled=subagent_batch_worker_running,
repository_available=getattr(request.app.state, "subagent_batch_repo", None) is not None,
worker_running=subagent_batch_worker_running,
max_running=configured_subagent_max_running(),
),
)