Expose connector skill activation

Add a protected skills_activate endpoint and context-aware skills_list support so connector clients can activate skills in live chats. Advertise the capability through the connector API.

Alessandro committed May 22, 2026 at 17:03 UTC 770b53e292de5cf57d6c700f2e8dedd79cc7bd75
3 files changed +58 -1
plugins/_a0_connector/api/v1/capabilities.py
+1
@@ -35,6 +35,7 @@ _OPTIONAL_FEATURES: dict[str, tuple[str, ...]] = {
35 "agent_profile_set": ("api.agent_profile_set",),
36 "agents_list": ("helpers.subagents",),
37 "skills_list": ("helpers.skills", "helpers.files", "helpers.projects", "helpers.runtime"),
38 + "skills_activate": ("helpers.skills", "helpers.persist_chat"),
39 "skills_delete": ("helpers.skills", "helpers.files", "helpers.projects", "helpers.runtime"),
40 "model_presets": ("plugins._model_config.helpers.model_config",),
41 "model_switcher": ("plugins._model_config.helpers.model_config",),
plugins/_a0_connector/api/v1/skills_activate.py new
+49
@@ -0,0 +1,49 @@
1 +"""POST /api/plugins/_a0_connector/v1/skills_activate."""
2 +from __future__ import annotations
3 +
4 +import json
5 +from typing import Any
6 +
7 +from helpers.api import Request, Response
8 +import plugins._a0_connector.api.v1.base as connector_base
9 +
10 +
11 +def _json_error(message: str, status: int) -> Response:
12 + return Response(
13 + response=json.dumps({"ok": False, "error": message}),
14 + status=status,
15 + mimetype="application/json",
16 + )
17 +
18 +
19 +class SkillsActivate(connector_base.ProtectedConnectorApiHandler):
20 + async def process(self, input: dict, request: Request) -> dict | Response:
21 + from agent import AgentContext
22 + from helpers import skills
23 + from helpers.persist_chat import save_tmp_chat
24 +
25 + context_id = str(input.get("context_id", "") or "").strip()
26 + if not context_id:
27 + return _json_error("context_id is required", 400)
28 +
29 + context = AgentContext.get(context_id)
30 + if context is None:
31 + return _json_error("Context not found", 404)
32 +
33 + entries = skills.normalize_active_skills([input.get("skill")])
34 + if not entries:
35 + return _json_error("skill is required", 400)
36 +
37 + skill_entry: dict[str, Any] = dict(entries[0])
38 + try:
39 + active_skills = skills.activate_chat_skill(context.get_agent(), skill_entry)
40 + save_tmp_chat(context)
41 + except ValueError as exc:
42 + return _json_error(str(exc), 400)
43 +
44 + return {
45 + "ok": True,
46 + "context_id": context.id,
47 + "skill": skill_entry,
48 + "active_skills": active_skills,
49 + }
plugins/_a0_connector/api/v1/skills_list.py
+8 -1
@@ -7,10 +7,17 @@ import plugins._a0_connector.api.v1.base as connector_base
7
8 class SkillsList(connector_base.ProtectedConnectorApiHandler):
9 async def process(self, input: dict, request: Request) -> dict | Response:
10 + from agent import AgentContext
11 from helpers import files, projects, skills
12
13 + context_id = str(input.get("context_id", "") or "").strip()
14 project_name = str(input.get("project_name", "")).strip() or None
13 - skill_list = skills.list_skill_catalog(project_name=project_name or "")
15 + context = AgentContext.get(context_id) if context_id else None
16 + agent = context.get_agent() if context else None
17 + if context is not None and not project_name:
18 + project_name = projects.get_context_project_name(context) or None
19 +
20 + skill_list = skills.list_skill_catalog(project_name=project_name or "", agent=agent)
21
22 agent_profile = str(input.get("agent_profile", "")).strip() or None
23 if agent_profile: