feat(agent): 接入 Skill Plugin 与知识库工具
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from app.agent.tools import ToolExecutionContext, ToolRegistry
|
||||
from app.contracts import ToolDefinition
|
||||
from app.contracts import SearchMode, SearchRequest, ToolDefinition
|
||||
from app.retrieval.engine import engine
|
||||
from app.services import note_service
|
||||
|
||||
|
||||
class ToolArguments(BaseModel):
|
||||
@@ -17,6 +19,41 @@ class AddArguments(ToolArguments):
|
||||
right: float
|
||||
|
||||
|
||||
class NoteSearchArguments(ToolArguments):
|
||||
query: str = Field(min_length=1)
|
||||
mode: SearchMode = SearchMode.hybrid
|
||||
folders: list[str] = Field(default_factory=list)
|
||||
note_ids: list[str] = Field(default_factory=list)
|
||||
tags: list[str] = Field(default_factory=list)
|
||||
limit: int = Field(default=10, ge=1, le=100)
|
||||
offset: int = Field(default=0, ge=0)
|
||||
|
||||
|
||||
class NoteReadArguments(ToolArguments):
|
||||
note_id: str = Field(min_length=1)
|
||||
|
||||
|
||||
class NoteCreateArguments(ToolArguments):
|
||||
title: str = Field(min_length=1)
|
||||
markdown: str = ""
|
||||
folder: str | None = None
|
||||
tags: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
class NoteUpdateArguments(ToolArguments):
|
||||
note_id: str = Field(min_length=1)
|
||||
title: str | None = None
|
||||
markdown: str | None = None
|
||||
tags: list[str] | None = None
|
||||
|
||||
|
||||
class NoteListArguments(ToolArguments):
|
||||
limit: int = Field(default=50, ge=1, le=100)
|
||||
offset: int = Field(default=0, ge=0)
|
||||
folder: str | None = None
|
||||
tag: str | None = None
|
||||
|
||||
|
||||
async def echo(arguments: EchoArguments, _: ToolExecutionContext) -> dict[str, str]:
|
||||
return {"text": arguments.text}
|
||||
|
||||
@@ -25,22 +62,119 @@ async def add(arguments: AddArguments, _: ToolExecutionContext) -> dict[str, flo
|
||||
return {"value": arguments.left + arguments.right}
|
||||
|
||||
|
||||
async def search_notes(arguments: NoteSearchArguments, _: ToolExecutionContext) -> dict:
|
||||
request = SearchRequest(**arguments.model_dump(), include_snippet=True)
|
||||
return (await engine.search(request)).model_dump(mode="json")
|
||||
|
||||
|
||||
async def read_note(arguments: NoteReadArguments, _: ToolExecutionContext) -> dict:
|
||||
note = await note_service.get_note(arguments.note_id)
|
||||
if note is None:
|
||||
raise LookupError(f"Note does not exist: {arguments.note_id}")
|
||||
return note.model_dump(mode="json")
|
||||
|
||||
|
||||
async def create_note(arguments: NoteCreateArguments, _: ToolExecutionContext) -> dict:
|
||||
note = await note_service.create_note(**arguments.model_dump())
|
||||
return note.model_dump(mode="json")
|
||||
|
||||
|
||||
async def update_note(arguments: NoteUpdateArguments, _: ToolExecutionContext) -> dict:
|
||||
values = arguments.model_dump()
|
||||
note_id = values.pop("note_id")
|
||||
note = await note_service.update_note(note_id, **values)
|
||||
return note.model_dump(mode="json")
|
||||
|
||||
|
||||
def list_notes(arguments: NoteListArguments, _: ToolExecutionContext) -> dict:
|
||||
items, total = note_service.list_notes(**arguments.model_dump())
|
||||
return {
|
||||
"items": [item.model_dump(mode="json") for item in items],
|
||||
"page": {"total": total, "limit": arguments.limit, "offset": arguments.offset},
|
||||
}
|
||||
|
||||
|
||||
def _register(
|
||||
registry: ToolRegistry,
|
||||
*,
|
||||
name: str,
|
||||
description: str,
|
||||
arguments_model: type[BaseModel],
|
||||
executor,
|
||||
permission: str | None = None,
|
||||
) -> None:
|
||||
registry.register(
|
||||
ToolDefinition(
|
||||
name=name,
|
||||
description=description,
|
||||
parameters=arguments_model.model_json_schema(),
|
||||
permission=permission,
|
||||
),
|
||||
arguments_model,
|
||||
executor,
|
||||
)
|
||||
|
||||
|
||||
def register_builtin_tools(registry: ToolRegistry) -> None:
|
||||
registry.register(
|
||||
ToolDefinition(
|
||||
name="system.echo",
|
||||
description="Echo text for local Agent integration testing.",
|
||||
parameters=EchoArguments.model_json_schema(),
|
||||
),
|
||||
EchoArguments,
|
||||
echo,
|
||||
_register(
|
||||
registry,
|
||||
name="system.echo",
|
||||
description="Echo text for local Agent integration testing.",
|
||||
arguments_model=EchoArguments,
|
||||
executor=echo,
|
||||
)
|
||||
registry.register(
|
||||
ToolDefinition(
|
||||
name="math.add",
|
||||
description="Add two numbers without external side effects.",
|
||||
parameters=AddArguments.model_json_schema(),
|
||||
),
|
||||
AddArguments,
|
||||
add,
|
||||
_register(
|
||||
registry,
|
||||
name="math.add",
|
||||
description="Add two numbers without external side effects.",
|
||||
arguments_model=AddArguments,
|
||||
executor=add,
|
||||
)
|
||||
_register(
|
||||
registry,
|
||||
name="notes.search",
|
||||
description="Search indexed notes and return snippets with citations.",
|
||||
arguments_model=NoteSearchArguments,
|
||||
executor=search_notes,
|
||||
permission="notes.search",
|
||||
)
|
||||
_register(
|
||||
registry,
|
||||
name="rag.search",
|
||||
description="Retrieve relevant note blocks for Agent context with citations.",
|
||||
arguments_model=NoteSearchArguments,
|
||||
executor=search_notes,
|
||||
permission="notes.search",
|
||||
)
|
||||
_register(
|
||||
registry,
|
||||
name="notes.read",
|
||||
description="Read a note and its parsed blocks by note_id.",
|
||||
arguments_model=NoteReadArguments,
|
||||
executor=read_note,
|
||||
permission="notes.read",
|
||||
)
|
||||
_register(
|
||||
registry,
|
||||
name="notes.create",
|
||||
description="Create a Markdown note in the current Vault.",
|
||||
arguments_model=NoteCreateArguments,
|
||||
executor=create_note,
|
||||
permission="notes.write",
|
||||
)
|
||||
_register(
|
||||
registry,
|
||||
name="notes.update",
|
||||
description="Update an existing Markdown note.",
|
||||
arguments_model=NoteUpdateArguments,
|
||||
executor=update_note,
|
||||
permission="notes.write",
|
||||
)
|
||||
_register(
|
||||
registry,
|
||||
name="notes.list",
|
||||
description="List note summaries with folder and tag filters.",
|
||||
arguments_model=NoteListArguments,
|
||||
executor=list_notes,
|
||||
permission="notes.read",
|
||||
)
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from collections.abc import AsyncIterator
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from typing import TYPE_CHECKING
|
||||
from uuid import uuid4
|
||||
|
||||
from app.agent.permissions import PermissionManager, PermissionMode
|
||||
@@ -13,6 +16,7 @@ from app.contracts import (
|
||||
AgentRun,
|
||||
AgentRunCreateRequest,
|
||||
AgentRunStatus,
|
||||
Citation,
|
||||
Message,
|
||||
MessageRole,
|
||||
ModelRequest,
|
||||
@@ -22,6 +26,9 @@ from app.contracts import (
|
||||
from app.providers.registry import ProviderRegistry
|
||||
from app.providers.base import ProviderError
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from app.extensions import AgentConfiguration, SkillRuntime
|
||||
|
||||
|
||||
class AgentRunNotFoundError(LookupError):
|
||||
pass
|
||||
@@ -38,6 +45,8 @@ TERMINAL_STATUSES = {
|
||||
class RunRecord:
|
||||
run: AgentRun
|
||||
request: AgentRunCreateRequest
|
||||
skill_config: AgentConfiguration | None = None
|
||||
allowed_tools: list[str] = field(default_factory=list)
|
||||
events: list[AgentEvent] = field(default_factory=list)
|
||||
subscribers: set[asyncio.Queue[AgentEvent]] = field(default_factory=set)
|
||||
task: asyncio.Task[None] | None = None
|
||||
@@ -49,14 +58,23 @@ class AgentRuntime:
|
||||
providers: ProviderRegistry,
|
||||
tools: ToolRegistry,
|
||||
permissions: PermissionManager,
|
||||
skills: SkillRuntime | None = None,
|
||||
) -> None:
|
||||
self.providers = providers
|
||||
self.tools = tools
|
||||
self.permissions = permissions
|
||||
self.skills = skills
|
||||
self._records: dict[str, RunRecord] = {}
|
||||
|
||||
async def create_run(self, request: AgentRunCreateRequest) -> AgentRun:
|
||||
self.providers.get(request.provider_id)
|
||||
provider = self.providers.get(request.provider_id)
|
||||
skill_config = None
|
||||
if request.skill_id:
|
||||
if self.skills is None:
|
||||
raise RuntimeError("Skill Runtime is not configured.")
|
||||
skill_config = self.skills.build_agent_configuration(
|
||||
request.skill_id, provider.config.capabilities
|
||||
)
|
||||
now = datetime.now(timezone.utc)
|
||||
run = AgentRun(
|
||||
run_id=f"run_{uuid4().hex}",
|
||||
@@ -70,7 +88,19 @@ class AgentRuntime:
|
||||
created_at=now,
|
||||
updated_at=now,
|
||||
)
|
||||
record = RunRecord(run=run, request=request)
|
||||
allowed_tools = list(request.allowed_tools)
|
||||
if skill_config is not None:
|
||||
allowed_tools = (
|
||||
[name for name in skill_config.allowed_tools if name in allowed_tools]
|
||||
if allowed_tools
|
||||
else list(skill_config.allowed_tools)
|
||||
)
|
||||
record = RunRecord(
|
||||
run=run,
|
||||
request=request,
|
||||
skill_config=skill_config,
|
||||
allowed_tools=allowed_tools,
|
||||
)
|
||||
self._records[run.run_id] = record
|
||||
record.task = asyncio.create_task(self._execute(record), name=run.run_id)
|
||||
return run.model_copy(deep=True)
|
||||
@@ -157,7 +187,7 @@ class AgentRuntime:
|
||||
)
|
||||
|
||||
messages = [Message(role=MessageRole.user, content=record.request.input)]
|
||||
allowed_tools = self.tools.definitions(record.request.allowed_tools)
|
||||
allowed_tools = self.tools.definitions(record.allowed_tools)
|
||||
provider = self.providers.get(record.request.provider_id).adapter
|
||||
|
||||
for step in range(1, record.request.max_steps + 1):
|
||||
@@ -167,9 +197,10 @@ class AgentRuntime:
|
||||
ModelRequest(
|
||||
provider_id=record.request.provider_id,
|
||||
model=record.request.model,
|
||||
system=(record.skill_config.system_prompt if record.skill_config else None),
|
||||
messages=messages,
|
||||
tools=allowed_tools,
|
||||
metadata=record.request.metadata,
|
||||
metadata=self._request_metadata(record),
|
||||
)
|
||||
)
|
||||
record.run.token_usage += turn.input_tokens + turn.output_tokens
|
||||
@@ -197,9 +228,16 @@ class AgentRuntime:
|
||||
messages.append(
|
||||
Message(role=MessageRole.assistant, content=turn.text or "", tool_calls=calls)
|
||||
)
|
||||
for call in calls:
|
||||
result = await self._execute_tool(record, call)
|
||||
semaphore = asyncio.Semaphore(record.request.max_concurrent_tools)
|
||||
|
||||
async def execute(call: ToolCall) -> ToolResult:
|
||||
async with semaphore:
|
||||
return await self._execute_tool(record, call)
|
||||
|
||||
results = await asyncio.gather(*(execute(call) for call in calls))
|
||||
for call, result in zip(calls, results):
|
||||
record.run.tool_results.append(result)
|
||||
self._collect_citations(record, result)
|
||||
messages.append(
|
||||
Message(
|
||||
role=MessageRole.tool,
|
||||
@@ -234,7 +272,7 @@ class AgentRuntime:
|
||||
except ToolNotFoundError:
|
||||
registered = None
|
||||
|
||||
if registered is not None and call.name not in record.request.allowed_tools:
|
||||
if registered is not None and call.name not in record.allowed_tools:
|
||||
result = ToolResult(
|
||||
tool_call_id=call.tool_call_id,
|
||||
name=call.name,
|
||||
@@ -246,6 +284,16 @@ class AgentRuntime:
|
||||
return result
|
||||
|
||||
permission = registered.definition.permission if registered else None
|
||||
if permission == "network.request" and not record.request.allow_network:
|
||||
result = ToolResult(
|
||||
tool_call_id=call.tool_call_id,
|
||||
name=call.name,
|
||||
success=False,
|
||||
error_code="NETWORK_NOT_ALLOWED",
|
||||
error_message="Agent run does not allow network tools.",
|
||||
)
|
||||
self._publish(record, AgentEventType.tool_result, result.model_dump(mode="json"))
|
||||
return result
|
||||
mode = self.permissions.mode_for(permission)
|
||||
if mode == PermissionMode.deny:
|
||||
result = self._permission_denied(call)
|
||||
@@ -348,6 +396,34 @@ class AgentRuntime:
|
||||
for queue in record.subscribers:
|
||||
queue.put_nowait(event)
|
||||
|
||||
@staticmethod
|
||||
def _request_metadata(record: RunRecord) -> dict[str, object]:
|
||||
metadata = dict(record.request.metadata)
|
||||
if record.skill_config is not None:
|
||||
metadata["skill_id"] = record.skill_config.skill_id
|
||||
metadata["retrieval"] = record.skill_config.retrieval.model_dump(mode="json")
|
||||
return metadata
|
||||
|
||||
def _collect_citations(self, record: RunRecord, result: ToolResult) -> None:
|
||||
if not result.success or not isinstance(result.output, dict):
|
||||
return
|
||||
items = result.output.get("items")
|
||||
if not isinstance(items, list):
|
||||
return
|
||||
known = {citation.citation_id for citation in record.run.citations}
|
||||
for item in items:
|
||||
if not isinstance(item, dict) or not isinstance(item.get("citation"), dict):
|
||||
continue
|
||||
try:
|
||||
citation = Citation.model_validate(item["citation"])
|
||||
except ValueError:
|
||||
continue
|
||||
if citation.citation_id in known:
|
||||
continue
|
||||
known.add(citation.citation_id)
|
||||
record.run.citations.append(citation)
|
||||
self._publish(record, AgentEventType.citation, citation.model_dump(mode="json"))
|
||||
|
||||
def _get_record(self, run_id: str) -> RunRecord:
|
||||
try:
|
||||
return self._records[run_id]
|
||||
|
||||
@@ -3,6 +3,8 @@ from dataclasses import dataclass
|
||||
from app.agent import AgentRuntime, PermissionManager, PermissionPolicy, ToolRegistry
|
||||
from app.agent.builtin_tools import register_builtin_tools
|
||||
from app.contracts import ModelCapability, ProviderConfig, ProviderType
|
||||
from app.config import BACKEND_DIR
|
||||
from app.extensions import PluginRuntime, SkillRuntime
|
||||
from app.providers import MockProvider, ProviderFactory, ProviderRegistry
|
||||
from app.providers.credentials import EnvironmentCredentialResolver
|
||||
|
||||
@@ -13,6 +15,8 @@ class ApplicationContainer:
|
||||
provider_factory: ProviderFactory
|
||||
tools: ToolRegistry
|
||||
permissions: PermissionManager
|
||||
skills: SkillRuntime
|
||||
plugins: PluginRuntime
|
||||
agent: AgentRuntime
|
||||
|
||||
|
||||
@@ -38,14 +42,29 @@ def build_container() -> ApplicationContainer:
|
||||
tools = ToolRegistry()
|
||||
register_builtin_tools(tools)
|
||||
|
||||
plugins = PluginRuntime(tools)
|
||||
plugins.install(BACKEND_DIR / "extensions" / "plugins" / "text-tools")
|
||||
plugins.enable("text-tools")
|
||||
|
||||
skills = SkillRuntime(tools)
|
||||
skills.install(BACKEND_DIR / "extensions" / "skills" / "knowledge-assistant")
|
||||
skills.enable("knowledge-assistant")
|
||||
|
||||
policy = PermissionPolicy()
|
||||
permissions = PermissionManager(policy)
|
||||
agent = AgentRuntime(providers=providers, tools=tools, permissions=permissions)
|
||||
agent = AgentRuntime(
|
||||
providers=providers,
|
||||
tools=tools,
|
||||
permissions=permissions,
|
||||
skills=skills,
|
||||
)
|
||||
return ApplicationContainer(
|
||||
providers=providers,
|
||||
provider_factory=provider_factory,
|
||||
tools=tools,
|
||||
permissions=permissions,
|
||||
skills=skills,
|
||||
plugins=plugins,
|
||||
agent=agent,
|
||||
)
|
||||
|
||||
|
||||
+31
-29
@@ -49,6 +49,7 @@ from app.contracts import (
|
||||
from app.agent import AgentRunNotFoundError
|
||||
from app.container import container
|
||||
from app.errors import ApiError, not_implemented
|
||||
from app.extensions import ExtensionError
|
||||
from app.providers.registry import ProviderNotFoundError
|
||||
from app.providers.factory import UnsupportedProviderError
|
||||
from app.retrieval.engine import engine
|
||||
@@ -102,6 +103,13 @@ def configurable_provider_or_404(provider_id: str):
|
||||
) from exc
|
||||
|
||||
|
||||
def extension_call(operation):
|
||||
try:
|
||||
return operation()
|
||||
except ExtensionError as exc:
|
||||
raise ApiError(exc.status_code, exc.code, exc.message, exc.details) from exc
|
||||
|
||||
|
||||
# Notes
|
||||
@router.get("/notes", response_model=NoteListResponse, tags=["Notes"])
|
||||
async def list_notes(
|
||||
@@ -203,18 +211,19 @@ async def list_agent_runs(
|
||||
"/agent/runs",
|
||||
response_model=AgentRun,
|
||||
status_code=202,
|
||||
responses=not_implemented_response,
|
||||
tags=["Agent"],
|
||||
)
|
||||
async def create_agent_run(request: AgentRunCreateRequest) -> AgentRun:
|
||||
provider_or_404(request.provider_id)
|
||||
return await container.agent.create_run(request)
|
||||
try:
|
||||
return await container.agent.create_run(request)
|
||||
except ExtensionError as exc:
|
||||
raise ApiError(exc.status_code, exc.code, exc.message, exc.details) from exc
|
||||
|
||||
|
||||
@router.get(
|
||||
"/agent/runs/{run_id}",
|
||||
response_model=AgentRun,
|
||||
responses=not_implemented_response,
|
||||
tags=["Agent"],
|
||||
)
|
||||
async def get_agent_run(run_id: str) -> AgentRun:
|
||||
@@ -224,7 +233,6 @@ async def get_agent_run(run_id: str) -> AgentRun:
|
||||
@router.post(
|
||||
"/agent/runs/{run_id}/cancel",
|
||||
response_model=OperationResponse,
|
||||
responses=not_implemented_response,
|
||||
tags=["Agent"],
|
||||
)
|
||||
async def cancel_agent_run(run_id: str) -> OperationResponse:
|
||||
@@ -261,7 +269,6 @@ async def agent_events(run_id: str) -> StreamingResponse:
|
||||
@router.post(
|
||||
"/agent/runs/{run_id}/permissions/{request_id}",
|
||||
response_model=OperationResponse,
|
||||
responses=not_implemented_response,
|
||||
tags=["Agent"],
|
||||
)
|
||||
async def decide_agent_permission(
|
||||
@@ -288,112 +295,107 @@ async def list_tools() -> ToolListResponse:
|
||||
# Skills
|
||||
@router.get("/skills", response_model=SkillListResponse, tags=["Skills"])
|
||||
async def list_skills() -> SkillListResponse:
|
||||
return SkillListResponse()
|
||||
return SkillListResponse(items=container.skills.list())
|
||||
|
||||
|
||||
@router.get(
|
||||
"/skills/{skill_id}", response_model=Skill, responses=not_implemented_response, tags=["Skills"]
|
||||
"/skills/{skill_id}", response_model=Skill, tags=["Skills"]
|
||||
)
|
||||
async def get_skill(skill_id: str) -> Skill:
|
||||
not_implemented(f"skills.read:{skill_id}")
|
||||
return extension_call(lambda: container.skills.get(skill_id))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/skills/install",
|
||||
response_model=Skill,
|
||||
status_code=202,
|
||||
responses=not_implemented_response,
|
||||
tags=["Skills"],
|
||||
)
|
||||
async def install_skill(_: ExtensionInstallRequest) -> Skill:
|
||||
not_implemented("skills.install")
|
||||
async def install_skill(request: ExtensionInstallRequest) -> Skill:
|
||||
return extension_call(lambda: container.skills.install(request.package_path))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/skills/{skill_id}/enable",
|
||||
response_model=Skill,
|
||||
responses=not_implemented_response,
|
||||
tags=["Skills"],
|
||||
)
|
||||
async def enable_skill(skill_id: str) -> Skill:
|
||||
not_implemented(f"skills.enable:{skill_id}")
|
||||
return extension_call(lambda: container.skills.enable(skill_id))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/skills/{skill_id}/disable",
|
||||
response_model=Skill,
|
||||
responses=not_implemented_response,
|
||||
tags=["Skills"],
|
||||
)
|
||||
async def disable_skill(skill_id: str) -> Skill:
|
||||
not_implemented(f"skills.disable:{skill_id}")
|
||||
return extension_call(lambda: container.skills.disable(skill_id))
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/skills/{skill_id}",
|
||||
response_model=OperationResponse,
|
||||
responses=not_implemented_response,
|
||||
tags=["Skills"],
|
||||
)
|
||||
async def uninstall_skill(skill_id: str) -> OperationResponse:
|
||||
not_implemented(f"skills.uninstall:{skill_id}")
|
||||
extension_call(lambda: container.skills.uninstall(skill_id))
|
||||
return OperationResponse(status="completed", resource_id=skill_id, message="uninstalled")
|
||||
|
||||
|
||||
# Plugins
|
||||
@router.get("/plugins", response_model=PluginListResponse, tags=["Plugins"])
|
||||
async def list_plugins() -> PluginListResponse:
|
||||
return PluginListResponse()
|
||||
return PluginListResponse(items=container.plugins.list())
|
||||
|
||||
|
||||
@router.get(
|
||||
"/plugins/{plugin_id}",
|
||||
response_model=Plugin,
|
||||
responses=not_implemented_response,
|
||||
tags=["Plugins"],
|
||||
)
|
||||
async def get_plugin(plugin_id: str) -> Plugin:
|
||||
not_implemented(f"plugins.read:{plugin_id}")
|
||||
return extension_call(lambda: container.plugins.get(plugin_id))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/plugins/install",
|
||||
response_model=Plugin,
|
||||
status_code=202,
|
||||
responses=not_implemented_response,
|
||||
tags=["Plugins"],
|
||||
)
|
||||
async def install_plugin(_: ExtensionInstallRequest) -> Plugin:
|
||||
not_implemented("plugins.install")
|
||||
async def install_plugin(request: ExtensionInstallRequest) -> Plugin:
|
||||
return extension_call(lambda: container.plugins.install(request.package_path))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/plugins/{plugin_id}/enable",
|
||||
response_model=Plugin,
|
||||
responses=not_implemented_response,
|
||||
tags=["Plugins"],
|
||||
)
|
||||
async def enable_plugin(plugin_id: str) -> Plugin:
|
||||
not_implemented(f"plugins.enable:{plugin_id}")
|
||||
return extension_call(lambda: container.plugins.enable(plugin_id))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/plugins/{plugin_id}/disable",
|
||||
response_model=Plugin,
|
||||
responses=not_implemented_response,
|
||||
tags=["Plugins"],
|
||||
)
|
||||
async def disable_plugin(plugin_id: str) -> Plugin:
|
||||
not_implemented(f"plugins.disable:{plugin_id}")
|
||||
return extension_call(lambda: container.plugins.disable(plugin_id))
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/plugins/{plugin_id}",
|
||||
response_model=OperationResponse,
|
||||
responses=not_implemented_response,
|
||||
tags=["Plugins"],
|
||||
)
|
||||
async def uninstall_plugin(plugin_id: str) -> OperationResponse:
|
||||
not_implemented(f"plugins.uninstall:{plugin_id}")
|
||||
plugin = extension_call(lambda: container.plugins.get(plugin_id))
|
||||
dependent_skills = container.skills.depending_on_tools(plugin.manifest.contributes.tools)
|
||||
extension_call(lambda: container.plugins.uninstall(plugin_id, dependent_skills))
|
||||
return OperationResponse(status="completed", resource_id=plugin_id, message="uninstalled")
|
||||
|
||||
|
||||
# Providers
|
||||
|
||||
Reference in New Issue
Block a user