llms-py 3.0.0b7__py3-none-any.whl → 3.0.0b8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. llms/__pycache__/main.cpython-314.pyc +0 -0
  2. llms/extensions/analytics/ui/index.mjs +51 -162
  3. llms/extensions/app/__init__.py +519 -0
  4. llms/extensions/app/__pycache__/__init__.cpython-314.pyc +0 -0
  5. llms/extensions/app/__pycache__/db.cpython-314.pyc +0 -0
  6. llms/extensions/app/__pycache__/db_manager.cpython-314.pyc +0 -0
  7. llms/extensions/app/db.py +641 -0
  8. llms/extensions/app/db_manager.py +195 -0
  9. llms/extensions/app/requests.json +9073 -0
  10. llms/extensions/app/threads.json +15290 -0
  11. llms/{ui/modules/threads → extensions/app/ui}/Recents.mjs +82 -55
  12. llms/{ui/modules/threads → extensions/app/ui}/index.mjs +78 -9
  13. llms/extensions/app/ui/threadStore.mjs +407 -0
  14. llms/extensions/core_tools/__init__.py +272 -32
  15. llms/extensions/core_tools/__pycache__/__init__.cpython-314.pyc +0 -0
  16. llms/extensions/core_tools/ui/codemirror/addon/edit/closebrackets.js +201 -0
  17. llms/extensions/core_tools/ui/codemirror/addon/edit/closetag.js +185 -0
  18. llms/extensions/core_tools/ui/codemirror/addon/edit/continuelist.js +101 -0
  19. llms/extensions/core_tools/ui/codemirror/addon/edit/matchbrackets.js +160 -0
  20. llms/extensions/core_tools/ui/codemirror/addon/edit/matchtags.js +66 -0
  21. llms/extensions/core_tools/ui/codemirror/addon/edit/trailingspace.js +27 -0
  22. llms/extensions/core_tools/ui/codemirror/addon/selection/active-line.js +72 -0
  23. llms/extensions/core_tools/ui/codemirror/addon/selection/mark-selection.js +119 -0
  24. llms/extensions/core_tools/ui/codemirror/addon/selection/selection-pointer.js +98 -0
  25. llms/extensions/core_tools/ui/codemirror/doc/docs.css +225 -0
  26. llms/extensions/core_tools/ui/codemirror/doc/source_sans.woff +0 -0
  27. llms/extensions/core_tools/ui/codemirror/lib/codemirror.css +344 -0
  28. llms/extensions/core_tools/ui/codemirror/lib/codemirror.js +9884 -0
  29. llms/extensions/core_tools/ui/codemirror/mode/clike/clike.js +942 -0
  30. llms/extensions/core_tools/ui/codemirror/mode/javascript/index.html +118 -0
  31. llms/extensions/core_tools/ui/codemirror/mode/javascript/javascript.js +962 -0
  32. llms/extensions/core_tools/ui/codemirror/mode/javascript/typescript.html +62 -0
  33. llms/extensions/core_tools/ui/codemirror/mode/python/python.js +402 -0
  34. llms/extensions/core_tools/ui/codemirror/theme/dracula.css +40 -0
  35. llms/extensions/core_tools/ui/codemirror/theme/mocha.css +135 -0
  36. llms/extensions/core_tools/ui/index.mjs +650 -0
  37. llms/extensions/gallery/__pycache__/db.cpython-314.pyc +0 -0
  38. llms/extensions/gallery/db.py +4 -4
  39. llms/extensions/gallery/ui/index.mjs +2 -1
  40. llms/extensions/katex/__init__.py +6 -0
  41. llms/extensions/katex/__pycache__/__init__.cpython-314.pyc +0 -0
  42. llms/extensions/katex/ui/README.md +125 -0
  43. llms/extensions/katex/ui/contrib/auto-render.js +338 -0
  44. llms/extensions/katex/ui/contrib/auto-render.min.js +1 -0
  45. llms/extensions/katex/ui/contrib/auto-render.mjs +244 -0
  46. llms/extensions/katex/ui/contrib/copy-tex.js +127 -0
  47. llms/extensions/katex/ui/contrib/copy-tex.min.js +1 -0
  48. llms/extensions/katex/ui/contrib/copy-tex.mjs +105 -0
  49. llms/extensions/katex/ui/contrib/mathtex-script-type.js +109 -0
  50. llms/extensions/katex/ui/contrib/mathtex-script-type.min.js +1 -0
  51. llms/extensions/katex/ui/contrib/mathtex-script-type.mjs +24 -0
  52. llms/extensions/katex/ui/contrib/mhchem.js +3213 -0
  53. llms/extensions/katex/ui/contrib/mhchem.min.js +1 -0
  54. llms/extensions/katex/ui/contrib/mhchem.mjs +3109 -0
  55. llms/extensions/katex/ui/contrib/render-a11y-string.js +887 -0
  56. llms/extensions/katex/ui/contrib/render-a11y-string.min.js +1 -0
  57. llms/extensions/katex/ui/contrib/render-a11y-string.mjs +800 -0
  58. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.ttf +0 -0
  59. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff +0 -0
  60. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  61. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  62. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  63. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  64. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  65. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  66. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  67. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  68. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  69. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  70. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  71. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  72. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  73. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.ttf +0 -0
  74. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff +0 -0
  75. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff2 +0 -0
  76. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  77. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  78. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  79. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.ttf +0 -0
  80. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff +0 -0
  81. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff2 +0 -0
  82. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.ttf +0 -0
  83. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff +0 -0
  84. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff2 +0 -0
  85. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  86. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  87. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  88. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.ttf +0 -0
  89. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff +0 -0
  90. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff2 +0 -0
  91. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  92. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  93. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  94. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  95. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  96. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  97. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  98. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  99. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  100. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.ttf +0 -0
  101. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff +0 -0
  102. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff2 +0 -0
  103. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.ttf +0 -0
  104. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff +0 -0
  105. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  106. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.ttf +0 -0
  107. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff +0 -0
  108. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  109. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.ttf +0 -0
  110. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff +0 -0
  111. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  112. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.ttf +0 -0
  113. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff +0 -0
  114. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  115. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  116. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  117. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  118. llms/extensions/katex/ui/index.mjs +92 -0
  119. llms/extensions/katex/ui/katex-swap.css +1230 -0
  120. llms/extensions/katex/ui/katex-swap.min.css +1 -0
  121. llms/extensions/katex/ui/katex.css +1230 -0
  122. llms/extensions/katex/ui/katex.js +19080 -0
  123. llms/extensions/katex/ui/katex.min.css +1 -0
  124. llms/extensions/katex/ui/katex.min.js +1 -0
  125. llms/extensions/katex/ui/katex.min.mjs +1 -0
  126. llms/extensions/katex/ui/katex.mjs +18547 -0
  127. llms/extensions/providers/__pycache__/anthropic.cpython-314.pyc +0 -0
  128. llms/extensions/providers/anthropic.py +44 -1
  129. llms/extensions/system_prompts/ui/index.mjs +2 -1
  130. llms/extensions/tools/__init__.py +5 -0
  131. llms/extensions/tools/__pycache__/__init__.cpython-314.pyc +0 -0
  132. llms/extensions/tools/ui/index.mjs +8 -8
  133. llms/index.html +26 -38
  134. llms/llms.json +4 -1
  135. llms/main.py +492 -103
  136. llms/ui/App.mjs +2 -3
  137. llms/ui/ai.mjs +29 -13
  138. llms/ui/app.css +250 -398
  139. llms/ui/ctx.mjs +84 -6
  140. llms/ui/index.mjs +4 -6
  141. llms/ui/lib/vue.min.mjs +10 -9
  142. llms/ui/lib/vue.mjs +1796 -1635
  143. llms/ui/markdown.mjs +4 -2
  144. llms/ui/modules/chat/ChatBody.mjs +90 -86
  145. llms/ui/modules/chat/HomeTools.mjs +0 -242
  146. llms/ui/modules/chat/index.mjs +103 -170
  147. llms/ui/modules/model-selector.mjs +2 -2
  148. llms/ui/tailwind.input.css +35 -1
  149. llms/ui/utils.mjs +12 -0
  150. {llms_py-3.0.0b7.dist-info → llms_py-3.0.0b8.dist-info}/METADATA +1 -1
  151. llms_py-3.0.0b8.dist-info/RECORD +198 -0
  152. llms/ui/modules/threads/threadStore.mjs +0 -640
  153. llms_py-3.0.0b7.dist-info/RECORD +0 -80
  154. {llms_py-3.0.0b7.dist-info → llms_py-3.0.0b8.dist-info}/WHEEL +0 -0
  155. {llms_py-3.0.0b7.dist-info → llms_py-3.0.0b8.dist-info}/entry_points.txt +0 -0
  156. {llms_py-3.0.0b7.dist-info → llms_py-3.0.0b8.dist-info}/licenses/LICENSE +0 -0
  157. {llms_py-3.0.0b7.dist-info → llms_py-3.0.0b8.dist-info}/top_level.txt +0 -0
