modus-sdk 0.2.2__tar.gz → 0.2.3__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 (116) hide show
  1. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/CHANGELOG.md +2 -0
  2. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/PKG-INFO +1 -1
  3. modus_sdk-0.2.3/modus/_agent_run_request.py +22 -0
  4. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/_chat.py +2 -3
  5. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/_http.py +60 -2
  6. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/_version.py +2 -2
  7. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/resources/workflows/runs.py +3 -1
  8. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/.gitignore +0 -0
  9. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/CONTRIBUTING.md +0 -0
  10. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/LICENSE +0 -0
  11. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/README.md +0 -0
  12. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/assets/modus-logo.png +0 -0
  13. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/assets/modus-logo.svg +0 -0
  14. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/examples/apps/modus_skill_chat/.streamlit/config.toml +0 -0
  15. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/examples/apps/modus_skill_chat/README.md +0 -0
  16. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/examples/apps/modus_skill_chat/app.py +0 -0
  17. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/examples/apps/modus_skill_chat/pyproject.toml +0 -0
  18. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/examples/scripts/chat.py +0 -0
  19. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/examples/scripts/manage_skill.py +0 -0
  20. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/examples/scripts/modus_chat.py +0 -0
  21. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/examples/scripts/quickstart.py +0 -0
  22. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/__init__.py +0 -0
  23. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/_auth.py +0 -0
  24. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/_client.py +0 -0
  25. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/_config.py +0 -0
  26. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/_deprecation.py +0 -0
  27. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/_exceptions.py +0 -0
  28. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/_generated/__init__.py +0 -0
  29. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/_generated/models.py +0 -0
  30. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/_generated/operations.py +0 -0
  31. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/_openapi_invoke.py +0 -0
  32. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/_pagination.py +0 -0
  33. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/_query.py +0 -0
  34. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/_retry.py +0 -0
  35. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/_streaming.py +0 -0
  36. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/_validation.py +0 -0
  37. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/__init__.py +0 -0
  38. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/_client.py +0 -0
  39. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/resources/__init__.py +0 -0
  40. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/resources/_access_config_write.py +0 -0
  41. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/resources/context/__init__.py +0 -0
  42. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/resources/context/_content_merge.py +0 -0
  43. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/resources/context/context.py +0 -0
  44. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/resources/context/custom_items.py +0 -0
  45. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/resources/context/items.py +0 -0
  46. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/resources/organization.py +0 -0
  47. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/resources/scopes/__init__.py +0 -0
  48. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/resources/scopes/_update_body.py +0 -0
  49. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/resources/scopes/conversations.py +0 -0
  50. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/resources/scopes/evaluations.py +0 -0
  51. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/resources/scopes/memories.py +0 -0
  52. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/resources/scopes/scopes.py +0 -0
  53. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/resources/scopes/supervision.py +0 -0
  54. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/resources/usage.py +0 -0
  55. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/resources/users.py +0 -0
  56. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/resources/workflows/__init__.py +0 -0
  57. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/resources/workflows/_update_body.py +0 -0
  58. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/resources/workflows/interfaces.py +0 -0
  59. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/resources/workflows/workflows.py +0 -0
  60. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/types/__init__.py +0 -0
  61. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/types/_guardrails_access.py +0 -0
  62. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/types/_serialize.py +0 -0
  63. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/types/agent_selection.py +0 -0
  64. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/types/agents.py +0 -0
  65. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/types/context.py +0 -0
  66. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/types/conversations.py +0 -0
  67. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/types/evaluations.py +0 -0
  68. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/types/json_blob.py +0 -0
  69. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/types/memories.py +0 -0
  70. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/types/ownership.py +0 -0
  71. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/types/skills.py +0 -0
  72. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/types/toolset.py +0 -0
  73. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/types/triggers.py +0 -0
  74. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/types/usage.py +0 -0
  75. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/management/types/workflow.py +0 -0
  76. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/py.typed +0 -0
  77. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/resources/__init__.py +0 -0
  78. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/resources/connections/__init__.py +0 -0
  79. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/resources/connections/connections.py +0 -0
  80. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/resources/context/__init__.py +0 -0
  81. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/resources/context/context.py +0 -0
  82. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/resources/context/items.py +0 -0
  83. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/resources/modus/__init__.py +0 -0
  84. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/resources/modus/conversations.py +0 -0
  85. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/resources/modus/modus.py +0 -0
  86. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/resources/scopes/__init__.py +0 -0
  87. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/resources/scopes/conversations.py +0 -0
  88. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/resources/scopes/scopes.py +0 -0
  89. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/resources/suggestions/__init__.py +0 -0
  90. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/resources/suggestions/suggestions.py +0 -0
  91. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/resources/workflows/__init__.py +0 -0
  92. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/resources/workflows/workflow_actions.py +0 -0
  93. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/resources/workflows/workflows.py +0 -0
  94. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/types/__init__.py +0 -0
  95. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/types/agent_runs.py +0 -0
  96. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/types/agents.py +0 -0
  97. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/types/chat.py +0 -0
  98. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/types/compose.py +0 -0
  99. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/types/connections.py +0 -0
  100. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/types/context.py +0 -0
  101. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/types/context_accessors.py +0 -0
  102. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/types/context_compose.py +0 -0
  103. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/types/context_values.py +0 -0
  104. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/types/conversations.py +0 -0
  105. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/types/json_blob.py +0 -0
  106. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/types/pagination.py +0 -0
  107. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/types/runs.py +0 -0
  108. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/types/skills.py +0 -0
  109. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/types/suggestions.py +0 -0
  110. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/types/tool_calls.py +0 -0
  111. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/modus/types/views.py +0 -0
  112. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/project.json +0 -0
  113. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/pyproject.toml +0 -0
  114. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/pyrightconfig.json +0 -0
  115. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/requirements.txt +0 -0
  116. {modus_sdk-0.2.2 → modus_sdk-0.2.3}/uv.lock +0 -0
