modus-sdk 0.2.0__tar.gz → 0.2.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 (115) hide show
  1. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/CHANGELOG.md +5 -4
  2. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/PKG-INFO +5 -1
  3. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/README.md +4 -0
  4. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/_auth.py +17 -3
  5. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/_chat.py +61 -19
  6. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/_client.py +56 -36
  7. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/_config.py +6 -2
  8. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/_exceptions.py +1 -1
  9. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/_generated/operations.py +87 -87
  10. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/_version.py +2 -2
  11. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/_client.py +2 -2
  12. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/types/context.py +9 -1
  13. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/project.json +9 -0
  14. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/.gitignore +0 -0
  15. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/CONTRIBUTING.md +0 -0
  16. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/LICENSE +0 -0
  17. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/assets/modus-logo.png +0 -0
  18. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/assets/modus-logo.svg +0 -0
  19. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/examples/apps/modus_skill_chat/.streamlit/config.toml +0 -0
  20. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/examples/apps/modus_skill_chat/README.md +0 -0
  21. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/examples/apps/modus_skill_chat/app.py +0 -0
  22. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/examples/apps/modus_skill_chat/pyproject.toml +0 -0
  23. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/examples/scripts/chat.py +0 -0
  24. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/examples/scripts/manage_skill.py +0 -0
  25. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/examples/scripts/modus_chat.py +0 -0
  26. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/examples/scripts/quickstart.py +0 -0
  27. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/__init__.py +0 -0
  28. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/_deprecation.py +0 -0
  29. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/_generated/__init__.py +0 -0
  30. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/_generated/models.py +0 -0
  31. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/_http.py +0 -0
  32. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/_openapi_invoke.py +0 -0
  33. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/_pagination.py +0 -0
  34. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/_query.py +0 -0
  35. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/_retry.py +0 -0
  36. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/_streaming.py +0 -0
  37. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/_validation.py +0 -0
  38. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/__init__.py +0 -0
  39. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/resources/__init__.py +0 -0
  40. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/resources/_access_config_write.py +0 -0
  41. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/resources/context/__init__.py +0 -0
  42. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/resources/context/_content_merge.py +0 -0
  43. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/resources/context/context.py +0 -0
  44. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/resources/context/custom_items.py +0 -0
  45. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/resources/context/items.py +0 -0
  46. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/resources/organization.py +0 -0
  47. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/resources/scopes/__init__.py +0 -0
  48. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/resources/scopes/_update_body.py +0 -0
  49. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/resources/scopes/conversations.py +0 -0
  50. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/resources/scopes/evaluations.py +0 -0
  51. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/resources/scopes/memories.py +0 -0
  52. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/resources/scopes/scopes.py +0 -0
  53. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/resources/scopes/supervision.py +0 -0
  54. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/resources/usage.py +0 -0
  55. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/resources/users.py +0 -0
  56. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/resources/workflows/__init__.py +0 -0
  57. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/resources/workflows/_update_body.py +0 -0
  58. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/resources/workflows/interfaces.py +0 -0
  59. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/resources/workflows/workflows.py +0 -0
  60. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/types/__init__.py +0 -0
  61. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/types/_guardrails_access.py +0 -0
  62. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/types/_serialize.py +0 -0
  63. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/types/agent_selection.py +0 -0
  64. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/types/agents.py +0 -0
  65. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/types/context.py +0 -0
  66. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/types/conversations.py +0 -0
  67. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/types/evaluations.py +0 -0
  68. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/types/json_blob.py +0 -0
  69. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/types/memories.py +0 -0
  70. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/types/ownership.py +0 -0
  71. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/types/skills.py +0 -0
  72. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/types/toolset.py +0 -0
  73. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/types/triggers.py +0 -0
  74. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/types/usage.py +0 -0
  75. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/management/types/workflow.py +0 -0
  76. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/py.typed +0 -0
  77. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/resources/__init__.py +0 -0
  78. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/resources/connections/__init__.py +0 -0
  79. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/resources/connections/connections.py +0 -0
  80. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/resources/context/__init__.py +0 -0
  81. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/resources/context/context.py +0 -0
  82. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/resources/context/items.py +0 -0
  83. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/resources/modus/__init__.py +0 -0
  84. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/resources/modus/conversations.py +0 -0
  85. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/resources/modus/modus.py +0 -0
  86. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/resources/scopes/__init__.py +0 -0
  87. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/resources/scopes/conversations.py +0 -0
  88. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/resources/scopes/scopes.py +0 -0
  89. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/resources/suggestions/__init__.py +0 -0
  90. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/resources/suggestions/suggestions.py +0 -0
  91. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/resources/workflows/__init__.py +0 -0
  92. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/resources/workflows/runs.py +0 -0
  93. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/resources/workflows/workflow_actions.py +0 -0
  94. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/resources/workflows/workflows.py +0 -0
  95. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/types/__init__.py +0 -0
  96. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/types/agent_runs.py +0 -0
  97. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/types/agents.py +0 -0
  98. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/types/chat.py +0 -0
  99. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/types/compose.py +0 -0
  100. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/types/connections.py +0 -0
  101. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/types/context_accessors.py +0 -0
  102. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/types/context_compose.py +0 -0
  103. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/types/context_values.py +0 -0
  104. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/types/conversations.py +0 -0
  105. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/types/json_blob.py +0 -0
  106. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/types/pagination.py +0 -0
  107. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/types/runs.py +0 -0
  108. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/types/skills.py +0 -0
  109. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/types/suggestions.py +0 -0
  110. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/types/tool_calls.py +0 -0
  111. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/modus/types/views.py +0 -0
  112. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/pyproject.toml +0 -0
  113. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/pyrightconfig.json +0 -0
  114. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/requirements.txt +0 -0
  115. {modus_sdk-0.2.0 → modus_sdk-0.2.2}/uv.lock +0 -0