@@ -3,11 +3,13 @@ Core System Tools providing essential file operations, memory persistence, math
3
3
  """
4
4
 
5
5
  import ast
6
+ import contextlib
6
7
  import glob
7
8
  import json
8
9
  import math
9
10
  import operator
10
11
  import os
12
+ import shutil
11
13
  import subprocess
12
14
  import sys
13
15
  import tempfile
@@ -15,6 +17,10 @@ from datetime import datetime, timezone
15
17
  from statistics import mean, median, stdev, variance
16
18
  from typing import Any, Dict, List, Optional
17
19
 
20
+ from aiohttp import web
21
+
22
+ g_ctx = None
23
+
18
24
  # -----------------------------
19
25
  # In-memory storage (replace later)
20
26
  # -----------------------------
@@ -179,6 +185,26 @@ def glob_paths(
179
185
  # -----------------------------
180
186
 
181
187
 
188
+ def get_calculator_functions():
189
+ # 2. Define allowed math functions and constants
190
+ allowed_functions = {
191
+ "mod": operator.mod,
192
+ "mean": mean,
193
+ "median": median,
194
+ "stdev": stdev,
195
+ "variance": variance,
196
+ "abs": abs,
197
+ "min": min,
198
+ "max": max,
199
+ "sum": sum,
200
+ "round": round,
201
+ }
202
+ allowed_functions.update(
203
+ {name: getattr(math, name) for name in dir(math) if not name.startswith("_") and name not in allowed_functions}
204
+ )
205
+ return allowed_functions
206
+
207
+
182
208
  def calc(expression: str) -> str:
183
209
  """Evaluate a mathematical expression with boolean operations"""
184
210
  # 1. Define allowed operators
@@ -204,68 +230,87 @@ def calc(expression: str) -> str:
204
230
  }
205
231
 
206
232
  # 2. Define allowed math functions and constants
207
- allowed_functions = {name: getattr(math, name) for name in dir(math) if not name.startswith("_")}
208
- allowed_functions.update(
209
- {
210
- "mod": operator.mod,
211
- "mean": mean,
212
- "median": median,
213
- "stdev": stdev,
214
- "variance": variance,
215
- "abs": abs,
216
- "min": min,
217
- "max": max,
218
- "sum": sum,
219
- "round": round,
220
- }
221
- )
233
+ allowed_functions = get_calculator_functions()
234
+
235
+ def eval_node(node, context=None):
236
+ if context is None:
237
+ context = {}
222
238
 
223
- def eval_node(node):
224
239
  if isinstance(node, ast.Constant): # Numbers and booleans
225
240
  return node.value
226
241
  elif isinstance(node, ast.BinOp): # Binary Ops (1 + 2)
227
- return operators[type(node.op)](eval_node(node.left), eval_node(node.right))
242
+ return operators[type(node.op)](eval_node(node.left, context), eval_node(node.right, context))
228
243
  elif isinstance(node, ast.UnaryOp): # Unary Ops (-5, not True)
229
- return operators[type(node.op)](eval_node(node.operand))
244
+ return operators[type(node.op)](eval_node(node.operand, context))
230
245
  elif isinstance(node, ast.Compare): # Comparison (5 > 3)
231
- left = eval_node(node.left)
246
+ left = eval_node(node.left, context)
232
247
  for op, comparator in zip(node.ops, node.comparators):
233
- right = eval_node(comparator)
248
+ right = eval_node(comparator, context)
234
249
  if not operators[type(op)](left, right):
235
250
  return False
236
251
  left = right
237
252
  return True
238
253
  elif isinstance(node, ast.BoolOp): # Boolean operations (True and False, True or False)
239
- op = operators[type(node.op)]
240
254
  if isinstance(node.op, ast.And):
241
255
  # Short-circuit evaluation for 'and'
242
256
  result = True
243
257
  for value in node.values:
244
- result = eval_node(value)
258
+ result = eval_node(value, context)
245
259
  if not result:
246
260
  return False
247
261
  return result
248
262
  elif isinstance(node.op, ast.Or):
249
263
  # Short-circuit evaluation for 'or'
250
264
  for value in node.values:
251
- result = eval_node(value)
265
+ result = eval_node(value, context)
252
266
  if result:
253
267
  return True
254
268
  return False
255
269
  elif isinstance(node, ast.Call): # Function calls (sqrt(16))
256
270
  func_name = node.func.id
257
271
  if func_name in allowed_functions:
258
- args = [eval_node(arg) for arg in node.args]
272
+ args = [eval_node(arg, context) for arg in node.args]
259
273
  return allowed_functions[func_name](*args)
274
+ if func_name == "range":
275
+ args = [eval_node(arg, context) for arg in node.args]
276
+ return range(*args)
260
277
  raise NameError(f"Function '{func_name}' is not allowed.")
261
- elif isinstance(node, ast.Name): # Constants (pi, e, True, False)
278
+ elif isinstance(node, ast.Name): # Constants (pi, e, True, False) or context variables
279
+ if node.id in context:
280
+ return context[node.id]
262
281
  if node.id in allowed_functions:
263
282
  return allowed_functions[node.id]
264
283
  elif node.id in ("True", "False"):
265
284
  return node.id == "True"
266
285
  raise NameError(f"Variable '{node.id}' is not defined.")
267
286
  elif isinstance(node, ast.List): # List literals [1, 2, 3]
268
- return [eval_node(item) for item in node.elts]
287
+ return [eval_node(item, context) for item in node.elts]
288
+ elif isinstance(node, ast.ListComp): # List comprehensions [x*2 for x in [1,2,3]]
289
+ result = []
290
+ generators = node.generators
291
+ if len(generators) != 1:
292
+ raise ValueError("Only single-generator list comprehensions are supported")
293
+ gen = generators[0]
294
+ if not isinstance(gen.target, ast.Name):
295
+ raise ValueError("Only simple name targets in list comprehensions are supported")
296
+
297
+ target_name = gen.target.id
298
+ iterable = eval_node(gen.iter, context)
299
+
300
+ for item in iterable:
301
+ new_context = context.copy()
302
+ new_context[target_name] = item
303
+
304
+ # Check ifs
305
+ include = True
306
+ for if_node in gen.ifs:
307
+ if not eval_node(if_node, new_context):
308
+ include = False
309
+ break
310
+
311
+ if include:
312
+ result.append(eval_node(node.elt, new_context))
313
+ return result
269
314
  else:
270
315
  raise TypeError(f"Unsupported operation: {type(node).__name__}")
271
316
 
@@ -274,13 +319,19 @@ def calc(expression: str) -> str:
274
319
 
275
320
  # Parse and evaluate
276
321
  node = ast.parse(expression, mode="eval").body
277
- return eval_node(node)
322
+ ret = eval_node(node)
323
+ g_ctx.dbg(f"calc ({expression}) = {ret}")
324
+ return ret
278
325
 
279
326
 
280
327
  # -----------------------------
281
- # Python execution tool
328
+ # code execution tools
282
329
  # -----------------------------
283
330
 
331
+ mem_limit = 8589934592 # Max virtual memory 8GB
332
+ cpu_time_limit = 5 # Max CPU time 5 seconds
333
+ resource_limits = f"ulimit -t {cpu_time_limit}; ulimit -v {mem_limit};"
334
+
284
335
 
285
336
  def run_python(code: str) -> Dict[str, Any]:
286
337
  """
