abstractcore 2.6.9__py3-none-any.whl → 2.9.0__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 (46) hide show
  1. abstractcore/apps/summarizer.py +69 -27
  2. abstractcore/architectures/detection.py +190 -25
  3. abstractcore/assets/architecture_formats.json +129 -6
  4. abstractcore/assets/model_capabilities.json +803 -141
  5. abstractcore/config/main.py +2 -2
  6. abstractcore/config/manager.py +3 -1
  7. abstractcore/events/__init__.py +7 -1
  8. abstractcore/mcp/__init__.py +30 -0
  9. abstractcore/mcp/client.py +213 -0
  10. abstractcore/mcp/factory.py +64 -0
  11. abstractcore/mcp/naming.py +28 -0
  12. abstractcore/mcp/stdio_client.py +336 -0
  13. abstractcore/mcp/tool_source.py +164 -0
  14. abstractcore/processing/__init__.py +2 -2
  15. abstractcore/processing/basic_deepsearch.py +1 -1
  16. abstractcore/processing/basic_summarizer.py +379 -93
  17. abstractcore/providers/anthropic_provider.py +91 -10
  18. abstractcore/providers/base.py +540 -16
  19. abstractcore/providers/huggingface_provider.py +17 -8
  20. abstractcore/providers/lmstudio_provider.py +170 -25
  21. abstractcore/providers/mlx_provider.py +13 -10
  22. abstractcore/providers/ollama_provider.py +42 -26
  23. abstractcore/providers/openai_compatible_provider.py +87 -22
  24. abstractcore/providers/openai_provider.py +12 -9
  25. abstractcore/providers/streaming.py +201 -39
  26. abstractcore/providers/vllm_provider.py +78 -21
  27. abstractcore/server/app.py +116 -30
  28. abstractcore/structured/retry.py +20 -7
  29. abstractcore/tools/__init__.py +46 -24
  30. abstractcore/tools/abstractignore.py +166 -0
  31. abstractcore/tools/arg_canonicalizer.py +61 -0
  32. abstractcore/tools/common_tools.py +2443 -742
  33. abstractcore/tools/core.py +109 -13
  34. abstractcore/tools/handler.py +17 -3
  35. abstractcore/tools/parser.py +894 -159
  36. abstractcore/tools/registry.py +122 -18
  37. abstractcore/tools/syntax_rewriter.py +68 -6
  38. abstractcore/tools/tag_rewriter.py +186 -1
  39. abstractcore/utils/jsonish.py +111 -0
  40. abstractcore/utils/version.py +1 -1
  41. {abstractcore-2.6.9.dist-info → abstractcore-2.9.0.dist-info}/METADATA +55 -2
  42. {abstractcore-2.6.9.dist-info → abstractcore-2.9.0.dist-info}/RECORD +46 -37
  43. {abstractcore-2.6.9.dist-info → abstractcore-2.9.0.dist-info}/WHEEL +0 -0
  44. {abstractcore-2.6.9.dist-info → abstractcore-2.9.0.dist-info}/entry_points.txt +0 -0
  45. {abstractcore-2.6.9.dist-info → abstractcore-2.9.0.dist-info}/licenses/LICENSE +0 -0
  46. {abstractcore-2.6.9.dist-info → abstractcore-2.9.0.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"]