abstractcore 2.6.9__py3-none-any.whl → 2.9.1__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.
- abstractcore/apps/summarizer.py +69 -27
- abstractcore/architectures/detection.py +190 -25
- abstractcore/assets/architecture_formats.json +129 -6
- abstractcore/assets/model_capabilities.json +803 -141
- abstractcore/config/main.py +2 -2
- abstractcore/config/manager.py +3 -1
- abstractcore/events/__init__.py +7 -1
- abstractcore/mcp/__init__.py +30 -0
- abstractcore/mcp/client.py +213 -0
- abstractcore/mcp/factory.py +64 -0
- abstractcore/mcp/naming.py +28 -0
- abstractcore/mcp/stdio_client.py +336 -0
- abstractcore/mcp/tool_source.py +164 -0
- abstractcore/processing/__init__.py +2 -2
- abstractcore/processing/basic_deepsearch.py +1 -1
- abstractcore/processing/basic_summarizer.py +379 -93
- abstractcore/providers/anthropic_provider.py +91 -10
- abstractcore/providers/base.py +540 -16
- abstractcore/providers/huggingface_provider.py +17 -8
- abstractcore/providers/lmstudio_provider.py +170 -25
- abstractcore/providers/mlx_provider.py +13 -10
- abstractcore/providers/ollama_provider.py +42 -26
- abstractcore/providers/openai_compatible_provider.py +87 -22
- abstractcore/providers/openai_provider.py +12 -9
- abstractcore/providers/streaming.py +201 -39
- abstractcore/providers/vllm_provider.py +78 -21
- abstractcore/server/app.py +116 -30
- abstractcore/structured/retry.py +20 -7
- abstractcore/tools/__init__.py +46 -24
- abstractcore/tools/abstractignore.py +166 -0
- abstractcore/tools/arg_canonicalizer.py +61 -0
- abstractcore/tools/common_tools.py +2443 -742
- abstractcore/tools/core.py +109 -13
- abstractcore/tools/handler.py +17 -3
- abstractcore/tools/parser.py +894 -159
- abstractcore/tools/registry.py +122 -18
- abstractcore/tools/syntax_rewriter.py +68 -6
- abstractcore/tools/tag_rewriter.py +186 -1
- abstractcore/utils/jsonish.py +111 -0
- abstractcore/utils/version.py +1 -1
- {abstractcore-2.6.9.dist-info → abstractcore-2.9.1.dist-info}/METADATA +56 -2
- {abstractcore-2.6.9.dist-info → abstractcore-2.9.1.dist-info}/RECORD +46 -37
- {abstractcore-2.6.9.dist-info → abstractcore-2.9.1.dist-info}/WHEEL +0 -0
- {abstractcore-2.6.9.dist-info → abstractcore-2.9.1.dist-info}/entry_points.txt +0 -0
- {abstractcore-2.6.9.dist-info → abstractcore-2.9.1.dist-info}/licenses/LICENSE +0 -0
- {abstractcore-2.6.9.dist-info → abstractcore-2.9.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""`.abstractignore` support for filesystem tools.
|
|
2
|
+
|
|
3
|
+
This module provides a small, runtime-enforced ignore policy for AbstractCore
|
|
4
|
+
filesystem tools (search/list/read/write/edit/analyze).
|
|
5
|
+
|
|
6
|
+
Goals:
|
|
7
|
+
- Avoid accidental scanning/editing of runtime artifacts (e.g. JsonFileRunStore
|
|
8
|
+
directories ending in `.d/`).
|
|
9
|
+
- Allow users to define additional ignore rules via a `.abstractignore` file,
|
|
10
|
+
inspired by `.gitignore` (not full parity).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
import fnmatch
|
|
18
|
+
from typing import Iterable, List, Optional, Tuple
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
_DEFAULT_IGNORE_LINES: List[str] = [
|
|
22
|
+
# VCS + caches
|
|
23
|
+
".git/",
|
|
24
|
+
".hg/",
|
|
25
|
+
".svn/",
|
|
26
|
+
"__pycache__/",
|
|
27
|
+
".pytest_cache/",
|
|
28
|
+
".mypy_cache/",
|
|
29
|
+
".ruff_cache/",
|
|
30
|
+
# Common build/env dirs
|
|
31
|
+
"node_modules/",
|
|
32
|
+
"dist/",
|
|
33
|
+
"build/",
|
|
34
|
+
".venv/",
|
|
35
|
+
"venv/",
|
|
36
|
+
"env/",
|
|
37
|
+
".env/",
|
|
38
|
+
# Editor/host artifacts
|
|
39
|
+
".cursor/",
|
|
40
|
+
# AbstractFramework runtime stores (AbstractCode/Runtime file-backed stores)
|
|
41
|
+
"*.d/",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass(frozen=True)
|
|
46
|
+
class AbstractIgnoreRule:
|
|
47
|
+
pattern: str
|
|
48
|
+
negate: bool = False
|
|
49
|
+
dir_only: bool = False
|
|
50
|
+
anchored: bool = False
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _parse_rules(lines: Iterable[str]) -> List[AbstractIgnoreRule]:
|
|
54
|
+
rules: List[AbstractIgnoreRule] = []
|
|
55
|
+
for raw in lines:
|
|
56
|
+
line = str(raw or "").strip()
|
|
57
|
+
if not line or line.startswith("#"):
|
|
58
|
+
continue
|
|
59
|
+
negate = False
|
|
60
|
+
if line.startswith("!"):
|
|
61
|
+
negate = True
|
|
62
|
+
line = line[1:].strip()
|
|
63
|
+
if not line:
|
|
64
|
+
continue
|
|
65
|
+
dir_only = line.endswith("/")
|
|
66
|
+
if dir_only:
|
|
67
|
+
line = line[:-1].strip()
|
|
68
|
+
anchored = line.startswith("/")
|
|
69
|
+
if anchored:
|
|
70
|
+
line = line[1:].strip()
|
|
71
|
+
if not line:
|
|
72
|
+
continue
|
|
73
|
+
rules.append(AbstractIgnoreRule(pattern=line, negate=negate, dir_only=dir_only, anchored=anchored))
|
|
74
|
+
return rules
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _find_nearest_abstractignore(start: Path) -> Optional[Path]:
|
|
78
|
+
"""Find the nearest `.abstractignore` by walking upward from start."""
|
|
79
|
+
cur = start if start.is_dir() else start.parent
|
|
80
|
+
cur = cur.resolve()
|
|
81
|
+
for p in (cur, *cur.parents):
|
|
82
|
+
candidate = p / ".abstractignore"
|
|
83
|
+
if candidate.is_file():
|
|
84
|
+
return candidate
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class AbstractIgnore:
|
|
89
|
+
"""A simple ignore matcher loaded from `.abstractignore` + defaults."""
|
|
90
|
+
|
|
91
|
+
def __init__(self, *, root: Path, rules: List[AbstractIgnoreRule], source: Optional[Path] = None):
|
|
92
|
+
self.root = root.resolve()
|
|
93
|
+
self.rules = list(rules)
|
|
94
|
+
self.source = source.resolve() if isinstance(source, Path) else None
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def for_path(cls, path: Path) -> "AbstractIgnore":
|
|
98
|
+
"""Create an ignore matcher for a given path (file or dir)."""
|
|
99
|
+
start = path if path.is_dir() else path.parent
|
|
100
|
+
ignore_file = _find_nearest_abstractignore(start)
|
|
101
|
+
root = ignore_file.parent if ignore_file is not None else start
|
|
102
|
+
file_rules: List[AbstractIgnoreRule] = []
|
|
103
|
+
if ignore_file is not None:
|
|
104
|
+
try:
|
|
105
|
+
file_rules = _parse_rules(ignore_file.read_text(encoding="utf-8").splitlines())
|
|
106
|
+
except Exception:
|
|
107
|
+
file_rules = []
|
|
108
|
+
# Defaults first; user file rules can override via negation.
|
|
109
|
+
rules = _parse_rules(_DEFAULT_IGNORE_LINES) + file_rules
|
|
110
|
+
return cls(root=root, rules=rules, source=ignore_file)
|
|
111
|
+
|
|
112
|
+
def _rel(self, path: Path) -> Tuple[str, List[str]]:
|
|
113
|
+
p = path.resolve()
|
|
114
|
+
try:
|
|
115
|
+
rel = p.relative_to(self.root)
|
|
116
|
+
rel_str = rel.as_posix()
|
|
117
|
+
parts = list(rel.parts)
|
|
118
|
+
except Exception:
|
|
119
|
+
# If the target is outside root, fall back to absolute matching.
|
|
120
|
+
rel_str = p.as_posix().lstrip("/")
|
|
121
|
+
parts = list(p.parts)
|
|
122
|
+
return rel_str, [str(x) for x in parts if str(x)]
|
|
123
|
+
|
|
124
|
+
def is_ignored(self, path: Path, *, is_dir: Optional[bool] = None) -> bool:
|
|
125
|
+
p = path.resolve()
|
|
126
|
+
if is_dir is None:
|
|
127
|
+
try:
|
|
128
|
+
is_dir = p.is_dir()
|
|
129
|
+
except Exception:
|
|
130
|
+
is_dir = False
|
|
131
|
+
|
|
132
|
+
rel_str, parts = self._rel(p)
|
|
133
|
+
name = parts[-1] if parts else p.name
|
|
134
|
+
dir_parts = parts if is_dir else parts[:-1]
|
|
135
|
+
|
|
136
|
+
ignored = False
|
|
137
|
+
for rule in self.rules:
|
|
138
|
+
pat = rule.pattern
|
|
139
|
+
if not pat:
|
|
140
|
+
continue
|
|
141
|
+
|
|
142
|
+
matched = False
|
|
143
|
+
if rule.dir_only:
|
|
144
|
+
# Match against any directory prefix.
|
|
145
|
+
for i in range(1, len(dir_parts) + 1):
|
|
146
|
+
prefix = "/".join(dir_parts[:i])
|
|
147
|
+
if fnmatch.fnmatchcase(prefix, pat) or fnmatch.fnmatchcase(dir_parts[i - 1], pat):
|
|
148
|
+
matched = True
|
|
149
|
+
break
|
|
150
|
+
if not matched and is_dir:
|
|
151
|
+
matched = fnmatch.fnmatchcase(rel_str, pat) or fnmatch.fnmatchcase(name, pat)
|
|
152
|
+
else:
|
|
153
|
+
if rule.anchored or ("/" in pat):
|
|
154
|
+
matched = fnmatch.fnmatchcase(rel_str, pat)
|
|
155
|
+
else:
|
|
156
|
+
matched = fnmatch.fnmatchcase(name, pat)
|
|
157
|
+
|
|
158
|
+
if matched:
|
|
159
|
+
ignored = not rule.negate
|
|
160
|
+
|
|
161
|
+
return ignored
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
__all__ = ["AbstractIgnore", "AbstractIgnoreRule"]
|
|
165
|
+
|
|
166
|
+
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Tool argument canonicalization (aliases -> canonical).
|
|
2
|
+
|
|
3
|
+
AbstractCore owns tool-call parsing/rewriting. This module provides a small,
|
|
4
|
+
central place to normalize common argument-name drift that appears in LLM
|
|
5
|
+
generated calls, while keeping the runtime/tool implementations stable.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any, Dict, Optional
|
|
11
|
+
|
|
12
|
+
from ..utils.jsonish import loads_dict_like
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _loads_dict(value: Any) -> Optional[Dict[str, Any]]:
|
|
16
|
+
if value is None:
|
|
17
|
+
return None
|
|
18
|
+
if isinstance(value, dict):
|
|
19
|
+
return dict(value)
|
|
20
|
+
if isinstance(value, str):
|
|
21
|
+
parsed = loads_dict_like(value)
|
|
22
|
+
return dict(parsed) if isinstance(parsed, dict) else None
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def canonicalize_tool_arguments(tool_name: str, arguments: Any) -> Dict[str, Any]:
|
|
27
|
+
"""Return a canonical argument dict for a tool call (best-effort)."""
|
|
28
|
+
name = str(tool_name or "").strip()
|
|
29
|
+
args = _loads_dict(arguments) or {}
|
|
30
|
+
|
|
31
|
+
if not name or not args:
|
|
32
|
+
return args
|
|
33
|
+
|
|
34
|
+
if name == "read_file":
|
|
35
|
+
return _canonicalize_read_file_args(args)
|
|
36
|
+
|
|
37
|
+
return args
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _canonicalize_read_file_args(arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
41
|
+
out = dict(arguments)
|
|
42
|
+
|
|
43
|
+
if "start_line" not in out:
|
|
44
|
+
if "start_line_one_indexed" in out:
|
|
45
|
+
out["start_line"] = out.get("start_line_one_indexed")
|
|
46
|
+
elif "start" in out:
|
|
47
|
+
out["start_line"] = out.get("start")
|
|
48
|
+
|
|
49
|
+
if "end_line" not in out:
|
|
50
|
+
if "end_line_one_indexed_inclusive" in out:
|
|
51
|
+
out["end_line"] = out.get("end_line_one_indexed_inclusive")
|
|
52
|
+
elif "end" in out:
|
|
53
|
+
out["end_line"] = out.get("end")
|
|
54
|
+
|
|
55
|
+
for k in ("start_line_one_indexed", "end_line_one_indexed_inclusive", "start", "end"):
|
|
56
|
+
out.pop(k, None)
|
|
57
|
+
|
|
58
|
+
return out
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
__all__ = ["canonicalize_tool_arguments"]
|