soliplex 0.76__tar.gz → 0.77__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 (96) hide show
  1. {soliplex-0.76/src/soliplex.egg-info → soliplex-0.77}/PKG-INFO +3 -2
  2. {soliplex-0.76 → soliplex-0.77}/pyproject.toml +3 -2
  3. {soliplex-0.76 → soliplex-0.77}/src/soliplex/agents.py +24 -14
  4. {soliplex-0.76 → soliplex-0.77}/src/soliplex/config/agents.py +10 -20
  5. {soliplex-0.76 → soliplex-0.77}/src/soliplex/config/authsystem.py +23 -6
  6. {soliplex-0.76 → soliplex-0.77}/src/soliplex/config/completions.py +21 -0
  7. {soliplex-0.76 → soliplex-0.77}/src/soliplex/config/installation.py +3 -3
  8. soliplex-0.77/src/soliplex/config/meta.py +997 -0
  9. {soliplex-0.76 → soliplex-0.77}/src/soliplex/config/quizzes.py +2 -5
  10. {soliplex-0.76 → soliplex-0.77}/src/soliplex/config/secrets.py +56 -3
  11. {soliplex-0.76 → soliplex-0.77}/src/soliplex/config/skills.py +6 -84
  12. {soliplex-0.76 → soliplex-0.77}/src/soliplex/installation.py +6 -7
  13. {soliplex-0.76 → soliplex-0.77}/src/soliplex/models.py +0 -8
  14. {soliplex-0.76 → soliplex-0.77}/src/soliplex/secrets.py +24 -58
  15. {soliplex-0.76 → soliplex-0.77}/src/soliplex/skills/bwrap_sandbox/SKILL.md +62 -32
  16. {soliplex-0.76 → soliplex-0.77}/src/soliplex/skills/bwrap_sandbox/__init__.py +81 -58
  17. {soliplex-0.76 → soliplex-0.77}/src/soliplex/tools/agui_run_feedback.py +64 -43
  18. {soliplex-0.76 → soliplex-0.77}/src/soliplex/views/agui.py +1 -9
  19. {soliplex-0.76 → soliplex-0.77/src/soliplex.egg-info}/PKG-INFO +3 -2
  20. {soliplex-0.76 → soliplex-0.77}/src/soliplex.egg-info/requires.txt +2 -1
  21. soliplex-0.76/src/soliplex/config/meta.py +0 -687
  22. {soliplex-0.76 → soliplex-0.77}/LICENSE +0 -0
  23. {soliplex-0.76 → soliplex-0.77}/MANIFEST.in +0 -0
  24. {soliplex-0.76 → soliplex-0.77}/README.md +0 -0
  25. {soliplex-0.76 → soliplex-0.77}/example/skills/bare-bones/SKILL.md +0 -0
  26. {soliplex-0.76 → soliplex-0.77}/example/skills/stanzas/SKILL.md +0 -0
  27. {soliplex-0.76 → soliplex-0.77}/setup.cfg +0 -0
  28. {soliplex-0.76 → soliplex-0.77}/skills/soliplex-docs/SKILL.md +0 -0
  29. {soliplex-0.76 → soliplex-0.77}/src/soliplex/agui/__init__.py +0 -0
  30. {soliplex-0.76 → soliplex-0.77}/src/soliplex/agui/parser.py +0 -0
  31. {soliplex-0.76 → soliplex-0.77}/src/soliplex/agui/persistence.py +0 -0
  32. {soliplex-0.76 → soliplex-0.77}/src/soliplex/agui/schema.py +0 -0
  33. {soliplex-0.76 → soliplex-0.77}/src/soliplex/agui/util.py +0 -0
  34. {soliplex-0.76 → soliplex-0.77}/src/soliplex/authn.py +0 -0
  35. {soliplex-0.76 → soliplex-0.77}/src/soliplex/authz/__init__.py +0 -0
  36. {soliplex-0.76 → soliplex-0.77}/src/soliplex/authz/persistence.py +0 -0
  37. {soliplex-0.76 → soliplex-0.77}/src/soliplex/authz/schema.py +0 -0
  38. {soliplex-0.76 → soliplex-0.77}/src/soliplex/capabilities/filesystem.py +0 -0
  39. {soliplex-0.76 → soliplex-0.77}/src/soliplex/capabilities/rag_audit.py +0 -0
  40. {soliplex-0.76 → soliplex-0.77}/src/soliplex/cli/admin_users.py +0 -0
  41. {soliplex-0.76 → soliplex-0.77}/src/soliplex/cli/ask.py +0 -0
  42. {soliplex-0.76 → soliplex-0.77}/src/soliplex/cli/audit.py +0 -0
  43. {soliplex-0.76 → soliplex-0.77}/src/soliplex/cli/cli_util.py +0 -0
  44. {soliplex-0.76 → soliplex-0.77}/src/soliplex/cli/main.py +0 -0
  45. {soliplex-0.76 → soliplex-0.77}/src/soliplex/cli/ollama.py +0 -0
  46. {soliplex-0.76 → soliplex-0.77}/src/soliplex/cli/room_authz.py +0 -0
  47. {soliplex-0.76 → soliplex-0.77}/src/soliplex/cli/serve.py +0 -0
  48. {soliplex-0.76 → soliplex-0.77}/src/soliplex/cli/types.py +0 -0
  49. {soliplex-0.76 → soliplex-0.77}/src/soliplex/completions.py +0 -0
  50. {soliplex-0.76 → soliplex-0.77}/src/soliplex/config/__init__.py +0 -0
  51. {soliplex-0.76 → soliplex-0.77}/src/soliplex/config/_utils.py +0 -0
  52. {soliplex-0.76 → soliplex-0.77}/src/soliplex/config/agui.py +0 -0
  53. {soliplex-0.76 → soliplex-0.77}/src/soliplex/config/exceptions.py +0 -0
  54. {soliplex-0.76 → soliplex-0.77}/src/soliplex/config/logfire.py +0 -0
  55. {soliplex-0.76 → soliplex-0.77}/src/soliplex/config/middleware.py +0 -0
  56. {soliplex-0.76 → soliplex-0.77}/src/soliplex/config/rag.py +0 -0
  57. {soliplex-0.76 → soliplex-0.77}/src/soliplex/config/rooms.py +0 -0
  58. {soliplex-0.76 → soliplex-0.77}/src/soliplex/config/routing.py +0 -0
  59. {soliplex-0.76 → soliplex-0.77}/src/soliplex/config/tools.py +0 -0
  60. {soliplex-0.76 → soliplex-0.77}/src/soliplex/examples.py +0 -0
  61. {soliplex-0.76 → soliplex-0.77}/src/soliplex/log_ingest.py +0 -0
  62. {soliplex-0.76 → soliplex-0.77}/src/soliplex/loggers.py +0 -0
  63. {soliplex-0.76 → soliplex-0.77}/src/soliplex/main.py +0 -0
  64. {soliplex-0.76 → soliplex-0.77}/src/soliplex/mcp_auth.py +0 -0
  65. {soliplex-0.76 → soliplex-0.77}/src/soliplex/mcp_client.py +0 -0
  66. {soliplex-0.76 → soliplex-0.77}/src/soliplex/mcp_server.py +0 -0
  67. {soliplex-0.76 → soliplex-0.77}/src/soliplex/ollama.py +0 -0
  68. {soliplex-0.76 → soliplex-0.77}/src/soliplex/quizzes.py +0 -0
  69. {soliplex-0.76 → soliplex-0.77}/src/soliplex/rag_audit.py +0 -0
  70. {soliplex-0.76 → soliplex-0.77}/src/soliplex/sandbox_audit.py +0 -0
  71. {soliplex-0.76 → soliplex-0.77}/src/soliplex/titles.py +0 -0
  72. {soliplex-0.76 → soliplex-0.77}/src/soliplex/tools/__init__.py +0 -0
  73. {soliplex-0.76 → soliplex-0.77}/src/soliplex/tools/file_uploads.py +0 -0
  74. {soliplex-0.76 → soliplex-0.77}/src/soliplex/tools/rag.py +0 -0
  75. {soliplex-0.76 → soliplex-0.77}/src/soliplex/tui/cli.py +0 -0
  76. {soliplex-0.76 → soliplex-0.77}/src/soliplex/tui/main.py +0 -0
  77. {soliplex-0.76 → soliplex-0.77}/src/soliplex/tui/rest_api.py +0 -0
  78. {soliplex-0.76 → soliplex-0.77}/src/soliplex/tui/serve.py +0 -0
  79. {soliplex-0.76 → soliplex-0.77}/src/soliplex/util.py +0 -0
  80. {soliplex-0.76 → soliplex-0.77}/src/soliplex/views/__init__.py +0 -0
  81. {soliplex-0.76 → soliplex-0.77}/src/soliplex/views/authn.py +0 -0
  82. {soliplex-0.76 → soliplex-0.77}/src/soliplex/views/authz.py +0 -0
  83. {soliplex-0.76 → soliplex-0.77}/src/soliplex/views/completions.py +0 -0
  84. {soliplex-0.76 → soliplex-0.77}/src/soliplex/views/installation.py +0 -0
  85. {soliplex-0.76 → soliplex-0.77}/src/soliplex/views/log_ingest.py +0 -0
  86. {soliplex-0.76 → soliplex-0.77}/src/soliplex/views/quizzes.py +0 -0
  87. {soliplex-0.76 → soliplex-0.77}/src/soliplex/views/room_file_uploads.py +0 -0
  88. {soliplex-0.76 → soliplex-0.77}/src/soliplex/views/rooms.py +0 -0
  89. {soliplex-0.76 → soliplex-0.77}/src/soliplex/views/sandbox_workdirs.py +0 -0
  90. {soliplex-0.76 → soliplex-0.77}/src/soliplex/views/stats.py +0 -0
  91. {soliplex-0.76 → soliplex-0.77}/src/soliplex/views/streaming.py +0 -0
  92. {soliplex-0.76 → soliplex-0.77}/src/soliplex/views/thread_file_uploads.py +0 -0
  93. {soliplex-0.76 → soliplex-0.77}/src/soliplex.egg-info/SOURCES.txt +0 -0
  94. {soliplex-0.76 → soliplex-0.77}/src/soliplex.egg-info/dependency_links.txt +0 -0
  95. {soliplex-0.76 → soliplex-0.77}/src/soliplex.egg-info/entry_points.txt +0 -0
  96. {soliplex-0.76 → soliplex-0.77}/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.76
