modus-sdk 0.2.0__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.0 → modus_sdk-0.2.3}/CHANGELOG.md +7 -4
  2. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/PKG-INFO +5 -1
  3. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/README.md +4 -0
  4. modus_sdk-0.2.3/modus/_agent_run_request.py +22 -0
  5. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_auth.py +17 -3
  6. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_chat.py +60 -19
  7. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_client.py +56 -36
  8. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_config.py +6 -2
  9. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_exceptions.py +1 -1
  10. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_generated/operations.py +87 -87
  11. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_http.py +60 -2
  12. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_version.py +2 -2
  13. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/_client.py +2 -2
  14. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/workflows/runs.py +3 -1
  15. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/context.py +9 -1
  16. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/project.json +9 -0
  17. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/.gitignore +0 -0
  18. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/CONTRIBUTING.md +0 -0
  19. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/LICENSE +0 -0
  20. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/assets/modus-logo.png +0 -0
  21. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/assets/modus-logo.svg +0 -0
  22. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/examples/apps/modus_skill_chat/.streamlit/config.toml +0 -0
  23. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/examples/apps/modus_skill_chat/README.md +0 -0
  24. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/examples/apps/modus_skill_chat/app.py +0 -0
  25. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/examples/apps/modus_skill_chat/pyproject.toml +0 -0
  26. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/examples/scripts/chat.py +0 -0
  27. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/examples/scripts/manage_skill.py +0 -0
  28. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/examples/scripts/modus_chat.py +0 -0
  29. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/examples/scripts/quickstart.py +0 -0
  30. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/__init__.py +0 -0
  31. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_deprecation.py +0 -0
  32. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_generated/__init__.py +0 -0
  33. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_generated/models.py +0 -0
  34. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_openapi_invoke.py +0 -0
  35. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_pagination.py +0 -0
  36. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_query.py +0 -0
  37. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_retry.py +0 -0
  38. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_streaming.py +0 -0
  39. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_validation.py +0 -0
  40. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/__init__.py +0 -0
  41. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/__init__.py +0 -0
  42. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/_access_config_write.py +0 -0
  43. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/context/__init__.py +0 -0
  44. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/context/_content_merge.py +0 -0
  45. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/context/context.py +0 -0
  46. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/context/custom_items.py +0 -0
  47. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/context/items.py +0 -0
  48. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/organization.py +0 -0
  49. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/scopes/__init__.py +0 -0
  50. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/scopes/_update_body.py +0 -0
  51. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/scopes/conversations.py +0 -0
  52. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/scopes/evaluations.py +0 -0
  53. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/scopes/memories.py +0 -0
  54. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/scopes/scopes.py +0 -0
  55. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/scopes/supervision.py +0 -0
  56. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/usage.py +0 -0
  57. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/users.py +0 -0
  58. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/workflows/__init__.py +0 -0
  59. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/workflows/_update_body.py +0 -0
  60. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/workflows/interfaces.py +0 -0
  61. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/workflows/workflows.py +0 -0
  62. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/types/__init__.py +0 -0
  63. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/types/_guardrails_access.py +0 -0
  64. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/types/_serialize.py +0 -0
  65. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/types/agent_selection.py +0 -0
  66. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/types/agents.py +0 -0
  67. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/types/context.py +0 -0
  68. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/types/conversations.py +0 -0
  69. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/types/evaluations.py +0 -0
  70. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/types/json_blob.py +0 -0
  71. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/types/memories.py +0 -0
  72. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/types/ownership.py +0 -0
  73. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/types/skills.py +0 -0
  74. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/types/toolset.py +0 -0
  75. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/types/triggers.py +0 -0
  76. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/types/usage.py +0 -0
  77. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/types/workflow.py +0 -0
  78. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/py.typed +0 -0
  79. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/__init__.py +0 -0
  80. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/connections/__init__.py +0 -0
  81. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/connections/connections.py +0 -0
  82. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/context/__init__.py +0 -0
  83. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/context/context.py +0 -0
  84. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/context/items.py +0 -0
  85. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/modus/__init__.py +0 -0
  86. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/modus/conversations.py +0 -0
  87. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/modus/modus.py +0 -0
  88. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/scopes/__init__.py +0 -0
  89. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/scopes/conversations.py +0 -0
  90. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/scopes/scopes.py +0 -0
  91. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/suggestions/__init__.py +0 -0
  92. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/suggestions/suggestions.py +0 -0
  93. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/workflows/__init__.py +0 -0
  94. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/workflows/workflow_actions.py +0 -0
  95. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/workflows/workflows.py +0 -0
  96. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/__init__.py +0 -0
  97. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/agent_runs.py +0 -0
  98. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/agents.py +0 -0
  99. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/chat.py +0 -0
  100. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/compose.py +0 -0
  101. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/connections.py +0 -0
  102. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/context_accessors.py +0 -0
  103. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/context_compose.py +0 -0
  104. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/context_values.py +0 -0
  105. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/conversations.py +0 -0
  106. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/json_blob.py +0 -0
  107. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/pagination.py +0 -0
  108. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/runs.py +0 -0
  109. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/skills.py +0 -0
  110. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/suggestions.py +0 -0
  111. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/tool_calls.py +0 -0
  112. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/views.py +0 -0
  113. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/pyproject.toml +0 -0
  114. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/pyrightconfig.json +0 -0
  115. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/requirements.txt +0 -0
  116. {modus_sdk-0.2.0 → modus_sdk-0.2.3}/uv.lock +0 -0
