minder-cli 0.2.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.
- minder/__init__.py +12 -0
- minder/api/routers/prompts.py +177 -0
- minder/application/__init__.py +1 -0
- minder/application/admin/__init__.py +11 -0
- minder/application/admin/dto.py +453 -0
- minder/application/admin/jobs.py +327 -0
- minder/application/admin/use_cases.py +1895 -0
- minder/auth/__init__.py +12 -0
- minder/auth/context.py +26 -0
- minder/auth/middleware.py +70 -0
- minder/auth/principal.py +59 -0
- minder/auth/rate_limiter.py +89 -0
- minder/auth/rbac.py +60 -0
- minder/auth/service.py +541 -0
- minder/bootstrap/__init__.py +9 -0
- minder/bootstrap/providers.py +109 -0
- minder/bootstrap/transport.py +807 -0
- minder/cache/__init__.py +10 -0
- minder/cache/providers.py +140 -0
- minder/chunking/__init__.py +4 -0
- minder/chunking/code_splitter.py +184 -0
- minder/chunking/splitter.py +136 -0
- minder/cli.py +1542 -0
- minder/config.py +179 -0
- minder/continuity.py +363 -0
- minder/dev.py +160 -0
- minder/embedding/__init__.py +9 -0
- minder/embedding/base.py +7 -0
- minder/embedding/local.py +65 -0
- minder/embedding/openai.py +7 -0
- minder/graph/__init__.py +11 -0
- minder/graph/edges.py +13 -0
- minder/graph/executor.py +127 -0
- minder/graph/graph.py +263 -0
- minder/graph/nodes/__init__.py +27 -0
- minder/graph/nodes/evaluator.py +21 -0
- minder/graph/nodes/guard.py +64 -0
- minder/graph/nodes/llm.py +59 -0
- minder/graph/nodes/planning.py +30 -0
- minder/graph/nodes/reasoning.py +87 -0
- minder/graph/nodes/reranker.py +141 -0
- minder/graph/nodes/retriever.py +86 -0
- minder/graph/nodes/verification.py +230 -0
- minder/graph/nodes/workflow_planner.py +250 -0
- minder/graph/runtime.py +15 -0
- minder/graph/state.py +26 -0
- minder/llm/__init__.py +5 -0
- minder/llm/base.py +14 -0
- minder/llm/local.py +381 -0
- minder/llm/openai.py +89 -0
- minder/models/__init__.py +109 -0
- minder/models/base.py +10 -0
- minder/models/client.py +137 -0
- minder/models/document.py +34 -0
- minder/models/error.py +32 -0
- minder/models/graph.py +114 -0
- minder/models/history.py +32 -0
- minder/models/job.py +62 -0
- minder/models/prompt.py +41 -0
- minder/models/repository.py +62 -0
- minder/models/rule.py +68 -0
- minder/models/session.py +51 -0
- minder/models/skill.py +52 -0
- minder/models/user.py +41 -0
- minder/models/workflow.py +35 -0
- minder/observability/__init__.py +57 -0
- minder/observability/audit.py +243 -0
- minder/observability/logging.py +253 -0
- minder/observability/metrics.py +448 -0
- minder/observability/tracing.py +215 -0
- minder/presentation/__init__.py +1 -0
- minder/presentation/http/__init__.py +1 -0
- minder/presentation/http/admin/__init__.py +3 -0
- minder/presentation/http/admin/api.py +1309 -0
- minder/presentation/http/admin/context.py +94 -0
- minder/presentation/http/admin/dashboard.py +111 -0
- minder/presentation/http/admin/jobs.py +208 -0
- minder/presentation/http/admin/memories.py +185 -0
- minder/presentation/http/admin/prompts.py +219 -0
- minder/presentation/http/admin/routes.py +127 -0
- minder/presentation/http/admin/runtime.py +650 -0
- minder/presentation/http/admin/search.py +368 -0
- minder/presentation/http/admin/skills.py +230 -0
- minder/prompts/__init__.py +646 -0
- minder/prompts/formatter.py +142 -0
- minder/resources/__init__.py +318 -0
- minder/retrieval/__init__.py +5 -0
- minder/retrieval/hybrid.py +178 -0
- minder/retrieval/mmr.py +116 -0
- minder/retrieval/multi_hop.py +115 -0
- minder/runtime.py +15 -0
- minder/server.py +145 -0
- minder/store/__init__.py +64 -0
- minder/store/document.py +115 -0
- minder/store/error.py +82 -0
- minder/store/feedback.py +114 -0
- minder/store/graph.py +588 -0
- minder/store/history.py +57 -0
- minder/store/interfaces.py +512 -0
- minder/store/milvus/__init__.py +11 -0
- minder/store/milvus/client.py +26 -0
- minder/store/milvus/collections.py +15 -0
- minder/store/milvus/vector_store.py +232 -0
- minder/store/mongodb/__init__.py +11 -0
- minder/store/mongodb/client.py +49 -0
- minder/store/mongodb/indexes.py +90 -0
- minder/store/mongodb/operational_store.py +993 -0
- minder/store/relational.py +1087 -0
- minder/store/repo_state.py +58 -0
- minder/store/rule.py +93 -0
- minder/store/vector.py +79 -0
- minder/tools/__init__.py +47 -0
- minder/tools/auth.py +94 -0
- minder/tools/graph.py +839 -0
- minder/tools/ingest.py +353 -0
- minder/tools/memory.py +381 -0
- minder/tools/query.py +307 -0
- minder/tools/registry.py +269 -0
- minder/tools/repo_scanner.py +1266 -0
- minder/tools/search.py +15 -0
- minder/tools/session.py +316 -0
- minder/tools/skills.py +899 -0
- minder/tools/workflow.py +215 -0
- minder/transport/__init__.py +4 -0
- minder/transport/base.py +286 -0
- minder/transport/sse.py +252 -0
- minder/transport/stdio.py +29 -0
- minder_cli-0.2.0.dist-info/METADATA +318 -0
- minder_cli-0.2.0.dist-info/RECORD +132 -0
- minder_cli-0.2.0.dist-info/WHEEL +4 -0
- minder_cli-0.2.0.dist-info/entry_points.txt +2 -0
- minder_cli-0.2.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import contextlib
|
|
5
|
+
import uuid
|
|
6
|
+
from collections.abc import AsyncIterator, Awaitable, Callable
|
|
7
|
+
from datetime import UTC, datetime
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from minder.config import MinderConfig
|
|
11
|
+
from minder.store.interfaces import IOperationalStore
|
|
12
|
+
from minder.tools.skills import SkillTools
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _utcnow() -> datetime:
|
|
16
|
+
return datetime.now(UTC)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AdminJobService:
|
|
20
|
+
_MAX_EVENT_HISTORY = 100
|
|
21
|
+
|
|
22
|
+
def __init__(self, store: IOperationalStore, config: MinderConfig) -> None:
|
|
23
|
+
self._store = store
|
|
24
|
+
self._config = config
|
|
25
|
+
self._tasks: dict[str, asyncio.Task[None]] = {}
|
|
26
|
+
self._subscribers: dict[
|
|
27
|
+
int, tuple[asyncio.Queue[dict[str, Any]], Callable[[dict[str, Any]], bool]]
|
|
28
|
+
] = {}
|
|
29
|
+
self._subscriber_id = 0
|
|
30
|
+
self._lock = asyncio.Lock()
|
|
31
|
+
|
|
32
|
+
async def enqueue(
|
|
33
|
+
self,
|
|
34
|
+
*,
|
|
35
|
+
job_type: str,
|
|
36
|
+
title: str,
|
|
37
|
+
requested_by_user_id: uuid.UUID | None,
|
|
38
|
+
payload: dict[str, Any],
|
|
39
|
+
) -> Any:
|
|
40
|
+
job = await self._store.create_admin_job(
|
|
41
|
+
id=uuid.uuid4(),
|
|
42
|
+
job_type=job_type,
|
|
43
|
+
title=title,
|
|
44
|
+
status="queued",
|
|
45
|
+
requested_by_user_id=requested_by_user_id,
|
|
46
|
+
payload=payload,
|
|
47
|
+
result_payload=None,
|
|
48
|
+
error_message=None,
|
|
49
|
+
progress_current=0,
|
|
50
|
+
progress_total=0,
|
|
51
|
+
message="Queued",
|
|
52
|
+
events=[
|
|
53
|
+
self._event_payload(
|
|
54
|
+
event_type="queued", status="queued", message="Job queued"
|
|
55
|
+
)
|
|
56
|
+
],
|
|
57
|
+
created_at=_utcnow(),
|
|
58
|
+
updated_at=_utcnow(),
|
|
59
|
+
started_at=None,
|
|
60
|
+
finished_at=None,
|
|
61
|
+
)
|
|
62
|
+
await self._publish(self._snapshot(job))
|
|
63
|
+
self._ensure_running(job)
|
|
64
|
+
return job
|
|
65
|
+
|
|
66
|
+
def subscribe(
|
|
67
|
+
self,
|
|
68
|
+
*,
|
|
69
|
+
job_id: str | None = None,
|
|
70
|
+
job_type: str | None = None,
|
|
71
|
+
statuses: set[str] | None = None,
|
|
72
|
+
) -> tuple[asyncio.Queue[dict[str, Any]], Callable[[], None]]:
|
|
73
|
+
queue: asyncio.Queue[dict[str, Any]] = asyncio.Queue()
|
|
74
|
+
|
|
75
|
+
def _matches(payload: dict[str, Any]) -> bool:
|
|
76
|
+
if job_id and str(payload.get("id")) != job_id:
|
|
77
|
+
return False
|
|
78
|
+
if job_type and str(payload.get("job_type")) != job_type:
|
|
79
|
+
return False
|
|
80
|
+
if statuses and str(payload.get("status")) not in statuses:
|
|
81
|
+
return False
|
|
82
|
+
return True
|
|
83
|
+
|
|
84
|
+
subscriber_key = self._subscriber_id
|
|
85
|
+
self._subscriber_id += 1
|
|
86
|
+
self._subscribers[subscriber_key] = (queue, _matches)
|
|
87
|
+
|
|
88
|
+
def unsubscribe() -> None:
|
|
89
|
+
self._subscribers.pop(subscriber_key, None)
|
|
90
|
+
|
|
91
|
+
return queue, unsubscribe
|
|
92
|
+
|
|
93
|
+
async def publish_existing(self, job: Any) -> None:
|
|
94
|
+
await self._publish(self._snapshot(job))
|
|
95
|
+
|
|
96
|
+
def _ensure_running(self, job: Any) -> None:
|
|
97
|
+
job_id = str(job.id)
|
|
98
|
+
existing = self._tasks.get(job_id)
|
|
99
|
+
if existing is not None and not existing.done():
|
|
100
|
+
return
|
|
101
|
+
self._tasks[job_id] = asyncio.create_task(self._run(job_id))
|
|
102
|
+
|
|
103
|
+
async def _run(self, job_id: str) -> None:
|
|
104
|
+
try:
|
|
105
|
+
job = await self._store.get_admin_job_by_id(uuid.UUID(job_id))
|
|
106
|
+
if job is None:
|
|
107
|
+
return
|
|
108
|
+
await self._update_job(
|
|
109
|
+
job_id,
|
|
110
|
+
status="running",
|
|
111
|
+
message="Starting job",
|
|
112
|
+
started_at=_utcnow(),
|
|
113
|
+
error_message=None,
|
|
114
|
+
events=self._append_event_list(
|
|
115
|
+
list(getattr(job, "events", []) or []),
|
|
116
|
+
self._event_payload(
|
|
117
|
+
event_type="started",
|
|
118
|
+
status="running",
|
|
119
|
+
message="Job started",
|
|
120
|
+
),
|
|
121
|
+
),
|
|
122
|
+
)
|
|
123
|
+
if str(getattr(job, "job_type", "")) == "skill_import_git":
|
|
124
|
+
await self._run_skill_import(job_id)
|
|
125
|
+
return
|
|
126
|
+
raise ValueError(
|
|
127
|
+
f"Unsupported admin job type: {getattr(job, 'job_type', None)}"
|
|
128
|
+
)
|
|
129
|
+
except Exception as exc:
|
|
130
|
+
await self._fail_job(job_id, str(exc))
|
|
131
|
+
finally:
|
|
132
|
+
self._tasks.pop(job_id, None)
|
|
133
|
+
|
|
134
|
+
async def _run_skill_import(self, job_id: str) -> None:
|
|
135
|
+
job = await self._store.get_admin_job_by_id(uuid.UUID(job_id))
|
|
136
|
+
if job is None:
|
|
137
|
+
return
|
|
138
|
+
payload = dict(getattr(job, "payload", {}) or {})
|
|
139
|
+
tools = SkillTools(self._store, self._config)
|
|
140
|
+
|
|
141
|
+
async def emit_progress(update: dict[str, Any]) -> None:
|
|
142
|
+
current_job = await self._store.get_admin_job_by_id(uuid.UUID(job_id))
|
|
143
|
+
if current_job is None:
|
|
144
|
+
return
|
|
145
|
+
message = str(
|
|
146
|
+
update.get("message") or getattr(current_job, "message", "Running")
|
|
147
|
+
)
|
|
148
|
+
progress_current = int(
|
|
149
|
+
update.get(
|
|
150
|
+
"progress_current", getattr(current_job, "progress_current", 0)
|
|
151
|
+
)
|
|
152
|
+
or 0
|
|
153
|
+
)
|
|
154
|
+
progress_total = int(
|
|
155
|
+
update.get("progress_total", getattr(current_job, "progress_total", 0))
|
|
156
|
+
or 0
|
|
157
|
+
)
|
|
158
|
+
details = (
|
|
159
|
+
update.get("details")
|
|
160
|
+
if isinstance(update.get("details"), dict)
|
|
161
|
+
else None
|
|
162
|
+
)
|
|
163
|
+
next_events = self._append_event_list(
|
|
164
|
+
list(getattr(current_job, "events", []) or []),
|
|
165
|
+
self._event_payload(
|
|
166
|
+
event_type=str(update.get("event_type") or "progress"),
|
|
167
|
+
status="running",
|
|
168
|
+
message=message,
|
|
169
|
+
progress_current=progress_current,
|
|
170
|
+
progress_total=progress_total,
|
|
171
|
+
details=details,
|
|
172
|
+
),
|
|
173
|
+
)
|
|
174
|
+
await self._update_job(
|
|
175
|
+
job_id,
|
|
176
|
+
status="running",
|
|
177
|
+
message=message,
|
|
178
|
+
progress_current=progress_current,
|
|
179
|
+
progress_total=progress_total,
|
|
180
|
+
events=next_events,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
result = await tools.minder_skill_import_git(
|
|
184
|
+
repo_url=str(payload.get("repo_url") or ""),
|
|
185
|
+
source_path=str(payload.get("path") or "skills"),
|
|
186
|
+
ref=str(payload.get("ref")) if payload.get("ref") else None,
|
|
187
|
+
provider=str(payload.get("provider")) if payload.get("provider") else None,
|
|
188
|
+
excerpt_kind=str(payload.get("excerpt_kind") or "none"),
|
|
189
|
+
progress_callback=emit_progress,
|
|
190
|
+
)
|
|
191
|
+
completed_job = await self._store.get_admin_job_by_id(uuid.UUID(job_id))
|
|
192
|
+
completed_events = self._append_event_list(
|
|
193
|
+
list(getattr(completed_job, "events", []) or []),
|
|
194
|
+
self._event_payload(
|
|
195
|
+
event_type="completed",
|
|
196
|
+
status="completed",
|
|
197
|
+
message="Job completed",
|
|
198
|
+
progress_current=int(result.get("imported_count", 0) or 0),
|
|
199
|
+
progress_total=int(result.get("imported_count", 0) or 0),
|
|
200
|
+
details={"imported_count": result.get("imported_count", 0)},
|
|
201
|
+
),
|
|
202
|
+
)
|
|
203
|
+
await self._update_job(
|
|
204
|
+
job_id,
|
|
205
|
+
status="completed",
|
|
206
|
+
message="Skill import completed",
|
|
207
|
+
progress_current=int(result.get("imported_count", 0) or 0),
|
|
208
|
+
progress_total=int(result.get("imported_count", 0) or 0),
|
|
209
|
+
result_payload=result,
|
|
210
|
+
error_message=None,
|
|
211
|
+
finished_at=_utcnow(),
|
|
212
|
+
events=completed_events,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
async def _fail_job(self, job_id: str, error_message: str) -> None:
|
|
216
|
+
job = await self._store.get_admin_job_by_id(uuid.UUID(job_id))
|
|
217
|
+
existing_events = (
|
|
218
|
+
list(getattr(job, "events", []) or []) if job is not None else []
|
|
219
|
+
)
|
|
220
|
+
await self._update_job(
|
|
221
|
+
job_id,
|
|
222
|
+
status="failed",
|
|
223
|
+
message=error_message,
|
|
224
|
+
error_message=error_message,
|
|
225
|
+
finished_at=_utcnow(),
|
|
226
|
+
events=self._append_event_list(
|
|
227
|
+
existing_events,
|
|
228
|
+
self._event_payload(
|
|
229
|
+
event_type="failed",
|
|
230
|
+
status="failed",
|
|
231
|
+
message=error_message,
|
|
232
|
+
),
|
|
233
|
+
),
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
async def _update_job(self, job_id: str, **kwargs: Any) -> Any:
|
|
237
|
+
kwargs["updated_at"] = _utcnow()
|
|
238
|
+
updated = await self._store.update_admin_job(uuid.UUID(job_id), **kwargs)
|
|
239
|
+
if updated is not None:
|
|
240
|
+
await self._publish(self._snapshot(updated))
|
|
241
|
+
return updated
|
|
242
|
+
|
|
243
|
+
async def _publish(self, payload: dict[str, Any]) -> None:
|
|
244
|
+
async with self._lock:
|
|
245
|
+
subscribers = list(self._subscribers.values())
|
|
246
|
+
for queue, matcher in subscribers:
|
|
247
|
+
if matcher(payload):
|
|
248
|
+
with contextlib.suppress(asyncio.QueueFull):
|
|
249
|
+
queue.put_nowait(payload)
|
|
250
|
+
|
|
251
|
+
@classmethod
|
|
252
|
+
def _event_payload(
|
|
253
|
+
cls,
|
|
254
|
+
*,
|
|
255
|
+
event_type: str,
|
|
256
|
+
status: str,
|
|
257
|
+
message: str,
|
|
258
|
+
progress_current: int | None = None,
|
|
259
|
+
progress_total: int | None = None,
|
|
260
|
+
details: dict[str, Any] | None = None,
|
|
261
|
+
) -> dict[str, Any]:
|
|
262
|
+
return {
|
|
263
|
+
"id": str(uuid.uuid4()),
|
|
264
|
+
"event_type": event_type,
|
|
265
|
+
"status": status,
|
|
266
|
+
"message": message,
|
|
267
|
+
"progress_current": progress_current,
|
|
268
|
+
"progress_total": progress_total,
|
|
269
|
+
"details": details or {},
|
|
270
|
+
"created_at": _utcnow().isoformat(),
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
@classmethod
|
|
274
|
+
def _append_event_list(
|
|
275
|
+
cls,
|
|
276
|
+
existing: list[dict[str, Any]],
|
|
277
|
+
event: dict[str, Any],
|
|
278
|
+
) -> list[dict[str, Any]]:
|
|
279
|
+
combined = [*existing, event]
|
|
280
|
+
if len(combined) <= cls._MAX_EVENT_HISTORY:
|
|
281
|
+
return combined
|
|
282
|
+
return combined[-cls._MAX_EVENT_HISTORY :]
|
|
283
|
+
|
|
284
|
+
@staticmethod
|
|
285
|
+
def _snapshot(job: Any) -> dict[str, Any]:
|
|
286
|
+
created_at = getattr(job, "created_at", None)
|
|
287
|
+
updated_at = getattr(job, "updated_at", None)
|
|
288
|
+
started_at = getattr(job, "started_at", None)
|
|
289
|
+
finished_at = getattr(job, "finished_at", None)
|
|
290
|
+
return {
|
|
291
|
+
"id": str(job.id),
|
|
292
|
+
"job_type": str(getattr(job, "job_type", "")),
|
|
293
|
+
"title": str(getattr(job, "title", "")),
|
|
294
|
+
"status": str(getattr(job, "status", "queued")),
|
|
295
|
+
"requested_by_user_id": (
|
|
296
|
+
str(getattr(job, "requested_by_user_id", ""))
|
|
297
|
+
if getattr(job, "requested_by_user_id", None)
|
|
298
|
+
else None
|
|
299
|
+
),
|
|
300
|
+
"payload": dict(getattr(job, "payload", {}) or {}),
|
|
301
|
+
"result_payload": getattr(job, "result_payload", None),
|
|
302
|
+
"error_message": getattr(job, "error_message", None),
|
|
303
|
+
"progress_current": int(getattr(job, "progress_current", 0) or 0),
|
|
304
|
+
"progress_total": int(getattr(job, "progress_total", 0) or 0),
|
|
305
|
+
"message": getattr(job, "message", None),
|
|
306
|
+
"events": list(getattr(job, "events", []) or []),
|
|
307
|
+
"created_at": created_at.isoformat() if created_at else None,
|
|
308
|
+
"updated_at": updated_at.isoformat() if updated_at else None,
|
|
309
|
+
"started_at": started_at.isoformat() if started_at else None,
|
|
310
|
+
"finished_at": finished_at.isoformat() if finished_at else None,
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
async def iter_job_stream(
|
|
315
|
+
*,
|
|
316
|
+
queue: asyncio.Queue[dict[str, Any]],
|
|
317
|
+
request_is_disconnected: Callable[[], Awaitable[bool]],
|
|
318
|
+
) -> AsyncIterator[dict[str, Any]]:
|
|
319
|
+
while True:
|
|
320
|
+
if await request_is_disconnected():
|
|
321
|
+
return
|
|
322
|
+
try:
|
|
323
|
+
payload = await asyncio.wait_for(queue.get(), timeout=15)
|
|
324
|
+
except TimeoutError:
|
|
325
|
+
yield {"type": "keepalive"}
|
|
326
|
+
continue
|
|
327
|
+
yield {"type": "job", "payload": payload}
|