soliplex 0.70.2__tar.gz → 0.71__tar.gz
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.
- {soliplex-0.70.2/src/soliplex.egg-info → soliplex-0.71}/PKG-INFO +2 -2
- {soliplex-0.70.2 → soliplex-0.71}/pyproject.toml +2 -3
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/agents.py +3 -3
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/agui/__init__.py +5 -1
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/agui/parser.py +4 -4
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/agui/persistence.py +17 -31
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/agui/schema.py +4 -4
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/authz/__init__.py +129 -21
- soliplex-0.71/src/soliplex/authz/persistence.py +491 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/authz/schema.py +47 -24
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/cli/admin_users.py +80 -117
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/cli/audit.py +48 -44
- soliplex-0.71/src/soliplex/cli/cli_util.py +243 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/cli/main.py +0 -2
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/cli/room_authz.py +185 -397
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/cli/serve.py +18 -3
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/config/exceptions.py +8 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/config/installation.py +65 -22
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/config/meta.py +3 -7
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/config/routing.py +3 -1
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/config/skills.py +18 -36
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/installation.py +22 -14
- soliplex-0.71/src/soliplex/loggers.py +640 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/main.py +15 -37
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/models.py +53 -18
- soliplex-0.71/src/soliplex/rag_audit.py +125 -0
- soliplex-0.71/src/soliplex/sandbox_audit.py +65 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/skills/bwrap_sandbox/__init__.py +106 -19
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/titles.py +9 -6
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/tools/agui_run_feedback.py +3 -3
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/tools/rag.py +18 -9
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/views/__init__.py +40 -3
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/views/agui.py +156 -101
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/views/authz.py +22 -21
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/views/installation.py +56 -9
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/views/quizzes.py +19 -16
- soliplex-0.71/src/soliplex/views/room_file_uploads.py +206 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/views/rooms.py +51 -32
- soliplex-0.71/src/soliplex/views/sandbox_workdirs.py +139 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/views/stats.py +12 -12
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/views/streaming.py +43 -23
- soliplex-0.71/src/soliplex/views/thread_file_uploads.py +202 -0
- {soliplex-0.70.2 → soliplex-0.71/src/soliplex.egg-info}/PKG-INFO +2 -2
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex.egg-info/SOURCES.txt +6 -3
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex.egg-info/requires.txt +1 -1
- soliplex-0.70.2/src/soliplex/auth.py +0 -10
- soliplex-0.70.2/src/soliplex/authz/persistence.py +0 -232
- soliplex-0.70.2/src/soliplex/cli/cli_util.py +0 -127
- soliplex-0.70.2/src/soliplex/loggers.py +0 -172
- soliplex-0.70.2/src/soliplex/views/file_uploads.py +0 -486
- {soliplex-0.70.2 → soliplex-0.71}/LICENSE +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/MANIFEST.in +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/README.md +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/example/skills/bare-bones/SKILL.md +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/setup.cfg +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/skills/soliplex-docs/SKILL.md +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/agui/util.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/authn.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/cli/ollama.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/cli/types.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/completions.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/config/__init__.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/config/_utils.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/config/agents.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/config/agui.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/config/authsystem.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/config/completions.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/config/logfire.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/config/quizzes.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/config/rag.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/config/rooms.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/config/secrets.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/config/tools.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/examples.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/log_ingest.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/mcp_auth.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/mcp_client.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/mcp_server.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/ollama.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/quizzes.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/secrets.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/skills/bwrap_sandbox/SKILL.md +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/tools/__init__.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/tools/file_uploads.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/tui/cli.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/tui/main.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/tui/rest_api.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/tui/serve.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/util.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/views/authn.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/views/completions.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex/views/log_ingest.py +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex.egg-info/dependency_links.txt +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex.egg-info/entry_points.txt +0 -0
- {soliplex-0.70.2 → soliplex-0.71}/src/soliplex.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: soliplex
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.71
|
|
4
4
|
Summary: An AI-powered Retrieval-Augmented Generation (RAG) system with a modern web interface.
|
|
5
5
|
Author-email: Enfold <info@enfoldsystems.net>
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -19,7 +19,7 @@ Requires-Dist: fastapi
|
|
|
19
19
|
Requires-Dist: fastmcp<3.4,>=3.3.0
|
|
20
20
|
Requires-Dist: fakeredis!=2.35.0
|
|
21
21
|
Requires-Dist: greenlet
|
|
22
|
-
Requires-Dist: haiku.rag-slim<0.
|
|
22
|
+
Requires-Dist: haiku.rag-slim<0.65,>=0.64
|
|
23
23
|
Requires-Dist: haiku-skills<0.19,>=0.18.0
|
|
24
24
|
Requires-Dist: idna>=3.15
|
|
25
25
|
Requires-Dist: itsdangerous
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "soliplex"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.71"
|
|
8
8
|
description = "An AI-powered Retrieval-Augmented Generation (RAG) system with a modern web interface."
|
|
9
9
|
authors = [{ name = "Enfold", email = "info@enfoldsystems.net" }]
|
|
10
10
|
readme = "README.md"
|
|
@@ -25,7 +25,7 @@ dependencies = [
|
|
|
25
25
|
"fastmcp >= 3.3.0, < 3.4",
|
|
26
26
|
"fakeredis != 2.35.0", # brownbag
|
|
27
27
|
"greenlet",
|
|
28
|
-
"haiku.rag-slim >= 0.
|
|
28
|
+
"haiku.rag-slim >= 0.64, < 0.65",
|
|
29
29
|
"haiku-skills >= 0.18.0, < 0.19",
|
|
30
30
|
"idna >= 3.15",
|
|
31
31
|
"itsdangerous",
|
|
@@ -92,7 +92,6 @@ markers = [
|
|
|
92
92
|
[tool.coverage.run]
|
|
93
93
|
omit = [
|
|
94
94
|
"src/soliplex/cli/__init__.py",
|
|
95
|
-
"src/soliplex/cli/serve.py",
|
|
96
95
|
|
|
97
96
|
"src/soliplex/examples.py",
|
|
98
97
|
"src/soliplex/tui.py",
|
|
@@ -11,7 +11,7 @@ from pydantic_ai import agent as ai_agent
|
|
|
11
11
|
from pydantic_ai import tools as ai_tools
|
|
12
12
|
from pydantic_ai import toolsets as ai_toolsets
|
|
13
13
|
|
|
14
|
-
from soliplex import agui
|
|
14
|
+
from soliplex import agui
|
|
15
15
|
from soliplex import mcp_client
|
|
16
16
|
from soliplex import models
|
|
17
17
|
from soliplex.config import agents as config_agents
|
|
@@ -41,8 +41,8 @@ class AgentDependencies:
|
|
|
41
41
|
"""
|
|
42
42
|
|
|
43
43
|
the_installation: typing.Any # installation.Installation
|
|
44
|
-
the_threads:
|
|
45
|
-
state:
|
|
44
|
+
the_threads: agui.ThreadStorage = None
|
|
45
|
+
state: agui.AGUI_State = dataclasses.field(default_factory=dict)
|
|
46
46
|
room_id: str | None = None
|
|
47
47
|
thread_id: str | None = None
|
|
48
48
|
run_id: str | None = None
|
|
@@ -587,7 +587,11 @@ async def get_the_threads(request: fastapi.Request) -> ThreadStorage:
|
|
|
587
587
|
|
|
588
588
|
engine = request.state.threads_engine
|
|
589
589
|
async with sqla_asyncio.AsyncSession(bind=engine) as session:
|
|
590
|
-
|
|
590
|
+
# One transaction per request: the storage methods no longer
|
|
591
|
+
# commit, so the request owns the unit of work -- committed on a
|
|
592
|
+
# clean response, rolled back if the handler raises.
|
|
593
|
+
async with session.begin():
|
|
594
|
+
yield persistence.ThreadStorage(session)
|
|
591
595
|
|
|
592
596
|
|
|
593
597
|
depend_the_threads = fastapi.Depends(get_the_threads)
|
|
@@ -10,7 +10,7 @@ from collections import abc
|
|
|
10
10
|
import jsonpatch
|
|
11
11
|
from ag_ui import core as agui_core
|
|
12
12
|
|
|
13
|
-
from soliplex import agui
|
|
13
|
+
from soliplex import agui
|
|
14
14
|
|
|
15
15
|
AGUI_EventIterator = abc.AsyncIterator[agui_core.Event]
|
|
16
16
|
AGUI_EventDictIterator = abc.AsyncIterator[agui_core.Event]
|
|
@@ -230,12 +230,12 @@ DEFAULT_STRIPPED_MESSAGE_TYPES: StrippedMessageTypes = None
|
|
|
230
230
|
@dataclasses.dataclass
|
|
231
231
|
class EventStreamParser:
|
|
232
232
|
run_agent_input: dataclasses.InitVar[agui_core.RunAgentInput] = None
|
|
233
|
-
run: dataclasses.InitVar[
|
|
233
|
+
run: dataclasses.InitVar[agui.Run] = None
|
|
234
234
|
|
|
235
235
|
_ = dataclasses.KW_ONLY
|
|
236
236
|
|
|
237
237
|
_run_input: agui_core.RunAgentInput = None
|
|
238
|
-
_the_run:
|
|
238
|
+
_the_run: agui.Run = None
|
|
239
239
|
|
|
240
240
|
run_status: RunStatus = RunStatus.INITIALIZED
|
|
241
241
|
active_steps: set[str] = dataclasses.field(default_factory=set)
|
|
@@ -282,7 +282,7 @@ class EventStreamParser:
|
|
|
282
282
|
self.messages_by_id = {msg.id: msg for msg in value.messages}
|
|
283
283
|
|
|
284
284
|
@property
|
|
285
|
-
def the_run(self) ->
|
|
285
|
+
def the_run(self) -> agui.Run:
|
|
286
286
|
return self._the_run
|
|
287
287
|
|
|
288
288
|
def _assert_running(self, event):
|
|
@@ -8,7 +8,7 @@ from ag_ui import core as agui_core
|
|
|
8
8
|
from sqlalchemy import sql as sqla_sql
|
|
9
9
|
from sqlalchemy.ext import asyncio as sqla_asyncio
|
|
10
10
|
|
|
11
|
-
from soliplex import agui
|
|
11
|
+
from soliplex import agui
|
|
12
12
|
from soliplex.agui import persistence as agui_persistence
|
|
13
13
|
from soliplex.agui import schema as agui_schema
|
|
14
14
|
from soliplex.agui import util as agui_util
|
|
@@ -16,7 +16,7 @@ from soliplex.agui import util as agui_util
|
|
|
16
16
|
# Temporary backward-compatibility: to be removed in 'v0.45'
|
|
17
17
|
from soliplex.agui.schema import * # noqa F403
|
|
18
18
|
|
|
19
|
-
FeedbackReviewStatus =
|
|
19
|
+
FeedbackReviewStatus = agui.FeedbackReviewStatus
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class NoFeedbackFound(ValueError):
|
|
@@ -48,15 +48,19 @@ _LAST_ACTIVITY = sqla_sql.func.max(
|
|
|
48
48
|
)
|
|
49
49
|
|
|
50
50
|
|
|
51
|
-
class ThreadStorage(
|
|
51
|
+
class ThreadStorage(agui.ThreadStorage):
|
|
52
52
|
def __init__(self, session: sqla_asyncio.AsyncSession):
|
|
53
53
|
self._session = session
|
|
54
54
|
|
|
55
55
|
@property
|
|
56
56
|
@contextlib.asynccontextmanager
|
|
57
57
|
async def session(self):
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
# Yield the caller-owned session as-is: the session owner (a
|
|
59
|
+
# FastAPI dependency, CLI context manager, or streaming helper)
|
|
60
|
+
# owns the transaction boundary and commits the unit of work
|
|
61
|
+
# exactly once. Methods here never open their own transaction or
|
|
62
|
+
# commit, so callers -- and tests -- need no interleaved commits.
|
|
63
|
+
yield self._session
|
|
60
64
|
|
|
61
65
|
async def _find_user_thread(
|
|
62
66
|
self,
|
|
@@ -74,12 +78,12 @@ class ThreadStorage(agui_package.ThreadStorage):
|
|
|
74
78
|
thread = (await session.scalars(query)).first()
|
|
75
79
|
|
|
76
80
|
if thread is None:
|
|
77
|
-
raise
|
|
81
|
+
raise agui.UnknownThread(user_name, thread_id)
|
|
78
82
|
|
|
79
83
|
t_room_id = await thread.awaitable_attrs.room_id
|
|
80
84
|
|
|
81
85
|
if t_room_id != room_id:
|
|
82
|
-
raise
|
|
86
|
+
raise agui.ThreadRoomMismatch(room_id, t_room_id)
|
|
83
87
|
|
|
84
88
|
return thread
|
|
85
89
|
|
|
@@ -91,7 +95,7 @@ class ThreadStorage(agui_package.ThreadStorage):
|
|
|
91
95
|
thread_id: str,
|
|
92
96
|
run_id: str,
|
|
93
97
|
session,
|
|
94
|
-
exc_type=
|
|
98
|
+
exc_type=agui.UnknownRun,
|
|
95
99
|
):
|
|
96
100
|
thread = await self._find_user_thread(
|
|
97
101
|
user_name=user_name,
|
|
@@ -294,7 +298,7 @@ class ThreadStorage(agui_package.ThreadStorage):
|
|
|
294
298
|
thread_id=thread_id,
|
|
295
299
|
run_id=parent_run_id,
|
|
296
300
|
session=session,
|
|
297
|
-
exc_type=
|
|
301
|
+
exc_type=agui.MissingParentRun,
|
|
298
302
|
)
|
|
299
303
|
else:
|
|
300
304
|
parent = None
|
|
@@ -362,7 +366,7 @@ class ThreadStorage(agui_package.ThreadStorage):
|
|
|
362
366
|
already = await run.awaitable_attrs.run_agent_input
|
|
363
367
|
|
|
364
368
|
if already is not None:
|
|
365
|
-
raise
|
|
369
|
+
raise agui.RunAlreadyStarted(
|
|
366
370
|
user_name,
|
|
367
371
|
thread_id,
|
|
368
372
|
run_id,
|
|
@@ -420,8 +424,6 @@ class ThreadStorage(agui_package.ThreadStorage):
|
|
|
420
424
|
event: agui_core.Event,
|
|
421
425
|
) -> None:
|
|
422
426
|
"""Save a single event for a run (incremental persistence)"""
|
|
423
|
-
await self._session.commit()
|
|
424
|
-
|
|
425
427
|
async with self.session as session:
|
|
426
428
|
run = await self._find_thread_run(
|
|
427
429
|
user_name=user_name,
|
|
@@ -448,8 +450,6 @@ class ThreadStorage(agui_package.ThreadStorage):
|
|
|
448
450
|
run_id: str,
|
|
449
451
|
) -> None:
|
|
450
452
|
"""Mark a run as finished"""
|
|
451
|
-
await self._session.commit()
|
|
452
|
-
|
|
453
453
|
async with self.session as session:
|
|
454
454
|
run = await self._find_thread_run(
|
|
455
455
|
user_name=user_name,
|
|
@@ -475,8 +475,6 @@ class ThreadStorage(agui_package.ThreadStorage):
|
|
|
475
475
|
|
|
476
476
|
Each element is a (event_index, event) tuple.
|
|
477
477
|
"""
|
|
478
|
-
await self._session.commit()
|
|
479
|
-
|
|
480
478
|
async with self.session as session:
|
|
481
479
|
run = await self._find_thread_run(
|
|
482
480
|
user_name=user_name,
|
|
@@ -504,8 +502,6 @@ class ThreadStorage(agui_package.ThreadStorage):
|
|
|
504
502
|
run_id: str,
|
|
505
503
|
) -> bool:
|
|
506
504
|
"""Return True if the run has a 'finished' timestamp"""
|
|
507
|
-
await self._session.commit()
|
|
508
|
-
|
|
509
505
|
async with self.session as session:
|
|
510
506
|
run = await self._find_thread_run(
|
|
511
507
|
user_name=user_name,
|
|
@@ -524,11 +520,9 @@ class ThreadStorage(agui_package.ThreadStorage):
|
|
|
524
520
|
room_id: str,
|
|
525
521
|
thread_id: str,
|
|
526
522
|
run_id: str,
|
|
527
|
-
events:
|
|
528
|
-
) ->
|
|
523
|
+
events: agui.AGUI_Events,
|
|
524
|
+
) -> agui.AGUI_Events:
|
|
529
525
|
"""Save the events for a gven run"""
|
|
530
|
-
await self._session.commit()
|
|
531
|
-
|
|
532
526
|
async with self.session as session:
|
|
533
527
|
run = await self._find_thread_run(
|
|
534
528
|
user_name=user_name,
|
|
@@ -560,8 +554,6 @@ class ThreadStorage(agui_package.ThreadStorage):
|
|
|
560
554
|
tool_calls: int,
|
|
561
555
|
):
|
|
562
556
|
"""Save the run usage statistics"""
|
|
563
|
-
await self._session.commit()
|
|
564
|
-
|
|
565
557
|
async with self.session as session:
|
|
566
558
|
run = await self._find_thread_run(
|
|
567
559
|
user_name=user_name,
|
|
@@ -591,8 +583,6 @@ class ThreadStorage(agui_package.ThreadStorage):
|
|
|
591
583
|
reason: str,
|
|
592
584
|
):
|
|
593
585
|
"""Save the run feedback"""
|
|
594
|
-
await self._session.commit()
|
|
595
|
-
|
|
596
586
|
async with self.session as session:
|
|
597
587
|
run = await self._find_thread_run(
|
|
598
588
|
user_name=user_name,
|
|
@@ -624,10 +614,8 @@ class ThreadStorage(agui_package.ThreadStorage):
|
|
|
624
614
|
room_id: str,
|
|
625
615
|
thread_id: str,
|
|
626
616
|
run_id: str,
|
|
627
|
-
) ->
|
|
617
|
+
) -> agui.RunFeedbackType | None:
|
|
628
618
|
"""Get the run feedback"""
|
|
629
|
-
await self._session.commit()
|
|
630
|
-
|
|
631
619
|
async with self.session as session:
|
|
632
620
|
run = await self._find_thread_run(
|
|
633
621
|
user_name=user_name,
|
|
@@ -658,8 +646,6 @@ class ThreadStorage(agui_package.ThreadStorage):
|
|
|
658
646
|
if limit is None and since is None:
|
|
659
647
|
limit = 20
|
|
660
648
|
|
|
661
|
-
await self._session.commit()
|
|
662
|
-
|
|
663
649
|
async with self.session as session:
|
|
664
650
|
query = (
|
|
665
651
|
sqla_sql.Select(
|
|
@@ -12,7 +12,7 @@ from sqlalchemy import schema as sqla_schema
|
|
|
12
12
|
from sqlalchemy.ext import asyncio as sqla_asyncio
|
|
13
13
|
from sqlalchemy.sql import sqltypes as sqla_sqltypes
|
|
14
14
|
|
|
15
|
-
from soliplex import agui
|
|
15
|
+
from soliplex import agui
|
|
16
16
|
from soliplex import util
|
|
17
17
|
from soliplex.agui import util as agui_util
|
|
18
18
|
from soliplex.config import installation as config_installation
|
|
@@ -42,7 +42,7 @@ JSON_Mapped_From = dict[str, typing.Any]
|
|
|
42
42
|
|
|
43
43
|
metadata = sqla_schema.MetaData(naming_convention=NAMING_CONVENTION)
|
|
44
44
|
|
|
45
|
-
FeedbackReviewStatus =
|
|
45
|
+
FeedbackReviewStatus = agui.FeedbackReviewStatus
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
class Base(AsyncAttrs, DeclarativeBase):
|
|
@@ -447,8 +447,8 @@ class RunUsage(Base):
|
|
|
447
447
|
requests: Mapped[int] = mapped_column()
|
|
448
448
|
tool_calls: Mapped[int] = mapped_column()
|
|
449
449
|
|
|
450
|
-
def as_tuple(self) ->
|
|
451
|
-
return
|
|
450
|
+
def as_tuple(self) -> agui.RunUsageStats:
|
|
451
|
+
return agui.RunUsageStats(
|
|
452
452
|
self.input_tokens,
|
|
453
453
|
self.output_tokens,
|
|
454
454
|
self.requests,
|
|
@@ -6,9 +6,7 @@ import json
|
|
|
6
6
|
import re
|
|
7
7
|
import typing
|
|
8
8
|
|
|
9
|
-
import fastapi
|
|
10
9
|
import jsonpath
|
|
11
|
-
from sqlalchemy.ext import asyncio as sqla_asyncio
|
|
12
10
|
|
|
13
11
|
# Avoid circular import when only used for typing
|
|
14
12
|
# from soliplex import models
|
|
@@ -85,6 +83,40 @@ class ExactlyOneDiscriminator(ValueError):
|
|
|
85
83
|
)
|
|
86
84
|
|
|
87
85
|
|
|
86
|
+
class NoSuchAdminUser(ValueError):
|
|
87
|
+
"""Raised when no admin user matches a given JSONPath discriminator."""
|
|
88
|
+
|
|
89
|
+
def __init__(self, json_path):
|
|
90
|
+
self.json_path = json_path
|
|
91
|
+
super().__init__(f"No admin user exists with json_path: {json_path}")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class AdminUserExists(ValueError):
|
|
95
|
+
"""Raised when an admin user already exists for a discriminator."""
|
|
96
|
+
|
|
97
|
+
def __init__(self, json_path):
|
|
98
|
+
self.json_path = json_path
|
|
99
|
+
super().__init__(
|
|
100
|
+
f"Admin user already exists with json_path: {json_path}"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class NoSuchRoomPolicy(ValueError):
|
|
105
|
+
"""Raised when no authorization policy exists for a room."""
|
|
106
|
+
|
|
107
|
+
def __init__(self, room_id):
|
|
108
|
+
self.room_id = room_id
|
|
109
|
+
super().__init__(f"No policy exists for room: {room_id}")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class NoSuchACLEntry(ValueError):
|
|
113
|
+
"""Raised when no stored ACL entry matches a room operation."""
|
|
114
|
+
|
|
115
|
+
def __init__(self, room_id):
|
|
116
|
+
self.room_id = room_id
|
|
117
|
+
super().__init__(f"No matching ACL entry in room: {room_id}")
|
|
118
|
+
|
|
119
|
+
|
|
88
120
|
def validate_json_path(value: str | None) -> str | None:
|
|
89
121
|
"""Validate a JSONPath query string.
|
|
90
122
|
|
|
@@ -144,24 +176,59 @@ class AllowDeny(enum.Enum):
|
|
|
144
176
|
DENY = "deny"
|
|
145
177
|
|
|
146
178
|
|
|
147
|
-
class
|
|
148
|
-
"""Protocol for checking / managing
|
|
179
|
+
class AdminUserPolicy(abc.ABC):
|
|
180
|
+
"""Protocol for checking / managing admin-user discriminators."""
|
|
149
181
|
|
|
150
182
|
@abc.abstractmethod
|
|
151
|
-
async def
|
|
152
|
-
"""List
|
|
183
|
+
async def list_admin_user_discriminators(self) -> list[str]:
|
|
184
|
+
"""List JSONPath discriminators which identify admin users.
|
|
185
|
+
|
|
186
|
+
Discriminators may be tied to individuals (e.g., via their
|
|
187
|
+
email addresses), or to claims which identify groups or
|
|
188
|
+
roles attached to the user.
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
@abc.abstractmethod
|
|
192
|
+
async def add_admin_user_discriminator(self, json_path: str):
|
|
193
|
+
"""Add a user discriminator to the admin users table.
|
|
194
|
+
|
|
195
|
+
Discriminators may be tied to individuals (e.g., via their
|
|
196
|
+
email addresses), or to claims which identify groups or
|
|
197
|
+
roles attached to the user.
|
|
198
|
+
"""
|
|
153
199
|
|
|
154
|
-
|
|
155
|
-
|
|
200
|
+
@abc.abstractmethod
|
|
201
|
+
async def remove_admin_user_discriminator(self, json_path: str):
|
|
202
|
+
"""Remove a user discriminator from the admin users table.
|
|
203
|
+
|
|
204
|
+
Discriminators may be tied to individuals (e.g., via their
|
|
205
|
+
email addresses), or to claims which identify groups or
|
|
206
|
+
roles attached to the user.
|
|
156
207
|
"""
|
|
157
208
|
|
|
209
|
+
@abc.abstractmethod
|
|
210
|
+
async def clear_admin_user_discriminators(self):
|
|
211
|
+
"""Remove all admin user discriminators from the admin users table."""
|
|
212
|
+
|
|
213
|
+
@abc.abstractmethod
|
|
214
|
+
async def list_admin_users(self) -> list[str]:
|
|
215
|
+
"""Deprecated alias for 'list_admin_user_discriminators'."""
|
|
216
|
+
|
|
158
217
|
@abc.abstractmethod
|
|
159
218
|
async def add_admin_user(self, email: str):
|
|
160
|
-
"""
|
|
219
|
+
"""Deprecated alias for 'add_admin_user_discriminator'.
|
|
220
|
+
|
|
221
|
+
Translates 'email' to the equivalent JSONPath expression,
|
|
222
|
+
'$[?$.email == "..."]', then delegates.
|
|
223
|
+
"""
|
|
161
224
|
|
|
162
225
|
@abc.abstractmethod
|
|
163
226
|
async def remove_admin_user(self, email: str):
|
|
164
|
-
"""
|
|
227
|
+
"""Deprecated alias for 'remove_admin_user_discriminator'.
|
|
228
|
+
|
|
229
|
+
Translates 'email' to the equivalent JSONPath expression,
|
|
230
|
+
'$[?$.email == "..."]', then delegates.
|
|
231
|
+
"""
|
|
165
232
|
|
|
166
233
|
@abc.abstractmethod
|
|
167
234
|
async def check_admin_access(self, user_token: UserToken) -> bool:
|
|
@@ -171,6 +238,10 @@ class AuthorizationPolicy(abc.ABC):
|
|
|
171
238
|
the user is an admin when any query matches.
|
|
172
239
|
"""
|
|
173
240
|
|
|
241
|
+
|
|
242
|
+
class RoomAuthorizationPolicy(abc.ABC):
|
|
243
|
+
"""Protocol for checking / managing room authorization policies."""
|
|
244
|
+
|
|
174
245
|
@abc.abstractmethod
|
|
175
246
|
async def check_room_access(
|
|
176
247
|
self,
|
|
@@ -191,7 +262,6 @@ class AuthorizationPolicy(abc.ABC):
|
|
|
191
262
|
async def get_room_policy(
|
|
192
263
|
self,
|
|
193
264
|
room_id: str,
|
|
194
|
-
user_token: UserToken,
|
|
195
265
|
) -> models.RoomPolicy | None: # noqa: F821
|
|
196
266
|
"""Return the authorization policy for the room"""
|
|
197
267
|
|
|
@@ -200,7 +270,6 @@ class AuthorizationPolicy(abc.ABC):
|
|
|
200
270
|
self,
|
|
201
271
|
room_id: str,
|
|
202
272
|
room_policy: models.RoomPolicy, # noqa: F821
|
|
203
|
-
user_token: UserToken,
|
|
204
273
|
) -> None:
|
|
205
274
|
"""Update the authorization policy for the room"""
|
|
206
275
|
|
|
@@ -208,19 +277,58 @@ class AuthorizationPolicy(abc.ABC):
|
|
|
208
277
|
async def delete_room_policy(
|
|
209
278
|
self,
|
|
210
279
|
room_id: str,
|
|
211
|
-
user_token: UserToken,
|
|
212
280
|
) -> None:
|
|
213
281
|
"""Delete the authorization policy for the room"""
|
|
214
282
|
|
|
283
|
+
@abc.abstractmethod
|
|
284
|
+
async def get_room_policy_unchecked(
|
|
285
|
+
self,
|
|
286
|
+
room_id: str,
|
|
287
|
+
) -> models.RoomPolicyUnchecked | None: # noqa: F821
|
|
288
|
+
"""Return the room policy, tolerating non-compiling 'json_path's."""
|
|
289
|
+
|
|
290
|
+
@abc.abstractmethod
|
|
291
|
+
async def list_room_policies(
|
|
292
|
+
self,
|
|
293
|
+
) -> list[models.RoomPolicyUnchecked]: # noqa: F821
|
|
294
|
+
"""List every stored room policy as an unchecked model.
|
|
295
|
+
|
|
296
|
+
Each policy is returned as the unchecked model -- the analogue
|
|
297
|
+
of 'get_room_policy_unchecked' over the whole table -- so a
|
|
298
|
+
stored ACL entry whose 'json_path' no longer compiles is
|
|
299
|
+
surfaced rather than raising, exactly what an audit needs. The
|
|
300
|
+
order is unspecified.
|
|
301
|
+
"""
|
|
302
|
+
|
|
303
|
+
@abc.abstractmethod
|
|
304
|
+
async def clear_room_acl(self, room_id: str) -> None:
|
|
305
|
+
"""Remove all ACL entries from the room's policy.
|
|
215
306
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
) -> AuthorizationPolicy:
|
|
219
|
-
from . import persistence
|
|
307
|
+
The policy row and its 'default_allow_deny' are preserved.
|
|
308
|
+
"""
|
|
220
309
|
|
|
221
|
-
|
|
222
|
-
async
|
|
223
|
-
|
|
310
|
+
@abc.abstractmethod
|
|
311
|
+
async def add_acl_entry(
|
|
312
|
+
self,
|
|
313
|
+
room_id: str,
|
|
314
|
+
entry: models.ACLEntry, # noqa: F821
|
|
315
|
+
) -> None:
|
|
316
|
+
"""Add an ACL entry to the room's policy, replacing any entry with
|
|
317
|
+
the same discriminator."""
|
|
224
318
|
|
|
319
|
+
@abc.abstractmethod
|
|
320
|
+
async def remove_acl_entry(
|
|
321
|
+
self,
|
|
322
|
+
room_id: str,
|
|
323
|
+
entry: models.ACLEntryUnchecked, # noqa: F821
|
|
324
|
+
) -> None:
|
|
325
|
+
"""Remove the matching ACL entry from the room's policy."""
|
|
225
326
|
|
|
226
|
-
|
|
327
|
+
@abc.abstractmethod
|
|
328
|
+
async def set_room_default(
|
|
329
|
+
self,
|
|
330
|
+
room_id: str,
|
|
331
|
+
allow_deny: AllowDeny,
|
|
332
|
+
) -> None:
|
|
333
|
+
"""Set the room policy's 'default_allow_deny', creating an empty
|
|
334
|
+
policy if none exists."""
|