@@ -1,8 +1,14 @@
1
1
  # Changelog
2
2
 
3
- **Current release:** [`0.2.0`](https://pypi.org/project/modus-sdk/0.2.0/).
3
+ **Current release:** [`0.2.2`](https://pypi.org/project/modus-sdk/0.2.2/).
4
4
 
5
5
 
6
+ ## [Unreleased]
7
+
8
+ ## [0.2.3] — 2026-07-20
9
+
10
+ ## [0.2.2] — 2026-07-20
11
+
6
12
  ## [0.2.0] — 2026-07-20
7
13
 
8
14
  ### Added
@@ -34,6 +40,3 @@
34
40
  | Version | Notes |
35
41
  |---------|--------|
36
42
  | 0.1.0–0.1.7 | Internal / TestPyPI previews. Not published to production PyPI. Superseded by `0.2.0`. |
37
-
38
- ## [Unreleased]
39
-
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modus-sdk
3
- Version: 0.2.0
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
@@ -166,6 +166,10 @@ from modus.management import ModusManagement
166
166
  mgmt = ModusManagement() # reads MODUS_API_KEY
167
167
  scope = mgmt.scopes.create(name="Analyst", model="claude-sonnet-5")
168
168
  mgmt.scopes.deploy(scope.id)
169
+
170
+ # Create returns context_item_id (also available as .uid); list/get use .uid
171
+ note = mgmt.context.create_note("Title", "Body")
172
+ print(note.context_item_id, note.uid) # same UUID
169
173
  ```
170
174
 
171
175
  Requires a token with write access to scopes and workflows (same as the Modus UI).
@@ -146,6 +146,10 @@ from modus.management import ModusManagement
146
146
  mgmt = ModusManagement() # reads MODUS_API_KEY
147
147
  scope = mgmt.scopes.create(name="Analyst", model="claude-sonnet-5")
148
148
  mgmt.scopes.deploy(scope.id)
149
+
150
+ # Create returns context_item_id (also available as .uid); list/get use .uid
151
+ note = mgmt.context.create_note("Title", "Body")
152
+ print(note.context_item_id, note.uid) # same UUID
149
153
  ```
150
154
 
151
155
  Requires a token with write access to scopes and workflows (same as the Modus UI).
@@ -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
@@ -21,6 +21,7 @@ from ._exceptions import AuthenticationError
21
21
  _ENV_VAR = "MODUS_API_KEY"
22
22
  _BASE_URL_ENV_VAR = "MODUS_BASE_URL"
23
23
  _AGENT_HOST_ENV_VAR = "MODUS_AGENT_HOST"
24
+ _ORGANIZATION_ID_ENV_VAR = "MODUS_ORGANIZATION_ID"
24
25
  _TIMEOUT_ENV_VAR = "MODUS_TIMEOUT"
25
26
  _MAX_RETRIES_ENV_VAR = "MODUS_MAX_RETRIES"
26
27
  _KEY_PREFIX = "modus_"
@@ -37,13 +38,13 @@ def resolve_api_key(api_key: Optional[str]) -> str:
37
38
  raise AuthenticationError(
38
39
  f"No API key provided. "
39
40
  f"Pass api_key= or set the {_ENV_VAR} environment variable. "
40
- f"Create a token at app.modus.com → Settings → API Tokens."
41
+ f"Create a token at app.getmodus.com → Settings → API Tokens."
41
42
  )
42
43
 
43
44
  if not key.startswith(_KEY_PREFIX):
44
45
  raise AuthenticationError(
45
46
  f"Invalid API key format. Modus API keys start with '{_KEY_PREFIX}'. "
46
- f"Create a token at app.modus.com → Settings → API Tokens."
47
+ f"Create a token at app.getmodus.com → Settings → API Tokens."
47
48
  )
48
49
 
49
50
  return key
@@ -62,11 +63,24 @@ def resolve_agent_host(agent_host: Optional[str]) -> Optional[str]:
62
63
  """
63
64
  Resolve the agent host from an explicit constructor argument, or from the
64
65
  ``MODUS_AGENT_HOST`` environment variable when set. Returns ``None`` if
65
- neither is set (streaming falls back to ``base_url``).
66
+ neither is set (caller uses the production default).
66
67
  """
67
68
  return agent_host or os.environ.get(_AGENT_HOST_ENV_VAR) or None
68
69
 
69
70
 
71
+ def resolve_organization_id(organization_id: Optional[str]) -> Optional[str]:
72
+ """
73
+ Resolve an optional Clerk organization id for agent-host run bodies.
74
+ From ``organization_id=`` or ``MODUS_ORGANIZATION_ID``. Usually omitted —
75
+ agent-service falls back to the PAT principal's org.
76
+ """
77
+ raw = organization_id if organization_id is not None else os.environ.get(_ORGANIZATION_ID_ENV_VAR)
78
+ if raw is None:
79
+ return None
80
+ stripped = raw.strip()
81
+ return stripped or None
82
+
83
+
70
84
  def resolve_timeout(explicit: Union[float, httpx.Timeout, None]) -> Union[float, httpx.Timeout, None]:
71
85
  """
72
86
  Resolve timeout from an explicit argument or the MODUS_TIMEOUT env var.
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  from typing import Any, AsyncIterator, Dict, Iterator, List, Literal, Optional, Union
6
6
  from urllib.parse import quote
7
7
 
8
+ from ._agent_run_request import resolve_agent_run_session_id
8
9
  from ._exceptions import ModusError, RunCancelledError, StreamTimeoutError
9
10
  from ._generated.models import SkillChatRequestDto, SkillChatResponseDto
10
11
  from ._http import AsyncHttpClient, HttpClient
@@ -34,6 +35,11 @@ def _effective_thread_id(thread_id: Optional[str]) -> Optional[str]:
34
35
  return thread_id
35
36
 
36
37
 
38
+ def _resolve_session_id(thread_id: Optional[str]) -> str:
39
+ """Agent-host runs always require sessionId — reuse thread_id or mint a UUID."""
40
+ return resolve_agent_run_session_id(_effective_thread_id(thread_id))
41
+
42
+
37
43
  def _chat_path(
38
44
  resource: str,
39
45
  resource_id: Union[int, str],
@@ -82,28 +88,35 @@ def _agent_runs_body(
82
88
  message: str,
83
89
  model: str,
84
90
  *,
85
- session_id: Optional[str] = None,
91
+ session_id: str,
92
+ organization_id: Optional[str] = None,
86
93
  version: Optional[RunVersion] = None,
87
94
  ) -> Dict[str, Any]:
88
95
  """Build the request body for agent-host runs (streaming) requests.
89
96
 
90
97
  Args:
91
- message: The user message.
92
- model: Model id, e.g. ``"claude-sonnet-5"``.
93
- session_id: Existing conversation/session ID (``thread_id`` on the SDK surface).
94
- version: Run a specific variation — ``"published"`` or ``"draft"``.
98
+ message: The user message.
99
+ model: Model id, e.g. ``"claude-sonnet-5"``.
100
+ session_id: Conversation/session ID (always required by agent-host).
101
+ organization_id: Optional Clerk org id; omit to let the server use the PAT org.
102
+ version: Run a specific variation — ``"published"`` or ``"draft"``.
95
103
  """
96
104
  body: Dict[str, Any] = {
97
105
  "message": message,
98
106
  "config": {"model": model},
107
+ "sessionId": session_id,
99
108
  }
100
- if session_id is not None:
101
- body["sessionId"] = session_id
109
+ if organization_id is not None:
110
+ body["organizationId"] = organization_id
102
111
  if version is not None:
103
112
  body["version"] = version
104
113
  return body
105
114
 
106
115
 
116
+ def _org_from_http(http: Union[HttpClient, AsyncHttpClient]) -> Optional[str]:
117
+ return getattr(http.config, "organization_id", None)
118
+
119
+
107
120
  def _raise_for_event(event: RunEvent) -> RunEvent:
108
121
  if isinstance(event, ErrorEvent):
109
122
  raise ModusError(event.message)
@@ -318,9 +331,13 @@ def modus_stream_session(
318
331
  ) -> ChatStream:
319
332
  """Stream a Modus chat via the agent host's by-reference runs path."""
320
333
  validate_chat_model(model)
321
- session_id = _effective_thread_id(thread_id)
322
334
  path = _agent_host_modus_runs_path()
323
- body = _agent_runs_body(message, model, session_id=session_id)
335
+ body = _agent_runs_body(
336
+ message,
337
+ model,
338
+ session_id=_resolve_session_id(thread_id),
339
+ organization_id=_org_from_http(agent_http),
340
+ )
324
341
  return ChatStream(agent_http, path, body)
325
342
 
326
343
 
@@ -333,9 +350,13 @@ def modus_stream_session_async(
333
350
  ) -> AsyncChatStream:
334
351
  """Async stream a Modus chat via the agent host's by-reference runs path."""
335
352
  validate_chat_model(model)
336
- session_id = _effective_thread_id(thread_id)
337
353
  path = _agent_host_modus_runs_path()
338
- body = _agent_runs_body(message, model, session_id=session_id)
354
+ body = _agent_runs_body(
355
+ message,
356
+ model,
357
+ session_id=_resolve_session_id(thread_id),
358
+ organization_id=_org_from_http(agent_http),
359
+ )
339
360
  return AsyncChatStream(agent_http, path, body)
340
361
 
341
362
 
@@ -351,9 +372,14 @@ def scope_stream_session(
351
372
  """Stream a scope chat via the agent host's by-reference runs path."""
352
373
  validate_id(resource_id, name="scope_id")
353
374
  validate_chat_model(model)
354
- session_id = _effective_thread_id(thread_id)
355
375
  path = format_operation_path("ScopeRunsController_create", {"id": resource_id})
356
- body = _agent_runs_body(message, model, session_id=session_id, version=version)
376
+ body = _agent_runs_body(
377
+ message,
378
+ model,
379
+ session_id=_resolve_session_id(thread_id),
380
+ organization_id=_org_from_http(agent_http),
381
+ version=version,
382
+ )
357
383
  return ChatStream(agent_http, path, body)
358
384
 
359
385
 
@@ -369,9 +395,14 @@ def scope_stream_session_async(
369
395
  """Async stream a scope chat via the agent host's by-reference runs path."""
370
396
  validate_id(resource_id, name="scope_id")
371
397
  validate_chat_model(model)
372
- session_id = _effective_thread_id(thread_id)
373
398
  path = format_operation_path("ScopeRunsController_create", {"id": resource_id})
374
- body = _agent_runs_body(message, model, session_id=session_id, version=version)
399
+ body = _agent_runs_body(
400
+ message,
401
+ model,
402
+ session_id=_resolve_session_id(thread_id),
403
+ organization_id=_org_from_http(agent_http),
404
+ version=version,
405
+ )
375
406
  return AsyncChatStream(agent_http, path, body)
376
407
 
377
408
 
@@ -387,9 +418,14 @@ def workflow_stream_session(
387
418
  """Stream a workflow chat via the agent host's by-reference runs path."""
388
419
  validate_id(resource_id, name="workflow_id")
389
420
  validate_chat_model(model)
390
- session_id = _effective_thread_id(thread_id)
391
421
  path = format_operation_path("WorkflowRunsController_create", {"id": resource_id})
392
- body = _agent_runs_body(message, model, session_id=session_id, version=version)
422
+ body = _agent_runs_body(
423
+ message,
424
+ model,
425
+ session_id=_resolve_session_id(thread_id),
426
+ organization_id=_org_from_http(agent_http),
427
+ version=version,
428
+ )
393
429
  return ChatStream(agent_http, path, body)
394
430
 
395
431
 
@@ -405,7 +441,12 @@ def workflow_stream_session_async(
405
441
  """Async stream a workflow chat via the agent host's by-reference runs path."""
406
442
  validate_id(resource_id, name="workflow_id")
407
443
  validate_chat_model(model)
408
- session_id = _effective_thread_id(thread_id)
409
444
  path = format_operation_path("WorkflowRunsController_create", {"id": resource_id})
410
- body = _agent_runs_body(message, model, session_id=session_id, version=version)
445
+ body = _agent_runs_body(
446
+ message,
447
+ model,
448
+ session_id=_resolve_session_id(thread_id),
449
+ organization_id=_org_from_http(agent_http),
450
+ version=version,
451
+ )
411
452
  return AsyncChatStream(agent_http, path, body)
@@ -16,9 +16,16 @@ from ._auth import (
16
16
  resolve_api_key,
17
17
  resolve_base_url,
18
18
  resolve_max_retries,
19
+ resolve_organization_id,
19
20
  resolve_timeout,
20
21
  )
21
- from ._config import DEFAULT_BASE_URL, DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT, ModusConfig
22
+ from ._config import (
23
+ DEFAULT_AGENT_HOST,
24
+ DEFAULT_BASE_URL,
25
+ DEFAULT_MAX_RETRIES,
26
+ DEFAULT_TIMEOUT,
27
+ ModusConfig,
28
+ )
22
29
  from ._http import AsyncHttpClient, HttpClient
23
30
  from .resources import (
24
31
  AsyncConnectionsResource,
@@ -47,11 +54,13 @@ class Modus:
47
54
  api_key: Your Modus API key. If omitted, reads from the
48
55
  MODUS_API_KEY environment variable.
49
56
  base_url: API origin without a path suffix (default
50
- https://api.modus.com).
51
- agent_host: Agent host origin for streaming runs (e.g.
52
- https://agent.modus.com). If omitted, reads from the
53
- MODUS_AGENT_HOST environment variable. When not set,
54
- streaming falls back to ``base_url``.
57
+ https://api.getmodus.com).
58
+ agent_host: Agent host origin for streaming runs (default
59
+ https://agent.getmodus.com). Override via argument or
60
+ MODUS_AGENT_HOST.
61
+ organization_id: Optional Clerk org id for agent-host run bodies.
62
+ Usually omit — agent-service uses the PAT principal org.
63
+ Override via MODUS_ORGANIZATION_ID when needed.
55
64
  base_urls: Optional service-specific API origins.
56
65
  timeout: Request timeout as seconds or httpx.Timeout. Default is
57
66
  5s connect and 300s read.
@@ -81,13 +90,20 @@ class Modus:
81
90
  *,
82
91
  base_url: Optional[str] = None,
83
92
  agent_host: Optional[str] = None,
93
+ organization_id: Optional[str] = None,
84
94
  base_urls: Optional[Mapping[str, str]] = None,
85
95
  timeout: Union[float, httpx.Timeout, None] = None,
86
96
  max_retries: Optional[int] = None,
87
97
  ) -> None:
88
98
  resolved_key = resolve_api_key(api_key)
89
99
  resolved_url = resolve_base_url(base_url) or DEFAULT_BASE_URL
90
- resolved_agent_host = resolve_agent_host(agent_host)
100
+ # Same precedence as TS: base_urls['agent-service'] drives streaming too.
101
+ resolved_agent_host = (
102
+ (base_urls or {}).get("agent-service")
103
+ or resolve_agent_host(agent_host)
104
+ or DEFAULT_AGENT_HOST
105
+ )
106
+ resolved_org = resolve_organization_id(organization_id)
91
107
  _t = resolve_timeout(timeout)
92
108
  resolved_timeout = _t if _t is not None else DEFAULT_TIMEOUT
93
109
  resolved_retries = resolve_max_retries(max_retries)
@@ -99,22 +115,20 @@ class Modus:
99
115
  timeout=resolved_timeout,
100
116
  max_retries=resolved_retries,
101
117
  agent_host=resolved_agent_host,
118
+ organization_id=resolved_org,
102
119
  base_urls=base_urls or {},
103
120
  )
104
121
  self._http = HttpClient(self._config)
105
122
  # Dedicated HTTP client for streaming to the agent host.
106
- # When agent_host is not configured, _agent_http shares the same
107
- # base_url as _http so streaming falls back gracefully.
108
- if resolved_agent_host is not None:
109
- _agent_config = ModusConfig(
110
- api_key=resolved_key,
111
- base_url=resolved_agent_host,
112
- timeout=resolved_timeout,
113
- max_retries=resolved_retries,
114
- )
115
- self._agent_http: HttpClient = HttpClient(_agent_config)
116
- else:
117
- self._agent_http = self._http
123
+ _agent_config = ModusConfig(
124
+ api_key=resolved_key,
125
+ base_url=resolved_agent_host,
126
+ timeout=resolved_timeout,
127
+ max_retries=resolved_retries,
128
+ agent_host=resolved_agent_host,
129
+ organization_id=resolved_org,
130
+ )
131
+ self._agent_http: HttpClient = HttpClient(_agent_config)
118
132
 
119
133
  self.scopes: ScopesResource = ScopesResource(self._http, self._agent_http, self._config)
120
134
  self.modus: ModusResource = ModusResource(self._http, self._agent_http, self._config)
@@ -148,11 +162,11 @@ class AsyncModus:
148
162
  Args:
149
163
  api_key: Your Modus API key. If omitted, reads from MODUS_API_KEY.
150
164
  base_url: API origin without a path suffix (default
151
- https://api.modus.com).
152
- agent_host: Agent host origin for streaming runs (e.g.
153
- https://agent.modus.com). If omitted, reads from the
154
- MODUS_AGENT_HOST environment variable. When not set,
155
- streaming falls back to ``base_url``.
165
+ https://api.getmodus.com).
166
+ agent_host: Agent host origin for streaming runs (default
167
+ https://agent.getmodus.com). Override via argument or
168
+ MODUS_AGENT_HOST.
169
+ organization_id: Optional Clerk org id for agent-host run bodies.
156
170
  base_urls: Optional service-specific API origins.
157
171
  timeout: Request timeout as seconds or httpx.Timeout. Default is
158
172
  5s connect and 300s read.
@@ -176,13 +190,20 @@ class AsyncModus:
176
190
  *,
177
191
  base_url: Optional[str] = None,
178
192
  agent_host: Optional[str] = None,
193
+ organization_id: Optional[str] = None,
179
194
  base_urls: Optional[Mapping[str, str]] = None,
180
195
  timeout: Union[float, httpx.Timeout, None] = None,
181
196
  max_retries: Optional[int] = None,
182
197
  ) -> None:
183
198
  resolved_key = resolve_api_key(api_key)
184
199
  resolved_url = resolve_base_url(base_url) or DEFAULT_BASE_URL
185
- resolved_agent_host = resolve_agent_host(agent_host)
200
+ # Same precedence as TS: base_urls['agent-service'] drives streaming too.
201
+ resolved_agent_host = (
202
+ (base_urls or {}).get("agent-service")
203
+ or resolve_agent_host(agent_host)
204
+ or DEFAULT_AGENT_HOST
205
+ )
206
+ resolved_org = resolve_organization_id(organization_id)
186
207
  _t = resolve_timeout(timeout)
187
208
  resolved_timeout = _t if _t is not None else DEFAULT_TIMEOUT
188
209
  resolved_retries = resolve_max_retries(max_retries)
@@ -194,20 +215,19 @@ class AsyncModus:
194
215
  timeout=resolved_timeout,
195
216
  max_retries=resolved_retries,
196
217
  agent_host=resolved_agent_host,
218
+ organization_id=resolved_org,
197
219
  base_urls=base_urls or {},
198
220
  )
199
221
  self._http = AsyncHttpClient(self._config)
200
- # Dedicated HTTP client for streaming to the agent host.
201
- if resolved_agent_host is not None:
202
- _agent_config = ModusConfig(
203
- api_key=resolved_key,
204
- base_url=resolved_agent_host,
205
- timeout=resolved_timeout,
206
- max_retries=resolved_retries,
207
- )
208
- self._agent_http: AsyncHttpClient = AsyncHttpClient(_agent_config)
209
- else:
210
- self._agent_http = self._http
222
+ _agent_config = ModusConfig(
223
+ api_key=resolved_key,
224
+ base_url=resolved_agent_host,
225
+ timeout=resolved_timeout,
226
+ max_retries=resolved_retries,
227
+ agent_host=resolved_agent_host,
228
+ organization_id=resolved_org,
229
+ )
230
+ self._agent_http: AsyncHttpClient = AsyncHttpClient(_agent_config)
211
231
 
212
232
  self.scopes: AsyncScopesResource = AsyncScopesResource(
213
233
  self._http, self._agent_http, self._config
@@ -13,8 +13,9 @@ from typing import Mapping, Optional, Union
13
13
 
14
14
  import httpx
15
15
 
16
- # Defaults — override via Modus(base_url=..., timeout=..., max_retries=...)
17
- DEFAULT_BASE_URL = "https://api.modus.com"
16
+ # Defaults — override via Modus(base_url=..., agent_host=..., timeout=..., max_retries=...)
17
+ DEFAULT_BASE_URL = "https://api.getmodus.com"
18
+ DEFAULT_AGENT_HOST = "https://agent.getmodus.com"
18
19
 
19
20
 
20
21
  def _default_timeout() -> httpx.Timeout:
@@ -48,6 +49,7 @@ class ModusConfig:
48
49
  timeout: Union[float, httpx.Timeout] = field(default_factory=_default_timeout)
49
50
  max_retries: int = DEFAULT_MAX_RETRIES
50
51
  agent_host: Optional[str] = None
52
+ organization_id: Optional[str] = None
51
53
  base_urls: Mapping[str, str] = field(default_factory=dict)
52
54
 
53
55
  def __post_init__(self) -> None:
@@ -62,6 +64,8 @@ class ModusConfig:
62
64
  object.__setattr__(self, "base_url", _normalize_base_url(self.base_url))
63
65
  if self.agent_host is not None:
64
66
  object.__setattr__(self, "agent_host", _normalize_base_url(self.agent_host))
67
+ if self.organization_id is not None:
68
+ object.__setattr__(self, "organization_id", self.organization_id.strip() or None)
65
69
  object.__setattr__(self, "base_urls", _normalize_base_urls(self.base_urls))
66
70
 
67
71
  def __repr__(self) -> str:
@@ -68,7 +68,7 @@ class AuthenticationError(ModusError):
68
68
  The API key is missing, invalid, or expired (HTTP 401).
69
69
 
70
70
  Check that MODUS_API_KEY is set correctly, or pass api_key= explicitly.
71
- Create a token at app.modus.com → Settings → API Tokens.
71
+ Create a token at app.getmodus.com → Settings → API Tokens.
72
72
  """
73
73
  def __init__(self, message: str, **kwargs: Any) -> None:
74
74
  super().__init__(message, status_code=401, **kwargs)