modulex-python 0.1.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.
- modulex/__init__.py +39 -0
- modulex/_base.py +281 -0
- modulex/_client.py +237 -0
- modulex/_compat.py +39 -0
- modulex/_config.py +26 -0
- modulex/_exceptions.py +131 -0
- modulex/_streaming.py +118 -0
- modulex/py.typed +0 -0
- modulex/resources/__init__.py +1 -0
- modulex/resources/api_keys.py +39 -0
- modulex/resources/auth.py +38 -0
- modulex/resources/chats.py +62 -0
- modulex/resources/composer.py +134 -0
- modulex/resources/credentials.py +197 -0
- modulex/resources/dashboard.py +110 -0
- modulex/resources/deployments.py +92 -0
- modulex/resources/executions.py +97 -0
- modulex/resources/integrations.py +110 -0
- modulex/resources/knowledge.py +343 -0
- modulex/resources/notifications.py +39 -0
- modulex/resources/organizations.py +72 -0
- modulex/resources/schedules.py +172 -0
- modulex/resources/subscriptions.py +38 -0
- modulex/resources/system.py +28 -0
- modulex/resources/templates.py +115 -0
- modulex/resources/workflows.py +156 -0
- modulex/types/__init__.py +294 -0
- modulex/types/api_keys.py +19 -0
- modulex/types/auth.py +62 -0
- modulex/types/chats.py +55 -0
- modulex/types/composer.py +27 -0
- modulex/types/credentials.py +79 -0
- modulex/types/dashboard.py +54 -0
- modulex/types/executions.py +104 -0
- modulex/types/integrations.py +29 -0
- modulex/types/knowledge.py +75 -0
- modulex/types/notifications.py +16 -0
- modulex/types/organizations.py +43 -0
- modulex/types/schedules.py +48 -0
- modulex/types/shared.py +39 -0
- modulex/types/subscriptions.py +59 -0
- modulex/types/templates.py +50 -0
- modulex/types/workflows.py +253 -0
- modulex_python-0.1.0.dist-info/METADATA +435 -0
- modulex_python-0.1.0.dist-info/RECORD +47 -0
- modulex_python-0.1.0.dist-info/WHEEL +4 -0
- modulex_python-0.1.0.dist-info/licenses/LICENSE +21 -0
modulex/_exceptions.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""Exception classes for the ModuleX SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ModulexError(Exception):
|
|
11
|
+
"""Base exception for all ModuleX SDK errors."""
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
message: str,
|
|
16
|
+
*,
|
|
17
|
+
status_code: int | None = None,
|
|
18
|
+
response: httpx.Response | None = None,
|
|
19
|
+
body: Any = None,
|
|
20
|
+
) -> None:
|
|
21
|
+
super().__init__(message)
|
|
22
|
+
self.message = message
|
|
23
|
+
self.status_code = status_code
|
|
24
|
+
self.response = response
|
|
25
|
+
self.body = body
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class AuthenticationError(ModulexError):
|
|
29
|
+
"""Raised when authentication fails (401)."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class PermissionError(ModulexError):
|
|
33
|
+
"""Raised when the user lacks permissions (403)."""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class NotFoundError(ModulexError):
|
|
37
|
+
"""Raised when a resource is not found (404)."""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class BadRequestError(ModulexError):
|
|
41
|
+
"""Raised for malformed requests (400)."""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ValidationError(ModulexError):
|
|
45
|
+
"""Raised for validation errors (422)."""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ConflictError(ModulexError):
|
|
49
|
+
"""Raised for resource conflicts (409)."""
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class RateLimitError(ModulexError):
|
|
53
|
+
"""Raised when rate limited (429)."""
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
message: str,
|
|
58
|
+
*,
|
|
59
|
+
status_code: int | None = 429,
|
|
60
|
+
response: httpx.Response | None = None,
|
|
61
|
+
body: Any = None,
|
|
62
|
+
retry_after: float | None = None,
|
|
63
|
+
) -> None:
|
|
64
|
+
super().__init__(message, status_code=status_code, response=response, body=body)
|
|
65
|
+
self.retry_after = retry_after
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class InternalError(ModulexError):
|
|
69
|
+
"""Raised for internal server errors (500)."""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ExternalServiceError(ModulexError):
|
|
73
|
+
"""Raised for external service errors (502)."""
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class ServiceUnavailableError(ModulexError):
|
|
77
|
+
"""Raised when the service is unavailable (503)."""
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class StreamError(ModulexError):
|
|
81
|
+
"""Raised for SSE stream errors."""
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class TimeoutError(ModulexError):
|
|
85
|
+
"""Raised when a request times out."""
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
_STATUS_CODE_MAP: dict[int, type[ModulexError]] = {
|
|
89
|
+
400: BadRequestError,
|
|
90
|
+
401: AuthenticationError,
|
|
91
|
+
403: PermissionError,
|
|
92
|
+
404: NotFoundError,
|
|
93
|
+
409: ConflictError,
|
|
94
|
+
422: ValidationError,
|
|
95
|
+
429: RateLimitError,
|
|
96
|
+
500: InternalError,
|
|
97
|
+
502: ExternalServiceError,
|
|
98
|
+
503: ServiceUnavailableError,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
RETRYABLE_STATUS_CODES = {429, 500, 502, 503}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def raise_for_status(response: httpx.Response) -> None:
|
|
105
|
+
"""Raise an appropriate exception for error HTTP status codes."""
|
|
106
|
+
if response.status_code < 400:
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
body = response.json()
|
|
111
|
+
except Exception:
|
|
112
|
+
body = {"detail": response.text}
|
|
113
|
+
|
|
114
|
+
detail = body.get("detail", response.text) if isinstance(body, dict) else str(body)
|
|
115
|
+
if isinstance(detail, list):
|
|
116
|
+
detail = "; ".join(item.get("msg", str(item)) for item in detail)
|
|
117
|
+
|
|
118
|
+
exc_class = _STATUS_CODE_MAP.get(response.status_code, ModulexError)
|
|
119
|
+
|
|
120
|
+
kwargs: dict[str, Any] = {
|
|
121
|
+
"status_code": response.status_code,
|
|
122
|
+
"response": response,
|
|
123
|
+
"body": body,
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if exc_class is RateLimitError:
|
|
127
|
+
retry_after_header = response.headers.get("Retry-After")
|
|
128
|
+
retry_after = float(retry_after_header) if retry_after_header else None
|
|
129
|
+
kwargs["retry_after"] = retry_after
|
|
130
|
+
|
|
131
|
+
raise exc_class(str(detail), **kwargs)
|
modulex/_streaming.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""SSE streaming support for the ModuleX SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from collections.abc import AsyncIterator
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
from httpx_sse import aconnect_sse
|
|
12
|
+
|
|
13
|
+
from modulex._exceptions import StreamError
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class SSEEvent:
|
|
18
|
+
"""A Server-Sent Event."""
|
|
19
|
+
|
|
20
|
+
event: str
|
|
21
|
+
data: dict[str, Any]
|
|
22
|
+
id: str | None = None
|
|
23
|
+
retry: int | None = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SSEStream:
|
|
27
|
+
"""Async iterator for Server-Sent Events streams."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, response: httpx.Response) -> None:
|
|
30
|
+
self._response = response
|
|
31
|
+
self._closed = False
|
|
32
|
+
|
|
33
|
+
def __aiter__(self) -> AsyncIterator[SSEEvent]:
|
|
34
|
+
return self._iterate()
|
|
35
|
+
|
|
36
|
+
async def _iterate(self) -> AsyncIterator[SSEEvent]:
|
|
37
|
+
"""Iterate over SSE events from the response."""
|
|
38
|
+
try:
|
|
39
|
+
async for line in self._response.aiter_lines():
|
|
40
|
+
if self._closed:
|
|
41
|
+
break
|
|
42
|
+
|
|
43
|
+
line = line.strip()
|
|
44
|
+
if not line or line.startswith(":"):
|
|
45
|
+
continue
|
|
46
|
+
|
|
47
|
+
event = self._parse_event(line)
|
|
48
|
+
if event and event.event != "keepalive":
|
|
49
|
+
yield event
|
|
50
|
+
except httpx.StreamClosed:
|
|
51
|
+
return
|
|
52
|
+
except Exception as e:
|
|
53
|
+
if not self._closed:
|
|
54
|
+
raise StreamError(f"SSE stream error: {e}") from e
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def _parse_event(line: str) -> SSEEvent | None:
|
|
58
|
+
"""Parse a single SSE line into an event."""
|
|
59
|
+
if line.startswith("data:"):
|
|
60
|
+
data_str = line[5:].strip()
|
|
61
|
+
try:
|
|
62
|
+
data = json.loads(data_str)
|
|
63
|
+
except json.JSONDecodeError:
|
|
64
|
+
data = {"raw": data_str}
|
|
65
|
+
return SSEEvent(event="message", data=data)
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
async def close(self) -> None:
|
|
69
|
+
"""Close the SSE stream."""
|
|
70
|
+
self._closed = True
|
|
71
|
+
await self._response.aclose()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class EventSourceStream:
|
|
75
|
+
"""Full SSE parser that handles multi-line event blocks."""
|
|
76
|
+
|
|
77
|
+
def __init__(self, client: httpx.AsyncClient, method: str, url: str, **kwargs: Any) -> None:
|
|
78
|
+
self._client = client
|
|
79
|
+
self._method = method
|
|
80
|
+
self._url = url
|
|
81
|
+
self._kwargs = kwargs
|
|
82
|
+
self._closed = False
|
|
83
|
+
|
|
84
|
+
def __aiter__(self) -> AsyncIterator[SSEEvent]:
|
|
85
|
+
return self._iterate()
|
|
86
|
+
|
|
87
|
+
async def _iterate(self) -> AsyncIterator[SSEEvent]:
|
|
88
|
+
"""Connect and iterate over SSE events."""
|
|
89
|
+
try:
|
|
90
|
+
async with aconnect_sse(self._client, self._method, self._url, **self._kwargs) as event_source:
|
|
91
|
+
async for sse in event_source.aiter_sse():
|
|
92
|
+
if self._closed:
|
|
93
|
+
break
|
|
94
|
+
|
|
95
|
+
event_type = sse.event or "message"
|
|
96
|
+
if event_type == "keepalive":
|
|
97
|
+
continue
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
data = json.loads(sse.data) if sse.data else {}
|
|
101
|
+
except json.JSONDecodeError:
|
|
102
|
+
data = {"raw": sse.data}
|
|
103
|
+
|
|
104
|
+
yield SSEEvent(
|
|
105
|
+
event=event_type,
|
|
106
|
+
data=data,
|
|
107
|
+
id=sse.id,
|
|
108
|
+
retry=sse.retry,
|
|
109
|
+
)
|
|
110
|
+
except httpx.StreamClosed:
|
|
111
|
+
return
|
|
112
|
+
except Exception as e:
|
|
113
|
+
if not self._closed:
|
|
114
|
+
raise StreamError(f"SSE stream error: {e}") from e
|
|
115
|
+
|
|
116
|
+
async def close(self) -> None:
|
|
117
|
+
"""Close the SSE stream."""
|
|
118
|
+
self._closed = True
|
modulex/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Resources package for the ModuleX Python SDK.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""ApiKeys resource for the ModuleX Python SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from modulex._base import _BaseResource
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ApiKeys(_BaseResource):
|
|
11
|
+
"""Resource for managing API keys."""
|
|
12
|
+
|
|
13
|
+
async def create(
|
|
14
|
+
self,
|
|
15
|
+
name: str,
|
|
16
|
+
*,
|
|
17
|
+
organization_id: str | None = None,
|
|
18
|
+
expires_at: str | None = None,
|
|
19
|
+
rate_limit_per_minute: int = 60,
|
|
20
|
+
) -> Any:
|
|
21
|
+
"""Create a new API key with the given name and options."""
|
|
22
|
+
body: dict[str, Any] = {"name": name, "rate_limit_per_minute": rate_limit_per_minute}
|
|
23
|
+
if organization_id is not None:
|
|
24
|
+
body["organization_id"] = organization_id
|
|
25
|
+
if expires_at is not None:
|
|
26
|
+
body["expires_at"] = expires_at
|
|
27
|
+
return await self._post("/api-keys", json=body)
|
|
28
|
+
|
|
29
|
+
async def list(self, *, include_revoked: bool = False) -> Any:
|
|
30
|
+
"""Return all API keys, optionally including revoked ones."""
|
|
31
|
+
return await self._get("/api-keys", params={"include_revoked": include_revoked})
|
|
32
|
+
|
|
33
|
+
async def get(self, key_id: str) -> Any:
|
|
34
|
+
"""Return a single API key by its ID."""
|
|
35
|
+
return await self._get(f"/api-keys/{key_id}")
|
|
36
|
+
|
|
37
|
+
async def revoke(self, key_id: str) -> Any:
|
|
38
|
+
"""Revoke an API key by its ID."""
|
|
39
|
+
return await self._delete(f"/api-keys/{key_id}")
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Auth resource for the ModuleX Python SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from modulex._base import _BaseResource
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Auth(_BaseResource):
|
|
11
|
+
"""Resource for authentication and identity endpoints."""
|
|
12
|
+
|
|
13
|
+
async def me(self) -> Any:
|
|
14
|
+
"""Return the currently authenticated user's profile."""
|
|
15
|
+
return await self._get("/auth/me")
|
|
16
|
+
|
|
17
|
+
async def organizations(self, *, role: str | None = None) -> Any:
|
|
18
|
+
"""Return organizations the current user belongs to, optionally filtered by role."""
|
|
19
|
+
params: dict[str, Any] = {}
|
|
20
|
+
if role is not None:
|
|
21
|
+
params["role"] = role
|
|
22
|
+
return await self._get("/auth/me/organizations", params=params or None)
|
|
23
|
+
|
|
24
|
+
async def invitations(self) -> Any:
|
|
25
|
+
"""Return all pending invitations for the current user."""
|
|
26
|
+
return await self._get("/auth/invitations/my")
|
|
27
|
+
|
|
28
|
+
async def accept_invitation(self, invitation_id: str) -> Any:
|
|
29
|
+
"""Accept a pending organization invitation by its ID."""
|
|
30
|
+
return await self._post(f"/auth/invitations/{invitation_id}/accept")
|
|
31
|
+
|
|
32
|
+
async def reject_invitation(self, invitation_id: str) -> Any:
|
|
33
|
+
"""Reject a pending organization invitation by its ID."""
|
|
34
|
+
return await self._post(f"/auth/invitations/{invitation_id}/reject")
|
|
35
|
+
|
|
36
|
+
async def leave_organization(self, *, organization_id: str | None = None) -> Any:
|
|
37
|
+
"""Leave the organization identified by the organization_id header."""
|
|
38
|
+
return await self._post("/auth/organizations/leave", organization_id=organization_id)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Chats resource for the ModuleX Python SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from modulex._base import _BaseResource
|
|
8
|
+
from modulex._streaming import EventSourceStream
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Chats(_BaseResource):
|
|
12
|
+
"""Resource for managing chat sessions and their messages."""
|
|
13
|
+
|
|
14
|
+
async def list(self, *, organization_id: str | None = None) -> Any:
|
|
15
|
+
"""Return all chat sessions for the current user or organization."""
|
|
16
|
+
return await self._get("/chats", organization_id=organization_id)
|
|
17
|
+
|
|
18
|
+
async def get(self, chat_id: str, *, organization_id: str | None = None) -> Any:
|
|
19
|
+
"""Return a single chat session by its ID."""
|
|
20
|
+
return await self._get(f"/chats/{chat_id}", organization_id=organization_id)
|
|
21
|
+
|
|
22
|
+
async def messages(
|
|
23
|
+
self,
|
|
24
|
+
chat_id: str,
|
|
25
|
+
*,
|
|
26
|
+
limit: int = 20,
|
|
27
|
+
offset: int = 0,
|
|
28
|
+
organization_id: str | None = None,
|
|
29
|
+
) -> Any:
|
|
30
|
+
"""Return a paginated list of messages for a chat session."""
|
|
31
|
+
return await self._get(
|
|
32
|
+
f"/chats/{chat_id}/messages",
|
|
33
|
+
params={"limit": limit, "offset": offset},
|
|
34
|
+
organization_id=organization_id,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
async def update(
|
|
38
|
+
self,
|
|
39
|
+
chat_id: str,
|
|
40
|
+
*,
|
|
41
|
+
title: str | None = None,
|
|
42
|
+
is_private: bool | None = None,
|
|
43
|
+
folder: str | None = None,
|
|
44
|
+
organization_id: str | None = None,
|
|
45
|
+
) -> Any:
|
|
46
|
+
"""Update metadata for a chat session such as title, privacy, or folder."""
|
|
47
|
+
body: dict[str, Any] = {}
|
|
48
|
+
if title is not None:
|
|
49
|
+
body["title"] = title
|
|
50
|
+
if is_private is not None:
|
|
51
|
+
body["is_private"] = is_private
|
|
52
|
+
if folder is not None:
|
|
53
|
+
body["folder"] = folder
|
|
54
|
+
return await self._patch(f"/chats/{chat_id}", json=body or None, organization_id=organization_id)
|
|
55
|
+
|
|
56
|
+
async def delete(self, chat_id: str, *, organization_id: str | None = None) -> Any:
|
|
57
|
+
"""Delete a chat session by its ID."""
|
|
58
|
+
return await self._delete(f"/chats/{chat_id}", organization_id=organization_id)
|
|
59
|
+
|
|
60
|
+
def stream(self, *, organization_id: str | None = None) -> EventSourceStream:
|
|
61
|
+
"""Open an SSE stream to receive real-time chat events."""
|
|
62
|
+
return self._stream_sse("/chats/stream", organization_id=organization_id)
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Composer resource for the ModuleX Python SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from modulex._base import _BaseResource
|
|
8
|
+
from modulex._streaming import EventSourceStream
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Composer(_BaseResource):
|
|
12
|
+
"""Resource for the AI-assisted workflow composer and its chat sessions."""
|
|
13
|
+
|
|
14
|
+
async def chat(
|
|
15
|
+
self,
|
|
16
|
+
message: str,
|
|
17
|
+
*,
|
|
18
|
+
workflow_id: str | None = None,
|
|
19
|
+
composer_chat_id: str | None = None,
|
|
20
|
+
llm: str | None = None,
|
|
21
|
+
organization_id: str | None = None,
|
|
22
|
+
) -> Any:
|
|
23
|
+
"""Send a message to the composer and receive an AI-generated workflow response."""
|
|
24
|
+
body: dict[str, Any] = {
|
|
25
|
+
k: v
|
|
26
|
+
for k, v in {
|
|
27
|
+
"message": message,
|
|
28
|
+
"workflow_id": workflow_id,
|
|
29
|
+
"composer_chat_id": composer_chat_id,
|
|
30
|
+
"llm": llm,
|
|
31
|
+
}.items()
|
|
32
|
+
if v is not None
|
|
33
|
+
}
|
|
34
|
+
return await self._post("/composer/chat", json=body, organization_id=organization_id)
|
|
35
|
+
|
|
36
|
+
async def get(
|
|
37
|
+
self,
|
|
38
|
+
composer_chat_id: str,
|
|
39
|
+
*,
|
|
40
|
+
organization_id: str | None = None,
|
|
41
|
+
) -> Any:
|
|
42
|
+
"""Return a composer chat session by its ID."""
|
|
43
|
+
return await self._get(f"/composer/chat/{composer_chat_id}", organization_id=organization_id)
|
|
44
|
+
|
|
45
|
+
def listen(
|
|
46
|
+
self,
|
|
47
|
+
composer_chat_id: str,
|
|
48
|
+
run_id: str,
|
|
49
|
+
*,
|
|
50
|
+
organization_id: str | None = None,
|
|
51
|
+
) -> EventSourceStream:
|
|
52
|
+
"""Open an SSE stream to receive live events for a composer chat run."""
|
|
53
|
+
return self._stream_sse(
|
|
54
|
+
f"/composer/chat/{composer_chat_id}/listen/{run_id}",
|
|
55
|
+
organization_id=organization_id,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
async def save(
|
|
59
|
+
self,
|
|
60
|
+
composer_chat_id: str,
|
|
61
|
+
*,
|
|
62
|
+
organization_id: str | None = None,
|
|
63
|
+
) -> Any:
|
|
64
|
+
"""Save the current workflow state generated by a composer chat session."""
|
|
65
|
+
return await self._post(
|
|
66
|
+
f"/composer/chat/{composer_chat_id}/save",
|
|
67
|
+
organization_id=organization_id,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
async def revert(
|
|
71
|
+
self,
|
|
72
|
+
composer_chat_id: str,
|
|
73
|
+
*,
|
|
74
|
+
organization_id: str | None = None,
|
|
75
|
+
) -> Any:
|
|
76
|
+
"""Revert the composer chat session to its last saved workflow state."""
|
|
77
|
+
return await self._post(
|
|
78
|
+
f"/composer/chat/{composer_chat_id}/revert",
|
|
79
|
+
organization_id=organization_id,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
async def history(
|
|
83
|
+
self,
|
|
84
|
+
workflow_id: str,
|
|
85
|
+
*,
|
|
86
|
+
limit: int = 10,
|
|
87
|
+
organization_id: str | None = None,
|
|
88
|
+
) -> Any:
|
|
89
|
+
"""Return the composer chat history associated with a workflow."""
|
|
90
|
+
params: dict[str, Any] = {"limit": limit}
|
|
91
|
+
return await self._get(
|
|
92
|
+
f"/composer/chat/workflow/{workflow_id}/history",
|
|
93
|
+
params=params,
|
|
94
|
+
organization_id=organization_id,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
async def delete(
|
|
98
|
+
self,
|
|
99
|
+
composer_chat_id: str,
|
|
100
|
+
*,
|
|
101
|
+
permanent: bool = False,
|
|
102
|
+
organization_id: str | None = None,
|
|
103
|
+
) -> Any:
|
|
104
|
+
"""Delete a composer chat session, optionally removing it permanently."""
|
|
105
|
+
params: dict[str, Any] = {"permanent": permanent}
|
|
106
|
+
return await self._delete(
|
|
107
|
+
f"/composer/chat/{composer_chat_id}",
|
|
108
|
+
params=params,
|
|
109
|
+
organization_id=organization_id,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
async def status(
|
|
113
|
+
self,
|
|
114
|
+
composer_chat_id: str,
|
|
115
|
+
*,
|
|
116
|
+
organization_id: str | None = None,
|
|
117
|
+
) -> Any:
|
|
118
|
+
"""Return the current processing status of a composer chat session."""
|
|
119
|
+
return await self._get(
|
|
120
|
+
f"/composer/chat/{composer_chat_id}/status",
|
|
121
|
+
organization_id=organization_id,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
async def cancel(
|
|
125
|
+
self,
|
|
126
|
+
composer_chat_id: str,
|
|
127
|
+
*,
|
|
128
|
+
organization_id: str | None = None,
|
|
129
|
+
) -> Any:
|
|
130
|
+
"""Cancel an in-progress composer chat run."""
|
|
131
|
+
return await self._post(
|
|
132
|
+
f"/composer/chat/{composer_chat_id}/cancel",
|
|
133
|
+
organization_id=organization_id,
|
|
134
|
+
)
|