2026-02-18 12:50:53 -08:00
|
|
|
"""
|
|
|
|
|
RLM-MEM - REPL Functions
|
|
|
|
|
Memory access functions available within the REPL sandbox.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from typing import Dict, Any, List, Optional
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import re
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def read_chunk(chunk_id: str, chunk_store) -> Optional[Dict[str, Any]]:
|
|
|
|
|
"""
|
|
|
|
|
Read a chunk by ID.
|
2026-03-05 16:33:10 -08:00
|
|
|
|
2026-02-18 12:50:53 -08:00
|
|
|
Args:
|
|
|
|
|
chunk_id: The chunk ID to read
|
|
|
|
|
chunk_store: ChunkStore instance
|
2026-03-05 16:33:10 -08:00
|
|
|
|
2026-02-18 12:50:53 -08:00
|
|
|
Returns:
|
|
|
|
|
Chunk data dict or None if not found
|
|
|
|
|
"""
|
|
|
|
|
# Validate chunk_id format - reject path traversal attempts
|
|
|
|
|
if chunk_id is None:
|
|
|
|
|
return None
|
2026-03-05 16:33:10 -08:00
|
|
|
|
2026-02-18 12:50:53 -08:00
|
|
|
# Check for path traversal patterns
|
2026-03-05 16:33:10 -08:00
|
|
|
if ".." in chunk_id or "/" in chunk_id or "\\" in chunk_id:
|
2026-02-18 12:50:53 -08:00
|
|
|
return None
|
2026-03-05 16:33:10 -08:00
|
|
|
|
2026-02-18 12:50:53 -08:00
|
|
|
# Only allow alphanumeric, hyphens, and underscores
|
2026-03-05 16:33:10 -08:00
|
|
|
if not re.match(r"^[a-zA-Z0-9_-]+$", chunk_id):
|
2026-02-18 12:50:53 -08:00
|
|
|
return None
|
2026-03-05 16:33:10 -08:00
|
|
|
|
2026-02-18 12:50:53 -08:00
|
|
|
try:
|
|
|
|
|
chunk = chunk_store.get_chunk(chunk_id)
|
|
|
|
|
if chunk is None:
|
|
|
|
|
return None
|
2026-03-05 16:33:10 -08:00
|
|
|
|
2026-02-18 12:50:53 -08:00
|
|
|
# Convert Chunk dataclass to dict
|
|
|
|
|
return {
|
2026-03-05 16:33:10 -08:00
|
|
|
"id": chunk.id,
|
|
|
|
|
"content": chunk.content,
|
|
|
|
|
"tokens": chunk.tokens,
|
|
|
|
|
"type": chunk.type,
|
|
|
|
|
"metadata": chunk.metadata,
|
|
|
|
|
"links": chunk.links,
|
|
|
|
|
"tags": chunk.tags,
|
2026-02-18 12:50:53 -08:00
|
|
|
}
|
2026-03-05 16:33:10 -08:00
|
|
|
except (AttributeError, TypeError, KeyError, ValueError):
|
2026-02-18 12:50:53 -08:00
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def search_chunks(query: str, chunk_store, limit: int = 10) -> List[str]:
|
|
|
|
|
"""
|
|
|
|
|
Search for chunks matching query.
|
2026-03-05 16:33:10 -08:00
|
|
|
|
2026-02-18 12:50:53 -08:00
|
|
|
Args:
|
|
|
|
|
query: Search query string
|
|
|
|
|
chunk_store: ChunkStore instance
|
|
|
|
|
limit: Maximum results to return
|
2026-03-05 16:33:10 -08:00
|
|
|
|
2026-02-18 12:50:53 -08:00
|
|
|
Returns:
|
|
|
|
|
List of matching chunk IDs
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
# Simple keyword search for now
|
|
|
|
|
# In production, this could use embeddings or more sophisticated search
|
|
|
|
|
query_lower = query.lower()
|
|
|
|
|
words = set(query_lower.split())
|
2026-03-05 16:33:10 -08:00
|
|
|
|
2026-02-18 12:50:53 -08:00
|
|
|
all_chunks = chunk_store.list_chunks()
|
|
|
|
|
results = []
|
2026-03-05 16:33:10 -08:00
|
|
|
|
2026-02-18 12:50:53 -08:00
|
|
|
for chunk_id in all_chunks:
|
|
|
|
|
chunk = chunk_store.get_chunk(chunk_id)
|
|
|
|
|
if chunk is None:
|
|
|
|
|
continue
|
2026-03-05 16:33:10 -08:00
|
|
|
|
2026-02-18 12:50:53 -08:00
|
|
|
content_lower = chunk.content.lower()
|
2026-03-05 16:33:10 -08:00
|
|
|
|
2026-02-18 12:50:53 -08:00
|
|
|
# Check if any query word appears in content
|
|
|
|
|
if any(word in content_lower for word in words):
|
|
|
|
|
results.append(chunk_id)
|
2026-03-05 16:33:10 -08:00
|
|
|
|
2026-02-18 12:50:53 -08:00
|
|
|
if len(results) >= limit:
|
|
|
|
|
break
|
2026-03-05 16:33:10 -08:00
|
|
|
|
2026-02-18 12:50:53 -08:00
|
|
|
return results
|
2026-03-05 16:33:10 -08:00
|
|
|
except (AttributeError, TypeError, KeyError, ValueError):
|
2026-02-18 12:50:53 -08:00
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def list_chunks_by_tag(tags, chunk_store) -> List[str]:
|
|
|
|
|
"""
|
|
|
|
|
List all chunks with given tag(s).
|
2026-03-05 16:33:10 -08:00
|
|
|
|
2026-02-18 12:50:53 -08:00
|
|
|
Args:
|
|
|
|
|
tags: Single tag string or list of tags to search for
|
|
|
|
|
chunk_store: ChunkStore instance
|
2026-03-05 16:33:10 -08:00
|
|
|
|
2026-02-18 12:50:53 -08:00
|
|
|
Returns:
|
|
|
|
|
List of chunk IDs with the tag(s)
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
# Handle single tag or list of tags
|
|
|
|
|
if isinstance(tags, str):
|
|
|
|
|
return chunk_store.list_chunks(tags=[tags])
|
|
|
|
|
elif isinstance(tags, list):
|
|
|
|
|
return chunk_store.list_chunks(tags=tags)
|
|
|
|
|
return []
|
2026-03-05 16:33:10 -08:00
|
|
|
except (AttributeError, TypeError, KeyError, ValueError):
|
2026-02-18 12:50:53 -08:00
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
|
2026-03-05 16:33:10 -08:00
|
|
|
def get_linked_chunks(
|
|
|
|
|
chunk_id: str, chunk_store, link_type: Optional[str] = None
|
|
|
|
|
) -> List[Dict[str, Any]]:
|
2026-02-18 12:50:53 -08:00
|
|
|
"""
|
|
|
|
|
Get chunks linked to the given chunk.
|
2026-03-05 16:33:10 -08:00
|
|
|
|
2026-02-18 12:50:53 -08:00
|
|
|
Args:
|
|
|
|
|
chunk_id: Source chunk ID
|
|
|
|
|
chunk_store: ChunkStore instance
|
|
|
|
|
link_type: Optional link type filter (e.g., 'context_of', 'follows', 'related_to')
|
2026-03-05 16:33:10 -08:00
|
|
|
|
2026-02-18 12:50:53 -08:00
|
|
|
Returns:
|
|
|
|
|
List of linked chunk data dicts
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
chunk = chunk_store.get_chunk(chunk_id)
|
|
|
|
|
if chunk is None:
|
|
|
|
|
return []
|
2026-03-05 16:33:10 -08:00
|
|
|
|
2026-02-18 12:50:53 -08:00
|
|
|
linked = []
|
|
|
|
|
for link in chunk.links:
|
|
|
|
|
# Filter by link type if specified
|
2026-03-05 16:33:10 -08:00
|
|
|
if link_type and link.get("type") != link_type:
|
2026-02-18 12:50:53 -08:00
|
|
|
continue
|
2026-03-05 16:33:10 -08:00
|
|
|
|
|
|
|
|
target_id = link.get("target_id")
|
2026-02-18 12:50:53 -08:00
|
|
|
if target_id:
|
|
|
|
|
target_chunk = read_chunk(target_id, chunk_store)
|
|
|
|
|
if target_chunk:
|
|
|
|
|
# Include link metadata
|
2026-03-05 16:33:10 -08:00
|
|
|
target_chunk["_link_type"] = link.get("type", "unknown")
|
|
|
|
|
target_chunk["_link_strength"] = link.get("strength", 0.5)
|
2026-02-18 12:50:53 -08:00
|
|
|
linked.append(target_chunk)
|
2026-03-05 16:33:10 -08:00
|
|
|
|
2026-02-18 12:50:53 -08:00
|
|
|
return linked
|
2026-03-05 16:33:10 -08:00
|
|
|
except (AttributeError, TypeError, KeyError, ValueError):
|
2026-02-18 12:50:53 -08:00
|
|
|
return []
|