@@ -1,8 +1,12 @@
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.2] — 2026-07-20
9
+
6
10
  ## [0.2.0] — 2026-07-20
7
11
 
8
12
  ### Added
@@ -34,6 +38,3 @@
34
38
  | Version | Notes |
35
39
  |---------|--------|
36
40
  | 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.2
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).
@@ -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.
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import uuid
5
6
  from typing import Any, AsyncIterator, Dict, Iterator, List, Literal, Optional, Union
6
7
  from urllib.parse import quote
7
8
 
@@ -34,6 +35,12 @@ 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
+ tid = _effective_thread_id(thread_id)
41
+ return tid if tid is not None else str(uuid.uuid4())
42
+
43
+
37
44
  def _chat_path(
38
45
  resource: str,
39
46
  resource_id: Union[int, str],
@@ -82,28 +89,35 @@ def _agent_runs_body(
82
89
  message: str,
83
90
  model: str,
84
91
  *,
85
- session_id: Optional[str] = None,
92
+ session_id: str,
93
+ organization_id: Optional[str] = None,
86
94
  version: Optional[RunVersion] = None,
87
95
  ) -> Dict[str, Any]:
88
96
  """Build the request body for agent-host runs (streaming) requests.
89
97
 
90
98
  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"``.
99
+ message: The user message.
100
+ model: Model id, e.g. ``"claude-sonnet-5"``.
101
+ session_id: Conversation/session ID (always required by agent-host).
102
+ organization_id: Optional Clerk org id; omit to let the server use the PAT org.
103
+ version: Run a specific variation — ``"published"`` or ``"draft"``.
95
104
  """
96
105
  body: Dict[str, Any] = {
97
106
  "message": message,
98
107
  "config": {"model": model},
108
+ "sessionId": session_id,
99
109
  }
100
- if session_id is not None:
101
- body["sessionId"] = session_id
110
+ if organization_id is not None:
111
+ body["organizationId"] = organization_id
102
112
  if version is not None:
103
113
  body["version"] = version
104
114
  return body
105
115
 
106
116
 
117
+ def _org_from_http(http: Union[HttpClient, AsyncHttpClient]) -> Optional[str]:
118
+ return getattr(http.config, "organization_id", None)
119
+
120
+
107
121
  def _raise_for_event(event: RunEvent) -> RunEvent:
108
122
  if isinstance(event, ErrorEvent):
109
123
  raise ModusError(event.message)
@@ -318,9 +332,13 @@ def modus_stream_session(
318
332
  ) -> ChatStream:
319
333
  """Stream a Modus chat via the agent host's by-reference runs path."""
320
334
  validate_chat_model(model)
321
- session_id = _effective_thread_id(thread_id)
322
335
  path = _agent_host_modus_runs_path()
323
- body = _agent_runs_body(message, model, session_id=session_id)
336
+ body = _agent_runs_body(
337
+ message,
338
+ model,
339
+ session_id=_resolve_session_id(thread_id),
340
+ organization_id=_org_from_http(agent_http),
341
+ )
324
342
  return ChatStream(agent_http, path, body)
325
343
 
326
344
 
@@ -333,9 +351,13 @@ def modus_stream_session_async(
333
351
  ) -> AsyncChatStream:
334
352
  """Async stream a Modus chat via the agent host's by-reference runs path."""
335
353
  validate_chat_model(model)
336
- session_id = _effective_thread_id(thread_id)
337
354
  path = _agent_host_modus_runs_path()
338
- body = _agent_runs_body(message, model, session_id=session_id)
355
+ body = _agent_runs_body(
356
+ message,
357
+ model,
358
+ session_id=_resolve_session_id(thread_id),
359
+ organization_id=_org_from_http(agent_http),
360
+ )
339
361
  return AsyncChatStream(agent_http, path, body)
340
362
 
341
363
 
@@ -351,9 +373,14 @@ def scope_stream_session(
351
373
  """Stream a scope chat via the agent host's by-reference runs path."""
352
374
  validate_id(resource_id, name="scope_id")
353
375
  validate_chat_model(model)
354
- session_id = _effective_thread_id(thread_id)
355
376
  path = format_operation_path("ScopeRunsController_create", {"id": resource_id})
356
- body = _agent_runs_body(message, model, session_id=session_id, version=version)
377
+ body = _agent_runs_body(
378
+ message,
379
+ model,
380
+ session_id=_resolve_session_id(thread_id),
381
+ organization_id=_org_from_http(agent_http),
382
+ version=version,
383
+ )
357
384
  return ChatStream(agent_http, path, body)
358
385
 
359
386
 
@@ -369,9 +396,14 @@ def scope_stream_session_async(
369
396
  """Async stream a scope chat via the agent host's by-reference runs path."""
370
397
  validate_id(resource_id, name="scope_id")
371
398
  validate_chat_model(model)
372
- session_id = _effective_thread_id(thread_id)
373
399
  path = format_operation_path("ScopeRunsController_create", {"id": resource_id})
374
- body = _agent_runs_body(message, model, session_id=session_id, version=version)
400
+ body = _agent_runs_body(
401
+ message,
402
+ model,
403
+ session_id=_resolve_session_id(thread_id),
404
+ organization_id=_org_from_http(agent_http),
405
+ version=version,
406
+ )
375
407
  return AsyncChatStream(agent_http, path, body)
376
408
 
377
409
 
@@ -387,9 +419,14 @@ def workflow_stream_session(
387
419
  """Stream a workflow chat via the agent host's by-reference runs path."""
388
420
  validate_id(resource_id, name="workflow_id")
389
421
  validate_chat_model(model)
390
- session_id = _effective_thread_id(thread_id)
391
422
  path = format_operation_path("WorkflowRunsController_create", {"id": resource_id})
392
- body = _agent_runs_body(message, model, session_id=session_id, version=version)
423
+ body = _agent_runs_body(
424
+ message,
425
+ model,
426
+ session_id=_resolve_session_id(thread_id),
427
+ organization_id=_org_from_http(agent_http),
428
+ version=version,
429
+ )
393
430
  return ChatStream(agent_http, path, body)
394
431
 
395
432
 
@@ -405,7 +442,12 @@ def workflow_stream_session_async(
405
442
  """Async stream a workflow chat via the agent host's by-reference runs path."""
406
443
  validate_id(resource_id, name="workflow_id")
407
444
  validate_chat_model(model)
408
- session_id = _effective_thread_id(thread_id)
409
445
  path = format_operation_path("WorkflowRunsController_create", {"id": resource_id})
410
- body = _agent_runs_body(message, model, session_id=session_id, version=version)
446
+ body = _agent_runs_body(
447
+ message,
448
+ model,
449
+ session_id=_resolve_session_id(thread_id),
450
+ organization_id=_org_from_http(agent_http),
451
+ version=version,
452
+ )
411
453
  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)