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.
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/CHANGELOG.md +7 -4
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/PKG-INFO +5 -1
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/README.md +4 -0
- modus_sdk-0.2.3/modus/_agent_run_request.py +22 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_auth.py +17 -3
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_chat.py +60 -19
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_client.py +56 -36
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_config.py +6 -2
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_exceptions.py +1 -1
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_generated/operations.py +87 -87
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_http.py +60 -2
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_version.py +2 -2
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/_client.py +2 -2
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/workflows/runs.py +3 -1
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/context.py +9 -1
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/project.json +9 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/.gitignore +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/CONTRIBUTING.md +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/LICENSE +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/assets/modus-logo.png +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/assets/modus-logo.svg +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/examples/apps/modus_skill_chat/.streamlit/config.toml +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/examples/apps/modus_skill_chat/README.md +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/examples/apps/modus_skill_chat/app.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/examples/apps/modus_skill_chat/pyproject.toml +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/examples/scripts/chat.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/examples/scripts/manage_skill.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/examples/scripts/modus_chat.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/examples/scripts/quickstart.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/__init__.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_deprecation.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_generated/__init__.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_generated/models.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_openapi_invoke.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_pagination.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_query.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_retry.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_streaming.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/_validation.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/__init__.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/__init__.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/_access_config_write.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/context/__init__.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/context/_content_merge.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/context/context.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/context/custom_items.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/context/items.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/organization.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/scopes/__init__.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/scopes/_update_body.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/scopes/conversations.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/scopes/evaluations.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/scopes/memories.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/scopes/scopes.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/scopes/supervision.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/usage.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/users.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/workflows/__init__.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/workflows/_update_body.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/workflows/interfaces.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/resources/workflows/workflows.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/types/__init__.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/types/_guardrails_access.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/types/_serialize.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/types/agent_selection.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/types/agents.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/types/context.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/types/conversations.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/types/evaluations.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/types/json_blob.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/types/memories.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/types/ownership.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/types/skills.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/types/toolset.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/types/triggers.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/types/usage.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/management/types/workflow.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/py.typed +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/__init__.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/connections/__init__.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/connections/connections.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/context/__init__.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/context/context.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/context/items.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/modus/__init__.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/modus/conversations.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/modus/modus.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/scopes/__init__.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/scopes/conversations.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/scopes/scopes.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/suggestions/__init__.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/suggestions/suggestions.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/workflows/__init__.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/workflows/workflow_actions.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/resources/workflows/workflows.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/__init__.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/agent_runs.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/agents.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/chat.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/compose.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/connections.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/context_accessors.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/context_compose.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/context_values.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/conversations.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/json_blob.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/pagination.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/runs.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/skills.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/suggestions.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/tool_calls.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/modus/types/views.py +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/pyproject.toml +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/pyrightconfig.json +0 -0
- {modus_sdk-0.2.0 → modus_sdk-0.2.3}/requirements.txt +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 (
|
|
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:
|
|
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:
|
|
92
|
-
model:
|
|
93
|
-
session_id:
|
|
94
|
-
|
|
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
|
|
101
|
-
body["
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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.
|
|
51
|
-
agent_host: Agent host origin for streaming runs (
|
|
52
|
-
https://agent.
|
|
53
|
-
MODUS_AGENT_HOST
|
|
54
|
-
|
|
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
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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.
|
|
152
|
-
agent_host: Agent host origin for streaming runs (
|
|
153
|
-
https://agent.
|
|
154
|
-
MODUS_AGENT_HOST
|
|
155
|
-
|
|
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
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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.
|
|
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.
|
|
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)
|