@@ -293,16 +344,141 @@ def run_python(code: str) -> Dict[str, Any]:
293
344
  with open(script_path, "w", encoding="utf-8") as f:
294
345
  f.write(code)
295
346
 
296
- # Construct command with resource limits
297
- # ulimit -t 5: Max CPU time 5 seconds
298
- # ulimit -v 1048576: Max virtual memory 1GB
299
- cmd = f"ulimit -t 5; ulimit -v 1048576; {sys.executable} script.py"
347
+ cmd = f"{resource_limits} {sys.executable} script.py"
348
+
349
+ run_as = os.environ.get("LLMS_RUN_AS")
350
+ if run_as:
351
+ # Grant access to temp_dir
352
+ with contextlib.suppress(Exception):
353
+ os.chmod(temp_dir, 0o777)
354
+ cmd = f"sudo -u {run_as} bash -c '{cmd}'"
300
355
 
301
356
  try:
302
357
  # Run with restricted environment
303
358
  # We keep PATH to find basic tools if needed, but remove sensitive vars
304
359
  clean_env = {"PATH": os.environ.get("PATH", "")}
305
360
 
361
+ g_ctx.dbg(f"run_python ({temp_dir}): {cmd}\n{code}")
362
+ result = subprocess.run(
363
+ ["bash", "-c", cmd], cwd=temp_dir, env=clean_env, capture_output=True, text=True, timeout=10
364
+ )
365
+ return {"stdout": result.stdout, "stderr": result.stderr, "returncode": result.returncode}
366
+ except subprocess.TimeoutExpired:
367
+ return {"stdout": "", "stderr": "Execution timed out", "returncode": -1}
368
+ except Exception as e:
369
+ return {"stdout": "", "stderr": f"Error: {e}", "returncode": -1}
370
+
371
+
372
+ def run_javascript(code: str) -> Dict[str, Any]:
373
+ """
374
+ Execute JavaScript code in a temporary sandboxed environment using bun or node.
375
+ """
376
+ # Check for available runtime
377
+ runtime = shutil.which("bun") or shutil.which("node")
378
+ if not runtime:
379
+ return {"stdout": "", "stderr": "Error: Neither 'bun' nor 'node' is available on the system.", "returncode": -1}
380
+
381
+ with tempfile.TemporaryDirectory() as temp_dir:
382
+ script_path = os.path.join(temp_dir, "script.js")
383
+
384
+ with open(script_path, "w", encoding="utf-8") as f:
385
+ f.write(code)
386
+
387
+ cmd = f"{resource_limits} {runtime} script.js"
388
+
389
+ run_as = os.environ.get("LLMS_RUN_AS")
390
+ if run_as:
391
+ with contextlib.suppress(Exception):
392
+ os.chmod(temp_dir, 0o777)
393
+ cmd = f"sudo -u {run_as} bash -c '{cmd}'"
394
+
395
+ try:
396
+ # Run with restricted environment
397
+ clean_env = {"PATH": os.environ.get("PATH", "")}
398
+
399
+ g_ctx.dbg(f"run_javascript ({temp_dir}): {cmd}\n{code}")
400
+ result = subprocess.run(
401
+ ["bash", "-c", cmd], cwd=temp_dir, env=clean_env, capture_output=True, text=True, timeout=10
402
+ )
403
+ return {"stdout": result.stdout, "stderr": result.stderr, "returncode": result.returncode}
404
+ except subprocess.TimeoutExpired:
405
+ return {"stdout": "", "stderr": "Execution timed out", "returncode": -1}
406
+ except Exception as e:
407
+ return {"stdout": "", "stderr": f"Error: {e}", "returncode": -1}
408
+
409
+
410
+ def run_typescript(code: str) -> Dict[str, Any]:
411
+ """
412
+ Execute TypeScript code in a temporary sandboxed environment using bun or node.
413
+ """
414
+ # Check for available runtime
415
+ runtime = shutil.which("bun") or shutil.which("node")
416
+ if not runtime:
417
+ return {"stdout": "", "stderr": "Error: Neither 'bun' nor 'node' is available on the system.", "returncode": -1}
418
+
419
+ with tempfile.TemporaryDirectory() as temp_dir:
420
+ script_path = os.path.join(temp_dir, "script.ts")
421
+
422
+ with open(script_path, "w", encoding="utf-8") as f:
423
+ f.write(code)
424
+
425
+ cmd = f"{resource_limits} {runtime} script.ts"
426
+
427
+ run_as = os.environ.get("LLMS_RUN_AS")
428
+ if run_as:
429
+ with contextlib.suppress(Exception):
430
+ os.chmod(temp_dir, 0o777)
431
+ cmd = f"sudo -u {run_as} bash -c '{cmd}'"
432
+
433
+ try:
434
+ # Run with restricted environment
435
+ clean_env = {"PATH": os.environ.get("PATH", "")}
436
+
437
+ g_ctx.dbg(f"run_typescript ({temp_dir}): {cmd}\n{code}")
438
+ result = subprocess.run(
439
+ ["bash", "-c", cmd], cwd=temp_dir, env=clean_env, capture_output=True, text=True, timeout=10
440
+ )
441
+ return {"stdout": result.stdout, "stderr": result.stderr, "returncode": result.returncode}
442
+ except subprocess.TimeoutExpired:
443
+ return {"stdout": "", "stderr": "Execution timed out", "returncode": -1}
444
+ except Exception as e:
445
+ return {"stdout": "", "stderr": f"Error: {e}", "returncode": -1}
446
+
447
+
448
+ def run_csharp(code: str) -> Dict[str, Any]:
449
+ """
450
+ Execute C# code in a temporary sandboxed environment using dotnet.
451
+ """
452
+ # Check for available runtime
453
+ runtime = shutil.which("dotnet")
454
+ if not runtime:
455
+ return {"stdout": "", "stderr": "Error: 'dotnet' is not available on the system.", "returncode": -1}
456
+
457
+ with tempfile.TemporaryDirectory() as temp_dir:
458
+ script_path = os.path.join(temp_dir, "script.cs")
459
+
460
+ # Ensure we just have the code, user might pass it without wrapping class if it's top-level statements
461
+ with open(script_path, "w", encoding="utf-8") as f:
462
+ f.write(code)
463
+
464
+ # Note: 'dotnet run script.cs' is the command as per user request for .NET 10
465
+ cmd = f"{resource_limits} {runtime} run script.cs"
466
+
467
+ run_as = os.environ.get("LLMS_RUN_AS")
468
+ if run_as:
469
+ with contextlib.suppress(Exception):
470
+ os.chmod(temp_dir, 0o777)
471
+ # For dotnet, we need to set HOME and DOTNET_CLI_HOME to temp_dir for write access
472
+ cmd = f"sudo -u {run_as} env HOME={temp_dir} DOTNET_CLI_HOME={temp_dir} bash -c '{cmd}'"
473
+
474
+ try:
475
+ # Run with restricted environment
476
+ clean_env = {"PATH": os.environ.get("PATH", "")}
477
+
478
+ # Dotnet might need some ENV vars to work correctly, usually DOTNET_CLI_HOME or similar if strictly sandboxed
479
+ # But we are keeping PATH, hopefully commonly needed vars are there or default works.
480
+ # We might want to pass more env vars if it fails.
481
+ g_ctx.dbg(f"run_csharp ({temp_dir}): {cmd}\n{code}")
306
482
  result = subprocess.run(
307
483
  ["bash", "-c", cmd], cwd=temp_dir, env=clean_env, capture_output=True, text=True, timeout=10
308
484
  )
