tool improvment
This commit is contained in:
@@ -1,20 +1,42 @@
|
||||
"""
|
||||
Trilium notes executor for searching and creating notes via ETAPI.
|
||||
|
||||
This module provides integration with Trilium notes through the ETAPI HTTP API.
|
||||
This module provides integration with Trilium notes through the ETAPI HTTP API
|
||||
with improved resilience: timeout configuration, retry logic, and connection pooling.
|
||||
"""
|
||||
|
||||
import os
|
||||
import asyncio
|
||||
import aiohttp
|
||||
from typing import Dict
|
||||
from typing import Dict, Optional
|
||||
from ..utils.resilience import async_retry
|
||||
|
||||
|
||||
TRILIUM_URL = os.getenv("TRILIUM_URL", "http://localhost:8080")
|
||||
TRILIUM_TOKEN = os.getenv("TRILIUM_ETAPI_TOKEN", "")
|
||||
|
||||
# Module-level session for connection pooling
|
||||
_session: Optional[aiohttp.ClientSession] = None
|
||||
|
||||
|
||||
def get_session() -> aiohttp.ClientSession:
|
||||
"""Get or create shared aiohttp session for connection pooling."""
|
||||
global _session
|
||||
if _session is None or _session.closed:
|
||||
timeout = aiohttp.ClientTimeout(
|
||||
total=float(os.getenv("TRILIUM_TIMEOUT", "30.0")),
|
||||
connect=float(os.getenv("TRILIUM_CONNECT_TIMEOUT", "10.0"))
|
||||
)
|
||||
_session = aiohttp.ClientSession(timeout=timeout)
|
||||
return _session
|
||||
|
||||
|
||||
@async_retry(
|
||||
max_attempts=3,
|
||||
exceptions=(aiohttp.ClientError, asyncio.TimeoutError)
|
||||
)
|
||||
async def search_notes(args: Dict) -> Dict:
|
||||
"""Search Trilium notes via ETAPI.
|
||||
"""Search Trilium notes via ETAPI with retry logic.
|
||||
|
||||
Args:
|
||||
args: Dictionary containing:
|
||||
@@ -36,40 +58,72 @@ async def search_notes(args: Dict) -> Dict:
|
||||
return {"error": "No query provided"}
|
||||
|
||||
if not TRILIUM_TOKEN:
|
||||
return {"error": "TRILIUM_ETAPI_TOKEN not configured in environment"}
|
||||
return {
|
||||
"error": "TRILIUM_ETAPI_TOKEN not configured in environment",
|
||||
"hint": "Set TRILIUM_ETAPI_TOKEN in .env file"
|
||||
}
|
||||
|
||||
# Cap limit
|
||||
limit = min(max(limit, 1), 20)
|
||||
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(
|
||||
f"{TRILIUM_URL}/etapi/notes",
|
||||
params={"search": query, "limit": limit},
|
||||
headers={"Authorization": TRILIUM_TOKEN}
|
||||
) as resp:
|
||||
if resp.status == 200:
|
||||
data = await resp.json()
|
||||
# ETAPI returns {"results": [...]} format
|
||||
results = data.get("results", [])
|
||||
return {
|
||||
"notes": results,
|
||||
"count": len(results)
|
||||
}
|
||||
elif resp.status == 401:
|
||||
return {"error": "Authentication failed. Check TRILIUM_ETAPI_TOKEN"}
|
||||
else:
|
||||
error_text = await resp.text()
|
||||
return {"error": f"HTTP {resp.status}: {error_text}"}
|
||||
session = get_session()
|
||||
async with session.get(
|
||||
f"{TRILIUM_URL}/etapi/notes",
|
||||
params={"search": query, "limit": limit},
|
||||
headers={"Authorization": TRILIUM_TOKEN}
|
||||
) as resp:
|
||||
if resp.status == 200:
|
||||
data = await resp.json()
|
||||
# ETAPI returns {"results": [...]} format
|
||||
results = data.get("results", [])
|
||||
return {
|
||||
"notes": results,
|
||||
"count": len(results)
|
||||
}
|
||||
elif resp.status == 401:
|
||||
return {
|
||||
"error": "Authentication failed. Check TRILIUM_ETAPI_TOKEN",
|
||||
"status": 401
|
||||
}
|
||||
elif resp.status == 404:
|
||||
return {
|
||||
"error": "Trilium API endpoint not found. Check TRILIUM_URL",
|
||||
"status": 404,
|
||||
"url": TRILIUM_URL
|
||||
}
|
||||
else:
|
||||
error_text = await resp.text()
|
||||
return {
|
||||
"error": f"HTTP {resp.status}: {error_text}",
|
||||
"status": resp.status
|
||||
}
|
||||
|
||||
except aiohttp.ClientConnectorError:
|
||||
return {"error": f"Cannot connect to Trilium at {TRILIUM_URL}"}
|
||||
except aiohttp.ClientConnectorError as e:
|
||||
return {
|
||||
"error": f"Cannot connect to Trilium at {TRILIUM_URL}",
|
||||
"hint": "Check if Trilium is running and URL is correct",
|
||||
"details": str(e)
|
||||
}
|
||||
except asyncio.TimeoutError:
|
||||
timeout = os.getenv("TRILIUM_TIMEOUT", "30.0")
|
||||
return {
|
||||
"error": f"Trilium request timeout after {timeout}s",
|
||||
"hint": "Trilium may be slow or unresponsive"
|
||||
}
|
||||
except Exception as e:
|
||||
return {"error": f"Search failed: {str(e)}"}
|
||||
return {
|
||||
"error": f"Search failed: {str(e)}",
|
||||
"type": type(e).__name__
|
||||
}
|
||||
|
||||
|
||||
@async_retry(
|
||||
max_attempts=3,
|
||||
exceptions=(aiohttp.ClientError, asyncio.TimeoutError)
|
||||
)
|
||||
async def create_note(args: Dict) -> Dict:
|
||||
"""Create a note in Trilium via ETAPI.
|
||||
"""Create a note in Trilium via ETAPI with retry logic.
|
||||
|
||||
Args:
|
||||
args: Dictionary containing:
|
||||
@@ -97,7 +151,10 @@ async def create_note(args: Dict) -> Dict:
|
||||
return {"error": "No content provided"}
|
||||
|
||||
if not TRILIUM_TOKEN:
|
||||
return {"error": "TRILIUM_ETAPI_TOKEN not configured in environment"}
|
||||
return {
|
||||
"error": "TRILIUM_ETAPI_TOKEN not configured in environment",
|
||||
"hint": "Set TRILIUM_ETAPI_TOKEN in .env file"
|
||||
}
|
||||
|
||||
# Prepare payload
|
||||
payload = {
|
||||
@@ -109,26 +166,51 @@ async def create_note(args: Dict) -> Dict:
|
||||
}
|
||||
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
f"{TRILIUM_URL}/etapi/create-note",
|
||||
json=payload,
|
||||
headers={"Authorization": TRILIUM_TOKEN}
|
||||
) as resp:
|
||||
if resp.status in [200, 201]:
|
||||
data = await resp.json()
|
||||
return {
|
||||
"noteId": data.get("noteId"),
|
||||
"title": title,
|
||||
"success": True
|
||||
}
|
||||
elif resp.status == 401:
|
||||
return {"error": "Authentication failed. Check TRILIUM_ETAPI_TOKEN"}
|
||||
else:
|
||||
error_text = await resp.text()
|
||||
return {"error": f"HTTP {resp.status}: {error_text}"}
|
||||
session = get_session()
|
||||
async with session.post(
|
||||
f"{TRILIUM_URL}/etapi/create-note",
|
||||
json=payload,
|
||||
headers={"Authorization": TRILIUM_TOKEN}
|
||||
) as resp:
|
||||
if resp.status in [200, 201]:
|
||||
data = await resp.json()
|
||||
return {
|
||||
"noteId": data.get("noteId"),
|
||||
"title": title,
|
||||
"success": True
|
||||
}
|
||||
elif resp.status == 401:
|
||||
return {
|
||||
"error": "Authentication failed. Check TRILIUM_ETAPI_TOKEN",
|
||||
"status": 401
|
||||
}
|
||||
elif resp.status == 404:
|
||||
return {
|
||||
"error": "Trilium API endpoint not found. Check TRILIUM_URL",
|
||||
"status": 404,
|
||||
"url": TRILIUM_URL
|
||||
}
|
||||
else:
|
||||
error_text = await resp.text()
|
||||
return {
|
||||
"error": f"HTTP {resp.status}: {error_text}",
|
||||
"status": resp.status
|
||||
}
|
||||
|
||||
except aiohttp.ClientConnectorError:
|
||||
return {"error": f"Cannot connect to Trilium at {TRILIUM_URL}"}
|
||||
except aiohttp.ClientConnectorError as e:
|
||||
return {
|
||||
"error": f"Cannot connect to Trilium at {TRILIUM_URL}",
|
||||
"hint": "Check if Trilium is running and URL is correct",
|
||||
"details": str(e)
|
||||
}
|
||||
except asyncio.TimeoutError:
|
||||
timeout = os.getenv("TRILIUM_TIMEOUT", "30.0")
|
||||
return {
|
||||
"error": f"Trilium request timeout after {timeout}s",
|
||||
"hint": "Trilium may be slow or unresponsive"
|
||||
}
|
||||
except Exception as e:
|
||||
return {"error": f"Note creation failed: {str(e)}"}
|
||||
return {
|
||||
"error": f"Note creation failed: {str(e)}",
|
||||
"type": type(e).__name__
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user