dominus-sdk-python 5.1.0__tar.gz → 6.0.1__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.
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/PKG-INFO +4 -11
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/README.md +2 -8
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/__init__.py +5 -1
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/__init__.py +4 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/authority.py +33 -12
- dominus_sdk_python-6.0.1/dominus/namespaces/coder.py +221 -0
- dominus_sdk_python-6.0.1/dominus/namespaces/platform.py +130 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/recipes.py +24 -8
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/workflow.py +26 -35
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/start.py +4 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus_sdk_python.egg-info/PKG-INFO +4 -11
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus_sdk_python.egg-info/SOURCES.txt +3 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/pyproject.toml +3 -4
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/tests/test_authority_public_vocabulary.py +43 -3
- dominus_sdk_python-6.0.1/tests/test_platform_coder_namespaces.py +118 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/tests/test_public_exports.py +6 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/tests/test_recipes_namespace.py +36 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/tests/test_workflow_lifecycle.py +6 -6
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/tests/test_workflow_refs.py +13 -16
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/config/__init__.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/config/endpoints.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/errors.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/helpers/__init__.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/helpers/auth.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/helpers/cache.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/helpers/console_capture.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/helpers/core.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/helpers/crypto.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/helpers/sse.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/helpers/trace.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/admin.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/ai.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/artifacts.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/auth.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/browser.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/courier.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/db.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/ddl.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/deployer.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/fastapi.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/files.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/health.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/jobs.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/logs.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/portal.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/processor.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/redis.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/secrets.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/secure.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/stash.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/sync.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/warden.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/services/__init__.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus_sdk_python.egg-info/dependency_links.txt +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus_sdk_python.egg-info/requires.txt +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus_sdk_python.egg-info/top_level.txt +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/setup.cfg +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/tests/test_auth.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/tests/test_browser_namespace.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/tests/test_control_plane_namespaces.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/tests/test_errors.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/tests/test_flat_commands.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/tests/test_health.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/tests/test_logs.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/tests/test_provisioning_parity.py +0 -0
- {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/tests/test_transport_compat.py +0 -0
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dominus-sdk-python
|
|
3
|
-
Version:
|
|
3
|
+
Version: 6.0.1
|
|
4
4
|
Summary: Python SDK for the Dominus gateway-first platform
|
|
5
5
|
Author-email: CareBridge Systems <dev@carebridge.io>
|
|
6
|
-
License: Proprietary
|
|
6
|
+
License-Expression: LicenseRef-Proprietary
|
|
7
7
|
Project-URL: Homepage, https://github.com/carebridgesystems/dominus-sdk-python
|
|
8
8
|
Project-URL: Repository, https://github.com/carebridgesystems/dominus-sdk-python
|
|
9
9
|
Keywords: dominus,carebridge,sdk,gateway,api,async
|
|
10
10
|
Classifier: Development Status :: 4 - Beta
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
12
|
-
Classifier: License :: Other/Proprietary License
|
|
13
12
|
Classifier: Operating System :: OS Independent
|
|
14
13
|
Classifier: Programming Language :: Python :: 3
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.9
|
|
@@ -43,7 +42,7 @@ Async Python SDK for the Dominus gateway-first service plane.
|
|
|
43
42
|
- Gateway-scoped client mode for MCP and other user-JWT sessions
|
|
44
43
|
- Transport compatibility for wrapped `{success,data}` responses and unwrapped Warden/control-plane success objects
|
|
45
44
|
- Local helpers for JWT verification, trace propagation, retries, and console capture
|
|
46
|
-
- Current package version: `
|
|
45
|
+
- Current package version: `6.0.0`
|
|
47
46
|
|
|
48
47
|
## Install
|
|
49
48
|
|
|
@@ -66,12 +65,6 @@ users = await dominus.db.query("users", filters={"status": "active"})
|
|
|
66
65
|
await dominus.redis.set("session:123", {"user": "john"}, ttl=3600)
|
|
67
66
|
|
|
68
67
|
run = await dominus.workflow.ensure(
|
|
69
|
-
"wf://carebridge/report-cycle",
|
|
70
|
-
subject="PCM47474562",
|
|
71
|
-
company="summit-radiology",
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
recipe_run = await dominus.workflow.ensure(
|
|
75
68
|
workflow_recipe_ref="recipe://workflow-recipe-v1/report-cycle@v3",
|
|
76
69
|
subject="PCM47474562",
|
|
77
70
|
company="summit-radiology",
|
|
@@ -199,7 +192,7 @@ JWT and selected scope headers directly through Gateway.
|
|
|
199
192
|
| `health` | Gateway | Health and ping helpers |
|
|
200
193
|
| `admin` | Admin Worker | Admin category reseed/reset |
|
|
201
194
|
| `ai` | Agent Runtime | Agent, completion, RAG, artifacts, results, raw orchestration |
|
|
202
|
-
| `workflow` | Workflow Manager + Authority | Saved workflow CRUD and
|
|
195
|
+
| `workflow` | Workflow Manager + Authority | Saved workflow CRUD and recipe-backed run lifecycle |
|
|
203
196
|
| `artifacts` | Artifact Worker | Addressed V2 artifacts, bookmarks, watches |
|
|
204
197
|
| `jobs` | Job Worker | Enqueue, poll, dead-letter management |
|
|
205
198
|
| `processor` | Processor | Batch and single-job processing |
|
|
@@ -9,7 +9,7 @@ Async Python SDK for the Dominus gateway-first service plane.
|
|
|
9
9
|
- Gateway-scoped client mode for MCP and other user-JWT sessions
|
|
10
10
|
- Transport compatibility for wrapped `{success,data}` responses and unwrapped Warden/control-plane success objects
|
|
11
11
|
- Local helpers for JWT verification, trace propagation, retries, and console capture
|
|
12
|
-
- Current package version: `
|
|
12
|
+
- Current package version: `6.0.0`
|
|
13
13
|
|
|
14
14
|
## Install
|
|
15
15
|
|
|
@@ -32,12 +32,6 @@ users = await dominus.db.query("users", filters={"status": "active"})
|
|
|
32
32
|
await dominus.redis.set("session:123", {"user": "john"}, ttl=3600)
|
|
33
33
|
|
|
34
34
|
run = await dominus.workflow.ensure(
|
|
35
|
-
"wf://carebridge/report-cycle",
|
|
36
|
-
subject="PCM47474562",
|
|
37
|
-
company="summit-radiology",
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
recipe_run = await dominus.workflow.ensure(
|
|
41
35
|
workflow_recipe_ref="recipe://workflow-recipe-v1/report-cycle@v3",
|
|
42
36
|
subject="PCM47474562",
|
|
43
37
|
company="summit-radiology",
|
|
@@ -165,7 +159,7 @@ JWT and selected scope headers directly through Gateway.
|
|
|
165
159
|
| `health` | Gateway | Health and ping helpers |
|
|
166
160
|
| `admin` | Admin Worker | Admin category reseed/reset |
|
|
167
161
|
| `ai` | Agent Runtime | Agent, completion, RAG, artifacts, results, raw orchestration |
|
|
168
|
-
| `workflow` | Workflow Manager + Authority | Saved workflow CRUD and
|
|
162
|
+
| `workflow` | Workflow Manager + Authority | Saved workflow CRUD and recipe-backed run lifecycle |
|
|
169
163
|
| `artifacts` | Artifact Worker | Addressed V2 artifacts, bookmarks, watches |
|
|
170
164
|
| `jobs` | Job Worker | Enqueue, poll, dead-letter management |
|
|
171
165
|
| `processor` | Processor | Batch and single-job processing |
|
|
@@ -112,6 +112,8 @@ from .namespaces.browser import BrowserNamespace
|
|
|
112
112
|
from .namespaces.recipes import RecipesNamespace
|
|
113
113
|
from .namespaces.deployer import DeployerNamespace
|
|
114
114
|
from .namespaces.warden import WardenNamespace
|
|
115
|
+
from .namespaces.platform import PlatformNamespace
|
|
116
|
+
from .namespaces.coder import CoderNamespace
|
|
115
117
|
|
|
116
118
|
# Export AI namespace for agent-runtime operations
|
|
117
119
|
from .namespaces.ai import (
|
|
@@ -162,7 +164,7 @@ from .errors import (
|
|
|
162
164
|
TimeoutError as DominusTimeoutError,
|
|
163
165
|
)
|
|
164
166
|
|
|
165
|
-
__version__ = "
|
|
167
|
+
__version__ = "6.0.1"
|
|
166
168
|
__all__ = [
|
|
167
169
|
# Main SDK instance
|
|
168
170
|
"dominus",
|
|
@@ -213,6 +215,8 @@ __all__ = [
|
|
|
213
215
|
"RecipesNamespace",
|
|
214
216
|
"DeployerNamespace",
|
|
215
217
|
"WardenNamespace",
|
|
218
|
+
"PlatformNamespace",
|
|
219
|
+
"CoderNamespace",
|
|
216
220
|
# AI namespace for agent-runtime operations
|
|
217
221
|
"AiNamespace",
|
|
218
222
|
"RagSubNamespace",
|
|
@@ -19,6 +19,8 @@ from .browser import BrowserNamespace
|
|
|
19
19
|
from .recipes import RecipesNamespace
|
|
20
20
|
from .deployer import DeployerNamespace
|
|
21
21
|
from .warden import WardenNamespace
|
|
22
|
+
from .platform import PlatformNamespace
|
|
23
|
+
from .coder import CoderNamespace
|
|
22
24
|
from .ai import (
|
|
23
25
|
AiNamespace,
|
|
24
26
|
RagSubNamespace,
|
|
@@ -48,6 +50,8 @@ __all__ = [
|
|
|
48
50
|
"RecipesNamespace",
|
|
49
51
|
"DeployerNamespace",
|
|
50
52
|
"WardenNamespace",
|
|
53
|
+
"PlatformNamespace",
|
|
54
|
+
"CoderNamespace",
|
|
51
55
|
"AiNamespace",
|
|
52
56
|
"RagSubNamespace",
|
|
53
57
|
"ArtifactsSubNamespace",
|
|
@@ -12,9 +12,9 @@ All methods route through the gateway (``use_gateway=True``) which maps
|
|
|
12
12
|
|
|
13
13
|
Run launch is also exposed as ``dominus.workflow.ensure(...)``. Both call
|
|
14
14
|
``/api/authority/runs/ensure``. Use ``dominus.workflow.ensure`` for the
|
|
15
|
-
canonical
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
canonical recipe-backed launch path; use ``dominus.authority.ensure_run`` when
|
|
16
|
+
you want to think in Authority terms (run lifecycle, run dossiers, retries,
|
|
17
|
+
cancels).
|
|
18
18
|
"""
|
|
19
19
|
from __future__ import annotations
|
|
20
20
|
|
|
@@ -48,6 +48,14 @@ def _is_provisioning_bootstrap_run_kind(run_kind: Optional[str]) -> bool:
|
|
|
48
48
|
return _normalize_run_kind_token(run_kind) == "provisioning_bootstrap"
|
|
49
49
|
|
|
50
50
|
|
|
51
|
+
def _is_workflow_recipe_ref(value: Optional[str]) -> bool:
|
|
52
|
+
return str(value or "").strip().startswith("recipe://workflow-recipe-v1/")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _is_pipeline_recipe_ref(value: Optional[str]) -> bool:
|
|
56
|
+
return str(value or "").strip().startswith("recipe://pipeline-recipe-v1/")
|
|
57
|
+
|
|
58
|
+
|
|
51
59
|
def _query_string(params: Mapping[str, Any]) -> str:
|
|
52
60
|
cleaned = {k: v for k, v in params.items() if v is not None and v != ""}
|
|
53
61
|
if not cleaned:
|
|
@@ -61,7 +69,10 @@ class AuthorityNamespace:
|
|
|
61
69
|
|
|
62
70
|
Usage::
|
|
63
71
|
|
|
64
|
-
await dominus.authority.ensure_run(
|
|
72
|
+
await dominus.authority.ensure_run(
|
|
73
|
+
workflow_recipe_ref="recipe://workflow-recipe-v1/report-cycle@head",
|
|
74
|
+
subject="PCM47474562",
|
|
75
|
+
)
|
|
65
76
|
await dominus.authority.list_provisioning_targets()
|
|
66
77
|
await dominus.authority.get_run_dossier(run_id)
|
|
67
78
|
"""
|
|
@@ -167,8 +178,8 @@ class AuthorityNamespace:
|
|
|
167
178
|
async def ensure_run(
|
|
168
179
|
self,
|
|
169
180
|
*,
|
|
170
|
-
|
|
171
|
-
|
|
181
|
+
workflow_recipe_ref: Optional[str] = None,
|
|
182
|
+
pipeline_recipe_ref: Optional[str] = None,
|
|
172
183
|
run_kind: Optional[str] = None,
|
|
173
184
|
bootstrap_profile_ref: Optional[str] = None,
|
|
174
185
|
provisioning_target_slug: Optional[str] = None,
|
|
@@ -213,11 +224,23 @@ class AuthorityNamespace:
|
|
|
213
224
|
``bootstrap_provisioning_target`` (``execution_mode``, ``region``, ``bootstrap_profile_ref``, ...).
|
|
214
225
|
"""
|
|
215
226
|
bootstrap = _is_provisioning_bootstrap_run_kind(run_kind)
|
|
216
|
-
|
|
227
|
+
recipe_sources = [
|
|
228
|
+
value
|
|
229
|
+
for value in (
|
|
230
|
+
str(workflow_recipe_ref or "").strip(),
|
|
231
|
+
str(pipeline_recipe_ref or "").strip(),
|
|
232
|
+
)
|
|
233
|
+
if value
|
|
234
|
+
]
|
|
235
|
+
if not bootstrap and len(recipe_sources) != 1:
|
|
217
236
|
raise ValueError(
|
|
218
|
-
"ensure_run requires
|
|
237
|
+
"ensure_run requires exactly one of workflow_recipe_ref or pipeline_recipe_ref unless run_kind is "
|
|
219
238
|
"provisioning.bootstrap"
|
|
220
239
|
)
|
|
240
|
+
if workflow_recipe_ref and not _is_workflow_recipe_ref(workflow_recipe_ref):
|
|
241
|
+
raise ValueError("workflow_recipe_ref must start with recipe://workflow-recipe-v1/")
|
|
242
|
+
if pipeline_recipe_ref and not _is_pipeline_recipe_ref(pipeline_recipe_ref):
|
|
243
|
+
raise ValueError("pipeline_recipe_ref must start with recipe://pipeline-recipe-v1/")
|
|
221
244
|
if bootstrap:
|
|
222
245
|
target_slug = (provisioning_target_slug or "").strip()
|
|
223
246
|
if not target_slug:
|
|
@@ -236,8 +259,6 @@ class AuthorityNamespace:
|
|
|
236
259
|
"run_kind": run_kind,
|
|
237
260
|
"bootstrap_profile_ref": bootstrap_profile_ref,
|
|
238
261
|
"provisioning_target_slug": provisioning_target_slug,
|
|
239
|
-
"workflow_id": workflow_id,
|
|
240
|
-
"workflow_ref": workflow_ref,
|
|
241
262
|
"instance_id": instance_id,
|
|
242
263
|
"instance_key": instance_key,
|
|
243
264
|
"group": group,
|
|
@@ -262,8 +283,8 @@ class AuthorityNamespace:
|
|
|
262
283
|
})
|
|
263
284
|
else:
|
|
264
285
|
body = _compact({
|
|
265
|
-
"
|
|
266
|
-
"
|
|
286
|
+
"workflow_recipe_ref": workflow_recipe_ref,
|
|
287
|
+
"pipeline_recipe_ref": pipeline_recipe_ref,
|
|
267
288
|
"instance_id": instance_id,
|
|
268
289
|
"instance_key": instance_key,
|
|
269
290
|
"group": group,
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"""Coder namespace.
|
|
2
|
+
|
|
3
|
+
Lifecycle helpers for dominus-coder-runtime. This surface intentionally avoids
|
|
4
|
+
raw shell, filesystem browsing, and credential ingress helpers; Platform
|
|
5
|
+
decisions and Coder Runtime own executor policy and workspace operations.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any, Dict, Optional, TYPE_CHECKING
|
|
10
|
+
from urllib.parse import quote, urlencode
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from ..start import Dominus
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _compact(payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
17
|
+
out: Dict[str, Any] = {}
|
|
18
|
+
for key, value in payload.items():
|
|
19
|
+
if value is None:
|
|
20
|
+
continue
|
|
21
|
+
if isinstance(value, str) and value.strip() == "":
|
|
22
|
+
continue
|
|
23
|
+
out[key] = value
|
|
24
|
+
return out
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _query_string(params: Dict[str, Any]) -> str:
|
|
28
|
+
cleaned = _compact(params)
|
|
29
|
+
return f"?{urlencode(cleaned)}" if cleaned else ""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _normalize_coder_path(path: str) -> str:
|
|
33
|
+
trimmed = path.strip()
|
|
34
|
+
normalized = trimmed if trimmed.startswith("/") else f"/{trimmed}"
|
|
35
|
+
if normalized == "/svc/coder" or normalized.startswith("/svc/coder/"):
|
|
36
|
+
return normalized
|
|
37
|
+
if normalized == "/api/coder" or normalized.startswith("/api/coder/"):
|
|
38
|
+
return normalized.replace("/api/", "/svc/", 1)
|
|
39
|
+
return f"/svc/coder{normalized}"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _mutation_body(
|
|
43
|
+
*,
|
|
44
|
+
reason: Optional[str] = None,
|
|
45
|
+
idempotency_key: Optional[str] = None,
|
|
46
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
47
|
+
) -> Dict[str, Any]:
|
|
48
|
+
return _compact(
|
|
49
|
+
{
|
|
50
|
+
"reason": reason,
|
|
51
|
+
"idempotency_key": idempotency_key,
|
|
52
|
+
"metadata": metadata,
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class CoderNamespace:
|
|
58
|
+
"""Coder run lifecycle helpers."""
|
|
59
|
+
|
|
60
|
+
def __init__(self, client: "Dominus"):
|
|
61
|
+
self._client = client
|
|
62
|
+
|
|
63
|
+
async def request(
|
|
64
|
+
self,
|
|
65
|
+
path: str,
|
|
66
|
+
*,
|
|
67
|
+
method: str = "GET",
|
|
68
|
+
body: Optional[Dict[str, Any]] = None,
|
|
69
|
+
headers: Optional[Dict[str, str]] = None,
|
|
70
|
+
timeout: float = 30.0,
|
|
71
|
+
) -> Any:
|
|
72
|
+
return await self._client.gateway_fetch(
|
|
73
|
+
_normalize_coder_path(path),
|
|
74
|
+
method=method,
|
|
75
|
+
body=body,
|
|
76
|
+
headers=headers,
|
|
77
|
+
timeout=timeout,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
async def health(self) -> Dict[str, Any]:
|
|
81
|
+
return await self.request("/health", method="GET")
|
|
82
|
+
|
|
83
|
+
async def ready(self) -> Dict[str, Any]:
|
|
84
|
+
return await self.request("/ready", method="GET")
|
|
85
|
+
|
|
86
|
+
async def ensure_run(
|
|
87
|
+
self,
|
|
88
|
+
*,
|
|
89
|
+
policy_decision_id: str,
|
|
90
|
+
idempotency_key: Optional[str] = None,
|
|
91
|
+
mode: Optional[str] = None,
|
|
92
|
+
task_recipe_ref: Optional[str] = None,
|
|
93
|
+
workflow_recipe_ref: Optional[str] = None,
|
|
94
|
+
pipeline_recipe_ref: Optional[str] = None,
|
|
95
|
+
group: Optional[str] = None,
|
|
96
|
+
repository: Optional[str] = None,
|
|
97
|
+
branch: Optional[str] = None,
|
|
98
|
+
base_branch: Optional[str] = None,
|
|
99
|
+
working_branch: Optional[str] = None,
|
|
100
|
+
title: Optional[str] = None,
|
|
101
|
+
instructions: Optional[str] = None,
|
|
102
|
+
inputs: Optional[Dict[str, Any]] = None,
|
|
103
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
104
|
+
) -> Dict[str, Any]:
|
|
105
|
+
if not policy_decision_id or policy_decision_id.strip() == "":
|
|
106
|
+
raise ValueError("ensure_run requires policy_decision_id")
|
|
107
|
+
|
|
108
|
+
body = _compact(
|
|
109
|
+
{
|
|
110
|
+
"policy_decision_id": policy_decision_id,
|
|
111
|
+
"idempotency_key": idempotency_key,
|
|
112
|
+
"mode": mode,
|
|
113
|
+
"task_recipe_ref": task_recipe_ref,
|
|
114
|
+
"workflow_recipe_ref": workflow_recipe_ref,
|
|
115
|
+
"pipeline_recipe_ref": pipeline_recipe_ref,
|
|
116
|
+
"group": group,
|
|
117
|
+
"repository": repository,
|
|
118
|
+
"branch": branch,
|
|
119
|
+
"base_branch": base_branch,
|
|
120
|
+
"working_branch": working_branch,
|
|
121
|
+
"title": title,
|
|
122
|
+
"instructions": instructions,
|
|
123
|
+
"inputs": inputs,
|
|
124
|
+
"metadata": metadata,
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
return await self.request(
|
|
128
|
+
"/runs/ensure",
|
|
129
|
+
method="POST",
|
|
130
|
+
body=body,
|
|
131
|
+
timeout=600.0,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
async def list_runs(
|
|
135
|
+
self,
|
|
136
|
+
*,
|
|
137
|
+
status: Optional[str] = None,
|
|
138
|
+
group: Optional[str] = None,
|
|
139
|
+
repository: Optional[str] = None,
|
|
140
|
+
limit: Optional[int] = None,
|
|
141
|
+
offset: Optional[int] = None,
|
|
142
|
+
) -> Dict[str, Any]:
|
|
143
|
+
qs = _query_string(
|
|
144
|
+
{
|
|
145
|
+
"status": status,
|
|
146
|
+
"group": group,
|
|
147
|
+
"repository": repository,
|
|
148
|
+
"limit": limit,
|
|
149
|
+
"offset": offset,
|
|
150
|
+
}
|
|
151
|
+
)
|
|
152
|
+
return await self.request(f"/runs{qs}", method="GET")
|
|
153
|
+
|
|
154
|
+
async def get_run(self, run_id: str) -> Dict[str, Any]:
|
|
155
|
+
return await self.request(f"/runs/{quote(run_id, safe='')}", method="GET")
|
|
156
|
+
|
|
157
|
+
async def cancel_run(
|
|
158
|
+
self,
|
|
159
|
+
run_id: str,
|
|
160
|
+
*,
|
|
161
|
+
reason: Optional[str] = None,
|
|
162
|
+
idempotency_key: Optional[str] = None,
|
|
163
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
164
|
+
) -> Dict[str, Any]:
|
|
165
|
+
return await self.request(
|
|
166
|
+
f"/runs/{quote(run_id, safe='')}/cancel",
|
|
167
|
+
method="POST",
|
|
168
|
+
body=_mutation_body(
|
|
169
|
+
reason=reason,
|
|
170
|
+
idempotency_key=idempotency_key,
|
|
171
|
+
metadata=metadata,
|
|
172
|
+
),
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
async def retry_run(
|
|
176
|
+
self,
|
|
177
|
+
run_id: str,
|
|
178
|
+
*,
|
|
179
|
+
reason: Optional[str] = None,
|
|
180
|
+
idempotency_key: Optional[str] = None,
|
|
181
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
182
|
+
) -> Dict[str, Any]:
|
|
183
|
+
return await self.request(
|
|
184
|
+
f"/runs/{quote(run_id, safe='')}/retry",
|
|
185
|
+
method="POST",
|
|
186
|
+
body=_mutation_body(
|
|
187
|
+
reason=reason,
|
|
188
|
+
idempotency_key=idempotency_key,
|
|
189
|
+
metadata=metadata,
|
|
190
|
+
),
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
async def nudge_run(
|
|
194
|
+
self,
|
|
195
|
+
run_id: str,
|
|
196
|
+
*,
|
|
197
|
+
reason: Optional[str] = None,
|
|
198
|
+
idempotency_key: Optional[str] = None,
|
|
199
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
200
|
+
) -> Dict[str, Any]:
|
|
201
|
+
return await self.request(
|
|
202
|
+
f"/runs/{quote(run_id, safe='')}/nudge",
|
|
203
|
+
method="POST",
|
|
204
|
+
body=_mutation_body(
|
|
205
|
+
reason=reason,
|
|
206
|
+
idempotency_key=idempotency_key,
|
|
207
|
+
metadata=metadata,
|
|
208
|
+
),
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
async def get_run_summary(self, run_id: str) -> Dict[str, Any]:
|
|
212
|
+
return await self.request(
|
|
213
|
+
f"/runs/{quote(run_id, safe='')}/summary",
|
|
214
|
+
method="GET",
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
async def get_run_artifacts(self, run_id: str) -> Dict[str, Any]:
|
|
218
|
+
return await self.request(
|
|
219
|
+
f"/runs/{quote(run_id, safe='')}/artifacts",
|
|
220
|
+
method="GET",
|
|
221
|
+
)
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""Platform namespace.
|
|
2
|
+
|
|
3
|
+
Thin JSON-first helpers for the dominus-platform-worker Gateway surface.
|
|
4
|
+
Platform owns group/repository policy decisions; it does not execute coder work
|
|
5
|
+
or expose credentials.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any, Dict, Optional, TYPE_CHECKING
|
|
10
|
+
from urllib.parse import quote
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from ..start import Dominus
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _normalize_platform_path(path: str) -> str:
|
|
17
|
+
trimmed = path.strip()
|
|
18
|
+
normalized = trimmed if trimmed.startswith("/") else f"/{trimmed}"
|
|
19
|
+
if normalized == "/svc/platform" or normalized.startswith("/svc/platform/"):
|
|
20
|
+
return normalized
|
|
21
|
+
if normalized == "/api/platform" or normalized.startswith("/api/platform/"):
|
|
22
|
+
return normalized.replace("/api/", "/svc/", 1)
|
|
23
|
+
return f"/svc/platform{normalized}"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class PlatformNamespace:
|
|
27
|
+
"""Platform group/repository policy helpers."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, client: "Dominus"):
|
|
30
|
+
self._client = client
|
|
31
|
+
|
|
32
|
+
async def request(
|
|
33
|
+
self,
|
|
34
|
+
path: str,
|
|
35
|
+
*,
|
|
36
|
+
method: str = "GET",
|
|
37
|
+
body: Optional[Dict[str, Any]] = None,
|
|
38
|
+
headers: Optional[Dict[str, str]] = None,
|
|
39
|
+
timeout: float = 30.0,
|
|
40
|
+
) -> Any:
|
|
41
|
+
return await self._client.gateway_fetch(
|
|
42
|
+
_normalize_platform_path(path),
|
|
43
|
+
method=method,
|
|
44
|
+
body=body,
|
|
45
|
+
headers=headers,
|
|
46
|
+
timeout=timeout,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
async def health(self) -> Dict[str, Any]:
|
|
50
|
+
return await self.request("/health", method="GET")
|
|
51
|
+
|
|
52
|
+
async def ready(self) -> Dict[str, Any]:
|
|
53
|
+
return await self.request("/ready", method="GET")
|
|
54
|
+
|
|
55
|
+
async def list_groups(self) -> Dict[str, Any]:
|
|
56
|
+
return await self.request("/groups", method="GET")
|
|
57
|
+
|
|
58
|
+
async def get_group(self, group_slug: str) -> Dict[str, Any]:
|
|
59
|
+
return await self.request(f"/groups/{quote(group_slug, safe='')}", method="GET")
|
|
60
|
+
|
|
61
|
+
async def upsert_group(self, group: Dict[str, Any]) -> Dict[str, Any]:
|
|
62
|
+
return await self.request("/groups", method="POST", body=group)
|
|
63
|
+
|
|
64
|
+
async def update_group(self, group_slug: str, group: Dict[str, Any]) -> Dict[str, Any]:
|
|
65
|
+
return await self.request(
|
|
66
|
+
f"/groups/{quote(group_slug, safe='')}",
|
|
67
|
+
method="PATCH",
|
|
68
|
+
body=group,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
async def add_group_member(self, group_slug: str, subject_id: str) -> Dict[str, Any]:
|
|
72
|
+
return await self.request(
|
|
73
|
+
f"/groups/{quote(group_slug, safe='')}/members",
|
|
74
|
+
method="POST",
|
|
75
|
+
body={"subject_id": subject_id},
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
async def link_group_repository(self, group_slug: str, repository: str) -> Dict[str, Any]:
|
|
79
|
+
return await self.request(
|
|
80
|
+
f"/groups/{quote(group_slug, safe='')}/repositories",
|
|
81
|
+
method="POST",
|
|
82
|
+
body={"repository": repository},
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
async def list_repositories(self) -> Dict[str, Any]:
|
|
86
|
+
return await self.request("/repositories", method="GET")
|
|
87
|
+
|
|
88
|
+
async def get_repository(self, repository_id: str) -> Dict[str, Any]:
|
|
89
|
+
return await self.request(
|
|
90
|
+
f"/repositories/{quote(repository_id, safe='')}",
|
|
91
|
+
method="GET",
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
async def upsert_repository(self, repository: Dict[str, Any]) -> Dict[str, Any]:
|
|
95
|
+
return await self.request("/repositories", method="POST", body=repository)
|
|
96
|
+
|
|
97
|
+
async def update_repository(
|
|
98
|
+
self,
|
|
99
|
+
repository_id: str,
|
|
100
|
+
repository: Dict[str, Any],
|
|
101
|
+
) -> Dict[str, Any]:
|
|
102
|
+
return await self.request(
|
|
103
|
+
f"/repositories/{quote(repository_id, safe='')}",
|
|
104
|
+
method="PATCH",
|
|
105
|
+
body=repository,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
async def ensure_policy_decision(self, options: Dict[str, Any]) -> Dict[str, Any]:
|
|
109
|
+
return await self.request(
|
|
110
|
+
"/policy/decisions/ensure",
|
|
111
|
+
method="POST",
|
|
112
|
+
body=options,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
async def get_policy_decision(self, decision_id: str) -> Dict[str, Any]:
|
|
116
|
+
return await self.request(
|
|
117
|
+
f"/policy/decisions/{quote(decision_id, safe='')}",
|
|
118
|
+
method="GET",
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
async def rehydrate_policy_decision(
|
|
122
|
+
self,
|
|
123
|
+
decision_id: str,
|
|
124
|
+
options: Optional[Dict[str, Any]] = None,
|
|
125
|
+
) -> Dict[str, Any]:
|
|
126
|
+
return await self.request(
|
|
127
|
+
f"/policy/decisions/{quote(decision_id, safe='')}/rehydrate",
|
|
128
|
+
method="POST",
|
|
129
|
+
body=options or {},
|
|
130
|
+
)
|
|
@@ -41,14 +41,24 @@ class RecipesNamespace:
|
|
|
41
41
|
def __init__(self, client: "Dominus"):
|
|
42
42
|
self._client = client
|
|
43
43
|
|
|
44
|
-
async def _post(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
44
|
+
async def _post(
|
|
45
|
+
self,
|
|
46
|
+
endpoint: str,
|
|
47
|
+
body: Optional[Dict[str, Any]] = None,
|
|
48
|
+
*,
|
|
49
|
+
timeout: float = 30.0,
|
|
50
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
51
|
+
) -> Dict[str, Any]:
|
|
52
|
+
kwargs: Dict[str, Any] = {
|
|
53
|
+
"endpoint": endpoint,
|
|
54
|
+
"method": "POST",
|
|
55
|
+
"body": body or {},
|
|
56
|
+
"use_gateway": True,
|
|
57
|
+
"timeout": timeout,
|
|
58
|
+
}
|
|
59
|
+
if actor_context is not None:
|
|
60
|
+
kwargs["actor"] = actor_context
|
|
61
|
+
return await self._client._request(**kwargs)
|
|
52
62
|
|
|
53
63
|
async def _get(self, endpoint: str, *, timeout: float = 30.0) -> Dict[str, Any]:
|
|
54
64
|
return await self._client._request(
|
|
@@ -75,6 +85,7 @@ class RecipesNamespace:
|
|
|
75
85
|
name: str,
|
|
76
86
|
body: str,
|
|
77
87
|
description: Optional[str] = None,
|
|
88
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
78
89
|
timeout: float = 30.0,
|
|
79
90
|
) -> Dict[str, Any]:
|
|
80
91
|
"""Publish a recipe. ``POST /api/recipe/recipes/publish``."""
|
|
@@ -87,6 +98,7 @@ class RecipesNamespace:
|
|
|
87
98
|
"body": body,
|
|
88
99
|
"description": description,
|
|
89
100
|
}),
|
|
101
|
+
actor_context=actor_context,
|
|
90
102
|
timeout=timeout,
|
|
91
103
|
)
|
|
92
104
|
|
|
@@ -112,6 +124,7 @@ class RecipesNamespace:
|
|
|
112
124
|
name: str,
|
|
113
125
|
version: int,
|
|
114
126
|
reason: Optional[str] = None,
|
|
127
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
115
128
|
timeout: float = 30.0,
|
|
116
129
|
) -> Dict[str, Any]:
|
|
117
130
|
"""Mark the current head recipe version deprecated. ``POST /api/recipe/recipes/deprecate``."""
|
|
@@ -124,6 +137,7 @@ class RecipesNamespace:
|
|
|
124
137
|
"version": version,
|
|
125
138
|
"reason": reason,
|
|
126
139
|
}),
|
|
140
|
+
actor_context=actor_context,
|
|
127
141
|
timeout=timeout,
|
|
128
142
|
)
|
|
129
143
|
|
|
@@ -170,6 +184,7 @@ class RecipesNamespace:
|
|
|
170
184
|
owning_worker: str,
|
|
171
185
|
schema_version: int,
|
|
172
186
|
json_schema: Dict[str, Any],
|
|
187
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
173
188
|
timeout: float = 15.0,
|
|
174
189
|
) -> Dict[str, Any]:
|
|
175
190
|
"""Register a recipe type. ``POST /api/recipe/types/register``.
|
|
@@ -184,5 +199,6 @@ class RecipesNamespace:
|
|
|
184
199
|
"schema_version": schema_version,
|
|
185
200
|
"json_schema": json_schema,
|
|
186
201
|
},
|
|
202
|
+
actor_context=actor_context,
|
|
187
203
|
timeout=timeout,
|
|
188
204
|
)
|