@@ -342,6 +518,8 @@ def get_current_time(tz_name: Optional[str] = None) -> str:
342
518
 
343
519
 
344
520
  def install(ctx):
521
+ global g_ctx
522
+ g_ctx = ctx
345
523
  # Examples of registering tools using automatic definition generation
346
524
  ctx.register_tool(memory_read)
347
525
  ctx.register_tool(memory_write)
@@ -352,7 +530,69 @@ def install(ctx):
352
530
  ctx.register_tool(glob_paths)
353
531
  ctx.register_tool(calc)
354
532
  ctx.register_tool(run_python)
533
+ ctx.register_tool(run_typescript)
534
+ ctx.register_tool(run_javascript)
535
+ ctx.register_tool(run_csharp)
355
536
  ctx.register_tool(get_current_time)
356
537
 
538
+ def exec_language(language: str, code: str) -> Dict[str, Any]:
539
+ if language == "python":
540
+ return run_python(code)
541
+ elif language == "typescript":
542
+ return run_typescript(code)
543
+ elif language == "javascript":
544
+ return run_javascript(code)
545
+ elif language == "csharp":
546
+ return run_csharp(code)
547
+ else:
548
+ return {"stdout": "", "stderr": "Error: Invalid language", "returncode": -1}
549
+
550
+ async def run_code(request):
551
+ language = request.match_info["language"]
552
+ code = await request.text()
553
+ try:
554
+ result = exec_language(language, code)
555
+ except Exception as e:
556
+ result = {"stdout": "", "stderr": str(e), "returncode": -1}
557
+ return web.json_response(result)
558
+
559
+ ctx.add_post("code/{language}/run", run_code)
560
+
561
+ async def get_calculator_features(request):
562
+ operators = ["+", "-", "*", "/", "%", "^", "==", "!=", "<", "<=", ">", ">=", "and", "or", "not"]
563
+ operators = [f" {op} " for op in operators]
564
+ constants = ["pi", "e", "inf", "tau", "nan"]
565
+ functions = [f for f in get_calculator_functions() if f not in constants]
566
+ return web.json_response(
567
+ {
568
+ "numbers": ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
569
+ "constants": constants,
570
+ "operators": operators,
571
+ "functions": sorted(functions),
572
+ }
573
+ )
574
+
575
+ ctx.add_get("calc", get_calculator_features)
576
+
577
+ async def run_calc(request):
578
+ code = await request.text()
579
+ result = calc(code)
580
+ return web.json_response({"result": result})
581
+
582
+ ctx.add_post("calc", run_calc)
583
+
584
+ ctx.add_index_footer(
585
+ f"""
586
+ <link rel="stylesheet" href="{ctx.ext_prefix}/codemirror/lib/codemirror.css">
587
+ <link rel="stylesheet" href="{ctx.ext_prefix}/codemirror/theme/mocha.css">
588
+ <script src="{ctx.ext_prefix}/codemirror/lib/codemirror.js"></script>
589
+ <script src="{ctx.ext_prefix}/codemirror/mode/clike/clike.js"></script>
590
+ <script src="{ctx.ext_prefix}/codemirror/mode/javascript/javascript.js"></script>
591
+ <script src="{ctx.ext_prefix}/codemirror/mode/python/python.js"></script>
592
+ <script src="{ctx.ext_prefix}/codemirror/addon/edit/matchbrackets.js"></script>
593
+ <script src="{ctx.ext_prefix}/codemirror/addon/selection/active-line.js"></script>
594
+ """
595
+ )
596
+
357
597
 
