|
4 | 4 |
|
5 | 5 | import ast |
6 | 6 | import contextlib |
7 | | -import glob |
8 | | -import json |
9 | 7 | import math |
10 | 8 | import operator |
11 | 9 | import os |
|
21 | 19 |
|
22 | 20 | g_ctx = None |
23 | 21 |
|
24 | | -# ----------------------------- |
25 | | -# In-memory storage (replace later) |
26 | | -# ----------------------------- |
27 | | - |
28 | | -_MEMORY_STORE: Dict[str, Any] = {} |
29 | | -_SEMANTIC_STORE: List[Dict[str, Any]] = [] # {id, text, metadata} |
30 | | - |
31 | | - |
32 | | -# ----------------------------- |
33 | | -# Memory tools |
34 | | -# ----------------------------- |
35 | | - |
36 | | - |
37 | | -def memory_read(key: str) -> Any: |
38 | | - """Read a value from persistent memory.""" |
39 | | - return _MEMORY_STORE.get(key) |
40 | | - |
41 | | - |
42 | | -def memory_write(key: str, value: Any) -> bool: |
43 | | - """Write a value to persistent memory.""" |
44 | | - _MEMORY_STORE[key] = value |
45 | | - return True |
46 | | - |
47 | | - |
48 | | -# ----------------------------- |
49 | | -# Path safety helpers |
50 | | -# ----------------------------- |
51 | | - |
52 | | -# Limit tools to only access files and folders within LLMS_BASE_DIR if specified, otherwise the current working directory |
53 | | -_BASE_DIR = os.environ.get("LLMS_BASE_DIR") or os.path.realpath(os.getcwd()) |
54 | | - |
55 | | - |
56 | | -def _resolve_safe_path(path: str) -> str: |
57 | | - """ |
58 | | - Resolve a path and ensure it stays within the current working directory. |
59 | | - Raises ValueError if the path escapes the base directory. |
60 | | - """ |
61 | | - resolved = os.path.realpath(os.path.join(_BASE_DIR, path)) |
62 | | - if not resolved.startswith(_BASE_DIR + os.sep) and resolved != _BASE_DIR: |
63 | | - raise ValueError("Access denied: path is outside the working directory") |
64 | | - return resolved |
65 | | - |
66 | | - |
67 | | -# ----------------------------- |
68 | | -# Semantic search (placeholder) |
69 | | -# ----------------------------- |
70 | | - |
71 | | - |
72 | | -def semantic_search(query: str, top_k: int = 5) -> List[Dict[str, Any]]: |
73 | | - """ |
74 | | - Naive semantic search placeholder. |
75 | | - Replace with embeddings + vector DB. |
76 | | - """ |
77 | | - results = [] |
78 | | - for item in _SEMANTIC_STORE: |
79 | | - if query.lower() in item["text"].lower(): |
80 | | - results.append(item) |
81 | | - return results[:top_k] |
82 | | - |
83 | | - |
84 | | -# ----------------------------- |
85 | | -# File system tools (restricted to CWD) |
86 | | -# ----------------------------- |
87 | | - |
88 | | - |
89 | | -def read_file(path: str) -> str: |
90 | | - """Read a text file from disk within the current working directory.""" |
91 | | - safe_path = _resolve_safe_path(path) |
92 | | - with open(safe_path, encoding="utf-8") as f: |
93 | | - return f.read() |
94 | | - |
95 | | - |
96 | | -def write_file(path: str, content: str) -> bool: |
97 | | - """Write text to a file within the current working directory (overwrites).""" |
98 | | - safe_path = _resolve_safe_path(path) |
99 | | - os.makedirs(os.path.dirname(safe_path) or _BASE_DIR, exist_ok=True) |
100 | | - with open(safe_path, "w", encoding="utf-8") as f: |
101 | | - f.write(content) |
102 | | - return True |
103 | | - |
104 | | - |
105 | | -def list_directory(path: str) -> str: |
106 | | - """List directory contents""" |
107 | | - safe_path = _resolve_safe_path(path) |
108 | | - if not os.path.exists(safe_path): |
109 | | - return f"Error: Path not found: {path}" |
110 | | - |
111 | | - entries = [] |
112 | | - try: |
113 | | - for entry in os.scandir(safe_path): |
114 | | - stat = entry.stat() |
115 | | - entries.append( |
116 | | - { |
117 | | - "name": "/" + entry.name if entry.is_dir() else entry.name, |
118 | | - "size": stat.st_size, |
119 | | - "mtime": datetime.fromtimestamp(stat.st_mtime).isoformat(), |
120 | | - } |
121 | | - ) |
122 | | - return json.dumps({"path": os.path.relpath(safe_path, _BASE_DIR), "entries": entries}, indent=2) |
123 | | - except Exception as e: |
124 | | - return f"Error listing directory: {e}" |
125 | | - |
126 | | - |
127 | | -def glob_paths( |
128 | | - pattern: str, |
129 | | - extensions: Optional[List[str]] = None, |
130 | | - sort_by: str = "path", # "path" | "modified" | "size" |
131 | | - max_results: int = 100, |
132 | | -) -> Dict[str, List[Dict[str, str]]]: |
133 | | - """ |
134 | | - Find files and directories matching a glob pattern |
135 | | - """ |
136 | | - if sort_by not in {"path", "modified", "size"}: |
137 | | - raise ValueError("sort_by must be one of: path, modified, size") |
138 | | - |
139 | | - safe_pattern = _resolve_safe_path(pattern) |
140 | | - |
141 | | - results = [] |
142 | | - |
143 | | - for path in glob.glob(safe_pattern, recursive=True): |
144 | | - resolved = os.path.realpath(path) |
145 | | - |
146 | | - # Enforce CWD restriction (important for symlinks) |
147 | | - if not resolved.startswith(_BASE_DIR): |
148 | | - continue |
149 | | - |
150 | | - is_dir = os.path.isdir(resolved) |
151 | | - |
152 | | - # Extension filtering (files only) |
153 | | - if extensions and not is_dir: |
154 | | - ext = os.path.splitext(resolved)[1].lower().lstrip(".") |
155 | | - if ext not in {e.lower().lstrip(".") for e in extensions}: |
156 | | - continue |
157 | | - |
158 | | - stat = os.stat(resolved) |
159 | | - |
160 | | - results.append( |
161 | | - { |
162 | | - "path": os.path.relpath(resolved, _BASE_DIR), |
163 | | - "type": "directory" if is_dir else "file", |
164 | | - "size_bytes": stat.st_size, |
165 | | - "modified_time": stat.st_mtime, |
166 | | - } |
167 | | - ) |
168 | | - |
169 | | - if len(results) >= max_results: |
170 | | - break |
171 | | - |
172 | | - # Sorting |
173 | | - if sort_by == "path": |
174 | | - results.sort(key=lambda x: x["path"]) |
175 | | - elif sort_by == "modified": |
176 | | - results.sort(key=lambda x: x["modified_time"], reverse=True) |
177 | | - elif sort_by == "size": |
178 | | - results.sort(key=lambda x: x["size_bytes"], reverse=True) |
179 | | - |
180 | | - return {"pattern": pattern, "count": len(results), "results": results} |
181 | | - |
182 | | - |
183 | 22 | # ----------------------------- |
184 | 23 | # Expression evaluation tools |
185 | 24 | # ----------------------------- |
@@ -522,19 +361,12 @@ def install(ctx): |
522 | 361 | g_ctx = ctx |
523 | 362 | group = "core_tools" |
524 | 363 | # Examples of registering tools using automatic definition generation |
525 | | - ctx.register_tool(memory_read, group=group) |
526 | | - ctx.register_tool(memory_write, group=group) |
527 | | - # ctx.register_tool(semantic_search) # TODO: implement |
528 | | - ctx.register_tool(read_file, group=group) |
529 | | - ctx.register_tool(write_file, group=group) |
530 | | - ctx.register_tool(list_directory, group=group) |
531 | | - ctx.register_tool(glob_paths, group=group) |
| 364 | + ctx.register_tool(get_current_time, group=group) |
532 | 365 | ctx.register_tool(calc, group=group) |
533 | 366 | ctx.register_tool(run_python, group=group) |
534 | 367 | ctx.register_tool(run_typescript, group=group) |
535 | 368 | ctx.register_tool(run_javascript, group=group) |
536 | 369 | ctx.register_tool(run_csharp, group=group) |
537 | | - ctx.register_tool(get_current_time, group=group) |
538 | 370 |
|
539 | 371 | def exec_language(language: str, code: str) -> Dict[str, Any]: |
540 | 372 | if language == "python": |
|
0 commit comments