soliplex 0.75__tar.gz → 0.75.2__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.
Files changed (95) hide show
  1. {soliplex-0.75/src/soliplex.egg-info → soliplex-0.75.2}/PKG-INFO +3 -3
  2. {soliplex-0.75 → soliplex-0.75.2}/pyproject.toml +3 -3
  3. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/capabilities/filesystem.py +83 -30
  4. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/cli/audit.py +1 -2
  5. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/config/installation.py +19 -5
  6. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/examples.py +7 -2
  7. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/tools/agui_run_feedback.py +53 -67
  8. {soliplex-0.75 → soliplex-0.75.2/src/soliplex.egg-info}/PKG-INFO +3 -3
  9. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex.egg-info/requires.txt +2 -2
  10. {soliplex-0.75 → soliplex-0.75.2}/LICENSE +0 -0
  11. {soliplex-0.75 → soliplex-0.75.2}/MANIFEST.in +0 -0
  12. {soliplex-0.75 → soliplex-0.75.2}/README.md +0 -0
  13. {soliplex-0.75 → soliplex-0.75.2}/example/skills/bare-bones/SKILL.md +0 -0
  14. {soliplex-0.75 → soliplex-0.75.2}/example/skills/stanzas/SKILL.md +0 -0
  15. {soliplex-0.75 → soliplex-0.75.2}/setup.cfg +0 -0
  16. {soliplex-0.75 → soliplex-0.75.2}/skills/soliplex-docs/SKILL.md +0 -0
  17. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/agents.py +0 -0
  18. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/agui/__init__.py +0 -0
  19. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/agui/parser.py +0 -0
  20. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/agui/persistence.py +0 -0
  21. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/agui/schema.py +0 -0
  22. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/agui/util.py +0 -0
  23. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/authn.py +0 -0
  24. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/authz/__init__.py +0 -0
  25. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/authz/persistence.py +0 -0
  26. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/authz/schema.py +0 -0
  27. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/capabilities/rag_audit.py +0 -0
  28. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/cli/admin_users.py +0 -0
  29. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/cli/ask.py +0 -0
  30. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/cli/cli_util.py +0 -0
  31. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/cli/main.py +0 -0
  32. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/cli/ollama.py +0 -0
  33. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/cli/room_authz.py +0 -0
  34. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/cli/serve.py +0 -0
  35. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/cli/types.py +0 -0
  36. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/completions.py +0 -0
  37. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/config/__init__.py +0 -0
  38. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/config/_utils.py +0 -0
  39. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/config/agents.py +0 -0
  40. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/config/agui.py +0 -0
  41. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/config/authsystem.py +0 -0
  42. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/config/completions.py +0 -0
  43. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/config/exceptions.py +0 -0
  44. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/config/logfire.py +0 -0
  45. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/config/meta.py +0 -0
  46. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/config/middleware.py +0 -0
  47. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/config/quizzes.py +0 -0
  48. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/config/rag.py +0 -0
  49. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/config/rooms.py +0 -0
  50. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/config/routing.py +0 -0
  51. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/config/secrets.py +0 -0
  52. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/config/skills.py +0 -0
  53. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/config/tools.py +0 -0
  54. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/installation.py +0 -0
  55. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/log_ingest.py +0 -0
  56. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/loggers.py +0 -0
  57. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/main.py +0 -0
  58. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/mcp_auth.py +0 -0
  59. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/mcp_client.py +0 -0
  60. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/mcp_server.py +0 -0
  61. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/models.py +0 -0
  62. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/ollama.py +0 -0
  63. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/quizzes.py +0 -0
  64. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/rag_audit.py +0 -0
  65. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/sandbox_audit.py +0 -0
  66. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/secrets.py +0 -0
  67. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/skills/bwrap_sandbox/SKILL.md +0 -0
  68. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/skills/bwrap_sandbox/__init__.py +0 -0
  69. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/titles.py +0 -0
  70. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/tools/__init__.py +0 -0
  71. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/tools/file_uploads.py +0 -0
  72. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/tools/rag.py +0 -0
  73. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/tui/cli.py +0 -0
  74. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/tui/main.py +0 -0
  75. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/tui/rest_api.py +0 -0
  76. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/tui/serve.py +0 -0
  77. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/util.py +0 -0
  78. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/views/__init__.py +0 -0
  79. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/views/agui.py +0 -0
  80. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/views/authn.py +0 -0
  81. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/views/authz.py +0 -0
  82. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/views/completions.py +0 -0
  83. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/views/installation.py +0 -0
  84. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/views/log_ingest.py +0 -0
  85. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/views/quizzes.py +0 -0
  86. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/views/room_file_uploads.py +0 -0
  87. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/views/rooms.py +0 -0
  88. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/views/sandbox_workdirs.py +0 -0
  89. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/views/stats.py +0 -0
  90. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/views/streaming.py +0 -0
  91. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex/views/thread_file_uploads.py +0 -0
  92. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex.egg-info/SOURCES.txt +0 -0
  93. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex.egg-info/dependency_links.txt +0 -0
  94. {soliplex-0.75 → soliplex-0.75.2}/src/soliplex.egg-info/entry_points.txt +0 -0
  95. {soliplex-0.75 → soliplex-0.75.2}/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.75
