semantics

This commit is contained in:
2026-03-27 21:27:31 +03:00
parent 7c85552132
commit 2ed66bfebc
182 changed files with 21186 additions and 10254 deletions

View File

@@ -4,9 +4,41 @@
# @PURPOSE: Provide lazy route module loading to avoid heavyweight imports during tests.
# @LAYER: API
# @RELATION: [CALLS] ->[ApiRoutesGetAttr]
# @RELATION: [BINDS_TO] ->[Route_Group_Contracts]
# @INVARIANT: Only names listed in __all__ are importable via __getattr__.
__all__ = ['plugins', 'tasks', 'settings', 'connections', 'environments', 'mappings', 'migration', 'git', 'storage', 'admin', 'reports', 'assistant', 'clean_release', 'profile', 'dataset_review']
# [DEF:Route_Group_Contracts:Block]
# @COMPLEXITY: 3
# @PURPOSE: Declare the canonical route-module registry used by lazy imports and app router inclusion.
# @RELATION: DEPENDS_ON -> [PluginsRouter]
# @RELATION: DEPENDS_ON -> [TasksRouter]
# @RELATION: DEPENDS_ON -> [SettingsRouter]
# @RELATION: DEPENDS_ON -> [ConnectionsRouter]
# @RELATION: DEPENDS_ON -> [ReportsRouter]
# @RELATION: DEPENDS_ON -> [LlmRoutes]
__all__ = [
"plugins",
"tasks",
"settings",
"connections",
"environments",
"mappings",
"migration",
"git",
"storage",
"admin",
"reports",
"assistant",
"clean_release",
"clean_release_v2",
"profile",
"dataset_review",
"llm",
"dashboards",
"datasets",
"health",
]
# [/DEF:Route_Group_Contracts:Block]
# [DEF:ApiRoutesGetAttr:Function]
@@ -18,7 +50,10 @@ __all__ = ['plugins', 'tasks', 'settings', 'connections', 'environments', 'mappi
def __getattr__(name):
if name in __all__:
import importlib
return importlib.import_module(f".{name}", __name__)
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
# [/DEF:ApiRoutesGetAttr:Function]
# [/DEF:ApiRoutesModule:Module]

View File

@@ -559,6 +559,4 @@ def test_dataset_review_scoped_command_routes_field_semantics_update():
# [/DEF:test_dataset_review_scoped_command_routes_field_semantics_update:Function]
# [/DEF:test_capabilities_question_returns_successful_help:Function]
# [/DEF:AssistantApiTests:Module]

View File

@@ -1,6 +1,13 @@
# [DEF:CleanReleaseV2Api:Module]
# @COMPLEXITY: 4
# @PURPOSE: Redesigned clean release API for headless candidate lifecycle.
# @LAYER: UI (API)
# @RELATION: DEPENDS_ON -> [CleanReleaseRepository]
# @RELATION: CALLS -> [approve_candidate]
# @RELATION: CALLS -> [publish_candidate]
# @PRE: Clean release repository dependency is available for candidate lifecycle endpoints.
# @POST: Candidate registration, approval, publication, and revocation routes are registered without behavior changes.
# @SIDE_EFFECT: Persists candidate lifecycle state through clean release services and repository adapters.
from fastapi import APIRouter, Depends, HTTPException, status
from typing import List, Dict, Any
@@ -29,7 +36,6 @@ router = APIRouter(prefix="/api/v2/clean-release", tags=["Clean Release V2"])
# [DEF:ApprovalRequest:Class]
# @COMPLEXITY: 1
# @PURPOSE: Schema for approval request payload.
# @RELATION: USES -> [CandidateDTO]
class ApprovalRequest(dict):
pass
@@ -40,7 +46,6 @@ class ApprovalRequest(dict):
# [DEF:PublishRequest:Class]
# @COMPLEXITY: 1
# @PURPOSE: Schema for publication request payload.
# @RELATION: USES -> [CandidateDTO]
class PublishRequest(dict):
pass
@@ -51,7 +56,6 @@ class PublishRequest(dict):
# [DEF:RevokeRequest:Class]
# @COMPLEXITY: 1
# @PURPOSE: Schema for revocation request payload.
# @RELATION: USES -> [CandidateDTO]
class RevokeRequest(dict):
pass
@@ -66,7 +70,7 @@ class RevokeRequest(dict):
# @POST: Candidate is saved in repository.
# @RETURN: CandidateDTO
# @RELATION: CALLS -> [CleanReleaseRepository.save_candidate]
# @RELATION: USES -> [CandidateDTO]
# @RELATION: DEPENDS_ON -> [CandidateDTO]
@router.post(
"/candidates", response_model=CandidateDTO, status_code=status.HTTP_201_CREATED
)

View File

@@ -1,8 +1,12 @@
# [DEF:backend/src/api/routes/llm.py:Module]
# @COMPLEXITY: 2
# [DEF:LlmRoutes:Module]
# @COMPLEXITY: 3
# @SEMANTICS: api, routes, llm
# @PURPOSE: API routes for LLM provider configuration and management.
# @LAYER: UI (API)
# @RELATION: DEPENDS_ON -> [LLMProviderService]
# @RELATION: DEPENDS_ON -> [LLMProviderConfig]
# @RELATION: DEPENDS_ON -> [get_current_user]
# @RELATION: DEPENDS_ON -> [get_db]
from fastapi import APIRouter, Depends, HTTPException, status
from typing import List, Optional
@@ -24,6 +28,7 @@ router = APIRouter(tags=["LLM"])
# @PURPOSE: Validate decrypted runtime API key presence/shape.
# @PRE: value can be None.
# @POST: Returns True only for non-placeholder key.
# @RELATION: BINDS_TO -> [LlmRoutes]
def _is_valid_runtime_api_key(value: Optional[str]) -> bool:
key = (value or "").strip()
if not key:
@@ -40,6 +45,8 @@ def _is_valid_runtime_api_key(value: Optional[str]) -> bool:
# @PURPOSE: Retrieve all LLM provider configurations.
# @PRE: User is authenticated.
# @POST: Returns list of LLMProviderConfig.
# @RELATION: CALLS -> [LLMProviderService]
# @RELATION: DEPENDS_ON -> [LLMProviderConfig]
@router.get("/providers", response_model=List[LLMProviderConfig])
async def get_providers(
current_user: User = Depends(get_current_active_user), db: Session = Depends(get_db)
@@ -73,6 +80,8 @@ async def get_providers(
# @PURPOSE: Returns whether LLM runtime is configured for dashboard validation.
# @PRE: User is authenticated.
# @POST: configured=true only when an active provider with valid decrypted key exists.
# @RELATION: CALLS -> [LLMProviderService]
# @RELATION: CALLS -> [_is_valid_runtime_api_key]
@router.get("/status")
async def get_llm_status(
current_user: User = Depends(get_current_active_user), db: Session = Depends(get_db)
@@ -112,7 +121,9 @@ async def get_llm_status(
"configured": False,
"reason": "invalid_api_key",
"provider_count": len(providers),
"active_provider_count": len([provider for provider in providers if provider.is_active]),
"active_provider_count": len(
[provider for provider in providers if provider.is_active]
),
"provider_id": active_provider.id,
"provider_name": active_provider.name,
"provider_type": active_provider.provider_type,
@@ -123,7 +134,9 @@ async def get_llm_status(
"configured": True,
"reason": "ok",
"provider_count": len(providers),
"active_provider_count": len([provider for provider in providers if provider.is_active]),
"active_provider_count": len(
[provider for provider in providers if provider.is_active]
),
"provider_id": active_provider.id,
"provider_name": active_provider.name,
"provider_type": active_provider.provider_type,
@@ -138,6 +151,8 @@ async def get_llm_status(
# @PURPOSE: Create a new LLM provider configuration.
# @PRE: User is authenticated and has admin permissions.
# @POST: Returns the created LLMProviderConfig.
# @RELATION: CALLS -> [LLMProviderService]
# @RELATION: DEPENDS_ON -> [LLMProviderConfig]
@router.post(
"/providers", response_model=LLMProviderConfig, status_code=status.HTTP_201_CREATED
)
@@ -169,6 +184,8 @@ async def create_provider(
# @PURPOSE: Update an existing LLM provider configuration.
# @PRE: User is authenticated and has admin permissions.
# @POST: Returns the updated LLMProviderConfig.
# @RELATION: CALLS -> [LLMProviderService]
# @RELATION: DEPENDS_ON -> [LLMProviderConfig]
@router.put("/providers/{provider_id}", response_model=LLMProviderConfig)
async def update_provider(
provider_id: str,
@@ -202,6 +219,7 @@ async def update_provider(
# @PURPOSE: Delete an LLM provider configuration.
# @PRE: User is authenticated and has admin permissions.
# @POST: Returns success status.
# @RELATION: CALLS -> [LLMProviderService]
@router.delete("/providers/{provider_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_provider(
provider_id: str,
@@ -224,6 +242,8 @@ async def delete_provider(
# @PURPOSE: Test connection to an LLM provider.
# @PRE: User is authenticated.
# @POST: Returns success status and message.
# @RELATION: CALLS -> [LLMProviderService]
# @RELATION: DEPENDS_ON -> [LLMClient]
@router.post("/providers/{provider_id}/test")
async def test_connection(
provider_id: str,
@@ -276,6 +296,8 @@ async def test_connection(
# @PURPOSE: Test connection with a provided configuration (not yet saved).
# @PRE: User is authenticated.
# @POST: Returns success status and message.
# @RELATION: DEPENDS_ON -> [LLMClient]
# @RELATION: DEPENDS_ON -> [LLMProviderConfig]
@router.post("/providers/test")
async def test_provider_config(
config: LLMProviderConfig, current_user: User = Depends(get_current_active_user)
@@ -311,4 +333,4 @@ async def test_provider_config(
# [/DEF:test_provider_config:Function]
# [/DEF:backend/src/api/routes/llm.py:Module]
# [/DEF:LlmRoutes:Module]

View File

@@ -3,7 +3,9 @@
# @SEMANTICS: api, router, plugins, list
# @PURPOSE: Defines the FastAPI router for plugin-related endpoints, allowing clients to list available plugins.
# @LAYER: UI (API)
# @RELATION: Depends on the PluginLoader and PluginConfig. It is included by the main app.
# @RELATION: DEPENDS_ON -> [PluginConfig]
# @RELATION: DEPENDS_ON -> [get_plugin_loader]
# @RELATION: BINDS_TO -> [API_Routes]
from typing import List
from fastapi import APIRouter, Depends
@@ -13,20 +15,25 @@ from ...core.logger import belief_scope
router = APIRouter()
# [DEF:list_plugins:Function]
# @PURPOSE: Retrieve a list of all available plugins.
# @PRE: plugin_loader is injected via Depends.
# @POST: Returns a list of PluginConfig objects.
# @RETURN: List[PluginConfig] - List of registered plugins.
# @RELATION: CALLS -> [get_plugin_loader]
# @RELATION: DEPENDS_ON -> [PluginConfig]
@router.get("", response_model=List[PluginConfig])
async def list_plugins(
plugin_loader = Depends(get_plugin_loader),
_ = Depends(has_permission("plugins", "READ"))
plugin_loader=Depends(get_plugin_loader),
_=Depends(has_permission("plugins", "READ")),
):
with belief_scope("list_plugins"):
"""
Retrieve a list of all available plugins.
"""
return plugin_loader.get_all_plugin_configs()
# [/DEF:list_plugins:Function]
# [/DEF:PluginsRouter:Module]
# [/DEF:PluginsRouter:Module]

View File

@@ -49,6 +49,7 @@ router = APIRouter(prefix="/api/reports", tags=["Reports"])
# @PARAM: enum_cls (type) - Enum class for validation.
# @PARAM: field_name (str) - Query field name for diagnostics.
# @RETURN: List - Parsed enum values.
# @RELATION: BINDS_TO -> [ReportsRouter]
def _parse_csv_enum_list(raw: Optional[str], enum_cls, field_name: str) -> List:
with belief_scope("_parse_csv_enum_list"):
if raw is None or not raw.strip():
@@ -158,6 +159,7 @@ async def list_reports(
# @PURPOSE: Return one normalized report detail with diagnostics and next actions.
# @PRE: authenticated/authorized request and existing report_id.
# @POST: returns normalized detail envelope or 404 when report is not found.
# @RELATION: CALLS -> [ReportsService:Class]
@router.get("/{report_id}", response_model=ReportDetailView)
async def get_report_detail(
report_id: str,

View File

@@ -15,7 +15,12 @@ from ...core.logger import belief_scope
from ...core.task_manager import TaskManager, Task, TaskStatus, LogEntry
from ...core.task_manager.models import LogFilter, LogStats
from ...dependencies import get_task_manager, has_permission, get_current_user, get_config_manager
from ...dependencies import (
get_task_manager,
has_permission,
get_current_user,
get_config_manager,
)
from ...core.config_manager import ConfigManager
from ...services.llm_prompt_templates import (
is_multimodal_model,
@@ -32,16 +37,20 @@ TASK_TYPE_PLUGIN_MAP = {
"migration": ["superset-migration"],
}
class CreateTaskRequest(BaseModel):
plugin_id: str
params: Dict[str, Any]
class ResolveTaskRequest(BaseModel):
resolution_params: Dict[str, Any]
class ResumeTaskRequest(BaseModel):
passwords: Dict[str, str]
# [DEF:create_task:Function]
# @COMPLEXITY: 3
# @PURPOSE: Create and start a new task for a given plugin.
@@ -50,11 +59,14 @@ class ResumeTaskRequest(BaseModel):
# @PRE: plugin_id must exist and params must be valid for that plugin.
# @POST: A new task is created and started.
# @RETURN: Task - The created task instance.
# @RELATION: CALLS -> [TaskManager]
# @RELATION: DEPENDS_ON -> [ConfigManager]
# @RELATION: DEPENDS_ON -> [LLMProviderService]
@router.post("", response_model=Task, status_code=status.HTTP_201_CREATED)
async def create_task(
request: CreateTaskRequest,
task_manager: TaskManager = Depends(get_task_manager),
current_user = Depends(get_current_user),
current_user=Depends(get_current_user),
config_manager: ConfigManager = Depends(get_config_manager),
):
# Dynamic permission check based on plugin_id
@@ -65,19 +77,30 @@ async def create_task(
if request.plugin_id in {"llm_dashboard_validation", "llm_documentation"}:
from ...core.database import SessionLocal
from ...services.llm_provider import LLMProviderService
db = SessionLocal()
try:
llm_service = LLMProviderService(db)
provider_id = request.params.get("provider_id")
if not provider_id:
llm_settings = normalize_llm_settings(config_manager.get_config().settings.llm)
binding_key = "dashboard_validation" if request.plugin_id == "llm_dashboard_validation" else "documentation"
provider_id = resolve_bound_provider_id(llm_settings, binding_key)
llm_settings = normalize_llm_settings(
config_manager.get_config().settings.llm
)
binding_key = (
"dashboard_validation"
if request.plugin_id == "llm_dashboard_validation"
else "documentation"
)
provider_id = resolve_bound_provider_id(
llm_settings, binding_key
)
if provider_id:
request.params["provider_id"] = provider_id
if not provider_id:
providers = llm_service.get_all_providers()
active_provider = next((p for p in providers if p.is_active), None)
active_provider = next(
(p for p in providers if p.is_active), None
)
if active_provider:
provider_id = active_provider.id
request.params["provider_id"] = provider_id
@@ -86,9 +109,12 @@ async def create_task(
db_provider = llm_service.get_provider(provider_id)
if not db_provider:
raise ValueError(f"LLM Provider {provider_id} not found")
if request.plugin_id == "llm_dashboard_validation" and not is_multimodal_model(
db_provider.default_model,
db_provider.provider_type,
if (
request.plugin_id == "llm_dashboard_validation"
and not is_multimodal_model(
db_provider.default_model,
db_provider.provider_type,
)
):
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
@@ -98,14 +124,16 @@ async def create_task(
db.close()
task = await task_manager.create_task(
plugin_id=request.plugin_id,
params=request.params
plugin_id=request.plugin_id, params=request.params
)
return task
except ValueError as e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
# [/DEF:create_task:Function]
# [DEF:list_tasks:Function]
# @COMPLEXITY: 2
# @PURPOSE: Retrieve a list of tasks with pagination and optional status filter.
@@ -116,16 +144,24 @@ async def create_task(
# @PRE: task_manager must be available.
# @POST: Returns a list of tasks.
# @RETURN: List[Task] - List of tasks.
# @RELATION: CALLS -> [TaskManager]
# @RELATION: BINDS_TO -> [TASK_TYPE_PLUGIN_MAP]
@router.get("", response_model=List[Task])
async def list_tasks(
limit: int = 10,
offset: int = 0,
status_filter: Optional[TaskStatus] = Query(None, alias="status"),
task_type: Optional[str] = Query(None, description="Task category: llm_validation, backup, migration"),
plugin_id: Optional[List[str]] = Query(None, description="Filter by plugin_id (repeatable query param)"),
completed_only: bool = Query(False, description="Return only completed tasks (SUCCESS/FAILED)"),
task_type: Optional[str] = Query(
None, description="Task category: llm_validation, backup, migration"
),
plugin_id: Optional[List[str]] = Query(
None, description="Filter by plugin_id (repeatable query param)"
),
completed_only: bool = Query(
False, description="Return only completed tasks (SUCCESS/FAILED)"
),
task_manager: TaskManager = Depends(get_task_manager),
_ = Depends(has_permission("tasks", "READ"))
_=Depends(has_permission("tasks", "READ")),
):
with belief_scope("list_tasks"):
plugin_filters = list(plugin_id) if plugin_id else []
@@ -133,7 +169,7 @@ async def list_tasks(
if task_type not in TASK_TYPE_PLUGIN_MAP:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Unsupported task_type '{task_type}'. Allowed: {', '.join(TASK_TYPE_PLUGIN_MAP.keys())}"
detail=f"Unsupported task_type '{task_type}'. Allowed: {', '.join(TASK_TYPE_PLUGIN_MAP.keys())}",
)
plugin_filters.extend(TASK_TYPE_PLUGIN_MAP[task_type])
@@ -142,10 +178,13 @@ async def list_tasks(
offset=offset,
status=status_filter,
plugin_ids=plugin_filters or None,
completed_only=completed_only
completed_only=completed_only,
)
# [/DEF:list_tasks:Function]
# [DEF:get_task:Function]
# @COMPLEXITY: 2
# @PURPOSE: Retrieve the details of a specific task.
@@ -154,19 +193,25 @@ async def list_tasks(
# @PRE: task_id must exist.
# @POST: Returns task details or raises 404.
# @RETURN: Task - The task details.
# @RELATION: CALLS -> [TaskManager]
@router.get("/{task_id}", response_model=Task)
async def get_task(
task_id: str,
task_manager: TaskManager = Depends(get_task_manager),
_ = Depends(has_permission("tasks", "READ"))
_=Depends(has_permission("tasks", "READ")),
):
with belief_scope("get_task"):
task = task_manager.get_task(task_id)
if not task:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Task not found")
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Task not found"
)
return task
# [/DEF:get_task:Function]
# [DEF:get_task_logs:Function]
# @COMPLEXITY: 5
# @PURPOSE: Retrieve logs for a specific task with optional filtering.
@@ -180,6 +225,8 @@ async def get_task(
# @PRE: task_id must exist.
# @POST: Returns a list of log entries or raises 404.
# @RETURN: List[LogEntry] - List of log entries.
# @RELATION: CALLS -> [TaskManager]
# @RELATION: DEPENDS_ON -> [LogFilter]
# @TEST_CONTRACT: TaskLogQueryInput -> List[LogEntry]
# @TEST_SCENARIO: existing_task_logs_filtered -> Returns filtered logs by level/source/search with pagination.
# @TEST_FIXTURE: valid_task_with_mixed_logs -> backend/tests/fixtures/task_logs/valid_task_with_mixed_logs.json
@@ -190,28 +237,37 @@ async def get_task(
@router.get("/{task_id}/logs")
async def get_task_logs(
task_id: str,
level: Optional[str] = Query(None, description="Filter by log level (DEBUG, INFO, WARNING, ERROR)"),
level: Optional[str] = Query(
None, description="Filter by log level (DEBUG, INFO, WARNING, ERROR)"
),
source: Optional[str] = Query(None, description="Filter by source component"),
search: Optional[str] = Query(None, description="Text search in message"),
offset: int = Query(0, ge=0, description="Number of logs to skip"),
limit: int = Query(100, ge=1, le=1000, description="Maximum number of logs to return"),
limit: int = Query(
100, ge=1, le=1000, description="Maximum number of logs to return"
),
task_manager: TaskManager = Depends(get_task_manager),
):
with belief_scope("get_task_logs"):
task = task_manager.get_task(task_id)
if not task:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Task not found")
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Task not found"
)
log_filter = LogFilter(
level=level.upper() if level else None,
source=source,
search=search,
offset=offset,
limit=limit
limit=limit,
)
return task_manager.get_task_logs(task_id, log_filter)
# [/DEF:get_task_logs:Function]
# [DEF:get_task_log_stats:Function]
# @COMPLEXITY: 2
# @PURPOSE: Get statistics about logs for a task (counts by level and source).
@@ -220,6 +276,8 @@ async def get_task_logs(
# @PRE: task_id must exist.
# @POST: Returns log statistics or raises 404.
# @RETURN: LogStats - Statistics about task logs.
# @RELATION: CALLS -> [TaskManager]
# @RELATION: DEPENDS_ON -> [LogStats]
@router.get("/{task_id}/logs/stats", response_model=LogStats)
async def get_task_log_stats(
task_id: str,
@@ -228,26 +286,37 @@ async def get_task_log_stats(
with belief_scope("get_task_log_stats"):
task = task_manager.get_task(task_id)
if not task:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Task not found")
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Task not found"
)
stats_payload = task_manager.get_task_log_stats(task_id)
if isinstance(stats_payload, LogStats):
return stats_payload
if isinstance(stats_payload, dict) and (
"total_count" in stats_payload or "by_level" in stats_payload or "by_source" in stats_payload
"total_count" in stats_payload
or "by_level" in stats_payload
or "by_source" in stats_payload
):
return LogStats(
total_count=int(stats_payload.get("total_count", 0) or 0),
by_level=dict(stats_payload.get("by_level") or {}),
by_source=dict(stats_payload.get("by_source") or {}),
)
flat_by_level = dict(stats_payload or {}) if isinstance(stats_payload, dict) else {}
flat_by_level = (
dict(stats_payload or {}) if isinstance(stats_payload, dict) else {}
)
return LogStats(
total_count=sum(int(value or 0) for value in flat_by_level.values()),
by_level={str(key): int(value or 0) for key, value in flat_by_level.items()},
by_level={
str(key): int(value or 0) for key, value in flat_by_level.items()
},
by_source={},
)
# [/DEF:get_task_log_stats:Function]
# [DEF:get_task_log_sources:Function]
# @COMPLEXITY: 2
# @PURPOSE: Get unique sources for a task's logs.
@@ -256,6 +325,7 @@ async def get_task_log_stats(
# @PRE: task_id must exist.
# @POST: Returns list of unique source names or raises 404.
# @RETURN: List[str] - Unique source names.
# @RELATION: CALLS -> [TaskManager]
@router.get("/{task_id}/logs/sources", response_model=List[str])
async def get_task_log_sources(
task_id: str,
@@ -264,10 +334,15 @@ async def get_task_log_sources(
with belief_scope("get_task_log_sources"):
task = task_manager.get_task(task_id)
if not task:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Task not found")
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Task not found"
)
return task_manager.get_task_log_sources(task_id)
# [/DEF:get_task_log_sources:Function]
# [DEF:resolve_task:Function]
# @COMPLEXITY: 2
# @PURPOSE: Resolve a task that is awaiting mapping.
@@ -277,12 +352,13 @@ async def get_task_log_sources(
# @PRE: task must be in AWAITING_MAPPING status.
# @POST: Task is resolved and resumes execution.
# @RETURN: Task - The updated task object.
# @RELATION: CALLS -> [TaskManager]
@router.post("/{task_id}/resolve", response_model=Task)
async def resolve_task(
task_id: str,
request: ResolveTaskRequest,
task_manager: TaskManager = Depends(get_task_manager),
_ = Depends(has_permission("tasks", "WRITE"))
_=Depends(has_permission("tasks", "WRITE")),
):
with belief_scope("resolve_task"):
try:
@@ -290,8 +366,11 @@ async def resolve_task(
return task_manager.get_task(task_id)
except ValueError as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
# [/DEF:resolve_task:Function]
# [DEF:resume_task:Function]
# @COMPLEXITY: 2
# @PURPOSE: Resume a task that is awaiting input (e.g., passwords).
@@ -301,12 +380,13 @@ async def resolve_task(
# @PRE: task must be in AWAITING_INPUT status.
# @POST: Task resumes execution with provided input.
# @RETURN: Task - The updated task object.
# @RELATION: CALLS -> [TaskManager]
@router.post("/{task_id}/resume", response_model=Task)
async def resume_task(
task_id: str,
request: ResumeTaskRequest,
task_manager: TaskManager = Depends(get_task_manager),
_ = Depends(has_permission("tasks", "WRITE"))
_=Depends(has_permission("tasks", "WRITE")),
):
with belief_scope("resume_task"):
try:
@@ -314,8 +394,11 @@ async def resume_task(
return task_manager.get_task(task_id)
except ValueError as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
# [/DEF:resume_task:Function]
# [DEF:clear_tasks:Function]
# @COMPLEXITY: 2
# @PURPOSE: Clear tasks matching the status filter.
@@ -323,15 +406,18 @@ async def resume_task(
# @PARAM: task_manager (TaskManager) - The task manager instance.
# @PRE: task_manager is available.
# @POST: Tasks are removed from memory/persistence.
# @RELATION: CALLS -> [TaskManager]
@router.delete("", status_code=status.HTTP_204_NO_CONTENT)
async def clear_tasks(
status: Optional[TaskStatus] = None,
task_manager: TaskManager = Depends(get_task_manager),
_ = Depends(has_permission("tasks", "WRITE"))
_=Depends(has_permission("tasks", "WRITE")),
):
with belief_scope("clear_tasks", f"status={status}"):
task_manager.clear_tasks(status)
return
# [/DEF:clear_tasks:Function]
# [/DEF:TasksRouter:Module]