| 1 | """POST /api/plugins/_a0_connector/v1/projects.""" |
| 2 | from __future__ import annotations |
| 3 | |
| 4 | from typing import Any, Mapping |
| 5 | |
| 6 | from helpers.api import Request, Response |
| 7 | import plugins._a0_connector.api.v1.base as connector_base |
| 8 | |
| 9 | |
| 10 | def _string(value: object) -> str: |
| 11 | if value is None: |
| 12 | return "" |
| 13 | return str(value).strip() |
| 14 | |
| 15 | |
| 16 | def _normalize_project_summary(value: object) -> dict[str, str] | None: |
| 17 | if not isinstance(value, Mapping): |
| 18 | return None |
| 19 | |
| 20 | name = _string(value.get("name")) |
| 21 | if not name: |
| 22 | return None |
| 23 | |
| 24 | return { |
| 25 | "name": name, |
| 26 | "title": _string(value.get("title")), |
| 27 | "description": _string(value.get("description")), |
| 28 | "color": _string(value.get("color")), |
| 29 | } |
| 30 | |
| 31 | |
| 32 | class Projects(connector_base.ProtectedConnectorApiHandler): |
| 33 | """Thin connector proxy around the core `api.projects.Projects` surface.""" |
| 34 | |
| 35 | async def process(self, input: dict, request: Request) -> dict | Response: |
| 36 | action = _string(input.get("action")).lower() or "list" |
| 37 | if action not in {"list", "load", "update", "activate", "deactivate"}: |
| 38 | return {"ok": False, "error": f"Unsupported action: {action or '<missing>'}"} |
| 39 | |
| 40 | core_response = await self._call_core( |
| 41 | { |
| 42 | "action": action, |
| 43 | "context_id": _string(input.get("context_id")), |
| 44 | "name": _string(input.get("name")), |
| 45 | "project": input.get("project"), |
| 46 | }, |
| 47 | request, |
| 48 | ) |
| 49 | if isinstance(core_response, Response): |
| 50 | return core_response |
| 51 | if not isinstance(core_response, Mapping): |
| 52 | return {"ok": False, "error": "Invalid response from core projects handler"} |
| 53 | if not core_response.get("ok"): |
| 54 | return {"ok": False, "error": _string(core_response.get("error")) or "Project request failed"} |
| 55 | |
| 56 | if action in {"activate", "deactivate", "list"}: |
| 57 | return await self._normalized_list_state(_string(input.get("context_id")), request) |
| 58 | |
| 59 | project = core_response.get("data") |
| 60 | return { |
| 61 | "ok": True, |
| 62 | "project": dict(project) if isinstance(project, Mapping) else {}, |
| 63 | } |
| 64 | |
| 65 | async def _normalized_list_state(self, context_id: str, request: Request) -> dict[str, Any] | Response: |
| 66 | core_response = await self._call_core( |
| 67 | { |
| 68 | "action": "list", |
| 69 | "context_id": context_id, |
| 70 | }, |
| 71 | request, |
| 72 | ) |
| 73 | if isinstance(core_response, Response): |
| 74 | return core_response |
| 75 | if not isinstance(core_response, Mapping): |
| 76 | return {"ok": False, "error": "Invalid response from core projects handler"} |
| 77 | if not core_response.get("ok"): |
| 78 | return {"ok": False, "error": _string(core_response.get("error")) or "Project request failed"} |
| 79 | |
| 80 | projects: list[dict[str, str]] = [] |
| 81 | for item in core_response.get("data") or []: |
| 82 | normalized = _normalize_project_summary(item) |
| 83 | if normalized is not None: |
| 84 | projects.append(normalized) |
| 85 | |
| 86 | return { |
| 87 | "ok": True, |
| 88 | "projects": projects, |
| 89 | "current_project": self._load_current_project(context_id), |
| 90 | } |
| 91 | |
| 92 | async def _call_core(self, payload: dict[str, Any], request: Request) -> dict | Response: |
| 93 | from api.projects import Projects as CoreProjects |
| 94 | |
| 95 | handler = CoreProjects(self.app, self.thread_lock) |
| 96 | return await handler.process(payload, request) |
| 97 | |
| 98 | def _load_current_project(self, context_id: str) -> dict[str, str] | None: |
| 99 | if not context_id: |
| 100 | return None |
| 101 | |
| 102 | from agent import AgentContext |
| 103 | |
| 104 | context = AgentContext.get(context_id) |
| 105 | if context is None: |
| 106 | return None |
| 107 | |
| 108 | return _normalize_project_summary(context.get_output_data("project")) |