3
+ Version: 0.77
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.75,>=0.74.0
22
+ Requires-Dist: haiku.rag-slim<0.79,>=0.78.0
23
23
  Requires-Dist: idna>=3.15
24
24
  Requires-Dist: itsdangerous
25
25
  Requires-Dist: joserfc<1.7,>=1.6.7
@@ -36,6 +36,7 @@ Requires-Dist: pydantic-settings<2.15,>=2.14.2
36
36
  Requires-Dist: pyjwt<2.14,>=2.13.0
37
37
  Requires-Dist: pymdown-extensions>=11.0.0
38
38
  Requires-Dist: python-keycloak
39
+ Requires-Dist: skills-ref
39
40
  Requires-Dist: sqlmodel
40
41
  Requires-Dist: starlette>=1.3.1
41
42
  Requires-Dist: uvicorn[standard]
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "soliplex"
7
- version = "0.76"
7
+ version = "0.77"
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.74.0, < 0.75",
28
+ "haiku.rag-slim >= 0.78.0, < 0.79",
29
29
  "idna >= 3.15",
30
30
  "itsdangerous",
31
31
  "joserfc >= 1.6.7, < 1.7", # CVE-2026-48990
@@ -42,6 +42,7 @@ dependencies = [
42
42
  "pyjwt >= 2.13.0, < 2.14", # PYSEC-2026-{175,177,178,179}
43
43
  "pymdown-extensions >= 11.0.0", # CVE-2026-61632
44
44
  "python-keycloak",
45
+ "skills-ref",
45
46
  "sqlmodel",
46
47
  "starlette >= 1.3.1", # Multiple CVEs
47
48
  "uvicorn[standard]",
@@ -5,13 +5,12 @@ import typing
5
5
 
6
6
  import pydantic_ai
7
7
  from haiku.rag.capabilities import analysis as hr_caps_analysis
8
- from haiku.rag.capabilities import compaction as hr_caps_compaction
9
- from haiku.rag.capabilities import policy as hr_caps_policy
10
8
  from haiku.rag.capabilities import rag as hr_caps_rag
11
9
  from pydantic_ai import agent as ai_agent
12
10
  from pydantic_ai import capabilities as ai_capabilities
13
11
  from pydantic_ai import tools as ai_tools
14
12
  from pydantic_ai import toolsets as ai_toolsets
13
+ from sqlalchemy.ext import asyncio as sqla_asyncio
15
14
 
16
15
  from soliplex import agui
17
16
  from soliplex import mcp_client
@@ -27,13 +26,6 @@ HR_VisionCapabilities = (
27
26
  hr_caps_rag.RAGCapability | hr_caps_analysis.AnalysisCapability
28
27
  )
29
28
 
30
- # Capabilities which can always be eagerly loaded
31
- HookOnlyCapabilities = (
32
- hr_caps_compaction.EvidenceCompactionCapability
33
- | hr_caps_policy.CitationPolicyCapability
34
- | cap_rag_audit.RAGAccessAuditCapability
35
- )
36
-
37
29
 
38
30
  class CapabilityConfig(typing.Protocol):
39
31
  """Room-specific native capabilities and their audit targets."""
@@ -54,7 +46,7 @@ class AgentDependencies:
54
46
  """
55
47
 
56
48
  the_installation: typing.Any # installation.Installation
57
- the_threads: agui.ThreadStorage = None
49
+ thread_persistence_engine: sqla_asyncio.AsyncEngine = None
58
50
  state: agui.AGUI_State = dataclasses.field(default_factory=dict)
59
51
  room_id: str | None = None
60
52
  thread_id: str | None = None
@@ -94,6 +86,27 @@ def make_mcp_client_toolset(
94
86
  return factory(**toolset_config.tool_kwargs)
95
87
 
96
88
 
89
+ def _is_routing_capability(
90
+ capability: ai_capabilities.AbstractCapability,
91
+ ) -> bool:
92
+ """Whether the model has anything to load from this capability.
93
+
94
+ Tools or instructions are what there is to route between; a hook-only
95
+ capability offers neither.
96
+ """
97
+ if capability.get_instructions() or capability.get_native_tools():
98
+ return True
99
+
100
+ toolset = capability.get_toolset()
101
+
102
+ if toolset is None:
103
+ return False
104
+
105
+ # A capability registering no tools of its own still gets an empty
106
+ # 'FunctionToolset', so ask whether it is empty, not whether it exists.
107
+ return bool(getattr(toolset, "tools", True))
108
+
109
+
97
110
  def get_default_agent_from_configs(
98
111
  *,
99
112
  agent_config: config_agents.AgentConfig,
@@ -135,13 +148,10 @@ def get_default_agent_from_configs(
135
148
  #
136
149
  # Multiple stay deferred so the model routes between them via
137
150
  # 'load_capability'.
138
- #
139
- # Hook-only capabilities expose no tools to route
140
- # between, so they neither defer nor count.
141
151
  routing_capabilities = [
142
152
  capability
143
153
  for capability in capabilities
144
- if not isinstance(capability, HookOnlyCapabilities)
154
+ if _is_routing_capability(capability)
145
155
  ]
146
156
  defer_loading = len(routing_capabilities) > 1
147
157
  for capability in routing_capabilities:
@@ -4,8 +4,8 @@ import dataclasses
4
4
  import enum
5
5
  import functools
6
6
  import pathlib
7
+ import re
7
8
  import typing
8
- import warnings
9
9
  from collections import abc
10
10
 
11
11
  from pydantic_ai import capabilities as ai_capabilities
@@ -25,6 +25,9 @@ _no_repr_no_compare_none = _utils._no_repr_no_compare_none
25
25
  _default_dict_field = _utils._default_dict_field
26
26
  _default_list_field = _utils._default_list_field
27
27
 
28
+ HAS_V_NUMBER_SUFFIX = r".*/v\d+"
29
+ has_v_number_suffix = re.compile(HAS_V_NUMBER_SUFFIX)
30
+
28
31
 
29
32
  #
30
33
  # Copy the pydantic_ai capability registry as defaults, so that we
@@ -114,13 +117,6 @@ def _apply_agent_config_template(
114
117
  return config_dict
115
118
 
116
119
 
117
- ACC_BBB_NO_NAME_KEY_DEPRECATED = """\
118
- AgentCapabilityConfig.from_yaml: '{<name>: <kwargs>}' form is deprecated;
119
- use '{"name": <name>, "kwargs": <kwargs>}' instead. Support for the form
120
- will be removed after 'v0.76'.
121
- """
122
-
123
-
124
120
  @dataclasses.dataclass(kw_only=True)
125
121
  class AgentCapabilityConfig:
126
122
  name: str
@@ -138,17 +134,8 @@ class AgentCapabilityConfig:
138
134
  name = config_dict_or_str
139
135
  config_dict = {"name": name}
140
136
  else:
141
- if "name" not in config_dict_or_str: # BBB deprecated
142
- warnings.warn(
143
- ACC_BBB_NO_NAME_KEY_DEPRECATED,
144
- DeprecationWarning,
145
- stacklevel=2,
146
- )
147
- ((name, kwargs),) = config_dict_or_str.items()
148
- config_dict = {"name": name, "kwargs": kwargs}
149
- else:
150
- name = config_dict_or_str["name"]
151
- config_dict = config_dict_or_str
137
+ name = config_dict_or_str["name"]
138
+ config_dict = config_dict_or_str
152
139
 
153
140
  if name not in AGENT_CAPABILITY_CLASSES_BY_NAME:
154
141
  raise UnknownCapability(name, config_path)
@@ -305,7 +292,10 @@ class AgentConfig:
305
292
  base_url = self.llm_provider_base_url
306
293
 
307
294
  if base_url is not None:
308
- provider_kw["base_url"] = f"{base_url}/v1"
295
+ if not has_v_number_suffix.fullmatch(base_url):
296
+ base_url = f"{base_url}/v1"
297
+
298
+ provider_kw["base_url"] = base_url
309
299
 
310
300
  if self.provider_key is not None:
311
301
  provider_kw["api_key"] = self._installation_config.get_secret(
@@ -9,7 +9,6 @@ from . import _utils
9
9
  from . import exceptions as config_exc
10
10
 
11
11
  _no_repr_no_compare_none = _utils._no_repr_no_compare_none
12
- _default_list_field = _utils._default_list_field
13
12
 
14
13
 
15
14
  # ============================================================================
@@ -62,6 +61,29 @@ class OIDCAuthSystemConfig:
62
61
  config_dict,
63
62
  ) from exc
64
63
 
64
+ @property
65
+ def as_yaml(self) -> dict:
66
+ result = {
67
+ "id": self.id,
68
+ "title": self.title,
69
+ "server_url": self.server_url,
70
+ "token_validation_pem": self.token_validation_pem,
71
+ "client_id": self.client_id,
72
+ }
73
+
74
+ if self.scope is not None:
75
+ result["scope"] = self.scope
76
+
77
+ if self.client_secret:
78
+ # Dumped raw: 'env:'/'secret:' interpolations are not resolved.
79
+ result["client_secret"] = self.client_secret
80
+
81
+ if self.oidc_client_pem_path is not None:
82
+ # Absolutized against the config dir on load. See #1228.
83
+ result["oidc_client_pem_path"] = str(self.oidc_client_pem_path)
84
+
85
+ return result
86
+
65
87
  @property
66
88
  def server_metadata_url(self):
67
89
  return f"{self.server_url}/{WELL_KNOWN_OPENID_CONFIGURATION}"
@@ -94,8 +116,3 @@ class OIDCAuthSystemConfig:
94
116
  # added by the auth setup
95
117
  # "authorize_state": main.SESSION_SECRET_KEY,
96
118
  }
97
-
98
-
99
- @dataclasses.dataclass(kw_only=True)
100
- class AvailableOIDCAuthSystemConfigs:
101
- systems: list[OIDCAuthSystemConfig] = _default_list_field()
@@ -81,5 +81,26 @@ class CompletionConfig:
81
81
 
82
82
  return cls(**config_dict)
83
83
 
84
+ @property
85
+ def as_yaml(self) -> dict:
86
+ result = {
87
+ "id": self.id,
88
+ "agent": self.agent_config.as_yaml,
89
+ }
90
+
91
+ if self.name is not None:
92
+ result["name"] = self.name
93
+
94
+ if self.tool_configs:
95
+ result["tools"] = [tc.as_yaml for tc in self.tool_configs.values()]
96
+
97
+ if self.mcp_client_toolset_configs:
98
+ result["mcp_client_toolsets"] = {
99
+ key: mctc.as_yaml
100
+ for key, mctc in self.mcp_client_toolset_configs.items()
101
+ }
102
+
103
+ return result
104
+
84
105
 
85
106
  CompletionConfigMap = dict[str, CompletionConfig]
@@ -14,6 +14,7 @@ import dotenv
14
14
  import yaml
15
15
  from haiku.rag import config as hr_config
16
16
 
17
+ from soliplex import secrets
17
18
  from soliplex.capabilities import filesystem as cap_fs
18
19
 
19
20
  from . import _utils
@@ -424,14 +425,13 @@ class InstallationConfig:
424
425
  return self._secrets_map
425
426
 
426
427
  def _resolve_secret(self, secret_name):
427
- from soliplex import secrets as secrets_module # avoid cycle
428
428
 
429
429
  try:
430
430
  secret_config = self.secrets_map[secret_name]
431
431
  except KeyError:
432
- raise secrets_module.UnknownSecret(secret_name) from None
432
+ raise secrets.UnknownSecret(secret_name) from None
433
433
 
434
- return secrets_module.get_secret(secret_config)
434
+ return config_secrets.get_secret(secret_config)
435
435
 
436
436
  def get_secret(self, secret_name) -> str:
437
437
  """Return the value for a given secret."""