358
598
  __install__ = install
@@ -0,0 +1,201 @@
1
+ // CodeMirror, copyright (c) by Marijn Haverbeke and others
2
+ // Distributed under an MIT license: https://codemirror.net/5/LICENSE
3
+
4
+ (function(mod) {
5
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
6
+ mod(require("../../lib/codemirror"));
7
+ else if (typeof define == "function" && define.amd) // AMD
8
+ define(["../../lib/codemirror"], mod);
9
+ else // Plain browser env
10
+ mod(CodeMirror);
11
+ })(function(CodeMirror) {
12
+ var defaults = {
13
+ pairs: "()[]{}''\"\"",
14
+ closeBefore: ")]}'\":;>",
15
+ triples: "",
16
+ explode: "[]{}"
17
+ };
18
+
19
+ var Pos = CodeMirror.Pos;
20
+
21
+ CodeMirror.defineOption("autoCloseBrackets", false, function(cm, val, old) {
22
+ if (old && old != CodeMirror.Init) {
23
+ cm.removeKeyMap(keyMap);
24
+ cm.state.closeBrackets = null;
25
+ }
26
+ if (val) {
27
+ ensureBound(getOption(val, "pairs"))
28
+ cm.state.closeBrackets = val;
29
+ cm.addKeyMap(keyMap);
30
+ }
31
+ });
32
+
33
+ function getOption(conf, name) {
34
+ if (name == "pairs" && typeof conf == "string") return conf;
35
+ if (typeof conf == "object" && conf[name] != null) return conf[name];
36
+ return defaults[name];
37
+ }
38
+
39
+ var keyMap = {Backspace: handleBackspace, Enter: handleEnter};
40
+ function ensureBound(chars) {
41
+ for (var i = 0; i < chars.length; i++) {
42
+ var ch = chars.charAt(i), key = "'" + ch + "'"
43
+ if (!keyMap[key]) keyMap[key] = handler(ch)
44
+ }
45
+ }
46
+ ensureBound(defaults.pairs + "`")
47
+
48
+ function handler(ch) {
49
+ return function(cm) { return handleChar(cm, ch); };
50
+ }
51
+
52
+ function getConfig(cm) {
53
+ var deflt = cm.state.closeBrackets;
54
+ if (!deflt || deflt.override) return deflt;
55
+ var mode = cm.getModeAt(cm.getCursor());
56
+ return mode.closeBrackets || deflt;
57
+ }
58
+
59
+ function handleBackspace(cm) {
60
+ var conf = getConfig(cm);
61
+ if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
62
+
63
+ var pairs = getOption(conf, "pairs");
64
+ var ranges = cm.listSelections();
65
+ for (var i = 0; i < ranges.length; i++) {
66
+ if (!ranges[i].empty()) return CodeMirror.Pass;
67
+ var around = charsAround(cm, ranges[i].head);
68
+ if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
69
+ }
70
+ for (var i = ranges.length - 1; i >= 0; i--) {
71
+ var cur = ranges[i].head;
72
+ cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1), "+delete");
73
+ }
74
+ }
75
+
76
+ function handleEnter(cm) {
77
+ var conf = getConfig(cm);
78
+ var explode = conf && getOption(conf, "explode");
79
+ if (!explode || cm.getOption("disableInput")) return CodeMirror.Pass;
80
+
81
+ var ranges = cm.listSelections();
82
+ for (var i = 0; i < ranges.length; i++) {
83
+ if (!ranges[i].empty()) return CodeMirror.Pass;
84
+ var around = charsAround(cm, ranges[i].head);
85
+ if (!around || explode.indexOf(around) % 2 != 0) return CodeMirror.Pass;
86
+ }
87
+ cm.operation(function() {
88
+ var linesep = cm.lineSeparator() || "\n";
89
+ cm.replaceSelection(linesep + linesep, null);
90
+ moveSel(cm, -1)
91
+ ranges = cm.listSelections();
92
+ for (var i = 0; i < ranges.length; i++) {
93
+ var line = ranges[i].head.line;
94
+ cm.indentLine(line, null, true);
95
+ cm.indentLine(line + 1, null, true);
96
+ }
97
+ });
98
+ }
99
+
100
+ function moveSel(cm, dir) {
101
+ var newRanges = [], ranges = cm.listSelections(), primary = 0
102
+ for (var i = 0; i < ranges.length; i++) {
103
+ var range = ranges[i]
104
+ if (range.head == cm.getCursor()) primary = i
105
+ var pos = range.head.ch || dir > 0 ? {line: range.head.line, ch: range.head.ch + dir} : {line: range.head.line - 1}
106
+ newRanges.push({anchor: pos, head: pos})
107
+ }
108
+ cm.setSelections(newRanges, primary)
109
+ }
110
+
111
+ function contractSelection(sel) {
112
+ var inverted = CodeMirror.cmpPos(sel.anchor, sel.head) > 0;
113
+ return {anchor: new Pos(sel.anchor.line, sel.anchor.ch + (inverted ? -1 : 1)),
114
+ head: new Pos(sel.head.line, sel.head.ch + (inverted ? 1 : -1))};
115
+ }
116
+
117
+ function handleChar(cm, ch) {
118
+ var conf = getConfig(cm);
119
+ if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
120
+
121
+ var pairs = getOption(conf, "pairs");
122
+ var pos = pairs.indexOf(ch);
123
+ if (pos == -1) return CodeMirror.Pass;
124
+
125
+ var closeBefore = getOption(conf,"closeBefore");
126
+
127
+ var triples = getOption(conf, "triples");
128
+
129
+ var identical = pairs.charAt(pos + 1) == ch;
130
+ var ranges = cm.listSelections();
131
+ var opening = pos % 2 == 0;
132
+
133
+ var type;
134
+ for (var i = 0; i < ranges.length; i++) {
135
+ var range = ranges[i], cur = range.head, curType;
136
+ var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1));
137
+ if (opening && !range.empty()) {
138
+ curType = "surround";
139
+ } else if ((identical || !opening) && next == ch) {
140
+ if (identical && stringStartsAfter(cm, cur))
141
+ curType = "both";
142
+ else if (triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch)
143
+ curType = "skipThree";
144
+ else
145
+ curType = "skip";
146
+ } else if (identical && cur.ch > 1 && triples.indexOf(ch) >= 0 &&
147
+ cm.getRange(Pos(cur.line, cur.ch - 2), cur) == ch + ch) {
148
+ if (cur.ch > 2 && /\bstring/.test(cm.getTokenTypeAt(Pos(cur.line, cur.ch - 2)))) return CodeMirror.Pass;
149
+ curType = "addFour";
150
+ } else if (identical) {
151
+ var prev = cur.ch == 0 ? " " : cm.getRange(Pos(cur.line, cur.ch - 1), cur)
152
+ if (!CodeMirror.isWordChar(next) && prev != ch && !CodeMirror.isWordChar(prev)) curType = "both";
153
+ else return CodeMirror.Pass;
154
+ } else if (opening && (next.length === 0 || /\s/.test(next) || closeBefore.indexOf(next) > -1)) {
155
+ curType = "both";
156
+ } else {
157
+ return CodeMirror.Pass;
158
+ }
159
+ if (!type) type = curType;
160
+ else if (type != curType) return CodeMirror.Pass;
161
+ }
162
+
163
+ var left = pos % 2 ? pairs.charAt(pos - 1) : ch;
164
+ var right = pos % 2 ? ch : pairs.charAt(pos + 1);
165
+ cm.operation(function() {
166
+ if (type == "skip") {
167
+ moveSel(cm, 1)
168
+ } else if (type == "skipThree") {
169
+ moveSel(cm, 3)
170
+ } else if (type == "surround") {
171
+ var sels = cm.getSelections();
172
+ for (var i = 0; i < sels.length; i++)
173
+ sels[i] = left + sels[i] + right;
174
+ cm.replaceSelections(sels, "around");
175
+ sels = cm.listSelections().slice();
176
+ for (var i = 0; i < sels.length; i++)
177
+ sels[i] = contractSelection(sels[i]);
178
+ cm.setSelections(sels);
179
+ } else if (type == "both") {
180
+ cm.replaceSelection(left + right, null);
181
+ cm.triggerElectric(left + right);
182
+ moveSel(cm, -1)
183
+ } else if (type == "addFour") {
184
+ cm.replaceSelection(left + left + left + left, "before");
185
+ moveSel(cm, 1)
186
+ }
187
+ });
188
+ }
189
+
190
+ function charsAround(cm, pos) {
191
+ var str = cm.getRange(Pos(pos.line, pos.ch - 1),
192
+ Pos(pos.line, pos.ch + 1));
193
+ return str.length == 2 ? str : null;
194
+ }
195
+
196
+ function stringStartsAfter(cm, pos) {
197
+ var token = cm.getTokenAt(Pos(pos.line, pos.ch + 1))
198
+ return /\bstring/.test(token.type) && token.start == pos.ch &&
199
+ (pos.ch == 0 || !/\bstring/.test(cm.getTokenTypeAt(pos)))
200
+ }
201
+ });