modus-sdk 0.2.0__py3-none-any.whl

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 (98) hide show
  1. modus/__init__.py +144 -0
  2. modus/_auth.py +111 -0
  3. modus/_chat.py +411 -0
  4. modus/_client.py +240 -0
  5. modus/_config.py +89 -0
  6. modus/_deprecation.py +29 -0
  7. modus/_exceptions.py +149 -0
  8. modus/_generated/__init__.py +341 -0
  9. modus/_generated/models.py +3182 -0
  10. modus/_generated/operations.py +891 -0
  11. modus/_http.py +471 -0
  12. modus/_openapi_invoke.py +137 -0
  13. modus/_pagination.py +175 -0
  14. modus/_query.py +38 -0
  15. modus/_retry.py +123 -0
  16. modus/_streaming.py +115 -0
  17. modus/_validation.py +61 -0
  18. modus/_version.py +24 -0
  19. modus/management/__init__.py +74 -0
  20. modus/management/_client.py +168 -0
  21. modus/management/resources/__init__.py +21 -0
  22. modus/management/resources/_access_config_write.py +31 -0
  23. modus/management/resources/context/__init__.py +0 -0
  24. modus/management/resources/context/_content_merge.py +128 -0
  25. modus/management/resources/context/context.py +362 -0
  26. modus/management/resources/context/custom_items.py +318 -0
  27. modus/management/resources/context/items.py +234 -0
  28. modus/management/resources/organization.py +52 -0
  29. modus/management/resources/scopes/__init__.py +0 -0
  30. modus/management/resources/scopes/_update_body.py +38 -0
  31. modus/management/resources/scopes/conversations.py +177 -0
  32. modus/management/resources/scopes/evaluations.py +142 -0
  33. modus/management/resources/scopes/memories.py +273 -0
  34. modus/management/resources/scopes/scopes.py +764 -0
  35. modus/management/resources/scopes/supervision.py +85 -0
  36. modus/management/resources/usage.py +92 -0
  37. modus/management/resources/users.py +56 -0
  38. modus/management/resources/workflows/__init__.py +0 -0
  39. modus/management/resources/workflows/_update_body.py +29 -0
  40. modus/management/resources/workflows/interfaces.py +136 -0
  41. modus/management/resources/workflows/workflows.py +647 -0
  42. modus/management/types/__init__.py +57 -0
  43. modus/management/types/_guardrails_access.py +33 -0
  44. modus/management/types/_serialize.py +52 -0
  45. modus/management/types/agent_selection.py +66 -0
  46. modus/management/types/agents.py +5 -0
  47. modus/management/types/context.py +25 -0
  48. modus/management/types/conversations.py +5 -0
  49. modus/management/types/evaluations.py +25 -0
  50. modus/management/types/json_blob.py +51 -0
  51. modus/management/types/memories.py +69 -0
  52. modus/management/types/ownership.py +9 -0
  53. modus/management/types/skills.py +5 -0
  54. modus/management/types/toolset.py +236 -0
  55. modus/management/types/triggers.py +284 -0
  56. modus/management/types/usage.py +13 -0
  57. modus/management/types/workflow.py +45 -0
  58. modus/py.typed +0 -0
  59. modus/resources/__init__.py +21 -0
  60. modus/resources/connections/__init__.py +3 -0
  61. modus/resources/connections/connections.py +189 -0
  62. modus/resources/context/__init__.py +3 -0
  63. modus/resources/context/context.py +48 -0
  64. modus/resources/context/items.py +410 -0
  65. modus/resources/modus/__init__.py +3 -0
  66. modus/resources/modus/conversations.py +144 -0
  67. modus/resources/modus/modus.py +138 -0
  68. modus/resources/scopes/__init__.py +3 -0
  69. modus/resources/scopes/conversations.py +213 -0
  70. modus/resources/scopes/scopes.py +431 -0
  71. modus/resources/suggestions/__init__.py +3 -0
  72. modus/resources/suggestions/suggestions.py +207 -0
  73. modus/resources/workflows/__init__.py +15 -0
  74. modus/resources/workflows/runs.py +833 -0
  75. modus/resources/workflows/workflow_actions.py +122 -0
  76. modus/resources/workflows/workflows.py +358 -0
  77. modus/types/__init__.py +80 -0
  78. modus/types/agent_runs.py +70 -0
  79. modus/types/agents.py +17 -0
  80. modus/types/chat.py +9 -0
  81. modus/types/compose.py +21 -0
  82. modus/types/connections.py +9 -0
  83. modus/types/context.py +28 -0
  84. modus/types/context_accessors.py +89 -0
  85. modus/types/context_compose.py +18 -0
  86. modus/types/context_values.py +21 -0
  87. modus/types/conversations.py +30 -0
  88. modus/types/json_blob.py +24 -0
  89. modus/types/pagination.py +142 -0
  90. modus/types/runs.py +71 -0
  91. modus/types/skills.py +16 -0
  92. modus/types/suggestions.py +16 -0
  93. modus/types/tool_calls.py +34 -0
  94. modus/types/views.py +6 -0
  95. modus_sdk-0.2.0.dist-info/METADATA +225 -0
  96. modus_sdk-0.2.0.dist-info/RECORD +98 -0
  97. modus_sdk-0.2.0.dist-info/WHEEL +4 -0
  98. modus_sdk-0.2.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,168 @@
1
+ """
2
+ ModusManagement — synchronous client for managing Modus scopes, workflows, and context.
3
+ AsyncModusManagement — asynchronous client for the same.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import Optional, Union
9
+
10
+ import httpx
11
+
12
+ from modus._auth import resolve_api_key, resolve_base_url, resolve_max_retries, resolve_timeout
13
+ from modus._config import DEFAULT_BASE_URL, DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT, ModusConfig
14
+ from modus._http import AsyncHttpClient, HttpClient
15
+
16
+ from .resources import (
17
+ AsyncContextResource,
18
+ AsyncOrganizationResource,
19
+ AsyncScopesResource,
20
+ AsyncUsageResource,
21
+ AsyncUsersResource,
22
+ AsyncWorkflowsResource,
23
+ ContextResource,
24
+ OrganizationResource,
25
+ ScopesResource,
26
+ UsageResource,
27
+ UsersResource,
28
+ WorkflowsResource,
29
+ )
30
+
31
+
32
+ class ModusManagement:
33
+ """
34
+ Synchronous Modus management client.
35
+
36
+ Instantiate once and reuse. Thread-safe for concurrent reads.
37
+
38
+ Args:
39
+ api_key: Your Modus API key (requires scopes:write + workflows:write scopes).
40
+ Falls back to MODUS_API_KEY env var.
41
+ base_url: API origin without a path suffix (default
42
+ https://api.modus.com).
43
+ timeout: Request timeout as seconds or httpx.Timeout. Default is
44
+ 5s connect and 300s read.
45
+ max_retries: Retries on 429/5xx (default 2).
46
+
47
+ Example:
48
+ from modus.management import ModusManagement
49
+
50
+ client = ModusManagement(api_key="modus_xxx")
51
+
52
+ scope = client.scopes.create(
53
+ name="Snowflake Analyst",
54
+ instructions=["Answer data questions using Snowflake."],
55
+ model="gpt-4o",
56
+ )
57
+ client.scopes.deploy(scope.id)
58
+ """
59
+
60
+ def __init__(
61
+ self,
62
+ api_key: Optional[str] = None,
63
+ *,
64
+ base_url: Optional[str] = None,
65
+ timeout: Union[float, httpx.Timeout, None] = None,
66
+ max_retries: Optional[int] = None,
67
+ ) -> None:
68
+ resolved_key = resolve_api_key(api_key)
69
+ resolved_url = resolve_base_url(base_url) or DEFAULT_BASE_URL
70
+ _t = resolve_timeout(timeout)
71
+ resolved_timeout = _t if _t is not None else DEFAULT_TIMEOUT
72
+ resolved_retries = resolve_max_retries(max_retries)
73
+ if resolved_retries is None:
74
+ resolved_retries = DEFAULT_MAX_RETRIES
75
+
76
+ self._config = ModusConfig(
77
+ api_key=resolved_key,
78
+ base_url=resolved_url,
79
+ timeout=resolved_timeout,
80
+ max_retries=resolved_retries,
81
+ )
82
+ self._http = HttpClient(self._config)
83
+
84
+ self.scopes: ScopesResource = ScopesResource(self._http, self._config)
85
+ self.workflows: WorkflowsResource = WorkflowsResource(self._http, self._config)
86
+ self.context: ContextResource = ContextResource(self._http, self._config)
87
+ self.usage: UsageResource = UsageResource(self._http, self._config)
88
+ self.users: UsersResource = UsersResource(self._http, self._config)
89
+ self.organization: OrganizationResource = OrganizationResource(self._http, self._config)
90
+
91
+ def close(self) -> None:
92
+ """Close the underlying HTTP connection pool."""
93
+ self._http.close()
94
+
95
+ def __enter__(self) -> "ModusManagement":
96
+ return self
97
+
98
+ def __exit__(self, *args: object) -> None:
99
+ self.close()
100
+
101
+ def __repr__(self) -> str:
102
+ return f"ModusManagement(base_url={self._config.base_url!r})"
103
+
104
+
105
+ class AsyncModusManagement:
106
+ """
107
+ Asynchronous Modus management client.
108
+
109
+ Args:
110
+ api_key: Modus API key with write scopes. Falls back to MODUS_API_KEY.
111
+ base_url: API origin without a path suffix (default
112
+ https://api.modus.com).
113
+ timeout: Request timeout as seconds or httpx.Timeout. Default is
114
+ 5s connect and 300s read.
115
+ max_retries: Retries on 429/5xx (default 2).
116
+
117
+ Example:
118
+ client = AsyncModusManagement(api_key="modus_xxx")
119
+ async def main():
120
+ scope = await client.scopes.create(name="Analyst", model="gpt-4o")
121
+ await client.aclose()
122
+ """
123
+
124
+ def __init__(
125
+ self,
126
+ api_key: Optional[str] = None,
127
+ *,
128
+ base_url: Optional[str] = None,
129
+ timeout: Union[float, httpx.Timeout, None] = None,
130
+ max_retries: Optional[int] = None,
131
+ ) -> None:
132
+ resolved_key = resolve_api_key(api_key)
133
+ resolved_url = resolve_base_url(base_url) or DEFAULT_BASE_URL
134
+ _t = resolve_timeout(timeout)
135
+ resolved_timeout = _t if _t is not None else DEFAULT_TIMEOUT
136
+ resolved_retries = resolve_max_retries(max_retries)
137
+ if resolved_retries is None:
138
+ resolved_retries = DEFAULT_MAX_RETRIES
139
+
140
+ self._config = ModusConfig(
141
+ api_key=resolved_key,
142
+ base_url=resolved_url,
143
+ timeout=resolved_timeout,
144
+ max_retries=resolved_retries,
145
+ )
146
+ self._http = AsyncHttpClient(self._config)
147
+
148
+ self.scopes: AsyncScopesResource = AsyncScopesResource(self._http, self._config)
149
+ self.workflows: AsyncWorkflowsResource = AsyncWorkflowsResource(self._http, self._config)
150
+ self.context: AsyncContextResource = AsyncContextResource(self._http, self._config)
151
+ self.usage: AsyncUsageResource = AsyncUsageResource(self._http, self._config)
152
+ self.users: AsyncUsersResource = AsyncUsersResource(self._http, self._config)
153
+ self.organization: AsyncOrganizationResource = AsyncOrganizationResource(
154
+ self._http, self._config
155
+ )
156
+
157
+ async def aclose(self) -> None:
158
+ """Close the underlying async HTTP connection pool."""
159
+ await self._http.aclose()
160
+
161
+ async def __aenter__(self) -> "AsyncModusManagement":
162
+ return self
163
+
164
+ async def __aexit__(self, *args: object) -> None:
165
+ await self.aclose()
166
+
167
+ def __repr__(self) -> str:
168
+ return f"AsyncModusManagement(base_url={self._config.base_url!r})"
@@ -0,0 +1,21 @@
1
+ from .context.context import AsyncContextResource, ContextResource
2
+ from .organization import AsyncOrganizationResource, OrganizationResource
3
+ from .scopes.scopes import AsyncScopesResource, ScopesResource
4
+ from .usage import AsyncUsageResource, UsageResource
5
+ from .users import AsyncUsersResource, UsersResource
6
+ from .workflows.workflows import AsyncWorkflowsResource, WorkflowsResource
7
+
8
+ __all__ = [
9
+ "AsyncContextResource",
10
+ "AsyncOrganizationResource",
11
+ "AsyncScopesResource",
12
+ "AsyncUsageResource",
13
+ "AsyncUsersResource",
14
+ "AsyncWorkflowsResource",
15
+ "ContextResource",
16
+ "OrganizationResource",
17
+ "ScopesResource",
18
+ "UsageResource",
19
+ "UsersResource",
20
+ "WorkflowsResource",
21
+ ]
@@ -0,0 +1,31 @@
1
+ """Resolve accessConfig wire blobs for guardrails= writes (private)."""
2
+ from __future__ import annotations
3
+
4
+ from typing import Any, Callable, Dict, List, Optional
5
+
6
+ from ..types._guardrails_access import (
7
+ access_config_wire_for_create,
8
+ access_config_wire_for_update,
9
+ )
10
+
11
+
12
+ def _access_from_response(raw: Dict[str, Any]) -> Dict[str, Any]:
13
+ value = raw.get("accessConfig") or raw.get("access_config") or {}
14
+ return value if isinstance(value, dict) else {}
15
+
16
+
17
+ def access_config_body_for_create(
18
+ guardrails: Optional[List[str]],
19
+ ) -> Optional[Dict[str, Any]]:
20
+ if guardrails is None:
21
+ return None
22
+ return access_config_wire_for_create(guardrails)
23
+
24
+
25
+ def access_config_body_for_update(
26
+ guardrails: Optional[List[str]],
27
+ load_resource: Callable[[], Dict[str, Any]],
28
+ ) -> Optional[Dict[str, Any]]:
29
+ if guardrails is None:
30
+ return None
31
+ return access_config_wire_for_update(_access_from_response(load_resource()), guardrails)
File without changes
@@ -0,0 +1,128 @@
1
+ """Merge helpers for user-created context item content blobs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from modus.types.context import ContextItem
8
+
9
+ from ...types.context import UserFeedback
10
+ from .items import AsyncContextItemsResource, ContextItemsResource
11
+
12
+
13
+ def merge_note_content(existing: dict[str, Any], *, title: str, body: str) -> dict[str, Any]:
14
+ """Build note ``content`` for ``items.update``, preserving server-owned keys."""
15
+ return {**existing, "title": title, "content": body}
16
+
17
+
18
+ def merge_link_content(
19
+ existing: dict[str, Any],
20
+ *,
21
+ title: str | None = None,
22
+ url: str | None = None,
23
+ ) -> dict[str, Any]:
24
+ """Build link ``content`` for ``items.update``. Does not re-fetch the URL."""
25
+ merged = dict(existing)
26
+ if title is not None:
27
+ merged["title"] = title
28
+ if url is not None:
29
+ merged["url"] = url
30
+ return merged
31
+
32
+
33
+ def merge_saved_query_content(
34
+ existing: dict[str, Any],
35
+ *,
36
+ name: str,
37
+ query: str,
38
+ connection_id: str | None = None,
39
+ path: list[str] | None = None,
40
+ ) -> dict[str, Any]:
41
+ """Build saved_query ``content`` for ``items.update``."""
42
+ merged: dict[str, Any] = {**existing, "name": name, "query": query}
43
+ if connection_id is not None:
44
+ merged["connectionId"] = connection_id
45
+ if path is not None:
46
+ merged["path"] = path
47
+ return merged
48
+
49
+
50
+ def _require_content_dict(item: ContextItem, *, context_type: str) -> dict[str, Any]:
51
+ if item.context_type != context_type:
52
+ raise ValueError(
53
+ f"Expected context_type {context_type!r}, got {item.context_type!r}."
54
+ )
55
+ content = item.content
56
+ if not isinstance(content, dict):
57
+ raise ValueError(
58
+ f"Context item {item.uid!r} has no dict content; cannot merge {context_type} fields."
59
+ )
60
+ return content
61
+
62
+
63
+ def update_with_merged_content(
64
+ items: ContextItemsResource,
65
+ uid: str,
66
+ *,
67
+ context_type: str,
68
+ merged_content: dict[str, Any],
69
+ existing: ContextItem | None = None,
70
+ description: str | None = None,
71
+ user_feedback: UserFeedback | str | None = None,
72
+ topics: list[str] | None = None,
73
+ ) -> ContextItem:
74
+ if existing is not None:
75
+ _require_content_dict(existing, context_type=context_type)
76
+ return items.update(
77
+ uid,
78
+ context_type=context_type,
79
+ content=merged_content,
80
+ description=description,
81
+ user_feedback=user_feedback,
82
+ topics=topics,
83
+ )
84
+
85
+
86
+ async def async_update_with_merged_content(
87
+ items: AsyncContextItemsResource,
88
+ uid: str,
89
+ *,
90
+ context_type: str,
91
+ merged_content: dict[str, Any],
92
+ existing: ContextItem | None = None,
93
+ description: str | None = None,
94
+ user_feedback: UserFeedback | str | None = None,
95
+ topics: list[str] | None = None,
96
+ ) -> ContextItem:
97
+ if existing is not None:
98
+ _require_content_dict(existing, context_type=context_type)
99
+ return await items.update(
100
+ uid,
101
+ context_type=context_type,
102
+ content=merged_content,
103
+ description=description,
104
+ user_feedback=user_feedback,
105
+ topics=topics,
106
+ )
107
+
108
+
109
+ def resolve_existing_content(
110
+ items: ContextItemsResource,
111
+ uid: str,
112
+ *,
113
+ context_type: str,
114
+ existing: ContextItem | None,
115
+ ) -> dict[str, Any]:
116
+ item = existing if existing is not None else items.get(uid)
117
+ return _require_content_dict(item, context_type=context_type)
118
+
119
+
120
+ async def async_resolve_existing_content(
121
+ items: AsyncContextItemsResource,
122
+ uid: str,
123
+ *,
124
+ context_type: str,
125
+ existing: ContextItem | None,
126
+ ) -> dict[str, Any]:
127
+ item = existing if existing is not None else await items.get(uid)
128
+ return _require_content_dict(item, context_type=context_type)