dominus-sdk-python 2.7.0__tar.gz → 2.7.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 (43) hide show
  1. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/PKG-INFO +1 -1
  2. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/__init__.py +1 -1
  3. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/namespaces/ai.py +226 -94
  4. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/start.py +43 -11
  5. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus_sdk_python.egg-info/PKG-INFO +1 -1
  6. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/pyproject.toml +1 -1
  7. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/README.md +0 -0
  8. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/config/__init__.py +0 -0
  9. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/config/endpoints.py +0 -0
  10. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/errors.py +0 -0
  11. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/helpers/__init__.py +0 -0
  12. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/helpers/auth.py +0 -0
  13. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/helpers/cache.py +0 -0
  14. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/helpers/core.py +0 -0
  15. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/helpers/crypto.py +0 -0
  16. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/helpers/sse.py +0 -0
  17. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/namespaces/__init__.py +0 -0
  18. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/namespaces/admin.py +0 -0
  19. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/namespaces/auth.py +0 -0
  20. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/namespaces/courier.py +0 -0
  21. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/namespaces/db.py +0 -0
  22. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/namespaces/ddl.py +0 -0
  23. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/namespaces/fastapi.py +0 -0
  24. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/namespaces/files.py +0 -0
  25. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/namespaces/health.py +0 -0
  26. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/namespaces/logs.py +0 -0
  27. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/namespaces/open.py +0 -0
  28. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/namespaces/oracle/__init__.py +0 -0
  29. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/namespaces/oracle/audio_capture.py +0 -0
  30. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/namespaces/oracle/oracle_websocket.py +0 -0
  31. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/namespaces/oracle/session.py +0 -0
  32. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/namespaces/oracle/types.py +0 -0
  33. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/namespaces/oracle/vad_gate.py +0 -0
  34. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/namespaces/portal.py +0 -0
  35. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/namespaces/redis.py +0 -0
  36. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/namespaces/secrets.py +0 -0
  37. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/namespaces/secure.py +0 -0
  38. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus/services/__init__.py +0 -0
  39. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus_sdk_python.egg-info/SOURCES.txt +0 -0
  40. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus_sdk_python.egg-info/dependency_links.txt +0 -0
  41. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus_sdk_python.egg-info/requires.txt +0 -0
  42. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/dominus_sdk_python.egg-info/top_level.txt +0 -0
  43. {dominus_sdk_python-2.7.0 → dominus_sdk_python-2.7.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dominus-sdk-python
3
- Version: 2.7.0
3
+ Version: 2.7.2
4
4
  Summary: Python SDK for the Dominus Orchestrator Platform
5
5
  Author-email: CareBridge Systems <dev@carebridge.io>
6
6
  License: Proprietary
@@ -140,7 +140,7 @@ from .errors import (
140
140
  TimeoutError as DominusTimeoutError,
141
141
  )
142
142
 
143
- __version__ = "2.7.0"
143
+ __version__ = "2.7.2"
144
144
  __all__ = [
145
145
  # Main SDK instance
146
146
  "dominus",
@@ -49,11 +49,90 @@ if TYPE_CHECKING:
49
49
  from ..start import Dominus
50
50
 
51
51
 
52
+ # ========================================
53
+ # Gateway-routed API helper
54
+ # ========================================
55
+
56
+ class GatewayMixin:
57
+ """
58
+ Mixin that provides gateway-routed API calls.
59
+
60
+ All AI namespace methods route through the gateway's /svc/* endpoints
61
+ which proxy to agent-runtime's /api/* endpoints.
62
+ """
63
+ _client: "Dominus"
64
+
65
+ async def _api(
66
+ self,
67
+ endpoint: str,
68
+ method: str = "POST",
69
+ body: Optional[Dict[str, Any]] = None
70
+ ) -> Dict[str, Any]:
71
+ """Make gateway-routed API request."""
72
+ return await self._client._request(
73
+ endpoint=endpoint,
74
+ method=method,
75
+ body=body,
76
+ use_gateway=True
77
+ )
78
+
79
+ async def _api_stream(
80
+ self,
81
+ endpoint: str,
82
+ body: Optional[Dict[str, Any]] = None,
83
+ on_chunk: Optional[Any] = None,
84
+ timeout: float = 300.0
85
+ ):
86
+ """Make gateway-routed streaming request."""
87
+ async for chunk in self._client._stream_request(
88
+ endpoint=endpoint,
89
+ body=body,
90
+ on_chunk=on_chunk,
91
+ timeout=timeout,
92
+ use_gateway=True
93
+ ):
94
+ yield chunk
95
+
96
+ async def _api_upload(
97
+ self,
98
+ endpoint: str,
99
+ file_bytes: bytes,
100
+ filename: str,
101
+ content_type: str = "application/octet-stream",
102
+ additional_fields: Optional[Dict[str, str]] = None,
103
+ timeout: float = 60.0
104
+ ) -> Dict[str, Any]:
105
+ """Make gateway-routed binary upload."""
106
+ return await self._client._binary_upload(
107
+ endpoint=endpoint,
108
+ file_bytes=file_bytes,
109
+ filename=filename,
110
+ content_type=content_type,
111
+ additional_fields=additional_fields,
112
+ timeout=timeout,
113
+ use_gateway=True
114
+ )
115
+
116
+ async def _api_download(
117
+ self,
118
+ endpoint: str,
119
+ body: Optional[Dict[str, Any]] = None,
120
+ timeout: float = 60.0
121
+ ) -> bytes:
122
+ """Make gateway-routed binary download."""
123
+ return await self._client._binary_download(
124
+ endpoint=endpoint,
125
+ body=body,
126
+ timeout=timeout,
127
+ use_gateway=True
128
+ )
129
+
130
+
52
131
  # ========================================
53
132
  # RAG Sub-namespace
54
133
  # ========================================
55
134
 
56
- class RagSubNamespace:
135
+ class RagSubNamespace(GatewayMixin):
57
136
  """
58
137
  RAG corpus management sub-namespace.
59
138
 
@@ -65,7 +144,7 @@ class RagSubNamespace:
65
144
 
66
145
  async def list(self) -> List[Dict[str, Any]]:
67
146
  """List all corpora."""
68
- result = await self._client._request(
147
+ result = await self._api(
69
148
  endpoint="/api/rag",
70
149
  method="GET"
71
150
  )
@@ -91,21 +170,21 @@ class RagSubNamespace:
91
170
  if embedding_model:
92
171
  body["embedding_model"] = embedding_model
93
172
 
94
- return await self._client._request(
173
+ return await self._api(
95
174
  endpoint=f"/api/rag/{slug}/ensure",
96
175
  body=body if body else {}
97
176
  )
98
177
 
99
178
  async def stats(self, slug: str) -> Dict[str, Any]:
100
179
  """Get corpus statistics."""
101
- return await self._client._request(
180
+ return await self._api(
102
181
  endpoint=f"/api/rag/{slug}/stats",
103
182
  method="GET"
104
183
  )
105
184
 
106
185
  async def drop(self, slug: str) -> Dict[str, Any]:
107
186
  """Drop/delete a corpus."""
108
- return await self._client._request(
187
+ return await self._api(
109
188
  endpoint=f"/api/rag/{slug}",
110
189
  method="DELETE"
111
190
  )
@@ -122,7 +201,7 @@ class RagSubNamespace:
122
201
  if category:
123
202
  body["category"] = category
124
203
 
125
- return await self._client._request(
204
+ return await self._api(
126
205
  endpoint=f"/api/rag/{slug}/entries",
127
206
  body=body
128
207
  )
@@ -132,7 +211,11 @@ class RagSubNamespace:
132
211
  slug: str,
133
212
  identifier: str,
134
213
  content: str,
214
+ name: Optional[str] = None,
215
+ description: Optional[str] = None,
135
216
  category: Optional[str] = None,
217
+ subcategory: Optional[str] = None,
218
+ source_reference: Optional[Dict[str, Any]] = None,
136
219
  metadata: Optional[Dict[str, Any]] = None
137
220
  ) -> Dict[str, Any]:
138
221
  """
@@ -141,17 +224,29 @@ class RagSubNamespace:
141
224
  Args:
142
225
  slug: Corpus identifier
143
226
  identifier: Entry identifier (unique within corpus)
144
- content: Text content to embed
227
+ content: Text content to embed (stored as content_markdown)
228
+ name: Optional human-readable name
229
+ description: Optional brief description
145
230
  category: Optional category for filtering
231
+ subcategory: Optional subcategory
232
+ source_reference: Optional source attribution
146
233
  metadata: Optional metadata dict
147
234
  """
148
- body: Dict[str, Any] = {"content": content}
235
+ body: Dict[str, Any] = {"content_markdown": content}
236
+ if name:
237
+ body["name"] = name
238
+ if description:
239
+ body["description"] = description
149
240
  if category:
150
241
  body["category"] = category
242
+ if subcategory:
243
+ body["subcategory"] = subcategory
244
+ if source_reference:
245
+ body["source_reference"] = source_reference
151
246
  if metadata:
152
247
  body["metadata"] = metadata
153
248
 
154
- return await self._client._request(
249
+ return await self._api(
155
250
  endpoint=f"/api/rag/{slug}/{identifier}",
156
251
  method="PUT",
157
252
  body=body
@@ -159,14 +254,14 @@ class RagSubNamespace:
159
254
 
160
255
  async def get(self, slug: str, identifier: str) -> Dict[str, Any]:
161
256
  """Get a specific entry."""
162
- return await self._client._request(
257
+ return await self._api(
163
258
  endpoint=f"/api/rag/{slug}/{identifier}",
164
259
  method="GET"
165
260
  )
166
261
 
167
262
  async def delete(self, slug: str, identifier: str) -> Dict[str, Any]:
168
263
  """Delete an entry."""
169
- return await self._client._request(
264
+ return await self._api(
170
265
  endpoint=f"/api/rag/{slug}/{identifier}",
171
266
  method="DELETE"
172
267
  )
@@ -181,11 +276,30 @@ class RagSubNamespace:
181
276
 
182
277
  Args:
183
278
  slug: Corpus identifier
184
- entries: List of {"identifier", "content", "category", "metadata"} dicts
279
+ entries: List of entry dicts with:
280
+ - identifier: string (required)
281
+ - content: string (required) - Will be sent as content_markdown
282
+ - name: string (optional)
283
+ - description: string (optional)
284
+ - category: string (optional)
285
+ - subcategory: string (optional)
286
+ - source_reference: dict (optional)
287
+ - metadata: dict (optional)
288
+
289
+ Returns:
290
+ Dict with "processed", "failed", "errors" counts
185
291
  """
186
- return await self._client._request(
292
+ # Transform 'content' to 'content_markdown' for API compatibility
293
+ transformed = []
294
+ for entry in entries:
295
+ e = dict(entry)
296
+ if "content" in e and "content_markdown" not in e:
297
+ e["content_markdown"] = e.pop("content")
298
+ transformed.append(e)
299
+
300
+ return await self._api(
187
301
  endpoint=f"/api/rag/{slug}/bulk",
188
- body={"entries": entries}
302
+ body={"entries": transformed}
189
303
  )
190
304
 
191
305
  async def search(
@@ -222,7 +336,7 @@ class RagSubNamespace:
222
336
  if filters:
223
337
  body["filters"] = filters
224
338
 
225
- result = await self._client._request(
339
+ result = await self._api(
226
340
  endpoint=f"/api/rag/{slug}/search",
227
341
  body=body
228
342
  )
@@ -251,7 +365,7 @@ class RagSubNamespace:
251
365
  if rerank_model:
252
366
  body["rerank_model"] = rerank_model
253
367
 
254
- result = await self._client._request(
368
+ result = await self._api(
255
369
  endpoint=f"/api/rag/{slug}/search/rerank",
256
370
  body=body
257
371
  )
@@ -277,7 +391,7 @@ class RagSubNamespace:
277
391
  chunk_size: Chunk size for splitting (default: 1000 chars)
278
392
  chunk_overlap: Overlap between chunks (default: 200 chars)
279
393
  """
280
- return await self._client._binary_upload(
394
+ return await self._api_upload(
281
395
  endpoint=f"/api/rag/{slug}/ingest",
282
396
  file_bytes=content,
283
397
  filename=filename,
@@ -290,7 +404,7 @@ class RagSubNamespace:
290
404
 
291
405
  async def delete_document(self, slug: str, document_id: str) -> Dict[str, Any]:
292
406
  """Delete a document and all its chunks."""
293
- return await self._client._request(
407
+ return await self._api(
294
408
  endpoint=f"/api/rag/{slug}/document/{document_id}",
295
409
  method="DELETE"
296
410
  )
@@ -300,7 +414,7 @@ class RagSubNamespace:
300
414
  # Artifacts Sub-namespace
301
415
  # ========================================
302
416
 
303
- class ArtifactsSubNamespace:
417
+ class ArtifactsSubNamespace(GatewayMixin):
304
418
  """
305
419
  Artifacts sub-namespace for conversation artifact management.
306
420
  """
@@ -311,16 +425,12 @@ class ArtifactsSubNamespace:
311
425
  async def get(
312
426
  self,
313
427
  artifact_id: str,
314
- conversation_id: Optional[str] = None
428
+ conversation_id: str
315
429
  ) -> Dict[str, Any]:
316
430
  """Get artifact by ID."""
317
- body: Dict[str, Any] = {"artifact_id": artifact_id}
318
- if conversation_id:
319
- body["conversation_id"] = conversation_id
320
-
321
- return await self._client._request(
322
- endpoint=f"/api/agent/artifacts/{artifact_id}",
323
- body=body
431
+ return await self._api(
432
+ endpoint=f"/api/agent/artifacts/{artifact_id}?conversation_id={conversation_id}",
433
+ method="GET"
324
434
  )
325
435
 
326
436
  async def create(
@@ -328,67 +438,54 @@ class ArtifactsSubNamespace:
328
438
  name: str,
329
439
  content: str,
330
440
  conversation_id: str,
331
- artifact_type: str = "text",
441
+ artifact_type: str = "text/plain",
442
+ is_base64: bool = False,
332
443
  metadata: Optional[Dict[str, Any]] = None
333
444
  ) -> Dict[str, Any]:
334
445
  """
335
446
  Create a new artifact.
336
447
 
337
448
  Args:
338
- name: Artifact name/filename
339
- content: Artifact content (text or base64 for binary)
449
+ name: Artifact key/filename
450
+ content: Artifact content (text or base64-encoded for binary)
340
451
  conversation_id: Associated conversation ID
341
- artifact_type: Type of artifact (text, code, image, etc.)
342
- metadata: Optional metadata dict
452
+ artifact_type: MIME content type (e.g., "text/plain", "text/html", "image/png")
453
+ is_base64: If True, content is base64-encoded binary
454
+ metadata: Optional metadata dict (not stored, for SDK use)
343
455
  """
344
456
  body: Dict[str, Any] = {
345
- "name": name,
457
+ "key": name,
346
458
  "content": content,
347
459
  "conversation_id": conversation_id,
348
- "type": artifact_type
460
+ "content_type": artifact_type,
461
+ "is_base64": is_base64
349
462
  }
350
- if metadata:
351
- body["metadata"] = metadata
352
463
 
353
- return await self._client._request(
464
+ return await self._api(
354
465
  endpoint="/api/agent/artifacts",
355
466
  body=body
356
467
  )
357
468
 
358
469
  async def list(
359
470
  self,
360
- conversation_id: str,
361
- limit: int = 100,
362
- offset: int = 0
471
+ conversation_id: str
363
472
  ) -> List[Dict[str, Any]]:
364
473
  """List artifacts for a conversation."""
365
- body: Dict[str, Any] = {
366
- "conversation_id": conversation_id,
367
- "limit": limit,
368
- "offset": offset
369
- }
370
-
371
- result = await self._client._request(
372
- endpoint="/api/agent/artifacts",
373
- method="GET",
374
- body=body
474
+ result = await self._api(
475
+ endpoint=f"/api/agent/artifacts?conversation_id={conversation_id}",
476
+ method="GET"
375
477
  )
376
478
  return result.get("artifacts", result) if isinstance(result, dict) else result
377
479
 
378
480
  async def delete(
379
481
  self,
380
482
  artifact_id: str,
381
- conversation_id: Optional[str] = None
483
+ conversation_id: str
382
484
  ) -> Dict[str, Any]:
383
485
  """Delete an artifact."""
384
- body: Dict[str, Any] = {}
385
- if conversation_id:
386
- body["conversation_id"] = conversation_id
387
-
388
- return await self._client._request(
389
- endpoint=f"/api/agent/artifacts/{artifact_id}",
390
- method="DELETE",
391
- body=body if body else None
486
+ return await self._api(
487
+ endpoint=f"/api/agent/artifacts/{artifact_id}?conversation_id={conversation_id}",
488
+ method="DELETE"
392
489
  )
393
490
 
394
491
 
@@ -396,7 +493,7 @@ class ArtifactsSubNamespace:
396
493
  # Results Sub-namespace
397
494
  # ========================================
398
495
 
399
- class ResultsSubNamespace:
496
+ class ResultsSubNamespace(GatewayMixin):
400
497
  """
401
498
  Async results sub-namespace for polling async operation results.
402
499
  """
@@ -415,7 +512,7 @@ class ResultsSubNamespace:
415
512
  Dict with "status" (pending|running|completed|failed),
416
513
  "result" (if completed), "error" (if failed)
417
514
  """
418
- return await self._client._request(
515
+ return await self._api(
419
516
  endpoint=f"/api/results/{result_key}",
420
517
  method="GET"
421
518
  )
@@ -460,7 +557,7 @@ class ResultsSubNamespace:
460
557
  # Workflow Sub-namespace
461
558
  # ========================================
462
559
 
463
- class WorkflowSubNamespace:
560
+ class WorkflowSubNamespace(GatewayMixin):
464
561
  """
465
562
  Multi-agent workflow orchestration sub-namespace.
466
563
  """
@@ -499,7 +596,7 @@ class WorkflowSubNamespace:
499
596
  if webhook_url:
500
597
  body["webhook_url"] = webhook_url
501
598
 
502
- return await self._client._request(
599
+ return await self._api(
503
600
  endpoint="/api/orchestration/execute",
504
601
  body=body
505
602
  )
@@ -517,7 +614,7 @@ class WorkflowSubNamespace:
517
614
  Returns:
518
615
  Dict with "valid", "errors" (if invalid)
519
616
  """
520
- return await self._client._request(
617
+ return await self._api(
521
618
  endpoint="/api/orchestration/validate",
522
619
  body={"definition": workflow_definition}
523
620
  )
@@ -529,7 +626,7 @@ class WorkflowSubNamespace:
529
626
  offset: int = 0
530
627
  ) -> List[Dict[str, Any]]:
531
628
  """Get messages from an execution."""
532
- result = await self._client._request(
629
+ result = await self._api(
533
630
  endpoint=f"/api/orchestration/messages/{execution_id}",
534
631
  body={"limit": limit, "offset": offset}
535
632
  )
@@ -545,7 +642,7 @@ class WorkflowSubNamespace:
545
642
  if from_timestamp:
546
643
  body["from"] = from_timestamp
547
644
 
548
- result = await self._client._request(
645
+ result = await self._api(
549
646
  endpoint=f"/api/orchestration/events/{execution_id}",
550
647
  body=body if body else None
551
648
  )
@@ -553,14 +650,14 @@ class WorkflowSubNamespace:
553
650
 
554
651
  async def status(self, execution_id: str) -> Dict[str, Any]:
555
652
  """Get execution status."""
556
- return await self._client._request(
653
+ return await self._api(
557
654
  endpoint=f"/api/orchestration/status/{execution_id}",
558
655
  method="GET"
559
656
  )
560
657
 
561
658
  async def output(self, execution_id: str) -> Dict[str, Any]:
562
659
  """Get final execution output."""
563
- return await self._client._request(
660
+ return await self._api(
564
661
  endpoint=f"/api/orchestration/output/{execution_id}",
565
662
  method="GET"
566
663
  )
@@ -570,7 +667,7 @@ class WorkflowSubNamespace:
570
667
  # Main AI Namespace
571
668
  # ========================================
572
669
 
573
- class AiNamespace:
670
+ class AiNamespace(GatewayMixin):
574
671
  """
575
672
  Unified AI namespace for agent-runtime operations.
576
673
 
@@ -656,7 +753,7 @@ class AiNamespace:
656
753
  if tool_endpoint:
657
754
  body["tool_endpoint"] = tool_endpoint
658
755
 
659
- return await self._client._request(
756
+ return await self._api(
660
757
  endpoint="/api/agent/run",
661
758
  body=body
662
759
  )
@@ -722,7 +819,7 @@ class AiNamespace:
722
819
  if tool_endpoint:
723
820
  body["tool_endpoint"] = tool_endpoint
724
821
 
725
- async for chunk in self._client._stream_request(
822
+ async for chunk in self._api_stream(
726
823
  endpoint="/api/agent/stream",
727
824
  body=body,
728
825
  on_chunk=on_chunk,
@@ -796,7 +893,7 @@ class AiNamespace:
796
893
  if webhook_url:
797
894
  body["webhook_url"] = webhook_url
798
895
 
799
- return await self._client._request(
896
+ return await self._api(
800
897
  endpoint="/api/agent/run-async",
801
898
  body=body
802
899
  )
@@ -820,11 +917,11 @@ class AiNamespace:
820
917
  Returns:
821
918
  List of history messages
822
919
  """
823
- result = await self._client._request(
824
- endpoint=f"/api/agent/history/{conversation_id}",
825
- body={"limit": limit}
920
+ result = await self._api(
921
+ endpoint=f"/api/agent/history/{conversation_id}?limit={limit}",
922
+ method="GET"
826
923
  )
827
- return result.get("history", result) if isinstance(result, dict) else result
924
+ return result.get("messages", result) if isinstance(result, dict) else result
828
925
 
829
926
  # ========================================
830
927
  # LLM Completions
@@ -832,58 +929,79 @@ class AiNamespace:
832
929
 
833
930
  async def complete(
834
931
  self,
835
- messages: List[Dict[str, Any]],
932
+ messages: Optional[List[Dict[str, Any]]] = None,
836
933
  provider: str = "claude",
837
934
  model: str = "claude-sonnet-4-5",
838
935
  conversation_id: Optional[str] = None,
839
936
  temperature: float = 0.7,
840
937
  max_tokens: Optional[int] = None,
841
938
  system_prompt: Optional[str] = None,
939
+ user_prompt: Optional[str] = None,
842
940
  timeout: float = 120.0
843
941
  ) -> Dict[str, Any]:
844
942
  """
845
943
  Blocking LLM completion.
846
944
 
847
945
  Args:
848
- messages: List of message dicts with "role" and "content"
946
+ messages: List of message dicts with "role" and "content" (optional)
849
947
  provider: LLM provider ("claude" or "openai")
850
948
  model: Model identifier
851
949
  conversation_id: Optional conversation ID
852
950
  temperature: Temperature (0-2, default: 0.7)
853
951
  max_tokens: Maximum tokens to generate
854
- system_prompt: Optional system prompt
952
+ system_prompt: System prompt (required if messages not provided)
953
+ user_prompt: User prompt (required if messages not provided)
855
954
  timeout: Request timeout in seconds
856
955
 
857
956
  Returns:
858
957
  Dict with "content", "model", "usage", etc.
958
+
959
+ Note:
960
+ Either provide `messages` OR both `system_prompt` and `user_prompt`.
961
+ If messages provided, the last user message becomes user_prompt,
962
+ and system messages become system_prompt.
859
963
  """
964
+ # Convert messages to system_prompt/user_prompt if needed
965
+ if messages and not user_prompt:
966
+ for msg in messages:
967
+ if msg.get("role") == "system":
968
+ system_prompt = msg.get("content", system_prompt)
969
+ elif msg.get("role") == "user":
970
+ user_prompt = msg.get("content")
971
+
972
+ if not system_prompt:
973
+ system_prompt = "You are a helpful assistant."
974
+
975
+ if not user_prompt:
976
+ raise ValueError("Either 'messages' with a user message or 'user_prompt' is required")
977
+
860
978
  body: Dict[str, Any] = {
861
- "messages": messages,
862
979
  "provider": provider,
863
980
  "model": model,
981
+ "system_prompt": system_prompt,
982
+ "user_prompt": user_prompt,
864
983
  "temperature": temperature
865
984
  }
866
985
  if conversation_id:
867
986
  body["conversation_id"] = conversation_id
868
987
  if max_tokens:
869
988
  body["max_tokens"] = max_tokens
870
- if system_prompt:
871
- body["system_prompt"] = system_prompt
872
989
 
873
- return await self._client._request(
990
+ return await self._api(
874
991
  endpoint="/api/llm/complete",
875
992
  body=body
876
993
  )
877
994
 
878
995
  async def complete_stream(
879
996
  self,
880
- messages: List[Dict[str, Any]],
997
+ messages: Optional[List[Dict[str, Any]]] = None,
881
998
  provider: str = "claude",
882
999
  model: str = "claude-sonnet-4-5",
883
1000
  conversation_id: Optional[str] = None,
884
1001
  temperature: float = 0.7,
885
1002
  max_tokens: Optional[int] = None,
886
1003
  system_prompt: Optional[str] = None,
1004
+ user_prompt: Optional[str] = None,
887
1005
  on_chunk: Optional[Callable[[Dict[str, Any]], None]] = None,
888
1006
  timeout: float = 120.0
889
1007
  ) -> AsyncGenerator[Dict[str, Any], None]:
@@ -891,33 +1009,47 @@ class AiNamespace:
891
1009
  Streaming LLM completion.
892
1010
 
893
1011
  Args:
894
- messages: List of message dicts
1012
+ messages: List of message dicts (optional)
895
1013
  provider: LLM provider
896
1014
  model: Model identifier
897
1015
  conversation_id: Optional conversation ID
898
1016
  temperature: Temperature (0-2)
899
1017
  max_tokens: Maximum tokens
900
- system_prompt: Optional system prompt
1018
+ system_prompt: System prompt (required if messages not provided)
1019
+ user_prompt: User prompt (required if messages not provided)
901
1020
  on_chunk: Optional callback for each chunk
902
1021
  timeout: Request timeout
903
1022
 
904
1023
  Yields:
905
1024
  Streaming chunks with "content" delta
906
1025
  """
1026
+ # Convert messages to system_prompt/user_prompt if needed
1027
+ if messages and not user_prompt:
1028
+ for msg in messages:
1029
+ if msg.get("role") == "system":
1030
+ system_prompt = msg.get("content", system_prompt)
1031
+ elif msg.get("role") == "user":
1032
+ user_prompt = msg.get("content")
1033
+
1034
+ if not system_prompt:
1035
+ system_prompt = "You are a helpful assistant."
1036
+
1037
+ if not user_prompt:
1038
+ raise ValueError("Either 'messages' with a user message or 'user_prompt' is required")
1039
+
907
1040
  body: Dict[str, Any] = {
908
- "messages": messages,
909
1041
  "provider": provider,
910
1042
  "model": model,
1043
+ "system_prompt": system_prompt,
1044
+ "user_prompt": user_prompt,
911
1045
  "temperature": temperature
912
1046
  }
913
1047
  if conversation_id:
914
1048
  body["conversation_id"] = conversation_id
915
1049
  if max_tokens:
916
1050
  body["max_tokens"] = max_tokens
917
- if system_prompt:
918
- body["system_prompt"] = system_prompt
919
1051
 
920
- async for chunk in self._client._stream_request(
1052
+ async for chunk in self._api_stream(
921
1053
  endpoint="/api/llm/stream",
922
1054
  body=body,
923
1055
  on_chunk=on_chunk,
@@ -970,7 +1102,7 @@ class AiNamespace:
970
1102
  if webhook_url:
971
1103
  body["webhook_url"] = webhook_url
972
1104
 
973
- return await self._client._request(
1105
+ return await self._api(
974
1106
  endpoint="/api/llm/complete-async",
975
1107
  body=body
976
1108
  )
@@ -996,7 +1128,7 @@ class AiNamespace:
996
1128
  Returns:
997
1129
  Dict with "text", "confidence", "language", "duration"
998
1130
  """
999
- return await self._client._binary_upload(
1131
+ return await self._api_upload(
1000
1132
  endpoint="/api/agent/stt",
1001
1133
  file_bytes=audio,
1002
1134
  filename=f"audio.{format}",
@@ -1023,7 +1155,7 @@ class AiNamespace:
1023
1155
  Returns:
1024
1156
  Raw audio bytes (NOT base64 encoded)
1025
1157
  """
1026
- return await self._client._binary_download(
1158
+ return await self._api_download(
1027
1159
  endpoint="/api/agent/tts",
1028
1160
  body={
1029
1161
  "text": text,
@@ -1070,7 +1202,7 @@ class AiNamespace:
1070
1202
  if context:
1071
1203
  body["context"] = context
1072
1204
 
1073
- return await self._client._request(
1205
+ return await self._api(
1074
1206
  endpoint="/api/session/setup",
1075
1207
  body=body
1076
1208
  )
@@ -59,15 +59,17 @@ from typing import Optional, Any, Dict, List
59
59
  _VALIDATED = False
60
60
  _TOKEN: Optional[str] = None
61
61
  _BASE_URL: Optional[str] = None
62
+ _GATEWAY_URL: Optional[str] = None
62
63
  _VALIDATION_ERROR: Optional[str] = None
63
64
 
64
65
 
65
66
  # === MODULE-LEVEL INITIALIZATION ===
66
67
  from .helpers.auth import _resolve_token
67
- from .config.endpoints import BASE_URL
68
+ from .config.endpoints import BASE_URL, GATEWAY_URL
68
69
 
69
70
  _TOKEN = _resolve_token()
70
71
  _BASE_URL = BASE_URL
72
+ _GATEWAY_URL = GATEWAY_URL
71
73
 
72
74
  # Initialize cache encryption
73
75
  from .helpers.cache import dominus_cache
@@ -365,10 +367,11 @@ class Dominus:
365
367
  endpoint: str,
366
368
  method: str = "POST",
367
369
  body: Optional[Dict[str, Any]] = None,
368
- user_token: Optional[str] = None
370
+ user_token: Optional[str] = None,
371
+ use_gateway: bool = False
369
372
  ) -> Dict[str, Any]:
370
373
  """
371
- Make an HTTP request to the orchestrator.
374
+ Make an HTTP request to the orchestrator or gateway.
372
375
 
373
376
  If user_token is provided, uses that JWT directly (for user-authenticated requests).
374
377
  Otherwise, uses the service token flow (PSK -> JWT).
@@ -378,6 +381,7 @@ class Dominus:
378
381
  method: HTTP method (GET, POST, PUT, DELETE)
379
382
  body: Request body (will be base64-encoded)
380
383
  user_token: Optional user JWT for user-authenticated requests
384
+ use_gateway: If True, route through gateway (/api/* -> /svc/*)
381
385
 
382
386
  Returns:
383
387
  Response data dict
@@ -391,6 +395,10 @@ class Dominus:
391
395
  def _b64_decode(s: str) -> dict:
392
396
  return json.loads(base64.b64decode(s.encode('utf-8')).decode('utf-8'))
393
397
 
398
+ # Transform endpoint for gateway routing
399
+ if use_gateway and endpoint.startswith("/api/"):
400
+ endpoint = "/svc/" + endpoint[5:] # /api/llm/complete -> /svc/llm/complete
401
+
394
402
  # Determine which JWT to use
395
403
  if user_token:
396
404
  # Use the provided user JWT directly (no validation needed - orchestrator validates it)
@@ -426,8 +434,11 @@ class Dominus:
426
434
  # Prepare body
427
435
  body_b64 = _b64_encode(body or {})
428
436
 
437
+ # Select base URL
438
+ base_url = _GATEWAY_URL if use_gateway else _BASE_URL
439
+
429
440
  # Make request
430
- async with httpx.AsyncClient(base_url=_BASE_URL, headers=headers, timeout=30.0) as client:
441
+ async with httpx.AsyncClient(base_url=base_url, headers=headers, timeout=30.0) as client:
431
442
  if method == "GET":
432
443
  response = await client.get(endpoint)
433
444
  elif method == "DELETE":
@@ -454,16 +465,18 @@ class Dominus:
454
465
  endpoint: str,
455
466
  body: Optional[Dict[str, Any]] = None,
456
467
  on_chunk: Optional[Any] = None,
457
- timeout: float = 300.0
468
+ timeout: float = 300.0,
469
+ use_gateway: bool = False
458
470
  ):
459
471
  """
460
- Make an SSE streaming request to the orchestrator.
472
+ Make an SSE streaming request to the orchestrator or gateway.
461
473
 
462
474
  Args:
463
475
  endpoint: API endpoint path (e.g., "/api/agent/stream")
464
476
  body: Request body (will be base64-encoded)
465
477
  on_chunk: Optional callback for each chunk
466
478
  timeout: Request timeout in seconds (default: 300s for long operations)
479
+ use_gateway: If True, route through gateway (/api/* -> /svc/*)
467
480
 
468
481
  Yields:
469
482
  Parsed JSON chunks from SSE stream
@@ -474,6 +487,10 @@ class Dominus:
474
487
  def _b64_encode(d: dict) -> str:
475
488
  return base64.b64encode(json.dumps(d).encode()).decode()
476
489
 
490
+ # Transform endpoint for gateway routing
491
+ if use_gateway and endpoint.startswith("/api/"):
492
+ endpoint = "/svc/" + endpoint[5:]
493
+
477
494
  # Validate and get JWT
478
495
  if _VALIDATION_ERROR:
479
496
  raise RuntimeError(_VALIDATION_ERROR)
@@ -499,7 +516,8 @@ class Dominus:
499
516
 
500
517
  from .helpers.sse import stream_sse
501
518
 
502
- async with httpx.AsyncClient(base_url=_BASE_URL, timeout=timeout) as client:
519
+ base_url = _GATEWAY_URL if use_gateway else _BASE_URL
520
+ async with httpx.AsyncClient(base_url=base_url, timeout=timeout) as client:
503
521
  async with client.stream("POST", endpoint, content=body_b64, headers=headers) as response:
504
522
  response.raise_for_status()
505
523
  async for chunk in stream_sse(response, on_chunk):
@@ -512,7 +530,8 @@ class Dominus:
512
530
  filename: str,
513
531
  content_type: str = "application/octet-stream",
514
532
  additional_fields: Optional[Dict[str, str]] = None,
515
- timeout: float = 60.0
533
+ timeout: float = 60.0,
534
+ use_gateway: bool = False
516
535
  ) -> Dict[str, Any]:
517
536
  """
518
537
  Upload binary file via multipart/form-data (no base64 encoding).
@@ -526,6 +545,7 @@ class Dominus:
526
545
  content_type: MIME type (default: application/octet-stream)
527
546
  additional_fields: Optional additional form fields
528
547
  timeout: Request timeout in seconds
548
+ use_gateway: If True, route through gateway (/api/* -> /svc/*)
529
549
 
530
550
  Returns:
531
551
  Response data dict
@@ -535,6 +555,10 @@ class Dominus:
535
555
  def _b64_decode(s: str) -> dict:
536
556
  return json.loads(base64.b64decode(s.encode('utf-8')).decode('utf-8'))
537
557
 
558
+ # Transform endpoint for gateway routing
559
+ if use_gateway and endpoint.startswith("/api/"):
560
+ endpoint = "/svc/" + endpoint[5:]
561
+
538
562
  if _VALIDATION_ERROR:
539
563
  raise RuntimeError(_VALIDATION_ERROR)
540
564
 
@@ -557,7 +581,8 @@ class Dominus:
557
581
  files = {"file": (filename, file_bytes, content_type)}
558
582
  data = additional_fields or {}
559
583
 
560
- async with httpx.AsyncClient(base_url=_BASE_URL, headers=headers, timeout=timeout) as client:
584
+ base_url = _GATEWAY_URL if use_gateway else _BASE_URL
585
+ async with httpx.AsyncClient(base_url=base_url, headers=headers, timeout=timeout) as client:
561
586
  response = await client.post(endpoint, files=files, data=data)
562
587
 
563
588
  response.raise_for_status()
@@ -578,7 +603,8 @@ class Dominus:
578
603
  self,
579
604
  endpoint: str,
580
605
  body: Optional[Dict[str, Any]] = None,
581
- timeout: float = 60.0
606
+ timeout: float = 60.0,
607
+ use_gateway: bool = False
582
608
  ) -> bytes:
583
609
  """
584
610
  Download binary content (no base64 encoding in response).
@@ -589,6 +615,7 @@ class Dominus:
589
615
  endpoint: API endpoint path (e.g., "/api/agent/tts")
590
616
  body: Request body (will be base64-encoded for auth)
591
617
  timeout: Request timeout in seconds
618
+ use_gateway: If True, route through gateway (/api/* -> /svc/*)
592
619
 
593
620
  Returns:
594
621
  Raw binary bytes
@@ -598,6 +625,10 @@ class Dominus:
598
625
  def _b64_encode(d: dict) -> str:
599
626
  return base64.b64encode(json.dumps(d).encode()).decode()
600
627
 
628
+ # Transform endpoint for gateway routing
629
+ if use_gateway and endpoint.startswith("/api/"):
630
+ endpoint = "/svc/" + endpoint[5:]
631
+
601
632
  if _VALIDATION_ERROR:
602
633
  raise RuntimeError(_VALIDATION_ERROR)
603
634
 
@@ -620,7 +651,8 @@ class Dominus:
620
651
 
621
652
  body_b64 = _b64_encode(body or {})
622
653
 
623
- async with httpx.AsyncClient(base_url=_BASE_URL, headers=headers, timeout=timeout) as client:
654
+ base_url = _GATEWAY_URL if use_gateway else _BASE_URL
655
+ async with httpx.AsyncClient(base_url=base_url, headers=headers, timeout=timeout) as client:
624
656
  response = await client.post(endpoint, content=body_b64)
625
657
 
626
658
  response.raise_for_status()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dominus-sdk-python
3
- Version: 2.7.0
3
+ Version: 2.7.2
4
4
  Summary: Python SDK for the Dominus Orchestrator Platform
5
5
  Author-email: CareBridge Systems <dev@carebridge.io>
6
6
  License: Proprietary
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dominus-sdk-python"
7
- version = "2.7.0"
7
+ version = "2.7.2"
8
8
  description = "Python SDK for the Dominus Orchestrator Platform"
9
9
  readme = "README.md"
10
10
  license = {text = "Proprietary"}