3
+ Version: 0.75.2
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
@@ -14,12 +14,12 @@ Requires-Dist: aiosqlite
14
14
  Requires-Dist: alembic<1.19,>=1.18.4
15
15
  Requires-Dist: authlib
16
16
  Requires-Dist: bubble-sandbox<0.11,>=0.10.0
17
- Requires-Dist: cryptography>=49.0.0
17
+ Requires-Dist: cryptography>=50.0.0
18
18
  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.73,>=0.72.1
22
+ Requires-Dist: haiku.rag-slim<0.74,>=0.73.0
23
23
  Requires-Dist: idna>=3.15
24
24
  Requires-Dist: itsdangerous
25
25
  Requires-Dist: joserfc<1.7,>=1.6.7
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "soliplex"
7
- version = "0.75"
7
+ version = "0.75.2"
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"
@@ -20,12 +20,12 @@ dependencies = [
20
20
  "alembic >= 1.18.4, < 1.19",
21
21
  "authlib",
22
22
  "bubble-sandbox >= 0.10.0, < 0.11",
23
- "cryptography >= 49.0.0", # GHSA-537c-gmf6-5ccf
23
+ "cryptography >= 50.0.0", # PYSEC-2026-3552
24
24
  "fastapi",
25
25
  "fastmcp >= 3.3.0, < 3.4",
26
26
  "fakeredis != 2.35.0", # brownbag
27
27
  "greenlet",
28
- "haiku.rag-slim >= 0.72.1, < 0.73",
28
+ "haiku.rag-slim >= 0.73.0, < 0.74",
29
29
  "idna >= 3.15",
30
30
  "itsdangerous",
31
31
  "joserfc >= 1.6.7, < 1.7", # CVE-2026-48990
@@ -8,8 +8,8 @@ import typing
8
8
  import anyio
9
9
  import pydantic
10
10
  import yaml
11
- from pydantic_ai import ModelRetry
12
11
  from pydantic_ai import capabilities as ai_capabilities
12
+ from pydantic_ai import exceptions as ai_exc
13
13
  from pydantic_ai import toolsets as ai_toolsets
14
14
 
15
15
  _SKILL_FILENAME = "SKILL.md"
@@ -28,6 +28,73 @@ class FilesystemCapabilityError(ValueError):
28
28
  super().__init__(message)
29
29
 
30
30
 
31
+ class No_YAML_Frontmatter(FilesystemCapabilityError):
32
+ def __init__(self, path: pathlib.Path):
33
+ super().__init__(
34
+ f"{_SKILL_FILENAME} must start with YAML frontmatter",
35
+ path,
36
+ )
37
+
38
+
39
+ class Invalid_YAML_Frontmatter(FilesystemCapabilityError):
40
+ def __init__(self, path: pathlib.Path):
41
+ super().__init__(
42
+ f"{_SKILL_FILENAME} has no closing frontmatter delimiter",
43
+ path,
44
+ )
45
+
46
+
47
+ class CapabilityNameMismatch(FilesystemCapabilityError):
48
+ def __init__(self, skill_path: pathlib.Path, cap_name: str):
49
+ self.cap_name = cap_name
50
+ super().__init__(
51
+ f"Capability name '{cap_name}' does not match directory "
52
+ f"name '{skill_path.name}'",
53
+ skill_path,
54
+ )
55
+
56
+
57
+ class SkillHasNoInstructions(FilesystemCapabilityError):
58
+ def __init__(self, skill_path: pathlib.Path):
59
+ super().__init__(
60
+ f"{_SKILL_FILENAME} contains no instructions",
61
+ skill_path,
62
+ )
63
+
64
+
65
+ class InvalidCapabilityName(ValueError):
66
+ def __init__(self, name):
67
+ self.name = name
68
+ super().__init__(
69
+ "name must contain lowercase letters, numbers, and single "
70
+ "hyphens only"
71
+ )
72
+
73
+
74
+ class MissingCapabilityFile(ai_exc.ModelRetry):
75
+ def __init__(self, dirname, filename):
76
+ self.dirname = dirname
77
+ self.filename = filename
78
+ super().__init__(f"'{filename}' is not under '{dirname}/'")
79
+
80
+
81
+ class NoSuchResource(ai_exc.ModelRetry):
82
+ def __init__(self, path):
83
+ self.path = path
84
+ super().__init__(f"No such resource: '{path}'")
85
+
86
+
87
+ class NoSuchScript(ai_exc.ModelRetry):
88
+ def __init__(self, path):
89
+ self.path = path
90
+ super().__init__(f"No such script: '{path}'")
91
+
92
+
93
+ class MalformedScriptArguments(ai_exc.ModelRetry):
94
+ def __init__(self, exc):
95
+ super().__init__(f"Malformed arguments: {exc}")
96
+
97
+
31
98
  class _CapabilityMetadata(pydantic.BaseModel):
32
99
  model_config = pydantic.ConfigDict(extra="ignore")
33
100
 
@@ -41,10 +108,8 @@ class _CapabilityMetadata(pydantic.BaseModel):
41
108
  @classmethod
42
109
  def validate_name(cls, value: str) -> str:
43
110
  if not _NAME_PATTERN.fullmatch(value):
44
- raise ValueError( # noqa: TRY003
45
- "name must contain lowercase letters, numbers, and single "
46
- "hyphens only"
47
- )
111
+ raise InvalidCapabilityName(value)
112
+
48
113
  return value
49
114
 
50
115
 
@@ -76,10 +141,10 @@ class FilesystemCapability(ai_capabilities.AbstractCapability[typing.Any]):
76
141
  def _confined(self, dirname: str, value: str) -> pathlib.Path:
77
142
  root = (self.path / dirname).resolve()
78
143
  target = (self.path / value).resolve()
144
+
79
145
  if not target.is_relative_to(root):
80
- raise ModelRetry( # noqa: TRY003
81
- f"'{value}' is not under '{dirname}/'"
82
- )
146
+ raise MissingCapabilityFile(dirname, value)
147
+
83
148
  return target
84
149
 
85
150
  async def read_resource(self, path: str) -> str:
@@ -89,8 +154,10 @@ class FilesystemCapability(ai_capabilities.AbstractCapability[typing.Any]):
89
154
  below ``resources/``, e.g. ``resources/jabberwocky.md``.
90
155
  """
91
156
  target = self._confined(_RESOURCES_DIRNAME, path)
157
+
92
158
  if not target.is_file():
93
- raise ModelRetry(f"No such resource: '{path}'") # noqa: TRY003
159
+ raise NoSuchResource(path)
160
+
94
161
  return target.read_text(encoding="utf-8")
95
162
 
96
163
  async def run_script(self, script: str, arguments: str = "") -> str:
@@ -101,14 +168,13 @@ class FilesystemCapability(ai_capabilities.AbstractCapability[typing.Any]):
101
168
  single shell-style string of command-line arguments.
102
169
  """
103
170
  target = self._confined(_SCRIPTS_DIRNAME, script)
171
+
104
172
  if not target.is_file():
105
- raise ModelRetry(f"No such script: '{script}'") # noqa: TRY003
173
+ raise NoSuchScript(script)
106
174
  try:
107
175
  argv = shlex.split(arguments)
108
176
  except ValueError as exc:
109
- raise ModelRetry( # noqa: TRY003
110
- f"Malformed arguments: {exc}"
111
- ) from exc
177
+ raise MalformedScriptArguments(exc) from exc
112
178
 
113
179
  try:
114
180
  with anyio.fail_after(_SCRIPT_TIMEOUT_SECONDS):
@@ -142,10 +208,7 @@ def _parse_skill_file(
142
208
 
143
209
  lines = text.splitlines()
144
210
  if not lines or lines[0].strip() != _FRONTMATTER_DELIMITER:
145
- raise FilesystemCapabilityError( # noqa: TRY003
146
- f"{_SKILL_FILENAME} must start with YAML frontmatter",
147
- skill_path,
148
- )
211
+ raise No_YAML_Frontmatter(skill_path)
149
212
 
150
213
  try:
151
214
  end = next(
@@ -154,10 +217,7 @@ def _parse_skill_file(
154
217
  if line.strip() == _FRONTMATTER_DELIMITER
155
218
  )
156
219
  except StopIteration as exc:
157
- raise FilesystemCapabilityError( # noqa: TRY003
158
- f"{_SKILL_FILENAME} has no closing frontmatter delimiter",
159
- skill_path,
160
- ) from exc
220
+ raise Invalid_YAML_Frontmatter(skill_path) from exc
161
221
 
162
222
  try:
163
223
  metadata = _CapabilityMetadata.model_validate(
@@ -167,18 +227,11 @@ def _parse_skill_file(
167
227
  raise FilesystemCapabilityError(str(exc), skill_path) from exc
168
228
 
169
229
  if metadata.name != skill_path.name:
170
- raise FilesystemCapabilityError( # noqa: TRY003
171
- f"Capability name '{metadata.name}' does not match directory "
172
- f"name '{skill_path.name}'",
173
- skill_path,
174
- )
230
+ raise CapabilityNameMismatch(skill_path, cap_name=metadata.name)
175
231
 
176
232
  instructions = "\n".join(lines[end + 1 :]).strip()
177
233
  if not instructions:
178
- raise FilesystemCapabilityError( # noqa: TRY003
179
- f"{_SKILL_FILENAME} contains no instructions",
180
- skill_path,
181
- )
234
+ raise SkillHasNoInstructions(skill_path)
182
235
 
183
236
  return FilesystemCapability(
184
237
  id=metadata.name,
@@ -241,8 +241,7 @@ def _missing_env_vars(the_installation: installation.Installation) -> dict:
241
241
  try:
242
242
  the_installation.resolve_environment()
243
243
  except config_installation.MissingEnvVars as exc:
244
- missing = exc.env_vars.split(",")
245
- return {"missing_env_vars": missing}
244
+ return {"missing_env_vars": exc.failed}
246
245
  return {}
247
246
 
248
247
 
@@ -58,13 +58,21 @@ class MissingEnvVar(ValueError):
58
58
 
59
59
 
60
60
  class MissingEnvVars(ExceptionGroup, ValueError):
61
+ failed = ()
62
+
61
63
  def __init__(self, env_vars, excs):
62
- self.env_vars = env_vars
63
64
  super().__init__(
64
65
  f"Environment variables cannot be resolved: {env_vars}",
65
66
  excs,
66
67
  )
67
68
 
69
+ @classmethod
70
+ def from_failed(cls, failed, excs):
71
+ msg = ",".join(failed)
72
+ inst = cls(msg, excs)
73
+ inst.failed = failed
74
+ return inst
75
+
68
76
 
69
77
  class UnknownEnvironmentVariable(KeyError):
70
78
  def __init__(self, env_var):
@@ -72,6 +80,12 @@ class UnknownEnvironmentVariable(KeyError):
72
80
  super().__init__(f"Unknown environment variable '{env_var}'")
73
81
 
74
82
 
83
+ class UnsupportedInstallationSkillKind(ValueError):
84
+ def __init__(self, kind):
85
+ self.kind = kind
86
+ super().__init__(f"Unsupported installation skill kind: {kind}")
87
+
88
+
75
89
  # ============================================================================
76
90
  # Installation configuration types
77
91
  # ============================================================================
@@ -211,9 +225,7 @@ def resolve_skill_configs(
211
225
 
212
226
  for entry in explicit:
213
227
  if entry["kind"] != config_skills.SkillKind.FILESYSTEM:
214
- raise ValueError( # noqa: TRY003
215
- f"Unsupported installation skill kind: {entry['kind']}"
216
- )
228
+ raise UnsupportedInstallationSkillKind(entry["kind"])
217
229
  skill_name = entry["skill_name"]
218
230
  selected[skill_name] = available[skill_name]
219
231
 
@@ -508,7 +520,9 @@ class InstallationConfig:
508
520
  failed.append(exc.env_var)
509
521
 
510
522
  if excs:
511
- raise MissingEnvVars(",".join(failed), excs) # noqa: TRY301
523
+ # 'BaseExceeptionGroup.__new__' *requires* a 'str' for the
524
+ # first agument
525
+ raise MissingEnvVars.from_failed(failed, excs)
512
526
 
513
527
  self.environment = {
514
528
  key: resolve_file_prefix(value, self._config_path)
@@ -122,6 +122,11 @@ class _FauxRunResult:
122
122
  return ai_usage.RunUsage()
123
123
 
124
124
 
125
+ class FailingOnRequest(ValueError):
126
+ def __init__(self):
127
+ super().__init__("failing on request")
128
+
129
+
125
130
  @dataclasses.dataclass
126
131
  class FauxAgent:
127
132
  agent_config: config_agents.FactoryAgentConfig
@@ -158,7 +163,7 @@ class FauxAgent:
158
163
  prompt = ups[-1].content
159
164
 
160
165
  if prompt == "fail":
161
- raise ValueError("failing on request") # noqa: TRY003
166
+ raise FailingOnRequest()
162
167
 
163
168
  return _FauxRunResult(output="I don't know!")
164
169
 
@@ -205,7 +210,7 @@ class FauxAgent:
205
210
  ),
206
211
  )
207
212
  if up.content == "fail":
208
- raise ValueError("failing on request") # noqa: TRY003
213
+ raise FailingOnRequest()
209
214
 
210
215
  await asyncio.sleep(random.uniform(0.5, 2.0))
211
216
 
@@ -21,10 +21,13 @@ FRS = agui.FeedbackReviewStatus
21
21
  STATE_NAMESPACE = "soliplex-agui-run-feedback"
22
22
 
23
23
 
24
- class UnknownFeedback(KeyError):
24
+ class UnknownFeedback(pydantic_ai.ModelRetry):
25
25
  def __init__(self, run_id: str):
26
26
  self.run_id = run_id
27
- super().__init__(f"Uknown feedback entry for run id {run_id}")
27
+ super().__init__(
28
+ f"Unknown feedback entry for run id {run_id}. "
29
+ "Call 'query_recent_feedback' and try again."
30
+ )
28
31
 
29
32
 
30
33
  class RunFeedbackEntry(pydantic.BaseModel):
@@ -56,15 +59,15 @@ class RunFeedbackEntry(pydantic.BaseModel):
56
59
  """
57
60
 
58
61
  user_name: str
59
- email: str | None
62
+ email: str | None = None
60
63
  room_id: str
61
64
  thread_id: str
62
65
  run_id: str
63
66
  created: datetime.datetime
64
67
  feedback: str
65
- reason: str | None
66
- status: agui.FeedbackReviewStatus | None
67
- note: str | None
68
+ reason: str | None = None
69
+ status: agui.FeedbackReviewStatus | None = None
70
+ note: str | None = None
68
71
 
69
72
 
70
73
  class RunFeedbackInfo(pydantic.BaseModel):
@@ -255,23 +258,20 @@ async def query_recent_feedback(
255
258
  been reviewed or resolved.
256
259
  """
257
260
  agui_state = ctx.deps.state
258
- our_state = agui_state.get(STATE_NAMESPACE)
259
-
260
- if our_state is None:
261
- before_state = {}
262
- our_state = RecentRunFeedback()
263
- else:
264
- our_state = RecentRunFeedback.model_validate(our_state)
265
- before_state = {STATE_NAMESPACE: our_state.model_dump(mode="json")}
261
+ our_state = RecentRunFeedback.model_validate(
262
+ agui_state.get(STATE_NAMESPACE) or {} # guard against actual 'None'
263
+ )
264
+ before_state = {STATE_NAMESPACE: our_state.model_dump(mode="json")}
266
265
 
267
266
  entries = await _do_query(ctx, query)
268
267
  our_state.query = query
269
268
  our_state.entries = entries
270
269
 
271
- after_state = {STATE_NAMESPACE: our_state.model_dump(mode="json")}
270
+ after_json = our_state.model_dump(mode="json")
271
+ after_state = {STATE_NAMESPACE: after_json}
272
272
  metadata = _response_metadata(before_state, after_state)
273
273
 
274
- agui_state[STATE_NAMESPACE] = our_state
274
+ agui_state[STATE_NAMESPACE] = after_json
275
275
 
276
276
  return pydantic_ai.ToolReturn(our_state.entries, metadata=metadata)
277
277
 
@@ -314,9 +314,8 @@ async def get_feedback_run_info(
314
314
  'run_id' is the UUID of the run.
315
315
  """
316
316
  agui_state = ctx.deps.state
317
-
318
317
  our_state = RecentRunFeedback.model_validate(
319
- agui_state.get(STATE_NAMESPACE, {}),
318
+ agui_state.get(STATE_NAMESPACE) or {} # guard against actual 'None'
320
319
  )
321
320
 
322
321
  to_query, _ = _find_feedback_by_run_id(our_state, run_id)
@@ -365,73 +364,46 @@ async def get_feedback_run_info(
365
364
  )
366
365
 
367
366
 
368
- async def _do_review_feedback(
369
- ctx: pydantic_ai.RunContext[agents.AgentDependencies],
370
- run_entry: RunFeedbackEntry,
371
- *,
372
- note: str | None,
373
- ):
374
- the_threads = ctx.deps.the_threads
375
- user = ctx.deps.user
376
- await the_threads.review_run_feedback(
377
- reviewer_user_name=user.preferred_username,
378
- reviewer_email=user.email,
379
- note=note,
380
- user_name=run_entry.user_name,
381
- room_id=run_entry.room_id,
382
- thread_id=run_entry.thread_id,
383
- run_id=run_entry.run_id,
384
- )
385
-
386
-
387
367
  async def review_recent_feedback(
388
368
  ctx: pydantic_ai.RunContext[agents.AgentDependencies],
389
369
  review: FeedbackReview,
390
370
  ) -> pydantic_ai.ToolReturn:
391
371
  """Add a user's review to a feedback entry for an AGUI run."""
392
372
  agui_state = ctx.deps.state
393
- to_review = None
394
-
395
373
  our_state = RecentRunFeedback.model_validate(
396
- agui_state.get(STATE_NAMESPACE, {}),
374
+ agui_state.get(STATE_NAMESPACE) or {} # guard against actual 'None'
397
375
  )
398
376
 
399
377
  to_review, _ = _find_feedback_by_run_id(
400
- our_state, review.run_id, ["opened"]
378
+ our_state, review.run_id, [FromWhichAttrs.OPENED]
401
379
  )
402
380
 
403
381
  before_state = {STATE_NAMESPACE: our_state.model_dump(mode="json")}
404
382
 
405
- await _do_review_feedback(ctx, to_review, note=review.note)
383
+ the_threads = ctx.deps.the_threads
384
+ user = ctx.deps.user
385
+ await the_threads.review_run_feedback(
386
+ reviewer_user_name=user.preferred_username,
387
+ reviewer_email=user.email,
388
+ note=review.note,
389
+ user_name=to_review.user_name,
390
+ room_id=to_review.room_id,
391
+ thread_id=to_review.thread_id,
392
+ run_id=to_review.run_id,
393
+ )
406
394
 
407
395
  to_review.status = FRS.REVIEWED
408
396
  to_review.note = review.note
409
397
  our_state.entries.opened.remove(to_review)
410
398
  our_state.entries.reviewed.insert(0, to_review)
411
399
 
412
- after_state = {STATE_NAMESPACE: our_state.model_dump(mode="json")}
400
+ after_json = our_state.model_dump(mode="json")
401
+ after_state = {STATE_NAMESPACE: after_json}
413
402
  metadata = _response_metadata(before_state, after_state)
414
403
 
415
- return pydantic_ai.ToolReturn(our_state.entries, metadata=metadata)
416
-
404
+ agui_state[STATE_NAMESPACE] = after_json
417
405
 
418
- async def _do_resolve_feedback(
419
- ctx: pydantic_ai.RunContext[agents.AgentDependencies],
420
- run_entry: RunFeedbackEntry,
421
- *,
422
- note: str | None,
423
- ):
424
- the_threads = ctx.deps.the_threads
425
- user = ctx.deps.user
426
- await the_threads.resolve_run_feedback(
427
- resolver_user_name=user.preferred_username,
428
- resolver_email=user.email,
429
- note=note,
430
- user_name=run_entry.user_name,
431
- room_id=run_entry.room_id,
432
- thread_id=run_entry.thread_id,
433
- run_id=run_entry.run_id,
434
- )
406
+ return pydantic_ai.ToolReturn(our_state.entries, metadata=metadata)
435
407
 
436
408
 
437
409
  async def resolve_recent_feedback(
@@ -440,25 +412,39 @@ async def resolve_recent_feedback(
440
412
  ) -> pydantic_ai.ToolReturn:
441
413
  """Add a user's resolution of a feedback entry for an AGUI run."""
442
414
  agui_state = ctx.deps.state
443
-
444
415
  our_state = RecentRunFeedback.model_validate(
445
- agui_state.get(STATE_NAMESPACE, {}),
416
+ agui_state.get(STATE_NAMESPACE) or {} # guard against actual 'None'
446
417
  )
447
418
 
448
419
  to_resolve, from_which = _find_feedback_by_run_id(
449
- our_state, resolution.run_id, ["opened", "reviewed"]
420
+ our_state,
421
+ resolution.run_id,
422
+ [FromWhichAttrs.OPENED, FromWhichAttrs.REVIEWED],
450
423
  )
451
424
 
452
425
  before_state = {STATE_NAMESPACE: our_state.model_dump(mode="json")}
453
426
 
454
- await _do_resolve_feedback(ctx, to_resolve, note=resolution.note)
427
+ the_threads = ctx.deps.the_threads
428
+ user = ctx.deps.user
429
+ await the_threads.resolve_run_feedback(
430
+ resolver_user_name=user.preferred_username,
431
+ resolver_email=user.email,
432
+ note=resolution.note,
433
+ user_name=to_resolve.user_name,
434
+ room_id=to_resolve.room_id,
435
+ thread_id=to_resolve.thread_id,
436
+ run_id=to_resolve.run_id,
437
+ )
455
438
 
456
439
  to_resolve.status = FRS.RESOLVED
457
440
  to_resolve.note = resolution.note
458
441
  from_which.remove(to_resolve)
459
442
  our_state.entries.resolved.insert(0, to_resolve)
460
443
 
461
- after_state = {STATE_NAMESPACE: our_state.model_dump(mode="json")}
444
+ after_json = our_state.model_dump(mode="json")
445
+ after_state = {STATE_NAMESPACE: after_json}
462
446
  metadata = _response_metadata(before_state, after_state)
463
447
 
448
+ agui_state[STATE_NAMESPACE] = after_json
449
+
464
450
  return pydantic_ai.ToolReturn(our_state.entries, metadata=metadata)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: soliplex
3
- Version: 0.75
3
+ Version: 0.75.2
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
@@ -14,12 +14,12 @@ Requires-Dist: aiosqlite
14
14
  Requires-Dist: alembic<1.19,>=1.18.4
15
15
  Requires-Dist: authlib
16
16
  Requires-Dist: bubble-sandbox<0.11,>=0.10.0
17
- Requires-Dist: cryptography>=49.0.0
17
+ Requires-Dist: cryptography>=50.0.0
18
18
  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.73,>=0.72.1
22
+ Requires-Dist: haiku.rag-slim<0.74,>=0.73.0
23
23
  Requires-Dist: idna>=3.15
24
24
  Requires-Dist: itsdangerous
25
25
  Requires-Dist: joserfc<1.7,>=1.6.7
@@ -4,12 +4,12 @@ aiosqlite
4
4
  alembic<1.19,>=1.18.4
5
5
  authlib
6
6
  bubble-sandbox<0.11,>=0.10.0
7
- cryptography>=49.0.0
7
+ cryptography>=50.0.0
8
8
  fastapi
9
9
  fastmcp<3.4,>=3.3.0
10
10
  fakeredis!=2.35.0
11
11
  greenlet
12
- haiku.rag-slim<0.73,>=0.72.1
12
+ haiku.rag-slim<0.74,>=0.73.0
13
13
  idna>=3.15
14
14
  itsdangerous
15
15
  joserfc<1.7,>=1.6.7
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes