llms-py 3.0.19__py3-none-any.whl → 3.0.21__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.
- llms/extensions/app/__init__.py +4 -1
- llms/extensions/computer/filesystem.py +37 -6
- llms/extensions/providers/openrouter.py +1 -1
- llms/main.py +15 -6
- llms/providers.json +1 -1
- llms/ui/ai.mjs +1 -1
- llms/ui/app.css +0 -463
- llms/ui/ctx.mjs +16 -3
- llms/ui/index.mjs +2 -2
- llms/ui/lib/servicestack-vue.mjs +3 -3
- llms/ui/markdown.mjs +2 -1
- llms/ui/modules/chat/ChatBody.mjs +3 -1
- llms/ui/utils.mjs +48 -1
- {llms_py-3.0.19.dist-info → llms_py-3.0.21.dist-info}/METADATA +1 -1
- {llms_py-3.0.19.dist-info → llms_py-3.0.21.dist-info}/RECORD +19 -19
- {llms_py-3.0.19.dist-info → llms_py-3.0.21.dist-info}/WHEEL +0 -0
- {llms_py-3.0.19.dist-info → llms_py-3.0.21.dist-info}/entry_points.txt +0 -0
- {llms_py-3.0.19.dist-info → llms_py-3.0.21.dist-info}/licenses/LICENSE +0 -0
- {llms_py-3.0.19.dist-info → llms_py-3.0.21.dist-info}/top_level.txt +0 -0
llms/extensions/app/__init__.py
CHANGED
|
@@ -223,7 +223,10 @@ def install(ctx):
|
|
|
223
223
|
await ctx.chat_completion(chat_req, context=context_req)
|
|
224
224
|
except Exception as ex:
|
|
225
225
|
ctx.err("run_chat", ex)
|
|
226
|
-
#
|
|
226
|
+
# shouldn't be necessary to update thread in db with error as it's done in chat_error filter
|
|
227
|
+
thread = thread_dto(g_db.get_thread(id, user=ctx.get_username(request)))
|
|
228
|
+
if thread and not thread.get("error"):
|
|
229
|
+
await chat_error(ex, context)
|
|
227
230
|
|
|
228
231
|
asyncio.create_task(run_chat(chat, context))
|
|
229
232
|
|
|
@@ -406,7 +406,7 @@ def directory_tree(
|
|
|
406
406
|
) -> str:
|
|
407
407
|
"""
|
|
408
408
|
Get a recursive tree view of files and directories as a JSON structure. Each entry includes 'name', 'type' (file/directory), and 'children' for directories.
|
|
409
|
-
Files have no children array, while directories always have a children array (which may be empty).
|
|
409
|
+
Files have no children array, while directories always have a children array (which may be empty). Respects any .gitignore rules from the root directory together with any exclude_patterns.
|
|
410
410
|
The output is formatted with 2-space indentation for readability. Only works within allowed directories.
|
|
411
411
|
"""
|
|
412
412
|
import json
|
|
@@ -416,6 +416,24 @@ def directory_tree(
|
|
|
416
416
|
if exclude_patterns is None:
|
|
417
417
|
exclude_patterns = []
|
|
418
418
|
|
|
419
|
+
def _parse_gitignore(directory: str) -> List[str]:
|
|
420
|
+
gitignore_path = os.path.join(directory, ".gitignore")
|
|
421
|
+
patterns = []
|
|
422
|
+
if os.path.exists(gitignore_path) and os.path.isfile(gitignore_path):
|
|
423
|
+
try:
|
|
424
|
+
with open(gitignore_path, encoding="utf-8") as f:
|
|
425
|
+
for line in f:
|
|
426
|
+
line = line.strip()
|
|
427
|
+
if line and not line.startswith("#"):
|
|
428
|
+
patterns.append(line)
|
|
429
|
+
except Exception as e:
|
|
430
|
+
logger.warning(f"Error reading .gitignore in {directory}: {e}")
|
|
431
|
+
return patterns
|
|
432
|
+
|
|
433
|
+
# Parse .gitignore only in the root directory (Simple Global Mappings)
|
|
434
|
+
gitignore_patterns = _parse_gitignore(valid_path)
|
|
435
|
+
all_exclude_patterns = exclude_patterns + gitignore_patterns
|
|
436
|
+
|
|
419
437
|
def _build_tree(current_path: str) -> List[Dict[str, Any]]:
|
|
420
438
|
entries = []
|
|
421
439
|
try:
|
|
@@ -423,14 +441,27 @@ def directory_tree(
|
|
|
423
441
|
items = sorted(it, key=lambda x: x.name)
|
|
424
442
|
|
|
425
443
|
for entry in items:
|
|
426
|
-
# Check exclusion
|
|
427
|
-
|
|
428
|
-
|
|
444
|
+
# 1. Check exclusion patterns
|
|
445
|
+
rel_path_from_root = entry.path[root_path_len:]
|
|
446
|
+
|
|
429
447
|
should_exclude = False
|
|
430
|
-
for pattern in
|
|
431
|
-
|
|
448
|
+
for pattern in all_exclude_patterns:
|
|
449
|
+
# Match against relative path or name
|
|
450
|
+
# Support ending with / for directory matching
|
|
451
|
+
is_dir_pattern = pattern.endswith("/")
|
|
452
|
+
norm_pattern = pattern.rstrip("/")
|
|
453
|
+
|
|
454
|
+
if fnmatch.fnmatch(rel_path_from_root, pattern) or fnmatch.fnmatch(entry.name, pattern):
|
|
432
455
|
should_exclude = True
|
|
433
456
|
break
|
|
457
|
+
|
|
458
|
+
# Handle patterns like "node_modules/" matching "node_modules" directory
|
|
459
|
+
if fnmatch.fnmatch(entry.name, norm_pattern):
|
|
460
|
+
if is_dir_pattern and not entry.is_dir():
|
|
461
|
+
continue
|
|
462
|
+
should_exclude = True
|
|
463
|
+
break
|
|
464
|
+
|
|
434
465
|
if should_exclude:
|
|
435
466
|
continue
|
|
436
467
|
|
|
@@ -14,7 +14,7 @@ def install_openrouter(ctx):
|
|
|
14
14
|
def __init__(self, **kwargs):
|
|
15
15
|
super().__init__(**kwargs)
|
|
16
16
|
|
|
17
|
-
def to_response(self, response, chat, started_at):
|
|
17
|
+
def to_response(self, response, chat, started_at, context=None):
|
|
18
18
|
# go through all image responses and save them to cache
|
|
19
19
|
cost = None
|
|
20
20
|
if "usage" in response and "cost" in response["usage"]:
|
llms/main.py
CHANGED
|
@@ -57,7 +57,7 @@ try:
|
|
|
57
57
|
except ImportError:
|
|
58
58
|
HAS_PIL = False
|
|
59
59
|
|
|
60
|
-
VERSION = "3.0.
|
|
60
|
+
VERSION = "3.0.21"
|
|
61
61
|
_ROOT = None
|
|
62
62
|
DEBUG = os.getenv("DEBUG") == "1"
|
|
63
63
|
MOCK = os.getenv("MOCK") == "1"
|
|
@@ -874,8 +874,18 @@ def save_image_to_cache(base64_data, filename, image_info, ignore_info=False):
|
|
|
874
874
|
async def response_json(response):
|
|
875
875
|
text = await response.text()
|
|
876
876
|
if response.status >= 400:
|
|
877
|
-
|
|
878
|
-
|
|
877
|
+
message = "HTTP " + str(response.status) + " " + response.reason
|
|
878
|
+
_dbg(f"HTTP {response.status} {response.reason}\n{dict(response.headers)}\n{text}")
|
|
879
|
+
try:
|
|
880
|
+
body = json.loads(text)
|
|
881
|
+
if "message" in body:
|
|
882
|
+
message = body["message"]
|
|
883
|
+
elif "error" in body:
|
|
884
|
+
message = body["error"]
|
|
885
|
+
except Exception:
|
|
886
|
+
if text:
|
|
887
|
+
message += ": " + text[:100]
|
|
888
|
+
raise Exception(message)
|
|
879
889
|
response.raise_for_status()
|
|
880
890
|
body = json.loads(text)
|
|
881
891
|
return body
|
|
@@ -1946,12 +1956,11 @@ async def g_chat_completion(chat, context=None):
|
|
|
1946
1956
|
first_exception = e
|
|
1947
1957
|
context["stackTrace"] = traceback.format_exc()
|
|
1948
1958
|
_err(f"Provider {provider_name} failed", first_exception)
|
|
1949
|
-
await g_app.on_chat_error(e, context)
|
|
1950
|
-
|
|
1951
1959
|
continue
|
|
1952
1960
|
|
|
1953
1961
|
# If we get here, all providers failed
|
|
1954
1962
|
if first_exception:
|
|
1963
|
+
await g_app.on_chat_error(first_exception, context or {"chat": chat})
|
|
1955
1964
|
raise first_exception
|
|
1956
1965
|
|
|
1957
1966
|
e = Exception("All providers failed")
|
|
@@ -3871,8 +3880,8 @@ def cli_exec(cli_args, extra_args):
|
|
|
3871
3880
|
asyncio.run(update_extensions(cli_args.update))
|
|
3872
3881
|
return ExitCode.SUCCESS
|
|
3873
3882
|
|
|
3874
|
-
g_app.add_allowed_directory(home_llms_path(".agent")) # info for agents, e.g: skills
|
|
3875
3883
|
g_app.add_allowed_directory(os.getcwd()) # add current directory
|
|
3884
|
+
g_app.add_allowed_directory(home_llms_path(".agent")) # info for agents, e.g: skills
|
|
3876
3885
|
g_app.add_allowed_directory(tempfile.gettempdir()) # add temp directory
|
|
3877
3886
|
|
|
3878
3887
|
g_app.extensions = install_extensions()
|