@@ -5,6 +5,8 @@
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.2.3] — 2026-07-20
9
+
8
10
  ## [0.2.2] — 2026-07-20
9
11
 
10
12
  ## [0.2.0] — 2026-07-20
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modus-sdk
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Official Python client for Modus — your organization's context layer for AI
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -0,0 +1,22 @@
1
+ """Shared agent-host run request helpers (chat_stream + runs.create*)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Mapping, Optional
6
+ from uuid import uuid4
7
+
8
+
9
+ def resolve_agent_run_session_id(session_id: Optional[str] = None) -> str:
10
+ """Mint or reuse a non-empty sessionId for agent-host runs."""
11
+ if isinstance(session_id, str) and session_id.strip():
12
+ return session_id.strip()
13
+ return str(uuid4())
14
+
15
+
16
+ def with_agent_run_session_id(body: Mapping[str, Any]) -> dict[str, Any]:
17
+ """Copy body and ensure sessionId is set (parity with chat_stream)."""
18
+ out: dict[str, Any] = dict(body)
19
+ out["sessionId"] = resolve_agent_run_session_id(
20
+ out.get("sessionId") if isinstance(out.get("sessionId"), str) else None
21
+ )
22
+ return out
@@ -2,10 +2,10 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import uuid
6
5
  from typing import Any, AsyncIterator, Dict, Iterator, List, Literal, Optional, Union
7
6
  from urllib.parse import quote
8
7
 
8
+ from ._agent_run_request import resolve_agent_run_session_id
9
9
  from ._exceptions import ModusError, RunCancelledError, StreamTimeoutError
10
10
  from ._generated.models import SkillChatRequestDto, SkillChatResponseDto
11
11
  from ._http import AsyncHttpClient, HttpClient
@@ -37,8 +37,7 @@ def _effective_thread_id(thread_id: Optional[str]) -> Optional[str]:
37
37
 
38
38
  def _resolve_session_id(thread_id: Optional[str]) -> str:
39
39
  """Agent-host runs always require sessionId — reuse thread_id or mint a UUID."""
40
- tid = _effective_thread_id(thread_id)
41
- return tid if tid is not None else str(uuid.uuid4())
40
+ return resolve_agent_run_session_id(_effective_thread_id(thread_id))
42
41
 
43
42
 
44
43
  def _chat_path(
@@ -174,6 +174,34 @@ async def _raise_for_status_async(response: httpx.Response) -> None:
174
174
  _raise_for_status_with_body(response, body)
175
175
 
176
176
 
177
+ def _ensure_sse_content_type(response: httpx.Response, body_preview: str = "") -> None:
178
+ """Reject non-SSE responses so empty streams never look like success."""
179
+ ct = (response.headers.get("content-type") or "").lower()
180
+ if "text/event-stream" in ct:
181
+ return
182
+ preview = body_preview[:200]
183
+ raise ModusError(
184
+ f"Unexpected stream response: expected Content-Type text/event-stream, "
185
+ f"got {response.headers.get('content-type')!r}. "
186
+ f"Body preview: {preview}",
187
+ status_code=response.status_code,
188
+ body=preview or None,
189
+ )
190
+
191
+
192
+ def _sse_body_preview_from_lines(lines: Iterator[str], limit: int = 200) -> str:
193
+ """Accumulate at most ``limit`` characters from an iterator of lines."""
194
+ preview = ""
195
+ for line in lines:
196
+ remaining = limit - len(preview)
197
+ if remaining <= 0:
198
+ break
199
+ preview += line[:remaining]
200
+ if len(preview) >= limit:
201
+ break
202
+ return preview
203
+
204
+
177
205
  # ---------------------------------------------------------------------------
178
206
  # Sync client
179
207
  # ---------------------------------------------------------------------------
@@ -307,8 +335,19 @@ class HttpClient:
307
335
  """Make a streaming request and yield raw SSE lines."""
308
336
  effective_base_url = base_url or self._config.base_url
309
337
  try:
310
- with self._client.stream(method, _request_url(path, base_url), json=json) as response:
338
+ # Override client default Accept: application/json — agent-service
339
+ # negotiates SSE vs JSON polling on Accept.
340
+ with self._client.stream(
341
+ method,
342
+ _request_url(path, base_url),
343
+ json=json,
344
+ headers={"Accept": "text/event-stream"},
345
+ ) as response:
311
346
  _raise_for_status(response)
347
+ ct = (response.headers.get("content-type") or "").lower()
348
+ if "text/event-stream" not in ct:
349
+ preview = _sse_body_preview_from_lines(response.iter_lines())
350
+ _ensure_sse_content_type(response, preview)
312
351
  for line in response.iter_lines():
313
352
  if line.strip():
314
353
  yield line
@@ -453,8 +492,27 @@ class AsyncHttpClient:
453
492
  """Streaming request — yields raw SSE lines."""
454
493
  effective_base_url = base_url or self._config.base_url
455
494
  try:
456
- async with self._client.stream(method, _request_url(path, base_url), json=json) as response:
495
+ async with self._client.stream(
496
+ method,
497
+ _request_url(path, base_url),
498
+ json=json,
499
+ headers={"Accept": "text/event-stream"},
500
+ ) as response:
457
501
  await _raise_for_status_async(response)
502
+ ct = (response.headers.get("content-type") or "").lower()
503
+ if "text/event-stream" not in ct:
504
+ chunks: list[str] = []
505
+ size = 0
506
+ async for line in response.aiter_lines():
507
+ remaining = 200 - size
508
+ if remaining <= 0:
509
+ break
510
+ piece = line[:remaining]
511
+ chunks.append(piece)
512
+ size += len(piece)
513
+ if size >= 200:
514
+ break
515
+ _ensure_sse_content_type(response, "".join(chunks))
458
516
  async for line in response.aiter_lines():
459
517
  if line.strip():
460
518
  yield line
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.2.2'
22
- __version_tuple__ = version_tuple = (0, 2, 2)
21
+ __version__ = version = '0.2.3'
22
+ __version_tuple__ = version_tuple = (0, 2, 3)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -15,6 +15,7 @@ from typing import (
15
15
  Union,
16
16
  )
17
17
 
18
+ from ..._agent_run_request import with_agent_run_session_id
18
19
  from ..._config import ModusConfig
19
20
  from ..._generated.models import (
20
21
  ActiveRunsBySessionResponseDto,
@@ -115,7 +116,8 @@ def _runs_list_params(
115
116
 
116
117
 
117
118
  def _request_body(body: Mapping[str, Any]) -> Dict[str, Any]:
118
- return dict(body)
119
+ """Copy body and mint sessionId when omitted (parity with chat_stream)."""
120
+ return with_agent_run_session_id(body)
119
121
 
120
122
 
121
123
  def _optional_body(body: Optional[Mapping[str, Any]]) -> Dict[str, Any]:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes