bareagent-cli 0.1.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.
- bareagent/__init__.py +10 -0
- bareagent/concurrency/__init__.py +6 -0
- bareagent/concurrency/background.py +97 -0
- bareagent/concurrency/notification.py +61 -0
- bareagent/concurrency/scheduler.py +136 -0
- bareagent/config.toml +299 -0
- bareagent/core/__init__.py +1 -0
- bareagent/core/config_paths.py +49 -0
- bareagent/core/context.py +127 -0
- bareagent/core/fileutil.py +103 -0
- bareagent/core/goal.py +214 -0
- bareagent/core/handlers/__init__.py +1 -0
- bareagent/core/handlers/bash.py +79 -0
- bareagent/core/handlers/file_edit.py +47 -0
- bareagent/core/handlers/file_read.py +270 -0
- bareagent/core/handlers/file_write.py +34 -0
- bareagent/core/handlers/glob_search.py +30 -0
- bareagent/core/handlers/goal.py +60 -0
- bareagent/core/handlers/grep_search.py +52 -0
- bareagent/core/handlers/memory.py +71 -0
- bareagent/core/handlers/plan.py +106 -0
- bareagent/core/handlers/search_utils.py +77 -0
- bareagent/core/handlers/skill.py +87 -0
- bareagent/core/handlers/subagent_send.py +70 -0
- bareagent/core/handlers/web_fetch.py +126 -0
- bareagent/core/handlers/web_search.py +165 -0
- bareagent/core/handlers/workflow.py +190 -0
- bareagent/core/loop.py +535 -0
- bareagent/core/retry.py +131 -0
- bareagent/core/sandbox.py +27 -0
- bareagent/core/schema.py +21 -0
- bareagent/core/tools.py +779 -0
- bareagent/core/workflow.py +517 -0
- bareagent/core/workflow_registry.py +219 -0
- bareagent/debug/__init__.py +0 -0
- bareagent/debug/interaction_log.py +263 -0
- bareagent/debug/viewer.html +1750 -0
- bareagent/debug/web_viewer.py +157 -0
- bareagent/hooks/__init__.py +32 -0
- bareagent/hooks/config.py +118 -0
- bareagent/hooks/engine.py +197 -0
- bareagent/hooks/errors.py +14 -0
- bareagent/hooks/events.py +22 -0
- bareagent/lsp/__init__.py +63 -0
- bareagent/lsp/config.py +134 -0
- bareagent/lsp/coord.py +118 -0
- bareagent/lsp/diagnostics.py +240 -0
- bareagent/lsp/errors.py +24 -0
- bareagent/lsp/manager.py +866 -0
- bareagent/lsp/tools.py +629 -0
- bareagent/lsp/workspace_edit.py +305 -0
- bareagent/main.py +4205 -0
- bareagent/mcp/__init__.py +69 -0
- bareagent/mcp/_sse.py +69 -0
- bareagent/mcp/client.py +341 -0
- bareagent/mcp/config.py +169 -0
- bareagent/mcp/errors.py +32 -0
- bareagent/mcp/manager.py +318 -0
- bareagent/mcp/protocol.py +187 -0
- bareagent/mcp/registry.py +557 -0
- bareagent/mcp/transport/__init__.py +15 -0
- bareagent/mcp/transport/base.py +149 -0
- bareagent/mcp/transport/http_legacy.py +192 -0
- bareagent/mcp/transport/http_streamable.py +217 -0
- bareagent/mcp/transport/stdio.py +202 -0
- bareagent/memory/__init__.py +1 -0
- bareagent/memory/compact.py +203 -0
- bareagent/memory/conversation_io.py +226 -0
- bareagent/memory/embedding.py +194 -0
- bareagent/memory/persistent.py +515 -0
- bareagent/memory/token_counter.py +67 -0
- bareagent/memory/token_tracker.py +262 -0
- bareagent/memory/transcript.py +100 -0
- bareagent/permission/__init__.py +1 -0
- bareagent/permission/guard.py +329 -0
- bareagent/permission/rules.py +19 -0
- bareagent/planning/__init__.py +19 -0
- bareagent/planning/agent_types.py +169 -0
- bareagent/planning/skill_gen.py +141 -0
- bareagent/planning/skill_store.py +173 -0
- bareagent/planning/skills.py +146 -0
- bareagent/planning/subagent.py +355 -0
- bareagent/planning/subagent_registry.py +77 -0
- bareagent/planning/tasks.py +348 -0
- bareagent/planning/todo.py +153 -0
- bareagent/planning/worktree.py +122 -0
- bareagent/provider/__init__.py +1 -0
- bareagent/provider/anthropic.py +348 -0
- bareagent/provider/base.py +136 -0
- bareagent/provider/factory.py +130 -0
- bareagent/provider/openai.py +881 -0
- bareagent/provider/presets.py +72 -0
- bareagent/provider/setup.py +356 -0
- bareagent/skills/.gitkeep +1 -0
- bareagent/skills/code-review/SKILL.md +68 -0
- bareagent/skills/git/SKILL.md +68 -0
- bareagent/skills/test/SKILL.md +70 -0
- bareagent/team/__init__.py +17 -0
- bareagent/team/autonomous.py +193 -0
- bareagent/team/mailbox.py +239 -0
- bareagent/team/manager.py +155 -0
- bareagent/team/protocols.py +129 -0
- bareagent/tracing/__init__.py +12 -0
- bareagent/tracing/_api.py +92 -0
- bareagent/tracing/_proxy.py +60 -0
- bareagent/tracing/composite.py +115 -0
- bareagent/tracing/json_file.py +115 -0
- bareagent/tracing/langfuse.py +139 -0
- bareagent/tracing/otel.py +107 -0
- bareagent/tracing/setup.py +85 -0
- bareagent/ui/__init__.py +24 -0
- bareagent/ui/console.py +167 -0
- bareagent/ui/prompt.py +78 -0
- bareagent/ui/protocol.py +24 -0
- bareagent/ui/stream.py +66 -0
- bareagent/ui/theme.py +240 -0
- bareagent_cli-0.1.0.dist-info/METADATA +331 -0
- bareagent_cli-0.1.0.dist-info/RECORD +121 -0
- bareagent_cli-0.1.0.dist-info/WHEEL +4 -0
- bareagent_cli-0.1.0.dist-info/entry_points.txt +2 -0
- bareagent_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""Pluggable text-embedding backends for semantic memory recall.
|
|
2
|
+
|
|
3
|
+
This module is the embedding layer behind :meth:`MemoryManager.recall`'s
|
|
4
|
+
semantic path. It is deliberately small and dependency-light: the OpenAI
|
|
5
|
+
backend reuses the already-present ``openai`` client, the local backend
|
|
6
|
+
lazy-imports ``fastembed`` (optional ``[embeddings]`` extra), and cosine
|
|
7
|
+
similarity is computed in pure Python so neither backend forces ``numpy`` into
|
|
8
|
+
the core install.
|
|
9
|
+
|
|
10
|
+
Every entry point fails open: :func:`build_embedder` returns ``None`` when the
|
|
11
|
+
chosen backend cannot be constructed (missing dependency, missing key, unknown
|
|
12
|
+
backend), and the recall layer treats ``None`` -- or any error raised by
|
|
13
|
+
``embed`` at call time -- as a signal to fall back to lexical recall. Embeddings
|
|
14
|
+
are a relevance enhancement, never a hard dependency.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import hashlib
|
|
20
|
+
import json
|
|
21
|
+
import logging
|
|
22
|
+
import math
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Protocol, runtime_checkable
|
|
25
|
+
|
|
26
|
+
from bareagent.core.fileutil import atomic_write_text
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
_OPENAI_DEFAULT_MODEL = "text-embedding-3-small"
|
|
31
|
+
_LOCAL_DEFAULT_MODEL = "BAAI/bge-small-en-v1.5"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@runtime_checkable
|
|
35
|
+
class Embedder(Protocol):
|
|
36
|
+
"""Embeds a batch of strings into float vectors.
|
|
37
|
+
|
|
38
|
+
``identity`` is a stable ``backend:model`` string used to invalidate the
|
|
39
|
+
on-disk vector cache when the backend or model changes.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
identity: str
|
|
43
|
+
|
|
44
|
+
def embed(self, texts: list[str]) -> list[list[float]]: ...
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class OpenAIEmbedder:
|
|
48
|
+
"""Embeds via an OpenAI-compatible ``/embeddings`` endpoint."""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
model: str,
|
|
53
|
+
*,
|
|
54
|
+
base_url: str | None = None,
|
|
55
|
+
api_key: str | None = None,
|
|
56
|
+
) -> None:
|
|
57
|
+
import openai
|
|
58
|
+
|
|
59
|
+
self.model = model
|
|
60
|
+
self.identity = f"openai:{model}"
|
|
61
|
+
# max_retries=0: app-layer fail-open owns retry semantics, not the SDK.
|
|
62
|
+
self._client = openai.OpenAI(
|
|
63
|
+
api_key=api_key or "", base_url=base_url or None, max_retries=0
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def embed(self, texts: list[str]) -> list[list[float]]:
|
|
67
|
+
response = self._client.embeddings.create(model=self.model, input=list(texts))
|
|
68
|
+
return [[float(x) for x in item.embedding] for item in response.data]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class LocalEmbedder:
|
|
72
|
+
"""Embeds locally via fastembed (ONNX, no torch; optional ``[embeddings]``)."""
|
|
73
|
+
|
|
74
|
+
def __init__(self, model: str) -> None:
|
|
75
|
+
from fastembed import TextEmbedding
|
|
76
|
+
|
|
77
|
+
self.model = model
|
|
78
|
+
self.identity = f"local:{model}"
|
|
79
|
+
self._model = TextEmbedding(model_name=model)
|
|
80
|
+
|
|
81
|
+
def embed(self, texts: list[str]) -> list[list[float]]:
|
|
82
|
+
return [[float(x) for x in vector] for vector in self._model.embed(list(texts))]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def build_embedder(
|
|
86
|
+
backend: str,
|
|
87
|
+
model: str,
|
|
88
|
+
*,
|
|
89
|
+
base_url: str | None = None,
|
|
90
|
+
api_key: str | None = None,
|
|
91
|
+
) -> Embedder | None:
|
|
92
|
+
"""Construct the requested embedder, or ``None`` on any failure (fail-open).
|
|
93
|
+
|
|
94
|
+
A missing fastembed install, a missing key, or an unknown backend name all
|
|
95
|
+
degrade to ``None`` so the caller falls back to lexical recall.
|
|
96
|
+
"""
|
|
97
|
+
normalized = (backend or "").strip().lower()
|
|
98
|
+
try:
|
|
99
|
+
if normalized == "local":
|
|
100
|
+
return LocalEmbedder(model or _LOCAL_DEFAULT_MODEL)
|
|
101
|
+
if normalized == "openai":
|
|
102
|
+
return OpenAIEmbedder(
|
|
103
|
+
model or _OPENAI_DEFAULT_MODEL, base_url=base_url, api_key=api_key
|
|
104
|
+
)
|
|
105
|
+
except Exception:
|
|
106
|
+
logger.warning(
|
|
107
|
+
"Embedding backend %r unavailable; semantic recall will fall back to "
|
|
108
|
+
"lexical.",
|
|
109
|
+
normalized,
|
|
110
|
+
exc_info=True,
|
|
111
|
+
)
|
|
112
|
+
return None
|
|
113
|
+
logger.warning("Unknown embedding backend %r; semantic recall disabled.", backend)
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def cosine(a: list[float], b: list[float]) -> float:
|
|
118
|
+
"""Cosine similarity in pure Python (no numpy). Returns 0.0 on degenerate input."""
|
|
119
|
+
if not a or not b or len(a) != len(b):
|
|
120
|
+
return 0.0
|
|
121
|
+
dot = sum(x * y for x, y in zip(a, b, strict=True))
|
|
122
|
+
norm_a = math.sqrt(sum(x * x for x in a))
|
|
123
|
+
norm_b = math.sqrt(sum(y * y for y in b))
|
|
124
|
+
if norm_a == 0.0 or norm_b == 0.0:
|
|
125
|
+
return 0.0
|
|
126
|
+
return dot / (norm_a * norm_b)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def text_hash(text: str) -> str:
|
|
130
|
+
"""Stable content hash keying a cached embedding to the text it embedded."""
|
|
131
|
+
return hashlib.sha256(text.encode("utf-8")).hexdigest()
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class EmbeddingCache:
|
|
135
|
+
"""On-disk vector cache: ``{relpath: (text_hash, vector)}`` + backend identity.
|
|
136
|
+
|
|
137
|
+
The cache is invalidated wholesale when ``identity`` (backend:model) changes,
|
|
138
|
+
and per-entry when the embedded text's hash changes. Corrupt / unreadable
|
|
139
|
+
cache files start fresh rather than raising.
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
def __init__(self, path: Path, identity: str) -> None:
|
|
143
|
+
self.path = path
|
|
144
|
+
self.identity = identity
|
|
145
|
+
self._entries: dict[str, tuple[str, list[float]]] = {}
|
|
146
|
+
self._load()
|
|
147
|
+
|
|
148
|
+
def _load(self) -> None:
|
|
149
|
+
try:
|
|
150
|
+
data = json.loads(self.path.read_text(encoding="utf-8"))
|
|
151
|
+
except (OSError, json.JSONDecodeError, ValueError):
|
|
152
|
+
return
|
|
153
|
+
if not isinstance(data, dict) or data.get("identity") != self.identity:
|
|
154
|
+
return # backend/model changed or unrecognized layout -> start fresh
|
|
155
|
+
entries = data.get("entries")
|
|
156
|
+
if not isinstance(entries, dict):
|
|
157
|
+
return
|
|
158
|
+
for rel, entry in entries.items():
|
|
159
|
+
if not isinstance(entry, dict):
|
|
160
|
+
continue
|
|
161
|
+
vector = entry.get("vector")
|
|
162
|
+
if not isinstance(vector, list):
|
|
163
|
+
continue
|
|
164
|
+
try:
|
|
165
|
+
self._entries[str(rel)] = (
|
|
166
|
+
str(entry.get("hash", "")),
|
|
167
|
+
[float(x) for x in vector],
|
|
168
|
+
)
|
|
169
|
+
except (TypeError, ValueError):
|
|
170
|
+
continue
|
|
171
|
+
|
|
172
|
+
def get(self, rel: str) -> tuple[str, list[float]] | None:
|
|
173
|
+
return self._entries.get(rel)
|
|
174
|
+
|
|
175
|
+
def put(self, rel: str, content_hash: str, vector: list[float]) -> None:
|
|
176
|
+
self._entries[rel] = (content_hash, vector)
|
|
177
|
+
|
|
178
|
+
def prune(self, live: set[str]) -> None:
|
|
179
|
+
for rel in list(self._entries):
|
|
180
|
+
if rel not in live:
|
|
181
|
+
del self._entries[rel]
|
|
182
|
+
|
|
183
|
+
def save(self) -> None:
|
|
184
|
+
payload = {
|
|
185
|
+
"identity": self.identity,
|
|
186
|
+
"entries": {
|
|
187
|
+
rel: {"hash": h, "vector": vec}
|
|
188
|
+
for rel, (h, vec) in self._entries.items()
|
|
189
|
+
},
|
|
190
|
+
}
|
|
191
|
+
try:
|
|
192
|
+
atomic_write_text(self.path, json.dumps(payload, ensure_ascii=False))
|
|
193
|
+
except OSError:
|
|
194
|
+
logger.warning("Could not persist embedding cache to %s", self.path, exc_info=True)
|