AgentToolStore / client /src /toolstore /index_manager.py
ToolStore Agent
fix: rename config/index to settings/registry, fix skill execution
ab4accd
Raw
History Blame Contribute Delete
5.4 kB
import json
import os
from datetime import datetime, timezone
from pathlib import Path
from typing import Dict, List, Any, Optional
class IndexManager:
def __init__(self, config_dir: Optional[Path] = None):
if config_dir:
self.config_dir = config_dir
else:
# Use same resolution as ConfigManager — respects TOOLSTORE_HOME
from toolstore.config_manager import ConfigManager as _CM
self.config_dir = _CM().config_dir
self.registry_file = self.config_dir / "registry.json"
self._legacy_file = self.config_dir / "index.json" # pre-rename
self.config_dir.mkdir(parents=True, exist_ok=True)
self.index_data: Dict[str, Any] = {"meta": {}, "tools": {}}
# ----------------------------------------------------------------
# Persistence
# ----------------------------------------------------------------
def load(self):
"""Load registry from disk into memory.
Auto-migrates from old 'index.json' name on first access."""
# Migration: if registry.json doesn't exist but index.json does, rename it
if not self.registry_file.exists() and self._legacy_file.exists():
self._legacy_file.rename(self.registry_file)
if self.registry_file.exists():
try:
with open(self.registry_file, "r", encoding="utf-8") as f:
self.index_data = json.load(f)
except json.JSONDecodeError:
self.index_data = {"meta": {}, "tools": {}}
def save(self):
"""Save current in-memory registry to disk."""
with open(self.registry_file, "w", encoding="utf-8") as f:
json.dump(self.index_data, f, indent=2)
# ----------------------------------------------------------------
# Tool management
# ----------------------------------------------------------------
def update_from_remote(self, remote_data: List[Dict[str, Any]]):
"""Merge remote data (public index, MCP scan, skill scan) into the index."""
for tool in remote_data:
name = tool.get("name")
if name:
tool.setdefault("source", "public")
self.index_data["tools"][name] = tool
self.index_data["meta"]["last_updated"] = datetime.now(
timezone.utc).isoformat()
self.index_data["meta"]["count"] = len(self.index_data["tools"])
self.save()
def register_tool(self, tool_def: Dict[str, Any]) -> None:
"""Register or update a single tool."""
name = tool_def.get("name")
if not name:
raise ValueError("Tool definition must have a 'name'")
tool_def.setdefault("source", "local")
self.index_data["tools"][name] = tool_def
self.index_data["meta"]["count"] = len(self.index_data["tools"])
self.save()
def unregister_tool(self, name: str) -> bool:
"""Remove a tool from the index. Returns True if it existed."""
existed = name in self.index_data.get("tools", {})
self.index_data["tools"].pop(name, None)
self.index_data["meta"]["count"] = len(self.index_data["tools"])
if existed:
self.save()
return existed
# ----------------------------------------------------------------
# Search
# ----------------------------------------------------------------
def search(self, query: str,
tool_type: str = None,
source: str = None) -> List[Dict[str, Any]]:
"""Search for tools matching query across name, description, and keywords.
Args:
query: Search string (case-insensitive substring match)
tool_type: Optional filter by tool type ('api', 'mcp', 'skill')
source: Optional filter by source ('public', 'mcp:name', 'skill')
"""
results = []
query = query.lower()
tools = self.index_data.get("tools", {})
for name, tool in tools.items():
# Type filter
if tool_type and tool.get("type") != tool_type:
continue
# Source filter
if source and tool.get("source") != source:
continue
description = tool.get("description", "").lower()
keywords = tool.get("keywords", [])
if (query in name.lower()
or query in description
or any(query in str(k).lower() for k in keywords)):
results.append(tool)
return results
def get_tool(self, tool_name: str) -> Optional[Dict[str, Any]]:
"""Get a specific tool definition by name."""
return self.index_data.get("tools", {}).get(tool_name)
# ----------------------------------------------------------------
# Type-aware queries
# ----------------------------------------------------------------
def list_by_type(self, tool_type: str) -> List[Dict[str, Any]]:
"""Return all tools of a given type."""
return [
t for t in self.index_data.get("tools", {}).values()
if t.get("type") == tool_type
]
def get_all(self) -> Dict[str, Dict[str, Any]]:
"""Return the full tools dict."""
return dict(self.index_data.get("tools", {}))
def count(self) -> int:
return len(self.index_data.get("tools", {}))