assistant-runtime-sdk 1.0.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.
- assistant_runtime_sdk/__init__.py +235 -0
- assistant_runtime_sdk/async_client.py +1989 -0
- assistant_runtime_sdk/auth.py +161 -0
- assistant_runtime_sdk/base.py +2277 -0
- assistant_runtime_sdk/client.py +3039 -0
- assistant_runtime_sdk/exceptions.py +146 -0
- assistant_runtime_sdk/skills.py +328 -0
- assistant_runtime_sdk/streaming.py +223 -0
- assistant_runtime_sdk/types.py +546 -0
- assistant_runtime_sdk-1.0.0.dist-info/METADATA +259 -0
- assistant_runtime_sdk-1.0.0.dist-info/RECORD +13 -0
- assistant_runtime_sdk-1.0.0.dist-info/WHEEL +4 -0
- assistant_runtime_sdk-1.0.0.dist-info/licenses/LICENSE +17 -0
|
@@ -0,0 +1,2277 @@
|
|
|
1
|
+
# Assistant Runtime SDK - Base Client
|
|
2
|
+
# Copyright (C) 2025 Paul Clinton
|
|
3
|
+
# AGPL-3.0 License
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Base client class with shared authentication and configuration logic.
|
|
7
|
+
|
|
8
|
+
Both AssistantRuntimeClient (sync) and AsyncAssistantRuntimeClient (async) inherit from this.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import logging
|
|
13
|
+
from typing import Dict, Any, List, Optional, Protocol, runtime_checkable
|
|
14
|
+
|
|
15
|
+
from .auth import generate_signature
|
|
16
|
+
from .streaming import parse_sse_line
|
|
17
|
+
from .exceptions import ARConfigurationError, ARBillingUnavailableError, ARAuthenticationError
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@runtime_checkable
|
|
21
|
+
class Logger(Protocol):
|
|
22
|
+
"""Protocol for logger interface."""
|
|
23
|
+
|
|
24
|
+
def error(self, msg: str, *args: Any, **kwargs: Any) -> None: ...
|
|
25
|
+
def warning(self, msg: str, *args: Any, **kwargs: Any) -> None: ...
|
|
26
|
+
def info(self, msg: str, *args: Any, **kwargs: Any) -> None: ...
|
|
27
|
+
def debug(self, msg: str, *args: Any, **kwargs: Any) -> None: ...
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class BaseAssistantRuntimeClient:
|
|
31
|
+
"""
|
|
32
|
+
Base class with shared authentication and configuration logic.
|
|
33
|
+
|
|
34
|
+
This class provides:
|
|
35
|
+
- HMAC signature generation for API requests
|
|
36
|
+
- SSE line parsing for streaming responses
|
|
37
|
+
- Common configuration and validation
|
|
38
|
+
- Multi API base routing (core + billing + memory + workflows)
|
|
39
|
+
|
|
40
|
+
Both AssistantRuntimeClient (sync) and AsyncAssistantRuntimeClient (async) inherit from this class.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
tenant_id: Unique tenant identifier from Assistant Runtime
|
|
44
|
+
tenant_secret: HMAC secret for request signing
|
|
45
|
+
ar_url: Base URL of Assistant Runtime server (default: https://ar.example.com)
|
|
46
|
+
logger: Optional logger instance (uses stdlib logging if None)
|
|
47
|
+
timeout: Default request timeout in seconds (default: 30.0)
|
|
48
|
+
billing_api_base: Override URL for billing API endpoints. Defaults to
|
|
49
|
+
``{ar_url}/api/method/assistant_runtime_payments.api``. Pass a full
|
|
50
|
+
URL or just a Frappe dotted module path (e.g.
|
|
51
|
+
``"assistant_runtime_payments.api"``).
|
|
52
|
+
memory_api_base: Override URL for memory/onboarding API endpoints. Defaults to
|
|
53
|
+
``{ar_url}/api/method/assistant_runtime_memory.api``. Pass a full
|
|
54
|
+
URL or just a Frappe dotted module path (e.g.
|
|
55
|
+
``"assistant_runtime_memory.api"``).
|
|
56
|
+
workflows_api_base: Override URL for workflow API endpoints. Defaults to
|
|
57
|
+
``{ar_url}/api/method/assistant_runtime_workflows.api``. Pass a full
|
|
58
|
+
URL or just a Frappe dotted module path (e.g.
|
|
59
|
+
``"assistant_runtime_workflows.api"``).
|
|
60
|
+
marketplace_api_base: Override URL for marketplace API endpoints. Defaults to
|
|
61
|
+
``{ar_url}/api/method/assistant_runtime_marketplace.api``. Pass a full
|
|
62
|
+
URL or just a Frappe dotted module path (e.g.
|
|
63
|
+
``"assistant_runtime_marketplace.api"``).
|
|
64
|
+
site_url: This installation's canonical URL (e.g. ``"https://mysite.example.com"``).
|
|
65
|
+
When set, it is automatically injected into every signed payload so that
|
|
66
|
+
AR's ``validate_tenant_signature`` can enforce origin binding (Phase 2).
|
|
67
|
+
Pass ``None`` (default) to preserve backward-compatible behaviour.
|
|
68
|
+
|
|
69
|
+
Example:
|
|
70
|
+
>>> # Don't instantiate directly - use AssistantRuntimeClient or AsyncAssistantRuntimeClient
|
|
71
|
+
>>> from assistant_runtime_sdk import AssistantRuntimeClient
|
|
72
|
+
>>> client = AssistantRuntimeClient("tenant-id", "secret")
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
DEFAULT_AR_URL = "https://ar.example.com"
|
|
76
|
+
DEFAULT_BILLING_API_MODULE = "assistant_runtime_payments.api"
|
|
77
|
+
DEFAULT_MEMORY_API_MODULE = "assistant_runtime_memory.api"
|
|
78
|
+
DEFAULT_WORKFLOWS_API_MODULE = "assistant_runtime_workflows.api"
|
|
79
|
+
DEFAULT_MARKETPLACE_API_MODULE = "assistant_runtime_marketplace.api"
|
|
80
|
+
DEFAULT_VOICE_API_MODULE = "assistant_runtime.api.voice"
|
|
81
|
+
DEFAULT_TIMEOUT = 30.0
|
|
82
|
+
STREAM_CONNECT_TIMEOUT = 10.0
|
|
83
|
+
STREAM_READ_TIMEOUT = 300.0 # 5 minutes for long responses
|
|
84
|
+
|
|
85
|
+
def __init__(
|
|
86
|
+
self,
|
|
87
|
+
tenant_id: str,
|
|
88
|
+
tenant_secret: str,
|
|
89
|
+
ar_url: str = DEFAULT_AR_URL,
|
|
90
|
+
logger: Optional[Logger] = None,
|
|
91
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
92
|
+
billing_api_base: Optional[str] = None,
|
|
93
|
+
memory_api_base: Optional[str] = None,
|
|
94
|
+
workflows_api_base: Optional[str] = None,
|
|
95
|
+
marketplace_api_base: Optional[str] = None,
|
|
96
|
+
voice_api_base: Optional[str] = None,
|
|
97
|
+
site_url: Optional[str] = None,
|
|
98
|
+
):
|
|
99
|
+
# Validate required parameters
|
|
100
|
+
if not tenant_id:
|
|
101
|
+
raise ARConfigurationError("tenant_id is required")
|
|
102
|
+
if not tenant_secret:
|
|
103
|
+
raise ARConfigurationError("tenant_secret is required")
|
|
104
|
+
|
|
105
|
+
self.tenant_id = tenant_id
|
|
106
|
+
self.tenant_secret = tenant_secret
|
|
107
|
+
self.site_url = site_url # Phase 2 origin binding: included in signed payloads when set
|
|
108
|
+
self.ar_url = ar_url.rstrip("/")
|
|
109
|
+
self.api_base = f"{self.ar_url}/api/method/assistant_runtime.api"
|
|
110
|
+
self.timeout = timeout
|
|
111
|
+
|
|
112
|
+
# Companion app API bases
|
|
113
|
+
self.billing_api_base = self._resolve_api_base(billing_api_base, self.DEFAULT_BILLING_API_MODULE)
|
|
114
|
+
self.memory_api_base = self._resolve_api_base(memory_api_base, self.DEFAULT_MEMORY_API_MODULE)
|
|
115
|
+
self.workflows_api_base = self._resolve_api_base(workflows_api_base, self.DEFAULT_WORKFLOWS_API_MODULE)
|
|
116
|
+
self.marketplace_api_base = self._resolve_api_base(marketplace_api_base, self.DEFAULT_MARKETPLACE_API_MODULE)
|
|
117
|
+
self.voice_api_base = self._resolve_api_base(voice_api_base, self.DEFAULT_VOICE_API_MODULE)
|
|
118
|
+
|
|
119
|
+
# Billing availability state — None means unknown (not yet probed)
|
|
120
|
+
self._billing_available: Optional[bool] = None
|
|
121
|
+
|
|
122
|
+
# Setup logger
|
|
123
|
+
if logger is None:
|
|
124
|
+
self.logger = logging.getLogger(__name__)
|
|
125
|
+
else:
|
|
126
|
+
self.logger = logger
|
|
127
|
+
|
|
128
|
+
def _resolve_api_base(self, override: Optional[str], default_module: str) -> str:
|
|
129
|
+
"""Resolve an API base URL from an override or default module path."""
|
|
130
|
+
if override is None:
|
|
131
|
+
return f"{self.ar_url}/api/method/{default_module}"
|
|
132
|
+
if override.startswith(("http://", "https://")):
|
|
133
|
+
return override.rstrip("/")
|
|
134
|
+
return f"{self.ar_url}/api/method/{override}"
|
|
135
|
+
|
|
136
|
+
def _with_site_url(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
137
|
+
"""Return a copy of ``params`` with ``site_url`` injected.
|
|
138
|
+
|
|
139
|
+
Origin binding (Phase 2): AR rebuilds the HMAC body from the request
|
|
140
|
+
payload (form_dict / query string), so anything signed but not sent on
|
|
141
|
+
the wire fails verification. Call this at every signed entry point to
|
|
142
|
+
keep the signed and sent payloads identical.
|
|
143
|
+
|
|
144
|
+
Returns the same dict if ``site_url`` is already present or if the
|
|
145
|
+
client wasn't configured with one. Never mutates the input.
|
|
146
|
+
"""
|
|
147
|
+
if self.site_url and "site_url" not in params:
|
|
148
|
+
return {**params, "site_url": self.site_url}
|
|
149
|
+
return params
|
|
150
|
+
|
|
151
|
+
def _generate_signature(self, params: Dict[str, Any], for_query_string: bool = False) -> str:
|
|
152
|
+
"""
|
|
153
|
+
Generate HMAC-SHA256 signature for Assistant Runtime API request.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
params: Request parameters (will be sorted by key)
|
|
157
|
+
for_query_string: If True, convert values to strings (for GET requests)
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Signature header value in format "timestamp:signature"
|
|
161
|
+
"""
|
|
162
|
+
# Pre-Phase-2 callers may have stopped injecting site_url before signing —
|
|
163
|
+
# be defensive and mirror `_with_site_url` here too. Callers that already
|
|
164
|
+
# inserted site_url via `_with_site_url` will be a no-op.
|
|
165
|
+
params = self._with_site_url(params)
|
|
166
|
+
return generate_signature(
|
|
167
|
+
self.tenant_id,
|
|
168
|
+
self.tenant_secret,
|
|
169
|
+
params,
|
|
170
|
+
for_query_string=for_query_string,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
def _get_headers(self, params: Dict[str, Any], for_query_string: bool = True) -> Dict[str, str]:
|
|
174
|
+
"""
|
|
175
|
+
Get request headers with HMAC signature.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
params: Request parameters for signature
|
|
179
|
+
for_query_string: True for GET requests, False for POST JSON
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Headers dict with X-AR-Signature
|
|
183
|
+
"""
|
|
184
|
+
return {"X-AR-Signature": self._generate_signature(params, for_query_string)}
|
|
185
|
+
|
|
186
|
+
def _get_stream_headers(
|
|
187
|
+
self,
|
|
188
|
+
params: Dict[str, Any],
|
|
189
|
+
for_json_body: bool = False,
|
|
190
|
+
) -> Dict[str, str]:
|
|
191
|
+
"""
|
|
192
|
+
Get headers for SSE streaming requests.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
params: Request parameters for signature
|
|
196
|
+
for_json_body: If True, signature is computed for JSON body (POST).
|
|
197
|
+
If False, signature is computed for query string (GET).
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
Headers dict with X-AR-Signature and SSE headers
|
|
201
|
+
"""
|
|
202
|
+
headers = {
|
|
203
|
+
"X-AR-Signature": self._generate_signature(params, for_query_string=not for_json_body),
|
|
204
|
+
"Accept": "text/event-stream",
|
|
205
|
+
"Cache-Control": "no-cache",
|
|
206
|
+
}
|
|
207
|
+
if for_json_body:
|
|
208
|
+
headers["Content-Type"] = "application/json"
|
|
209
|
+
return headers
|
|
210
|
+
|
|
211
|
+
def _parse_sse_line(self, line: str) -> Optional[Dict[str, Any]]:
|
|
212
|
+
"""
|
|
213
|
+
Parse a single SSE line.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
line: Raw SSE line
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
Parsed event dict or None
|
|
220
|
+
"""
|
|
221
|
+
return parse_sse_line(line)
|
|
222
|
+
|
|
223
|
+
def _build_endpoint_url(self, endpoint: str) -> str:
|
|
224
|
+
"""
|
|
225
|
+
Build full URL for a core API endpoint.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
endpoint: Endpoint path (e.g., 'streaming.stream_chat')
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
Full URL
|
|
232
|
+
"""
|
|
233
|
+
return f"{self.api_base}.{endpoint}"
|
|
234
|
+
|
|
235
|
+
def _build_billing_endpoint_url(self, endpoint: str) -> str:
|
|
236
|
+
"""
|
|
237
|
+
Build full URL for a billing/payments API endpoint.
|
|
238
|
+
|
|
239
|
+
Routes through ``billing_api_base`` which targets the payments app.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
endpoint: Endpoint path (e.g., 'get_usage_dashboard')
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
Full URL
|
|
246
|
+
"""
|
|
247
|
+
return f"{self.billing_api_base}.{endpoint}"
|
|
248
|
+
|
|
249
|
+
def _build_memory_endpoint_url(self, endpoint: str) -> str:
|
|
250
|
+
"""
|
|
251
|
+
Build full URL for a memory/onboarding API endpoint.
|
|
252
|
+
|
|
253
|
+
Routes through ``memory_api_base`` which targets the memory app.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
endpoint: Endpoint path (e.g., 'onboarding.get_onboarding_status')
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
Full URL
|
|
260
|
+
"""
|
|
261
|
+
return f"{self.memory_api_base}.{endpoint}"
|
|
262
|
+
|
|
263
|
+
def _build_workflows_endpoint_url(self, endpoint: str) -> str:
|
|
264
|
+
"""
|
|
265
|
+
Build full URL for a workflow API endpoint.
|
|
266
|
+
|
|
267
|
+
Routes through ``workflows_api_base`` which targets the workflows app.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
endpoint: Endpoint path (e.g., 'workflows.create_workflow')
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
Full URL
|
|
274
|
+
"""
|
|
275
|
+
return f"{self.workflows_api_base}.{endpoint}"
|
|
276
|
+
|
|
277
|
+
def _build_marketplace_endpoint_url(self, endpoint: str) -> str:
|
|
278
|
+
"""
|
|
279
|
+
Build full URL for a marketplace API endpoint.
|
|
280
|
+
|
|
281
|
+
Routes through ``marketplace_api_base`` which targets the marketplace app.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
endpoint: Endpoint path (e.g., 'listings.list_listings')
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
Full URL
|
|
288
|
+
"""
|
|
289
|
+
return f"{self.marketplace_api_base}.{endpoint}"
|
|
290
|
+
|
|
291
|
+
def _require_billing(self) -> None:
|
|
292
|
+
"""
|
|
293
|
+
Guard for billing methods.
|
|
294
|
+
|
|
295
|
+
Raises ``ARBillingUnavailableError`` only if billing has been explicitly
|
|
296
|
+
probed and found unavailable (``_billing_available is False``). When the
|
|
297
|
+
state is unknown (``None``), the call proceeds — it will succeed if the
|
|
298
|
+
payments app is installed, or fail with an HTTP error if not.
|
|
299
|
+
"""
|
|
300
|
+
if self._billing_available is False:
|
|
301
|
+
raise ARBillingUnavailableError()
|
|
302
|
+
|
|
303
|
+
def _prepare_stream_payload(
|
|
304
|
+
self,
|
|
305
|
+
session_id: str,
|
|
306
|
+
message: Optional[str],
|
|
307
|
+
user_id: str,
|
|
308
|
+
context: Optional[Dict[str, Any]] = None,
|
|
309
|
+
model_id: Optional[str] = None,
|
|
310
|
+
attachments: Optional[List[Dict[str, Any]]] = None,
|
|
311
|
+
system_prompt_addendum: Optional[str] = None,
|
|
312
|
+
client_type: Optional[str] = None,
|
|
313
|
+
interrupt_response: Optional[List[Dict[str, str]]] = None,
|
|
314
|
+
message_id: Optional[str] = None,
|
|
315
|
+
session_state: Optional[Dict[str, Any]] = None,
|
|
316
|
+
continue_from_message_id: Optional[str] = None,
|
|
317
|
+
web_search: Optional[bool] = None,
|
|
318
|
+
thinking_enabled: Optional[bool] = None,
|
|
319
|
+
) -> Dict[str, Any]:
|
|
320
|
+
"""
|
|
321
|
+
Prepare JSON payload for stream_chat POST request.
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
session_id: Conversation session identifier
|
|
325
|
+
message: User's message (optional when interrupt_response is provided)
|
|
326
|
+
user_id: User identifier (required)
|
|
327
|
+
context: Optional page context (sent as native dict, not JSON string)
|
|
328
|
+
model_id: Optional model ID
|
|
329
|
+
attachments: Optional list of attachments (images/documents)
|
|
330
|
+
Each attachment: {
|
|
331
|
+
"type": "image" | "document",
|
|
332
|
+
"format": "png" | "jpeg" | "gif" | "webp" | "pdf" | "txt",
|
|
333
|
+
"data": "<base64-encoded-data>",
|
|
334
|
+
"name": "optional-filename.png", # Optional
|
|
335
|
+
"file_url": "/files/..." # Optional, for storage reference
|
|
336
|
+
}
|
|
337
|
+
system_prompt_addendum: Optional per-request addition to the system prompt
|
|
338
|
+
client_type: Optional client type for tool filtering (widget/spa/mobile/api)
|
|
339
|
+
interrupt_response: Optional HITL resume responses. Each item:
|
|
340
|
+
{"interruptId": str, "response": "approve"|"rejected"|"trust"|"session"}
|
|
341
|
+
message_id: Optional message ID to reuse on HITL resume (ensures all
|
|
342
|
+
events across multiple resume cycles link to the same message)
|
|
343
|
+
session_state: Optional client-held signed session blob for
|
|
344
|
+
zero-retention conversations (sent up so the server can rehydrate
|
|
345
|
+
state without storing it server-side)
|
|
346
|
+
continue_from_message_id: Optional message ID to continue a
|
|
347
|
+
previous truncated response from
|
|
348
|
+
web_search: Optional explicit web-search toggle for this turn
|
|
349
|
+
thinking_enabled: Optional explicit extended-thinking toggle for this turn
|
|
350
|
+
|
|
351
|
+
Returns:
|
|
352
|
+
Payload dict ready for JSON body
|
|
353
|
+
|
|
354
|
+
Raises:
|
|
355
|
+
ARConfigurationError: If user_id is missing
|
|
356
|
+
"""
|
|
357
|
+
if not user_id:
|
|
358
|
+
raise ARConfigurationError("user_id is required for stream_chat")
|
|
359
|
+
|
|
360
|
+
payload: Dict[str, Any] = {
|
|
361
|
+
"tenant_id": self.tenant_id,
|
|
362
|
+
"session_id": session_id,
|
|
363
|
+
"user_id": user_id,
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if message:
|
|
367
|
+
payload["message"] = message
|
|
368
|
+
|
|
369
|
+
if context:
|
|
370
|
+
# Context is sent as a native dict in JSON body, not a JSON string
|
|
371
|
+
payload["context"] = context
|
|
372
|
+
|
|
373
|
+
if model_id:
|
|
374
|
+
payload["model_id"] = model_id
|
|
375
|
+
|
|
376
|
+
if attachments:
|
|
377
|
+
payload["attachments"] = attachments
|
|
378
|
+
|
|
379
|
+
if system_prompt_addendum:
|
|
380
|
+
payload["system_prompt_addendum"] = system_prompt_addendum
|
|
381
|
+
|
|
382
|
+
if client_type:
|
|
383
|
+
payload["client_type"] = client_type
|
|
384
|
+
|
|
385
|
+
if interrupt_response:
|
|
386
|
+
payload["interrupt_response"] = interrupt_response
|
|
387
|
+
|
|
388
|
+
if message_id:
|
|
389
|
+
payload["message_id"] = message_id
|
|
390
|
+
|
|
391
|
+
if session_state:
|
|
392
|
+
payload["session_state"] = session_state
|
|
393
|
+
|
|
394
|
+
if continue_from_message_id:
|
|
395
|
+
payload["continue_from_message_id"] = continue_from_message_id
|
|
396
|
+
|
|
397
|
+
if web_search is not None:
|
|
398
|
+
payload["web_search"] = bool(web_search)
|
|
399
|
+
|
|
400
|
+
if thinking_enabled is not None:
|
|
401
|
+
payload["thinking_enabled"] = bool(thinking_enabled)
|
|
402
|
+
|
|
403
|
+
return payload
|
|
404
|
+
|
|
405
|
+
def _prepare_resource_params(
|
|
406
|
+
self,
|
|
407
|
+
user_id: str,
|
|
408
|
+
uri: Optional[str] = None,
|
|
409
|
+
server: Optional[str] = None,
|
|
410
|
+
) -> Dict[str, Any]:
|
|
411
|
+
"""
|
|
412
|
+
Prepare parameters for resource requests.
|
|
413
|
+
|
|
414
|
+
Args:
|
|
415
|
+
user_id: User identifier (required)
|
|
416
|
+
uri: Optional resource URI (for read_resource)
|
|
417
|
+
server: Optional server name filter (for list_resources)
|
|
418
|
+
|
|
419
|
+
Returns:
|
|
420
|
+
Parameters dict ready for request
|
|
421
|
+
"""
|
|
422
|
+
params: Dict[str, Any] = {
|
|
423
|
+
"tenant_id": self.tenant_id,
|
|
424
|
+
"user_id": user_id,
|
|
425
|
+
}
|
|
426
|
+
if uri:
|
|
427
|
+
params["uri"] = uri
|
|
428
|
+
if server:
|
|
429
|
+
params["server"] = server
|
|
430
|
+
return params
|
|
431
|
+
|
|
432
|
+
def _log_error(self, message: str, category: str = "AR") -> None:
|
|
433
|
+
"""Log an error message."""
|
|
434
|
+
self.logger.error(message, extra={"category": category})
|
|
435
|
+
|
|
436
|
+
def _log_warning(self, message: str, category: str = "AR") -> None:
|
|
437
|
+
"""Log a warning message."""
|
|
438
|
+
self.logger.warning(message, extra={"category": category})
|
|
439
|
+
|
|
440
|
+
def _log_debug(self, message: str, category: str = "AR") -> None:
|
|
441
|
+
"""Log a debug message."""
|
|
442
|
+
self.logger.debug(message, extra={"category": category})
|
|
443
|
+
|
|
444
|
+
# =========================================================================
|
|
445
|
+
# Error Extraction
|
|
446
|
+
# =========================================================================
|
|
447
|
+
|
|
448
|
+
@staticmethod
|
|
449
|
+
def _extract_error_from_data(data: dict) -> Optional[str]:
|
|
450
|
+
"""Extract a human-readable error message from a Frappe error response dict.
|
|
451
|
+
|
|
452
|
+
Works with both sync and async clients since it accepts a parsed dict
|
|
453
|
+
rather than a library-specific response object.
|
|
454
|
+
"""
|
|
455
|
+
try:
|
|
456
|
+
server_messages = data.get("_server_messages")
|
|
457
|
+
if server_messages:
|
|
458
|
+
messages = json.loads(server_messages)
|
|
459
|
+
if messages:
|
|
460
|
+
msg = json.loads(messages[0])
|
|
461
|
+
return msg.get("message", str(msg))
|
|
462
|
+
if data.get("exception"):
|
|
463
|
+
exc = data["exception"]
|
|
464
|
+
lines = exc.strip().splitlines()
|
|
465
|
+
if lines:
|
|
466
|
+
last = lines[-1]
|
|
467
|
+
if ": " in last:
|
|
468
|
+
return last.split(": ", 1)[1]
|
|
469
|
+
return last
|
|
470
|
+
if data.get("message"):
|
|
471
|
+
return data["message"]
|
|
472
|
+
except Exception:
|
|
473
|
+
pass
|
|
474
|
+
return None
|
|
475
|
+
|
|
476
|
+
# =========================================================================
|
|
477
|
+
# Prepare Methods — Tenant
|
|
478
|
+
# =========================================================================
|
|
479
|
+
|
|
480
|
+
def _prepare_get_tenant_info(self) -> tuple:
|
|
481
|
+
"""Returns (endpoint, params)."""
|
|
482
|
+
return "get_tenant_info", {"tenant_id": self.tenant_id}
|
|
483
|
+
|
|
484
|
+
def _prepare_accept_terms(self, terms_version: str, accepted_by: str) -> tuple:
|
|
485
|
+
"""Returns (endpoint, payload)."""
|
|
486
|
+
return "accept_terms", {
|
|
487
|
+
"tenant_id": self.tenant_id,
|
|
488
|
+
"terms_version": terms_version,
|
|
489
|
+
"accepted_by": accepted_by,
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
def _prepare_get_terms_status(self) -> tuple:
|
|
493
|
+
"""Returns (endpoint, params)."""
|
|
494
|
+
return "get_terms_status", {"tenant_id": self.tenant_id}
|
|
495
|
+
|
|
496
|
+
# =========================================================================
|
|
497
|
+
# Prepare Methods — Models
|
|
498
|
+
# =========================================================================
|
|
499
|
+
|
|
500
|
+
def _prepare_list_available_models(self) -> tuple:
|
|
501
|
+
return "streaming.list_available_models", {"tenant_id": self.tenant_id}
|
|
502
|
+
|
|
503
|
+
def _prepare_get_available_models(self) -> tuple:
|
|
504
|
+
return "get_available_models", {"tenant_id": self.tenant_id}
|
|
505
|
+
|
|
506
|
+
def _prepare_set_preferred_model(self, model_id: str) -> tuple:
|
|
507
|
+
return "set_preferred_model", {"tenant_id": self.tenant_id, "model_id": model_id}
|
|
508
|
+
|
|
509
|
+
# =========================================================================
|
|
510
|
+
# Prepare Methods — Prompts & Suggestions
|
|
511
|
+
# =========================================================================
|
|
512
|
+
|
|
513
|
+
def _prepare_list_prompts(self, user_id: str, cursor: Optional[str] = None) -> tuple:
|
|
514
|
+
params = {"tenant_id": self.tenant_id, "user_id": user_id}
|
|
515
|
+
if cursor:
|
|
516
|
+
params["cursor"] = cursor
|
|
517
|
+
return "prompts.list_prompts", params
|
|
518
|
+
|
|
519
|
+
def _prepare_get_prompt(self, prompt_name: str, user_id: str,
|
|
520
|
+
arguments: Optional[Dict[str, Any]] = None) -> tuple:
|
|
521
|
+
payload = {
|
|
522
|
+
"tenant_id": self.tenant_id,
|
|
523
|
+
"user_id": user_id,
|
|
524
|
+
"prompt_name": prompt_name,
|
|
525
|
+
}
|
|
526
|
+
if arguments:
|
|
527
|
+
payload["arguments"] = arguments
|
|
528
|
+
return "prompts.get_prompt", payload
|
|
529
|
+
|
|
530
|
+
def _prepare_get_suggestions(self, user_id: str,
|
|
531
|
+
context: Optional[Dict[str, Any]] = None,
|
|
532
|
+
limit: int = 8) -> tuple:
|
|
533
|
+
params: Dict[str, Any] = {
|
|
534
|
+
"tenant_id": self.tenant_id,
|
|
535
|
+
"user_id": user_id,
|
|
536
|
+
"limit": str(limit),
|
|
537
|
+
}
|
|
538
|
+
if context:
|
|
539
|
+
params["context"] = json.dumps(context)
|
|
540
|
+
return "suggestions.get_suggestions", params
|
|
541
|
+
|
|
542
|
+
def _prepare_generate_curated_suggestions(
|
|
543
|
+
self, user_id: str, signals: Optional[Dict[str, Any]] = None
|
|
544
|
+
) -> tuple:
|
|
545
|
+
payload: Dict[str, Any] = {
|
|
546
|
+
"tenant_id": self.tenant_id,
|
|
547
|
+
"user_id": user_id,
|
|
548
|
+
"signals": signals or {},
|
|
549
|
+
}
|
|
550
|
+
return "suggestions.generate_curated", payload
|
|
551
|
+
|
|
552
|
+
# =========================================================================
|
|
553
|
+
# Prepare Methods — Onboarding
|
|
554
|
+
# =========================================================================
|
|
555
|
+
|
|
556
|
+
def _prepare_get_onboarding_status(self, user_id: str) -> tuple:
|
|
557
|
+
return "onboarding.get_onboarding_status", {
|
|
558
|
+
"tenant_id": self.tenant_id,
|
|
559
|
+
"user_id": user_id,
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
def _prepare_complete_onboarding(self, user_id: str) -> tuple:
|
|
563
|
+
payload: Dict[str, Any] = {
|
|
564
|
+
"tenant_id": self.tenant_id,
|
|
565
|
+
"user_id": user_id,
|
|
566
|
+
}
|
|
567
|
+
return "onboarding.complete_onboarding", payload
|
|
568
|
+
|
|
569
|
+
# =========================================================================
|
|
570
|
+
# Prepare Methods — HITL
|
|
571
|
+
# =========================================================================
|
|
572
|
+
|
|
573
|
+
def _prepare_get_pending_interrupt(self, session_id: str, user_id: str) -> tuple:
|
|
574
|
+
if not session_id:
|
|
575
|
+
raise ValueError("session_id is required")
|
|
576
|
+
if not user_id:
|
|
577
|
+
raise ValueError("user_id is required")
|
|
578
|
+
return "hitl.get_pending_interrupt", {
|
|
579
|
+
"tenant_id": self.tenant_id,
|
|
580
|
+
"user_id": user_id,
|
|
581
|
+
"session_id": session_id,
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
def _prepare_cancel_session(self, session_id: str) -> tuple:
|
|
585
|
+
"""Returns (endpoint, payload)."""
|
|
586
|
+
if not session_id:
|
|
587
|
+
raise ValueError("session_id is required")
|
|
588
|
+
return "cancel.cancel_session", {
|
|
589
|
+
"tenant_id": self.tenant_id,
|
|
590
|
+
"session_id": session_id,
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
# =========================================================================
|
|
594
|
+
# Prepare Methods — Documents
|
|
595
|
+
# =========================================================================
|
|
596
|
+
|
|
597
|
+
def _prepare_upload_document(
|
|
598
|
+
self,
|
|
599
|
+
file_path: Optional[str] = None,
|
|
600
|
+
file_data: Optional[bytes] = None,
|
|
601
|
+
file_name: Optional[str] = None,
|
|
602
|
+
content_type: Optional[str] = None,
|
|
603
|
+
user_id: Optional[str] = None,
|
|
604
|
+
visibility: Optional[str] = None,
|
|
605
|
+
shared_with: Optional[List[str]] = None,
|
|
606
|
+
) -> tuple:
|
|
607
|
+
"""Returns (endpoint, params, file_field, file_name, file_data, content_type)."""
|
|
608
|
+
import mimetypes as _mt
|
|
609
|
+
import os as _os
|
|
610
|
+
|
|
611
|
+
if file_path and file_data:
|
|
612
|
+
raise ARConfigurationError("Provide either file_path or file_data, not both")
|
|
613
|
+
if not file_path and not file_data:
|
|
614
|
+
raise ARConfigurationError("Either file_path or file_data is required")
|
|
615
|
+
if file_data and not file_name:
|
|
616
|
+
raise ARConfigurationError("file_name is required when using file_data")
|
|
617
|
+
|
|
618
|
+
if visibility and visibility not in ("public", "private", "shared"):
|
|
619
|
+
raise ARConfigurationError(
|
|
620
|
+
f"Invalid visibility: {visibility}. Must be public, private, or shared."
|
|
621
|
+
)
|
|
622
|
+
if visibility == "shared" and not shared_with:
|
|
623
|
+
raise ARConfigurationError(
|
|
624
|
+
"shared_with is required when visibility is 'shared'"
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
if file_path:
|
|
628
|
+
with open(file_path, "rb") as f:
|
|
629
|
+
data = f.read()
|
|
630
|
+
if not file_name:
|
|
631
|
+
file_name = _os.path.basename(file_path)
|
|
632
|
+
else:
|
|
633
|
+
data = file_data
|
|
634
|
+
|
|
635
|
+
if not content_type:
|
|
636
|
+
guessed, _ = _mt.guess_type(file_name)
|
|
637
|
+
content_type = guessed or "application/octet-stream"
|
|
638
|
+
|
|
639
|
+
params: Dict[str, Any] = {"tenant_id": self.tenant_id}
|
|
640
|
+
if user_id:
|
|
641
|
+
params["user_id"] = user_id
|
|
642
|
+
if visibility:
|
|
643
|
+
params["visibility"] = visibility
|
|
644
|
+
if shared_with:
|
|
645
|
+
params["shared_with"] = json.dumps(shared_with)
|
|
646
|
+
|
|
647
|
+
return (
|
|
648
|
+
"documents.upload_document",
|
|
649
|
+
params,
|
|
650
|
+
"file",
|
|
651
|
+
file_name,
|
|
652
|
+
data,
|
|
653
|
+
content_type,
|
|
654
|
+
)
|
|
655
|
+
|
|
656
|
+
def _prepare_list_documents(
|
|
657
|
+
self, limit: int = 50, offset: int = 0, user_id: Optional[str] = None
|
|
658
|
+
) -> tuple:
|
|
659
|
+
params: Dict[str, Any] = {
|
|
660
|
+
"tenant_id": self.tenant_id,
|
|
661
|
+
"limit": str(limit),
|
|
662
|
+
"offset": str(offset),
|
|
663
|
+
}
|
|
664
|
+
if user_id:
|
|
665
|
+
params["user_id"] = user_id
|
|
666
|
+
return "documents.list_documents", params
|
|
667
|
+
|
|
668
|
+
def _prepare_get_document(self, document_id: str) -> tuple:
|
|
669
|
+
return "documents.get_document", {
|
|
670
|
+
"tenant_id": self.tenant_id,
|
|
671
|
+
"document_id": document_id,
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
def _prepare_list_chunks(
|
|
675
|
+
self,
|
|
676
|
+
document_id: str,
|
|
677
|
+
user_id: Optional[str] = None,
|
|
678
|
+
search: Optional[str] = None,
|
|
679
|
+
limit: int = 50,
|
|
680
|
+
offset: int = 0,
|
|
681
|
+
) -> tuple:
|
|
682
|
+
params: Dict[str, Any] = {
|
|
683
|
+
"tenant_id": self.tenant_id,
|
|
684
|
+
"document_id": document_id,
|
|
685
|
+
"limit": str(limit),
|
|
686
|
+
"offset": str(offset),
|
|
687
|
+
}
|
|
688
|
+
if user_id:
|
|
689
|
+
params["user_id"] = user_id
|
|
690
|
+
if search:
|
|
691
|
+
params["search"] = search
|
|
692
|
+
return "documents.list_chunks", params
|
|
693
|
+
|
|
694
|
+
def _prepare_get_document_content(
|
|
695
|
+
self, document_id: str, user_id: Optional[str] = None, download: bool = False
|
|
696
|
+
) -> tuple:
|
|
697
|
+
params: Dict[str, Any] = {
|
|
698
|
+
"tenant_id": self.tenant_id,
|
|
699
|
+
"document_id": document_id,
|
|
700
|
+
}
|
|
701
|
+
if user_id:
|
|
702
|
+
params["user_id"] = user_id
|
|
703
|
+
if download:
|
|
704
|
+
params["download"] = "1"
|
|
705
|
+
return "documents.get_document_content", params
|
|
706
|
+
|
|
707
|
+
def _prepare_delete_document(
|
|
708
|
+
self, document_id: str, user_id: Optional[str] = None
|
|
709
|
+
) -> tuple:
|
|
710
|
+
payload: Dict[str, Any] = {
|
|
711
|
+
"tenant_id": self.tenant_id,
|
|
712
|
+
"document_id": document_id,
|
|
713
|
+
}
|
|
714
|
+
if user_id:
|
|
715
|
+
payload["user_id"] = user_id
|
|
716
|
+
return "documents.delete_document", payload
|
|
717
|
+
|
|
718
|
+
def _prepare_get_storage_info(self) -> tuple:
|
|
719
|
+
return "documents.get_storage_info", {"tenant_id": self.tenant_id}
|
|
720
|
+
|
|
721
|
+
def _prepare_update_document_access(
|
|
722
|
+
self,
|
|
723
|
+
document_id: str,
|
|
724
|
+
user_id: str,
|
|
725
|
+
visibility: Optional[str] = None,
|
|
726
|
+
add_users: Optional[List[str]] = None,
|
|
727
|
+
remove_users: Optional[List[str]] = None,
|
|
728
|
+
) -> tuple:
|
|
729
|
+
"""Returns (endpoint, payload) for updating document visibility/sharing."""
|
|
730
|
+
payload: Dict[str, Any] = {
|
|
731
|
+
"tenant_id": self.tenant_id,
|
|
732
|
+
"document_id": document_id,
|
|
733
|
+
"user_id": user_id,
|
|
734
|
+
}
|
|
735
|
+
if visibility:
|
|
736
|
+
payload["visibility"] = visibility
|
|
737
|
+
if add_users:
|
|
738
|
+
payload["add_users"] = json.dumps(add_users)
|
|
739
|
+
if remove_users:
|
|
740
|
+
payload["remove_users"] = json.dumps(remove_users)
|
|
741
|
+
return "documents.update_document_access", payload
|
|
742
|
+
|
|
743
|
+
# =========================================================================
|
|
744
|
+
# Prepare Methods — Memories
|
|
745
|
+
# =========================================================================
|
|
746
|
+
|
|
747
|
+
def _prepare_list_memories(
|
|
748
|
+
self, user_id: Optional[str] = None,
|
|
749
|
+
memory_type: Optional[str] = None,
|
|
750
|
+
limit: int = 50, offset: int = 0
|
|
751
|
+
) -> tuple:
|
|
752
|
+
params: Dict[str, Any] = {
|
|
753
|
+
"tenant_id": self.tenant_id,
|
|
754
|
+
"limit": str(limit),
|
|
755
|
+
"offset": str(offset),
|
|
756
|
+
}
|
|
757
|
+
if user_id:
|
|
758
|
+
params["user_id"] = user_id
|
|
759
|
+
if memory_type:
|
|
760
|
+
params["memory_type"] = memory_type
|
|
761
|
+
return "memories.list_memories", params
|
|
762
|
+
|
|
763
|
+
def _prepare_delete_memory(self, user_id: str, memory_id: str) -> tuple:
|
|
764
|
+
return "memories.delete_memory", {
|
|
765
|
+
"tenant_id": self.tenant_id,
|
|
766
|
+
"user_id": user_id,
|
|
767
|
+
"memory_id": memory_id,
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
def _prepare_delete_all_memories(self, user_id: str) -> tuple:
|
|
771
|
+
return "memories.delete_all_memories", {
|
|
772
|
+
"tenant_id": self.tenant_id,
|
|
773
|
+
"user_id": user_id,
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
def _prepare_update_memory(self, user_id: str, memory_id: str, content: str) -> tuple:
|
|
777
|
+
return "memories.update_memory", {
|
|
778
|
+
"tenant_id": self.tenant_id,
|
|
779
|
+
"user_id": user_id,
|
|
780
|
+
"memory_id": memory_id,
|
|
781
|
+
"content": content,
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
def _prepare_get_memory_stats(self, user_id: Optional[str] = None) -> tuple:
|
|
785
|
+
params: Dict[str, Any] = {"tenant_id": self.tenant_id}
|
|
786
|
+
if user_id:
|
|
787
|
+
params["user_id"] = user_id
|
|
788
|
+
return "memories.get_memory_stats", params
|
|
789
|
+
|
|
790
|
+
def _prepare_get_memory_summary(self, user_id: str, force: bool = False) -> tuple:
|
|
791
|
+
params: Dict[str, Any] = {"tenant_id": self.tenant_id, "user_id": user_id}
|
|
792
|
+
if force:
|
|
793
|
+
params["force"] = "1"
|
|
794
|
+
return "memories.get_memory_summary", params
|
|
795
|
+
|
|
796
|
+
# =========================================================================
|
|
797
|
+
# Prepare Methods — Shared Knowledge
|
|
798
|
+
# =========================================================================
|
|
799
|
+
|
|
800
|
+
def _prepare_get_shared_knowledge(self) -> tuple:
|
|
801
|
+
return "shared_knowledge.get_shared_knowledge", {
|
|
802
|
+
"tenant_id": self.tenant_id,
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
def _prepare_update_shared_knowledge(self, content: str) -> tuple:
|
|
806
|
+
return "shared_knowledge.update_shared_knowledge", {
|
|
807
|
+
"tenant_id": self.tenant_id,
|
|
808
|
+
"content": content,
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
def _prepare_share_memory_to_knowledge(self, user_id: str, memory_id: str) -> tuple:
|
|
812
|
+
return "shared_knowledge.share_memory", {
|
|
813
|
+
"tenant_id": self.tenant_id,
|
|
814
|
+
"user_id": user_id,
|
|
815
|
+
"memory_id": memory_id,
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
# =========================================================================
|
|
819
|
+
# Prepare Methods — Tools
|
|
820
|
+
# =========================================================================
|
|
821
|
+
|
|
822
|
+
def _prepare_list_tools(self, user_id: str,
|
|
823
|
+
server: Optional[str] = None) -> tuple:
|
|
824
|
+
params: Dict[str, Any] = {"tenant_id": self.tenant_id, "user_id": user_id}
|
|
825
|
+
if server:
|
|
826
|
+
params["server"] = server
|
|
827
|
+
return "tools.list_tools", params
|
|
828
|
+
|
|
829
|
+
# =========================================================================
|
|
830
|
+
# Prepare Methods — Tool Preferences (per-user approval settings)
|
|
831
|
+
# =========================================================================
|
|
832
|
+
|
|
833
|
+
def _prepare_list_tool_preferences(self, user_id: str) -> tuple:
|
|
834
|
+
return "tool_preferences.list_tool_preferences", {
|
|
835
|
+
"tenant_id": self.tenant_id,
|
|
836
|
+
"user_id": user_id,
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
def _prepare_set_tool_preference(
|
|
840
|
+
self, user_id: str, tool_name: str, preference: str,
|
|
841
|
+
) -> tuple:
|
|
842
|
+
return "tool_preferences.set_tool_preference", {
|
|
843
|
+
"tenant_id": self.tenant_id,
|
|
844
|
+
"user_id": user_id,
|
|
845
|
+
"tool_name": tool_name,
|
|
846
|
+
"preference": preference,
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
# =========================================================================
|
|
850
|
+
# Prepare Methods — Billing
|
|
851
|
+
# =========================================================================
|
|
852
|
+
|
|
853
|
+
def _prepare_get_recommended_gateway(self) -> tuple:
|
|
854
|
+
self._require_billing()
|
|
855
|
+
return "get_recommended_gateway", {"tenant_id": self.tenant_id}
|
|
856
|
+
|
|
857
|
+
def _prepare_get_available_gateways(self) -> tuple:
|
|
858
|
+
self._require_billing()
|
|
859
|
+
return "get_available_gateways", {"tenant_id": self.tenant_id}
|
|
860
|
+
|
|
861
|
+
def _prepare_preview_plan_pricing(
|
|
862
|
+
self, plan: str, billing_cycle: str = "monthly",
|
|
863
|
+
) -> tuple:
|
|
864
|
+
self._require_billing()
|
|
865
|
+
return "preview_plan_pricing", {
|
|
866
|
+
"tenant_id": self.tenant_id,
|
|
867
|
+
"plan": plan,
|
|
868
|
+
"billing_cycle": billing_cycle,
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
def _prepare_download_invoice_pdf(self, ar_invoice_name: str) -> tuple:
|
|
872
|
+
self._require_billing()
|
|
873
|
+
return "download_invoice_pdf", {
|
|
874
|
+
"tenant_id": self.tenant_id,
|
|
875
|
+
"ar_invoice_name": ar_invoice_name,
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
def _prepare_add_user_seat(self) -> tuple:
|
|
879
|
+
self._require_billing()
|
|
880
|
+
return "add_user_seat", {"tenant_id": self.tenant_id}
|
|
881
|
+
|
|
882
|
+
def _prepare_remove_user_seat(self) -> tuple:
|
|
883
|
+
self._require_billing()
|
|
884
|
+
return "remove_user_seat", {"tenant_id": self.tenant_id}
|
|
885
|
+
|
|
886
|
+
def _prepare_preview_seat_charge(self) -> tuple:
|
|
887
|
+
self._require_billing()
|
|
888
|
+
return "preview_seat_charge", {"tenant_id": self.tenant_id}
|
|
889
|
+
|
|
890
|
+
def _prepare_initiate_checkout(
|
|
891
|
+
self, plan: str, billing_cycle: str = "monthly",
|
|
892
|
+
gateway: Optional[str] = None, billing_name: Optional[str] = None,
|
|
893
|
+
billing_email: Optional[str] = None,
|
|
894
|
+
) -> tuple:
|
|
895
|
+
self._require_billing()
|
|
896
|
+
payload: Dict[str, Any] = {
|
|
897
|
+
"tenant_id": self.tenant_id,
|
|
898
|
+
"plan": plan,
|
|
899
|
+
"billing_cycle": billing_cycle,
|
|
900
|
+
}
|
|
901
|
+
if gateway:
|
|
902
|
+
payload["gateway"] = gateway
|
|
903
|
+
if billing_name:
|
|
904
|
+
payload["billing_name"] = billing_name
|
|
905
|
+
if billing_email:
|
|
906
|
+
payload["billing_email"] = billing_email
|
|
907
|
+
return "initiate_checkout", payload
|
|
908
|
+
|
|
909
|
+
def _prepare_verify_checkout(self, session_id: Optional[str] = None) -> tuple:
|
|
910
|
+
self._require_billing()
|
|
911
|
+
payload: Dict[str, Any] = {"tenant_id": self.tenant_id}
|
|
912
|
+
if session_id:
|
|
913
|
+
payload["session_id"] = session_id
|
|
914
|
+
return "verify_checkout", payload
|
|
915
|
+
|
|
916
|
+
def _prepare_verify_razorpay_payment(
|
|
917
|
+
self, razorpay_payment_id: str,
|
|
918
|
+
razorpay_subscription_id: str, razorpay_signature: str,
|
|
919
|
+
) -> tuple:
|
|
920
|
+
self._require_billing()
|
|
921
|
+
return "verify_razorpay_payment", {
|
|
922
|
+
"tenant_id": self.tenant_id,
|
|
923
|
+
"razorpay_payment_id": razorpay_payment_id,
|
|
924
|
+
"razorpay_subscription_id": razorpay_subscription_id,
|
|
925
|
+
"razorpay_signature": razorpay_signature,
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
def _prepare_verify_razorpay_credit_payment(
|
|
929
|
+
self, razorpay_payment_id: str,
|
|
930
|
+
razorpay_order_id: str, razorpay_signature: str,
|
|
931
|
+
) -> tuple:
|
|
932
|
+
self._require_billing()
|
|
933
|
+
return "verify_razorpay_credit_payment", {
|
|
934
|
+
"tenant_id": self.tenant_id,
|
|
935
|
+
"razorpay_payment_id": razorpay_payment_id,
|
|
936
|
+
"razorpay_order_id": razorpay_order_id,
|
|
937
|
+
"razorpay_signature": razorpay_signature,
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
def _prepare_verify_seat_payment(
|
|
941
|
+
self, razorpay_payment_id: str,
|
|
942
|
+
razorpay_order_id: str, razorpay_signature: str,
|
|
943
|
+
) -> tuple:
|
|
944
|
+
self._require_billing()
|
|
945
|
+
return "verify_seat_payment", {
|
|
946
|
+
"tenant_id": self.tenant_id,
|
|
947
|
+
"razorpay_payment_id": razorpay_payment_id,
|
|
948
|
+
"razorpay_order_id": razorpay_order_id,
|
|
949
|
+
"razorpay_signature": razorpay_signature,
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
def _prepare_get_token_analytics(self, days: int = 30, user_id: str = None) -> tuple:
|
|
953
|
+
params = {"tenant_id": self.tenant_id, "days": days}
|
|
954
|
+
if user_id:
|
|
955
|
+
params["user_id"] = user_id
|
|
956
|
+
return "usage.get_token_analytics", params
|
|
957
|
+
|
|
958
|
+
def _prepare_get_conversation_analytics(
|
|
959
|
+
self, user_id: str = None, days: int = 30, limit: int = 50, offset: int = 0,
|
|
960
|
+
) -> tuple:
|
|
961
|
+
params = {"tenant_id": self.tenant_id, "days": days, "limit": limit, "offset": offset}
|
|
962
|
+
if user_id:
|
|
963
|
+
params["user_id"] = user_id
|
|
964
|
+
return "usage.get_conversation_analytics", params
|
|
965
|
+
|
|
966
|
+
def _prepare_get_message_credits(self, conversation_id: str, user_id: str = None) -> tuple:
|
|
967
|
+
params = {"tenant_id": self.tenant_id, "conversation_id": conversation_id}
|
|
968
|
+
if user_id:
|
|
969
|
+
params["user_id"] = user_id
|
|
970
|
+
return "usage.get_message_credits", params
|
|
971
|
+
|
|
972
|
+
def _prepare_get_usage_dashboard(self) -> tuple:
|
|
973
|
+
self._require_billing()
|
|
974
|
+
return "get_usage_dashboard", {"tenant_id": self.tenant_id}
|
|
975
|
+
|
|
976
|
+
def _prepare_get_usage_history(self, days: int = 30) -> tuple:
|
|
977
|
+
self._require_billing()
|
|
978
|
+
return "get_usage_history", {"tenant_id": self.tenant_id, "days": days}
|
|
979
|
+
|
|
980
|
+
def _prepare_get_invoices(self, limit: int = 10) -> tuple:
|
|
981
|
+
self._require_billing()
|
|
982
|
+
return "get_invoices", {"tenant_id": self.tenant_id, "limit": limit}
|
|
983
|
+
|
|
984
|
+
def _prepare_get_upcoming_invoice(self) -> tuple:
|
|
985
|
+
self._require_billing()
|
|
986
|
+
return "get_upcoming_invoice", {"tenant_id": self.tenant_id}
|
|
987
|
+
|
|
988
|
+
def _prepare_get_payment_methods(self) -> tuple:
|
|
989
|
+
self._require_billing()
|
|
990
|
+
return "get_payment_methods", {"tenant_id": self.tenant_id}
|
|
991
|
+
|
|
992
|
+
def _prepare_upgrade_plan(
|
|
993
|
+
self, new_plan: str, billing_cycle: str = "monthly",
|
|
994
|
+
gateway: Optional[str] = None, billing_name: Optional[str] = None,
|
|
995
|
+
billing_email: Optional[str] = None,
|
|
996
|
+
promo_code: Optional[str] = None,
|
|
997
|
+
payment_method: Optional[str] = None,
|
|
998
|
+
) -> tuple:
|
|
999
|
+
self._require_billing()
|
|
1000
|
+
payload: Dict[str, Any] = {
|
|
1001
|
+
"tenant_id": self.tenant_id,
|
|
1002
|
+
"new_plan": new_plan,
|
|
1003
|
+
"billing_cycle": billing_cycle,
|
|
1004
|
+
}
|
|
1005
|
+
if gateway:
|
|
1006
|
+
payload["gateway"] = gateway
|
|
1007
|
+
if billing_name:
|
|
1008
|
+
payload["billing_name"] = billing_name
|
|
1009
|
+
if billing_email:
|
|
1010
|
+
payload["billing_email"] = billing_email
|
|
1011
|
+
if promo_code:
|
|
1012
|
+
payload["promo_code"] = promo_code
|
|
1013
|
+
if payment_method:
|
|
1014
|
+
payload["payment_method"] = payment_method
|
|
1015
|
+
return "upgrade_plan", payload
|
|
1016
|
+
|
|
1017
|
+
def _prepare_reauthorize_mandate(
|
|
1018
|
+
self,
|
|
1019
|
+
billing_name: Optional[str] = None,
|
|
1020
|
+
payment_method: Optional[str] = None,
|
|
1021
|
+
) -> tuple:
|
|
1022
|
+
"""Build the request for ``reauthorize_mandate`` — re-authorize the
|
|
1023
|
+
saved Razorpay mandate without changing plans. Used when the
|
|
1024
|
+
renewal cron has flagged the mandate as exhausted (e.g., projected
|
|
1025
|
+
next-cycle debit exceeds UPI Autopay's per-debit ceiling)."""
|
|
1026
|
+
self._require_billing()
|
|
1027
|
+
payload: Dict[str, Any] = {"tenant_id": self.tenant_id}
|
|
1028
|
+
if billing_name:
|
|
1029
|
+
payload["billing_name"] = billing_name
|
|
1030
|
+
if payment_method:
|
|
1031
|
+
payload["payment_method"] = payment_method
|
|
1032
|
+
return "reauthorize_mandate", payload
|
|
1033
|
+
|
|
1034
|
+
def _prepare_validate_promo_code(
|
|
1035
|
+
self, promo_code: str, plan: Optional[str] = None,
|
|
1036
|
+
) -> tuple:
|
|
1037
|
+
self._require_billing()
|
|
1038
|
+
payload: Dict[str, Any] = {
|
|
1039
|
+
"tenant_id": self.tenant_id,
|
|
1040
|
+
"promo_code": promo_code,
|
|
1041
|
+
}
|
|
1042
|
+
if plan:
|
|
1043
|
+
payload["plan"] = plan
|
|
1044
|
+
return "promotions.validate_promo_code", payload
|
|
1045
|
+
|
|
1046
|
+
def _prepare_downgrade_to_free(self) -> tuple:
|
|
1047
|
+
self._require_billing()
|
|
1048
|
+
return "downgrade_to_free", {"tenant_id": self.tenant_id}
|
|
1049
|
+
|
|
1050
|
+
def _prepare_cancel_scheduled_change(self) -> tuple:
|
|
1051
|
+
self._require_billing()
|
|
1052
|
+
return "cancel_scheduled_change", {"tenant_id": self.tenant_id}
|
|
1053
|
+
|
|
1054
|
+
def _prepare_cancel_subscription(self, cancel_immediately: bool = False) -> tuple:
|
|
1055
|
+
self._require_billing()
|
|
1056
|
+
return "cancel_subscription", {
|
|
1057
|
+
"tenant_id": self.tenant_id,
|
|
1058
|
+
"cancel_immediately": cancel_immediately,
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
def _prepare_reactivate_subscription(self) -> tuple:
|
|
1062
|
+
self._require_billing()
|
|
1063
|
+
return "reactivate_subscription", {"tenant_id": self.tenant_id}
|
|
1064
|
+
|
|
1065
|
+
def _prepare_pause_subscription(self) -> tuple:
|
|
1066
|
+
self._require_billing()
|
|
1067
|
+
return "pause_subscription", {"tenant_id": self.tenant_id}
|
|
1068
|
+
|
|
1069
|
+
def _prepare_resume_subscription(self) -> tuple:
|
|
1070
|
+
self._require_billing()
|
|
1071
|
+
return "resume_subscription", {"tenant_id": self.tenant_id}
|
|
1072
|
+
|
|
1073
|
+
def _prepare_update_payment_method(self) -> tuple:
|
|
1074
|
+
self._require_billing()
|
|
1075
|
+
return "update_payment_method", {"tenant_id": self.tenant_id}
|
|
1076
|
+
|
|
1077
|
+
def _prepare_get_subscription_status(self) -> tuple:
|
|
1078
|
+
self._require_billing()
|
|
1079
|
+
return "get_subscription_status", {"tenant_id": self.tenant_id}
|
|
1080
|
+
|
|
1081
|
+
def _prepare_get_billing_history(self, limit: int = 20) -> tuple:
|
|
1082
|
+
self._require_billing()
|
|
1083
|
+
return "get_billing_history", {"tenant_id": self.tenant_id, "limit": limit}
|
|
1084
|
+
|
|
1085
|
+
def _prepare_get_credit_balance(self) -> tuple:
|
|
1086
|
+
self._require_billing()
|
|
1087
|
+
return "get_credit_balance", {"tenant_id": self.tenant_id}
|
|
1088
|
+
|
|
1089
|
+
def _prepare_purchase_credits(self, credit_amount: int,
|
|
1090
|
+
gateway: Optional[str] = None) -> tuple:
|
|
1091
|
+
self._require_billing()
|
|
1092
|
+
payload: Dict[str, Any] = {
|
|
1093
|
+
"tenant_id": self.tenant_id,
|
|
1094
|
+
"credit_amount": credit_amount,
|
|
1095
|
+
}
|
|
1096
|
+
if gateway:
|
|
1097
|
+
payload["gateway"] = gateway
|
|
1098
|
+
return "purchase_credits", payload
|
|
1099
|
+
|
|
1100
|
+
def _prepare_get_expiring_credits(self) -> tuple:
|
|
1101
|
+
self._require_billing()
|
|
1102
|
+
return "get_expiring_credits", {"tenant_id": self.tenant_id}
|
|
1103
|
+
|
|
1104
|
+
def _prepare_get_consumption_breakdown(self, days: int = 30) -> tuple:
|
|
1105
|
+
self._require_billing()
|
|
1106
|
+
return "get_consumption_breakdown", {
|
|
1107
|
+
"tenant_id": self.tenant_id,
|
|
1108
|
+
"days": int(days),
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
def _prepare_get_billing_details(self) -> tuple:
|
|
1112
|
+
self._require_billing()
|
|
1113
|
+
return "billing_details.get_billing_details", {"tenant_id": self.tenant_id}
|
|
1114
|
+
|
|
1115
|
+
def _prepare_save_billing_details(self, billing_fields: Dict[str, Any]) -> tuple:
|
|
1116
|
+
self._require_billing()
|
|
1117
|
+
payload: Dict[str, Any] = {"tenant_id": self.tenant_id, **billing_fields}
|
|
1118
|
+
return "billing_details.save_billing_details", payload
|
|
1119
|
+
|
|
1120
|
+
# =========================================================================
|
|
1121
|
+
# Prepare Methods — Conversations
|
|
1122
|
+
# =========================================================================
|
|
1123
|
+
|
|
1124
|
+
def _prepare_list_conversations(
|
|
1125
|
+
self, user_id: Optional[str] = None, limit: int = 50,
|
|
1126
|
+
offset: int = 0, include_deleted: bool = False,
|
|
1127
|
+
from_date: Optional[str] = None, to_date: Optional[str] = None,
|
|
1128
|
+
) -> tuple:
|
|
1129
|
+
params: Dict[str, Any] = {
|
|
1130
|
+
"tenant_id": self.tenant_id,
|
|
1131
|
+
"limit": limit,
|
|
1132
|
+
"offset": offset,
|
|
1133
|
+
"include_deleted": include_deleted,
|
|
1134
|
+
}
|
|
1135
|
+
if user_id:
|
|
1136
|
+
params["user_id"] = user_id
|
|
1137
|
+
if from_date:
|
|
1138
|
+
params["from_date"] = from_date
|
|
1139
|
+
if to_date:
|
|
1140
|
+
params["to_date"] = to_date
|
|
1141
|
+
return "conversations.list_conversations", params
|
|
1142
|
+
|
|
1143
|
+
def _prepare_get_conversation(self, conversation_id: str) -> tuple:
|
|
1144
|
+
return "conversations.get_conversation", {
|
|
1145
|
+
"tenant_id": self.tenant_id,
|
|
1146
|
+
"conversation_id": conversation_id,
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
def _prepare_get_messages(
|
|
1150
|
+
self, conversation_id: str, limit: int = 100,
|
|
1151
|
+
offset: int = 0, include_deleted: bool = False,
|
|
1152
|
+
) -> tuple:
|
|
1153
|
+
return "conversations.get_messages", {
|
|
1154
|
+
"tenant_id": self.tenant_id,
|
|
1155
|
+
"conversation_id": conversation_id,
|
|
1156
|
+
"limit": limit,
|
|
1157
|
+
"offset": offset,
|
|
1158
|
+
"include_deleted": include_deleted,
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
def _prepare_create_message(
|
|
1162
|
+
self, conversation_id: str, message_id: str, role: str, content: str,
|
|
1163
|
+
user_id: Optional[str] = None, tokens_used: int = 0,
|
|
1164
|
+
context: Optional[Dict[str, Any]] = None,
|
|
1165
|
+
) -> tuple:
|
|
1166
|
+
payload: Dict[str, Any] = {
|
|
1167
|
+
"tenant_id": self.tenant_id,
|
|
1168
|
+
"conversation_id": conversation_id,
|
|
1169
|
+
"message_id": message_id,
|
|
1170
|
+
"role": role,
|
|
1171
|
+
"content": content,
|
|
1172
|
+
"tokens_used": tokens_used,
|
|
1173
|
+
}
|
|
1174
|
+
if user_id:
|
|
1175
|
+
payload["user_id"] = user_id
|
|
1176
|
+
if context:
|
|
1177
|
+
payload["context"] = context
|
|
1178
|
+
return "conversations.create_message", payload
|
|
1179
|
+
|
|
1180
|
+
def _prepare_update_conversation(
|
|
1181
|
+
self, conversation_id: str,
|
|
1182
|
+
title: Optional[str] = None, user_id: Optional[str] = None,
|
|
1183
|
+
) -> tuple:
|
|
1184
|
+
payload: Dict[str, Any] = {
|
|
1185
|
+
"tenant_id": self.tenant_id,
|
|
1186
|
+
"conversation_id": conversation_id,
|
|
1187
|
+
}
|
|
1188
|
+
if title is not None:
|
|
1189
|
+
payload["title"] = title
|
|
1190
|
+
if user_id is not None:
|
|
1191
|
+
payload["user_id"] = user_id
|
|
1192
|
+
return "conversations.update_conversation", payload
|
|
1193
|
+
|
|
1194
|
+
def _prepare_delete_conversation(self, conversation_id: str, hard_delete: bool = False) -> tuple:
|
|
1195
|
+
payload = {
|
|
1196
|
+
"tenant_id": self.tenant_id,
|
|
1197
|
+
"conversation_id": conversation_id,
|
|
1198
|
+
}
|
|
1199
|
+
if hard_delete:
|
|
1200
|
+
payload["hard_delete"] = True
|
|
1201
|
+
return "conversations.delete_conversation", payload
|
|
1202
|
+
|
|
1203
|
+
def _prepare_delete_message(self, conversation_id: str, message_id: str) -> tuple:
|
|
1204
|
+
return "conversations.delete_message", {
|
|
1205
|
+
"tenant_id": self.tenant_id,
|
|
1206
|
+
"conversation_id": conversation_id,
|
|
1207
|
+
"message_id": message_id,
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
def _prepare_get_sync_stats(self) -> tuple:
|
|
1211
|
+
return "conversations.get_sync_stats", {"tenant_id": self.tenant_id}
|
|
1212
|
+
|
|
1213
|
+
def _prepare_get_message_events(
|
|
1214
|
+
self, conversation_id: str, message_id: Optional[str] = None,
|
|
1215
|
+
event_types: Optional[list] = None, limit: int = 100, offset: int = 0,
|
|
1216
|
+
) -> tuple:
|
|
1217
|
+
params: Dict[str, Any] = {
|
|
1218
|
+
"tenant_id": self.tenant_id,
|
|
1219
|
+
"conversation_id": conversation_id,
|
|
1220
|
+
"limit": limit,
|
|
1221
|
+
"offset": offset,
|
|
1222
|
+
}
|
|
1223
|
+
if message_id:
|
|
1224
|
+
params["message_id"] = message_id
|
|
1225
|
+
if event_types:
|
|
1226
|
+
params["event_types"] = ",".join(event_types) if isinstance(event_types, list) else event_types
|
|
1227
|
+
return "conversations.get_message_events", params
|
|
1228
|
+
|
|
1229
|
+
def _prepare_get_tool_execution_stats(
|
|
1230
|
+
self, conversation_id: Optional[str] = None,
|
|
1231
|
+
from_date: Optional[str] = None, to_date: Optional[str] = None,
|
|
1232
|
+
) -> tuple:
|
|
1233
|
+
params: Dict[str, Any] = {"tenant_id": self.tenant_id}
|
|
1234
|
+
if conversation_id:
|
|
1235
|
+
params["conversation_id"] = conversation_id
|
|
1236
|
+
if from_date:
|
|
1237
|
+
params["from_date"] = from_date
|
|
1238
|
+
if to_date:
|
|
1239
|
+
params["to_date"] = to_date
|
|
1240
|
+
return "conversations.get_tool_execution_stats", params
|
|
1241
|
+
|
|
1242
|
+
# =========================================================================
|
|
1243
|
+
# Prepare Methods — Users & MCP Servers
|
|
1244
|
+
# =========================================================================
|
|
1245
|
+
|
|
1246
|
+
def _prepare_register_user(
|
|
1247
|
+
self, user_id: str, display_name: Optional[str] = None,
|
|
1248
|
+
custom_instructions: Optional[str] = None,
|
|
1249
|
+
locale: Optional[str] = None,
|
|
1250
|
+
timezone: Optional[str] = None,
|
|
1251
|
+
user_role: Optional[str] = None,
|
|
1252
|
+
email: Optional[str] = None,
|
|
1253
|
+
registered_by: Optional[str] = None,
|
|
1254
|
+
) -> tuple:
|
|
1255
|
+
params: Dict[str, Any] = {"tenant_id": self.tenant_id, "user_id": user_id}
|
|
1256
|
+
if display_name:
|
|
1257
|
+
params["display_name"] = display_name
|
|
1258
|
+
if custom_instructions:
|
|
1259
|
+
params["custom_instructions"] = custom_instructions
|
|
1260
|
+
if locale:
|
|
1261
|
+
params["locale"] = locale
|
|
1262
|
+
if timezone:
|
|
1263
|
+
params["timezone"] = timezone
|
|
1264
|
+
if user_role:
|
|
1265
|
+
params["user_role"] = user_role
|
|
1266
|
+
if email:
|
|
1267
|
+
params["email"] = email
|
|
1268
|
+
if registered_by:
|
|
1269
|
+
params["registered_by"] = registered_by
|
|
1270
|
+
return "users.register_user", params
|
|
1271
|
+
|
|
1272
|
+
def _prepare_invite_user(
|
|
1273
|
+
self,
|
|
1274
|
+
user_id: str,
|
|
1275
|
+
user_role: Optional[str] = None,
|
|
1276
|
+
invited_by: Optional[str] = None,
|
|
1277
|
+
) -> tuple:
|
|
1278
|
+
"""Build the invite_user request: create a Pending invite (reserves a seat)."""
|
|
1279
|
+
params: Dict[str, Any] = {"tenant_id": self.tenant_id, "user_id": user_id}
|
|
1280
|
+
if user_role:
|
|
1281
|
+
params["user_role"] = user_role
|
|
1282
|
+
if invited_by:
|
|
1283
|
+
params["invited_by"] = invited_by
|
|
1284
|
+
return "users.invite_user", params
|
|
1285
|
+
|
|
1286
|
+
def _prepare_revoke_invite(
|
|
1287
|
+
self,
|
|
1288
|
+
user_id: str,
|
|
1289
|
+
revoked_by: Optional[str] = None,
|
|
1290
|
+
) -> tuple:
|
|
1291
|
+
"""Build the revoke_invite request: cancel a Pending invite (frees its seat)."""
|
|
1292
|
+
params: Dict[str, Any] = {"tenant_id": self.tenant_id, "user_id": user_id}
|
|
1293
|
+
if revoked_by:
|
|
1294
|
+
params["revoked_by"] = revoked_by
|
|
1295
|
+
return "users.revoke_invite", params
|
|
1296
|
+
|
|
1297
|
+
def _prepare_resend_invite(
|
|
1298
|
+
self,
|
|
1299
|
+
user_id: str,
|
|
1300
|
+
resent_by: Optional[str] = None,
|
|
1301
|
+
) -> tuple:
|
|
1302
|
+
"""Build the resend_invite request: restart a Pending invite's expiry window."""
|
|
1303
|
+
params: Dict[str, Any] = {"tenant_id": self.tenant_id, "user_id": user_id}
|
|
1304
|
+
if resent_by:
|
|
1305
|
+
params["resent_by"] = resent_by
|
|
1306
|
+
return "users.resend_invite", params
|
|
1307
|
+
|
|
1308
|
+
def _prepare_list_invites(self) -> tuple:
|
|
1309
|
+
"""Build the list_invites request: all Pending invites for this tenant."""
|
|
1310
|
+
return "users.list_invites", {"tenant_id": self.tenant_id}
|
|
1311
|
+
|
|
1312
|
+
def _prepare_get_member_audit_log(
|
|
1313
|
+
self, limit: int = 100, offset: int = 0,
|
|
1314
|
+
) -> tuple:
|
|
1315
|
+
"""Build the get_member_audit_log request (newest first, paginated).
|
|
1316
|
+
|
|
1317
|
+
Each returned entry's ``details`` field is a JSON string the caller
|
|
1318
|
+
must parse.
|
|
1319
|
+
"""
|
|
1320
|
+
params: Dict[str, Any] = {
|
|
1321
|
+
"tenant_id": self.tenant_id,
|
|
1322
|
+
"limit": str(limit),
|
|
1323
|
+
"offset": str(offset),
|
|
1324
|
+
}
|
|
1325
|
+
return "users.get_member_audit_log", params
|
|
1326
|
+
|
|
1327
|
+
def _prepare_get_user(self, user_id: str) -> tuple:
|
|
1328
|
+
return "users.get_user", {"tenant_id": self.tenant_id, "user_id": user_id}
|
|
1329
|
+
|
|
1330
|
+
def _prepare_get_user_auth_status(self, user_id: str) -> tuple:
|
|
1331
|
+
return "users.get_user_auth_status", {
|
|
1332
|
+
"tenant_id": self.tenant_id,
|
|
1333
|
+
"user_id": user_id,
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
def _prepare_add_user_mcp_server(
|
|
1337
|
+
self, user_id: str, server_name: str, endpoint_url: str,
|
|
1338
|
+
transport_type: str = "SSE", auth_type: str = "OAuth",
|
|
1339
|
+
oauth_client_id: Optional[str] = None,
|
|
1340
|
+
oauth_client_secret: Optional[str] = None,
|
|
1341
|
+
access_token: Optional[str] = None,
|
|
1342
|
+
refresh_token: Optional[str] = None,
|
|
1343
|
+
token_expires_in: int = 3600,
|
|
1344
|
+
api_key: Optional[str] = None,
|
|
1345
|
+
api_key_header: str = "Authorization",
|
|
1346
|
+
allowed_tools: Optional[list] = None,
|
|
1347
|
+
blocked_tools: Optional[list] = None,
|
|
1348
|
+
) -> tuple:
|
|
1349
|
+
params: Dict[str, Any] = {
|
|
1350
|
+
"tenant_id": self.tenant_id,
|
|
1351
|
+
"user_id": user_id,
|
|
1352
|
+
"server_name": server_name,
|
|
1353
|
+
"endpoint_url": endpoint_url,
|
|
1354
|
+
"transport_type": transport_type,
|
|
1355
|
+
"auth_type": auth_type,
|
|
1356
|
+
}
|
|
1357
|
+
if oauth_client_id:
|
|
1358
|
+
params["oauth_client_id"] = oauth_client_id
|
|
1359
|
+
if oauth_client_secret:
|
|
1360
|
+
params["oauth_client_secret"] = oauth_client_secret
|
|
1361
|
+
if access_token:
|
|
1362
|
+
params["access_token"] = access_token
|
|
1363
|
+
if refresh_token:
|
|
1364
|
+
params["refresh_token"] = refresh_token
|
|
1365
|
+
if token_expires_in:
|
|
1366
|
+
params["token_expires_in"] = str(token_expires_in)
|
|
1367
|
+
if api_key:
|
|
1368
|
+
params["api_key"] = api_key
|
|
1369
|
+
if api_key_header:
|
|
1370
|
+
params["api_key_header"] = api_key_header
|
|
1371
|
+
if allowed_tools:
|
|
1372
|
+
params["allowed_tools"] = json.dumps(allowed_tools)
|
|
1373
|
+
if blocked_tools:
|
|
1374
|
+
params["blocked_tools"] = json.dumps(blocked_tools)
|
|
1375
|
+
return "users.add_user_mcp_server", params
|
|
1376
|
+
|
|
1377
|
+
def _prepare_get_user_mcp_servers(self, user_id: str) -> tuple:
|
|
1378
|
+
return "users.get_user_mcp_servers", {
|
|
1379
|
+
"tenant_id": self.tenant_id,
|
|
1380
|
+
"user_id": user_id,
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
def _prepare_update_mcp_server_tokens(
|
|
1384
|
+
self, user_id: str, server_name: str, access_token: str,
|
|
1385
|
+
refresh_token: Optional[str] = None, token_expires_in: int = 3600,
|
|
1386
|
+
) -> tuple:
|
|
1387
|
+
params: Dict[str, Any] = {
|
|
1388
|
+
"tenant_id": self.tenant_id,
|
|
1389
|
+
"user_id": user_id,
|
|
1390
|
+
"server_name": server_name,
|
|
1391
|
+
"access_token": access_token,
|
|
1392
|
+
"token_expires_in": str(token_expires_in),
|
|
1393
|
+
}
|
|
1394
|
+
if refresh_token:
|
|
1395
|
+
params["refresh_token"] = refresh_token
|
|
1396
|
+
return "users.update_mcp_server_tokens", params
|
|
1397
|
+
|
|
1398
|
+
def _prepare_remove_user_mcp_server(self, user_id: str, server_name: str) -> tuple:
|
|
1399
|
+
return "users.remove_user_mcp_server", {
|
|
1400
|
+
"tenant_id": self.tenant_id,
|
|
1401
|
+
"user_id": user_id,
|
|
1402
|
+
"server_name": server_name,
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
def _prepare_list_users(
|
|
1406
|
+
self, status: Optional[str] = None, limit: int = 50,
|
|
1407
|
+
offset: int = 0, include_mcp_count: bool = True,
|
|
1408
|
+
) -> tuple:
|
|
1409
|
+
params: Dict[str, Any] = {
|
|
1410
|
+
"tenant_id": self.tenant_id,
|
|
1411
|
+
"limit": str(limit),
|
|
1412
|
+
"offset": str(offset),
|
|
1413
|
+
"include_mcp_count": str(include_mcp_count).lower(),
|
|
1414
|
+
}
|
|
1415
|
+
if status:
|
|
1416
|
+
params["status"] = status
|
|
1417
|
+
return "users.list_users", params
|
|
1418
|
+
|
|
1419
|
+
def _prepare_update_user(
|
|
1420
|
+
self, user_id: str, display_name: Optional[str] = None,
|
|
1421
|
+
custom_instructions: Optional[str] = None,
|
|
1422
|
+
locale: Optional[str] = None,
|
|
1423
|
+
timezone: Optional[str] = None,
|
|
1424
|
+
user_role: Optional[str] = None,
|
|
1425
|
+
email: Optional[str] = None,
|
|
1426
|
+
job_title: Optional[str] = None,
|
|
1427
|
+
department: Optional[str] = None,
|
|
1428
|
+
about: Optional[str] = None,
|
|
1429
|
+
) -> tuple:
|
|
1430
|
+
params: Dict[str, Any] = {
|
|
1431
|
+
"tenant_id": self.tenant_id,
|
|
1432
|
+
"user_id": user_id,
|
|
1433
|
+
}
|
|
1434
|
+
if display_name is not None:
|
|
1435
|
+
params["display_name"] = display_name
|
|
1436
|
+
if custom_instructions is not None:
|
|
1437
|
+
params["custom_instructions"] = custom_instructions
|
|
1438
|
+
if locale is not None:
|
|
1439
|
+
params["locale"] = locale
|
|
1440
|
+
if timezone is not None:
|
|
1441
|
+
params["timezone"] = timezone
|
|
1442
|
+
if user_role is not None:
|
|
1443
|
+
params["user_role"] = user_role
|
|
1444
|
+
if email is not None:
|
|
1445
|
+
params["email"] = email
|
|
1446
|
+
if job_title is not None:
|
|
1447
|
+
params["job_title"] = job_title
|
|
1448
|
+
if department is not None:
|
|
1449
|
+
params["department"] = department
|
|
1450
|
+
if about is not None:
|
|
1451
|
+
params["about"] = about
|
|
1452
|
+
return "users.update_user", params
|
|
1453
|
+
|
|
1454
|
+
def _prepare_deregister_user(self, user_id: str) -> tuple:
|
|
1455
|
+
return "users.deregister_user", {
|
|
1456
|
+
"tenant_id": self.tenant_id,
|
|
1457
|
+
"user_id": user_id,
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
def _prepare_get_user_limit_status(self) -> tuple:
|
|
1461
|
+
return "users.get_user_limit_status", {"tenant_id": self.tenant_id}
|
|
1462
|
+
|
|
1463
|
+
def _prepare_suspend_user(self, user_id: str) -> tuple:
|
|
1464
|
+
return "users.suspend_user", {
|
|
1465
|
+
"tenant_id": self.tenant_id,
|
|
1466
|
+
"user_id": user_id,
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
def _prepare_revoke_user(self, user_id: str) -> tuple:
|
|
1470
|
+
return "users.revoke_user", {
|
|
1471
|
+
"tenant_id": self.tenant_id,
|
|
1472
|
+
"user_id": user_id,
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
# =========================================================================
|
|
1476
|
+
# Prepare Methods — Workflows
|
|
1477
|
+
# =========================================================================
|
|
1478
|
+
|
|
1479
|
+
def _prepare_create_workflow(
|
|
1480
|
+
self, workflow_name: str, graph_json: Optional[str] = None,
|
|
1481
|
+
description: str = "", default_model_id: Optional[str] = None,
|
|
1482
|
+
default_user_id: Optional[str] = None,
|
|
1483
|
+
error_strategy: str = "fail_fast", timeout_seconds: int = 600,
|
|
1484
|
+
) -> tuple:
|
|
1485
|
+
payload: Dict[str, Any] = {
|
|
1486
|
+
"tenant_id": self.tenant_id,
|
|
1487
|
+
"workflow_name": workflow_name,
|
|
1488
|
+
"description": description,
|
|
1489
|
+
"error_strategy": error_strategy,
|
|
1490
|
+
"timeout_seconds": timeout_seconds,
|
|
1491
|
+
}
|
|
1492
|
+
if graph_json:
|
|
1493
|
+
payload["graph_json"] = graph_json
|
|
1494
|
+
if default_model_id:
|
|
1495
|
+
payload["default_model_id"] = default_model_id
|
|
1496
|
+
if default_user_id:
|
|
1497
|
+
payload["default_user_id"] = default_user_id
|
|
1498
|
+
return "workflows.create_workflow", payload
|
|
1499
|
+
|
|
1500
|
+
def _prepare_get_workflow(
|
|
1501
|
+
self, name: Optional[str] = None, workflow_name: Optional[str] = None,
|
|
1502
|
+
) -> tuple:
|
|
1503
|
+
params: Dict[str, Any] = {"tenant_id": self.tenant_id}
|
|
1504
|
+
if name:
|
|
1505
|
+
params["name"] = name
|
|
1506
|
+
if workflow_name:
|
|
1507
|
+
params["workflow_name"] = workflow_name
|
|
1508
|
+
return "workflows.get_workflow", params
|
|
1509
|
+
|
|
1510
|
+
def _prepare_update_workflow(
|
|
1511
|
+
self, name: str, graph_json: Optional[str] = None,
|
|
1512
|
+
workflow_name: Optional[str] = None, description: Optional[str] = None,
|
|
1513
|
+
status: Optional[str] = None, default_model_id: Optional[str] = None,
|
|
1514
|
+
default_user_id: Optional[str] = None,
|
|
1515
|
+
error_strategy: Optional[str] = None,
|
|
1516
|
+
timeout_seconds: Optional[int] = None,
|
|
1517
|
+
max_node_executions: Optional[int] = None,
|
|
1518
|
+
max_retries: Optional[int] = None,
|
|
1519
|
+
) -> tuple:
|
|
1520
|
+
payload: Dict[str, Any] = {
|
|
1521
|
+
"tenant_id": self.tenant_id,
|
|
1522
|
+
"name": name,
|
|
1523
|
+
}
|
|
1524
|
+
if graph_json is not None:
|
|
1525
|
+
payload["graph_json"] = graph_json
|
|
1526
|
+
if workflow_name is not None:
|
|
1527
|
+
payload["workflow_name"] = workflow_name
|
|
1528
|
+
if description is not None:
|
|
1529
|
+
payload["description"] = description
|
|
1530
|
+
if status is not None:
|
|
1531
|
+
payload["status"] = status
|
|
1532
|
+
if default_model_id is not None:
|
|
1533
|
+
payload["default_model_id"] = default_model_id
|
|
1534
|
+
if default_user_id is not None:
|
|
1535
|
+
payload["default_user_id"] = default_user_id
|
|
1536
|
+
if error_strategy is not None:
|
|
1537
|
+
payload["error_strategy"] = error_strategy
|
|
1538
|
+
if timeout_seconds is not None:
|
|
1539
|
+
payload["timeout_seconds"] = timeout_seconds
|
|
1540
|
+
if max_node_executions is not None:
|
|
1541
|
+
payload["max_node_executions"] = max_node_executions
|
|
1542
|
+
if max_retries is not None:
|
|
1543
|
+
payload["max_retries"] = max_retries
|
|
1544
|
+
return "workflows.update_workflow", payload
|
|
1545
|
+
|
|
1546
|
+
def _prepare_delete_workflow(self, name: str) -> tuple:
|
|
1547
|
+
return "workflows.delete_workflow", {
|
|
1548
|
+
"tenant_id": self.tenant_id,
|
|
1549
|
+
"name": name,
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
def _prepare_list_workflows(
|
|
1553
|
+
self, status: Optional[str] = None,
|
|
1554
|
+
page: int = 0, page_size: int = 20,
|
|
1555
|
+
) -> tuple:
|
|
1556
|
+
params: Dict[str, Any] = {
|
|
1557
|
+
"tenant_id": self.tenant_id,
|
|
1558
|
+
"page": str(page),
|
|
1559
|
+
"page_size": str(page_size),
|
|
1560
|
+
}
|
|
1561
|
+
if status:
|
|
1562
|
+
params["status"] = status
|
|
1563
|
+
return "workflows.list_workflows", params
|
|
1564
|
+
|
|
1565
|
+
def _prepare_execute_workflow(
|
|
1566
|
+
self, name: str, input_data: Optional[str] = None,
|
|
1567
|
+
user_id: Optional[str] = None,
|
|
1568
|
+
) -> tuple:
|
|
1569
|
+
payload: Dict[str, Any] = {
|
|
1570
|
+
"tenant_id": self.tenant_id,
|
|
1571
|
+
"name": name,
|
|
1572
|
+
}
|
|
1573
|
+
if input_data:
|
|
1574
|
+
payload["input_data"] = input_data
|
|
1575
|
+
if user_id:
|
|
1576
|
+
payload["user_id"] = user_id
|
|
1577
|
+
return "workflows.execute_workflow", payload
|
|
1578
|
+
|
|
1579
|
+
def _prepare_execute_workflow_from_event(
|
|
1580
|
+
self,
|
|
1581
|
+
workflow_name: str,
|
|
1582
|
+
input_data: Dict[str, Any],
|
|
1583
|
+
user_id: str,
|
|
1584
|
+
trigger_id: str,
|
|
1585
|
+
) -> tuple:
|
|
1586
|
+
return "workflows.execute_from_event", {
|
|
1587
|
+
"tenant_id": self.tenant_id,
|
|
1588
|
+
"workflow_name": workflow_name,
|
|
1589
|
+
"input_data": input_data,
|
|
1590
|
+
"user_id": user_id,
|
|
1591
|
+
"trigger_id": trigger_id,
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
def _prepare_cancel_workflow_run(self, run_name: str) -> tuple:
|
|
1595
|
+
return "workflows.cancel_run", {
|
|
1596
|
+
"tenant_id": self.tenant_id,
|
|
1597
|
+
"run_name": run_name,
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
def _prepare_get_workflow_run(self, run_name: str) -> tuple:
|
|
1601
|
+
return "workflows.get_run", {
|
|
1602
|
+
"tenant_id": self.tenant_id,
|
|
1603
|
+
"run_name": run_name,
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
def _prepare_list_workflow_runs(
|
|
1607
|
+
self, workflow_name: Optional[str] = None,
|
|
1608
|
+
status: Optional[str] = None,
|
|
1609
|
+
page: int = 0, page_size: int = 20,
|
|
1610
|
+
) -> tuple:
|
|
1611
|
+
params: Dict[str, Any] = {
|
|
1612
|
+
"tenant_id": self.tenant_id,
|
|
1613
|
+
"page": str(page),
|
|
1614
|
+
"page_size": str(page_size),
|
|
1615
|
+
}
|
|
1616
|
+
if workflow_name:
|
|
1617
|
+
params["workflow_name"] = workflow_name
|
|
1618
|
+
if status:
|
|
1619
|
+
params["status"] = status
|
|
1620
|
+
return "workflows.list_runs", params
|
|
1621
|
+
|
|
1622
|
+
def _prepare_get_workflow_audit_summary(
|
|
1623
|
+
self, workflow_id: str, window: str = "last_7_days",
|
|
1624
|
+
) -> tuple:
|
|
1625
|
+
return "audit.get_workflow_audit_summary", {
|
|
1626
|
+
"tenant_id": self.tenant_id,
|
|
1627
|
+
"workflow_id": workflow_id,
|
|
1628
|
+
"window": window,
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
def _prepare_set_workflow_schedule(
|
|
1632
|
+
self, name: str, cron_expression: str,
|
|
1633
|
+
timezone: str = "UTC", enabled: bool = True,
|
|
1634
|
+
default_input: Optional[str] = None,
|
|
1635
|
+
) -> tuple:
|
|
1636
|
+
payload: Dict[str, Any] = {
|
|
1637
|
+
"tenant_id": self.tenant_id,
|
|
1638
|
+
"name": name,
|
|
1639
|
+
"cron_expression": cron_expression,
|
|
1640
|
+
"timezone": timezone,
|
|
1641
|
+
"enabled": enabled,
|
|
1642
|
+
}
|
|
1643
|
+
if default_input is not None:
|
|
1644
|
+
payload["default_input"] = default_input
|
|
1645
|
+
return "workflows.set_schedule", payload
|
|
1646
|
+
|
|
1647
|
+
def _prepare_validate_workflow_graph(self, graph_json: str) -> tuple:
|
|
1648
|
+
return "workflows.validate_graph", {
|
|
1649
|
+
"tenant_id": self.tenant_id,
|
|
1650
|
+
"graph_json": graph_json,
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
def _prepare_test_workflow_node(
|
|
1654
|
+
self, node_json: str, input_text: str = "Test input",
|
|
1655
|
+
default_model_id: Optional[str] = None,
|
|
1656
|
+
default_user_id: Optional[str] = None,
|
|
1657
|
+
) -> tuple:
|
|
1658
|
+
payload: Dict[str, Any] = {
|
|
1659
|
+
"tenant_id": self.tenant_id,
|
|
1660
|
+
"node_json": node_json,
|
|
1661
|
+
"input_text": input_text,
|
|
1662
|
+
}
|
|
1663
|
+
if default_model_id:
|
|
1664
|
+
payload["default_model_id"] = default_model_id
|
|
1665
|
+
if default_user_id:
|
|
1666
|
+
payload["default_user_id"] = default_user_id
|
|
1667
|
+
return "workflows.test_node", payload
|
|
1668
|
+
|
|
1669
|
+
def _prepare_resolve_workflow_tools(
|
|
1670
|
+
self, user_id: str, tool_directives: List[Dict[str, Any]],
|
|
1671
|
+
) -> tuple:
|
|
1672
|
+
return "workflows.resolve_workflow_tools", {
|
|
1673
|
+
"tenant_id": self.tenant_id,
|
|
1674
|
+
"user_id": user_id,
|
|
1675
|
+
"tool_directives": json.dumps(tool_directives),
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
def _prepare_run_workflow_node(
|
|
1679
|
+
self, name: str, node_id: str, input_text: str = "Test input",
|
|
1680
|
+
user_id: Optional[str] = None,
|
|
1681
|
+
) -> tuple:
|
|
1682
|
+
payload: Dict[str, Any] = {
|
|
1683
|
+
"tenant_id": self.tenant_id,
|
|
1684
|
+
"name": name,
|
|
1685
|
+
"node_id": node_id,
|
|
1686
|
+
"input_text": input_text,
|
|
1687
|
+
}
|
|
1688
|
+
if user_id is not None:
|
|
1689
|
+
payload["user_id"] = user_id
|
|
1690
|
+
return "workflows.run_workflow_node", payload
|
|
1691
|
+
|
|
1692
|
+
# =========================================================================
|
|
1693
|
+
# Prepare Methods — Workflow Templates
|
|
1694
|
+
# =========================================================================
|
|
1695
|
+
|
|
1696
|
+
def _prepare_export_workflow(
|
|
1697
|
+
self, name: str, template_name: Optional[str] = None,
|
|
1698
|
+
category: str = "General", save_as_template: bool = False,
|
|
1699
|
+
is_public: bool = False,
|
|
1700
|
+
) -> tuple:
|
|
1701
|
+
payload: Dict[str, Any] = {
|
|
1702
|
+
"tenant_id": self.tenant_id,
|
|
1703
|
+
"name": name,
|
|
1704
|
+
"category": category,
|
|
1705
|
+
"save_as_template": save_as_template,
|
|
1706
|
+
"is_public": is_public,
|
|
1707
|
+
}
|
|
1708
|
+
if template_name:
|
|
1709
|
+
payload["template_name"] = template_name
|
|
1710
|
+
return "workflows.export_workflow", payload
|
|
1711
|
+
|
|
1712
|
+
# `_prepare_list_templates`, `_prepare_get_template`, `_prepare_import_template`,
|
|
1713
|
+
# and `_prepare_update_template` removed in chunk 4 — use the marketplace
|
|
1714
|
+
# listing prepare helpers (`_prepare_list_listings`, `_prepare_get_listing`,
|
|
1715
|
+
# `_prepare_install_listing`, `_prepare_update_listing`) instead.
|
|
1716
|
+
|
|
1717
|
+
# =========================================================================
|
|
1718
|
+
# Prepare Methods — GDPR / Privacy
|
|
1719
|
+
# =========================================================================
|
|
1720
|
+
|
|
1721
|
+
def _prepare_export_user_data(self, user_id: str) -> tuple:
|
|
1722
|
+
return "gdpr.export_user_data", {
|
|
1723
|
+
"tenant_id": self.tenant_id,
|
|
1724
|
+
"user_id": user_id,
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
def _prepare_erase_user_data(self, user_id: str) -> tuple:
|
|
1728
|
+
return "gdpr.erase_user_data", {
|
|
1729
|
+
"tenant_id": self.tenant_id,
|
|
1730
|
+
"user_id": user_id,
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
def _prepare_rectify_user_data(self, user_id: str, updates: dict) -> tuple:
|
|
1734
|
+
import json
|
|
1735
|
+
return "gdpr.rectify_user_data", {
|
|
1736
|
+
"tenant_id": self.tenant_id,
|
|
1737
|
+
"user_id": user_id,
|
|
1738
|
+
"updates": json.dumps(updates) if isinstance(updates, dict) else updates,
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
def _prepare_restrict_user_processing(self, user_id: str, restrict: bool = True) -> tuple:
|
|
1742
|
+
return "gdpr.restrict_user_processing", {
|
|
1743
|
+
"tenant_id": self.tenant_id,
|
|
1744
|
+
"user_id": user_id,
|
|
1745
|
+
"restrict": restrict,
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
def _prepare_update_user_consent(self, user_id: str, consent_type: str, granted: bool = True) -> tuple:
|
|
1749
|
+
return "gdpr.update_user_consent", {
|
|
1750
|
+
"tenant_id": self.tenant_id,
|
|
1751
|
+
"user_id": user_id,
|
|
1752
|
+
"consent_type": consent_type,
|
|
1753
|
+
"granted": granted,
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
# =========================================================================
|
|
1757
|
+
# Prepare Methods — Support (Tickets & Feedback)
|
|
1758
|
+
# =========================================================================
|
|
1759
|
+
|
|
1760
|
+
def _prepare_create_ticket(self, user_id: str, subject: str, description: str,
|
|
1761
|
+
category: Optional[str] = None,
|
|
1762
|
+
conversation_id: Optional[str] = None,
|
|
1763
|
+
environment: Optional[dict] = None,
|
|
1764
|
+
attachment_ids: Optional[List[str]] = None) -> tuple:
|
|
1765
|
+
"""Returns (endpoint, payload)."""
|
|
1766
|
+
payload: Dict[str, Any] = {
|
|
1767
|
+
"tenant_id": self.tenant_id,
|
|
1768
|
+
"user_id": user_id,
|
|
1769
|
+
"subject": subject,
|
|
1770
|
+
"description": description,
|
|
1771
|
+
}
|
|
1772
|
+
if category is not None:
|
|
1773
|
+
payload["category"] = category
|
|
1774
|
+
if conversation_id is not None:
|
|
1775
|
+
payload["conversation_id"] = conversation_id
|
|
1776
|
+
if environment is not None:
|
|
1777
|
+
payload["environment"] = environment
|
|
1778
|
+
if attachment_ids:
|
|
1779
|
+
payload["attachment_ids"] = attachment_ids
|
|
1780
|
+
return "support.create_ticket", payload
|
|
1781
|
+
|
|
1782
|
+
def _prepare_submit_feedback(self, user_id: str, rating: Optional[int] = None,
|
|
1783
|
+
comment: Optional[str] = None,
|
|
1784
|
+
category: Optional[str] = None,
|
|
1785
|
+
conversation_id: Optional[str] = None,
|
|
1786
|
+
environment: Optional[dict] = None) -> tuple:
|
|
1787
|
+
"""Returns (endpoint, payload)."""
|
|
1788
|
+
payload: Dict[str, Any] = {
|
|
1789
|
+
"tenant_id": self.tenant_id,
|
|
1790
|
+
"user_id": user_id,
|
|
1791
|
+
}
|
|
1792
|
+
if rating is not None:
|
|
1793
|
+
payload["rating"] = rating
|
|
1794
|
+
if comment is not None:
|
|
1795
|
+
payload["comment"] = comment
|
|
1796
|
+
if category is not None:
|
|
1797
|
+
payload["category"] = category
|
|
1798
|
+
if conversation_id is not None:
|
|
1799
|
+
payload["conversation_id"] = conversation_id
|
|
1800
|
+
if environment is not None:
|
|
1801
|
+
payload["environment"] = environment
|
|
1802
|
+
return "support.submit_feedback", payload
|
|
1803
|
+
|
|
1804
|
+
def _prepare_list_tickets(self, user_id: str, status: Optional[str] = None) -> tuple:
|
|
1805
|
+
"""Returns (endpoint, payload)."""
|
|
1806
|
+
payload: Dict[str, Any] = {"tenant_id": self.tenant_id, "user_id": user_id}
|
|
1807
|
+
if status is not None:
|
|
1808
|
+
payload["status"] = status
|
|
1809
|
+
return "support.list_tickets", payload
|
|
1810
|
+
|
|
1811
|
+
def _prepare_get_ticket_thread(self, user_id: str, ticket_id: str) -> tuple:
|
|
1812
|
+
"""Returns (endpoint, payload)."""
|
|
1813
|
+
return "support.get_ticket_thread", {
|
|
1814
|
+
"tenant_id": self.tenant_id,
|
|
1815
|
+
"user_id": user_id,
|
|
1816
|
+
"ticket_id": ticket_id,
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
def _prepare_reply_to_ticket(self, user_id: str, ticket_id: str, message: str,
|
|
1820
|
+
attachment_ids: Optional[List[str]] = None) -> tuple:
|
|
1821
|
+
"""Returns (endpoint, payload)."""
|
|
1822
|
+
payload: Dict[str, Any] = {
|
|
1823
|
+
"tenant_id": self.tenant_id,
|
|
1824
|
+
"user_id": user_id,
|
|
1825
|
+
"ticket_id": ticket_id,
|
|
1826
|
+
"message": message,
|
|
1827
|
+
}
|
|
1828
|
+
if attachment_ids:
|
|
1829
|
+
payload["attachment_ids"] = attachment_ids
|
|
1830
|
+
return "support.reply_to_ticket", payload
|
|
1831
|
+
|
|
1832
|
+
def _prepare_upload_ticket_attachment(
|
|
1833
|
+
self,
|
|
1834
|
+
user_id: str,
|
|
1835
|
+
file_name: str,
|
|
1836
|
+
file_data: bytes,
|
|
1837
|
+
content_type: Optional[str] = None,
|
|
1838
|
+
) -> tuple:
|
|
1839
|
+
"""Returns (endpoint, params, file_field, file_name, file_data, content_type).
|
|
1840
|
+
|
|
1841
|
+
Mirrors _prepare_upload_document: signs only the scalar form fields
|
|
1842
|
+
(tenant_id, user_id); the file bytes ride the multipart 'file' part,
|
|
1843
|
+
which Frappe's form_dict excludes from the signed body.
|
|
1844
|
+
"""
|
|
1845
|
+
import mimetypes as _mt
|
|
1846
|
+
|
|
1847
|
+
if not user_id:
|
|
1848
|
+
raise ARConfigurationError("user_id is required for upload_ticket_attachment")
|
|
1849
|
+
if not file_name:
|
|
1850
|
+
raise ARConfigurationError("file_name is required for upload_ticket_attachment")
|
|
1851
|
+
if not file_data:
|
|
1852
|
+
raise ARConfigurationError("file_data is required for upload_ticket_attachment")
|
|
1853
|
+
|
|
1854
|
+
if not content_type:
|
|
1855
|
+
guessed, _ = _mt.guess_type(file_name)
|
|
1856
|
+
content_type = guessed or "application/octet-stream"
|
|
1857
|
+
|
|
1858
|
+
params: Dict[str, Any] = {
|
|
1859
|
+
"tenant_id": self.tenant_id,
|
|
1860
|
+
"user_id": user_id,
|
|
1861
|
+
}
|
|
1862
|
+
return (
|
|
1863
|
+
"support.upload_ticket_attachment",
|
|
1864
|
+
params,
|
|
1865
|
+
"file",
|
|
1866
|
+
file_name,
|
|
1867
|
+
file_data,
|
|
1868
|
+
content_type,
|
|
1869
|
+
)
|
|
1870
|
+
|
|
1871
|
+
def _prepare_get_tenant_privacy_config(self) -> tuple:
|
|
1872
|
+
return "gdpr.get_tenant_privacy_config", {
|
|
1873
|
+
"tenant_id": self.tenant_id,
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
def _prepare_update_tenant_privacy_config(self, config: dict) -> tuple:
|
|
1877
|
+
import json
|
|
1878
|
+
return "gdpr.update_tenant_privacy_config", {
|
|
1879
|
+
"tenant_id": self.tenant_id,
|
|
1880
|
+
"config": json.dumps(config) if isinstance(config, dict) else config,
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
# `_prepare_delete_template` removed in chunk 4 — use `_prepare_delete_listing` instead.
|
|
1884
|
+
# `_prepare_upload_template` removed in chunk 5 — use `_prepare_upload_listing_from_json`
|
|
1885
|
+
# (in publishing.py) which wraps the workflow template in a marketplace listing.
|
|
1886
|
+
# `_prepare_rate_template` removed in chunk 4 — use `_prepare_rate_listing` instead.
|
|
1887
|
+
# `_prepare_download_template` removed in chunk 5 — use `_prepare_download_listing_as_json`
|
|
1888
|
+
# instead.
|
|
1889
|
+
|
|
1890
|
+
# --- Heartbeat & Notifications ---
|
|
1891
|
+
|
|
1892
|
+
def _prepare_heartbeat(
|
|
1893
|
+
self,
|
|
1894
|
+
faco_version: Optional[str] = None,
|
|
1895
|
+
fac_version: Optional[str] = None,
|
|
1896
|
+
frappe_version: Optional[str] = None,
|
|
1897
|
+
erpnext_version: Optional[str] = None,
|
|
1898
|
+
python_version: Optional[str] = None,
|
|
1899
|
+
copilot_version: Optional[str] = None,
|
|
1900
|
+
) -> tuple:
|
|
1901
|
+
payload: Dict[str, Any] = {"tenant_id": self.tenant_id}
|
|
1902
|
+
if faco_version:
|
|
1903
|
+
payload["faco_version"] = faco_version
|
|
1904
|
+
if fac_version:
|
|
1905
|
+
payload["fac_version"] = fac_version
|
|
1906
|
+
if frappe_version:
|
|
1907
|
+
payload["frappe_version"] = frappe_version
|
|
1908
|
+
if erpnext_version is not None:
|
|
1909
|
+
payload["erpnext_version"] = erpnext_version
|
|
1910
|
+
if python_version:
|
|
1911
|
+
payload["python_version"] = python_version
|
|
1912
|
+
if copilot_version:
|
|
1913
|
+
payload["copilot_version"] = copilot_version
|
|
1914
|
+
return "heartbeat.heartbeat", payload
|
|
1915
|
+
|
|
1916
|
+
def _prepare_get_notifications(self, user_id: str) -> tuple:
|
|
1917
|
+
return "notifications.get_notifications", {
|
|
1918
|
+
"tenant_id": self.tenant_id,
|
|
1919
|
+
"user_id": user_id,
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
def _prepare_dismiss_notification(
|
|
1923
|
+
self, notification_id: str, user_id: str,
|
|
1924
|
+
) -> tuple:
|
|
1925
|
+
return "notifications.dismiss_notification", {
|
|
1926
|
+
"tenant_id": self.tenant_id,
|
|
1927
|
+
"notification_id": notification_id,
|
|
1928
|
+
"user_id": user_id,
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
# -------------------------------------------------------------------
|
|
1932
|
+
# Marketplace API prepares — route via marketplace_api_base
|
|
1933
|
+
# -------------------------------------------------------------------
|
|
1934
|
+
|
|
1935
|
+
def _prepare_list_listings(
|
|
1936
|
+
self,
|
|
1937
|
+
listing_type: Optional[str] = None,
|
|
1938
|
+
category: Optional[str] = None,
|
|
1939
|
+
search: Optional[str] = None,
|
|
1940
|
+
featured_only: bool = False,
|
|
1941
|
+
min_rating: Optional[float] = None,
|
|
1942
|
+
plan_tier: Optional[str] = None,
|
|
1943
|
+
sort_by: Optional[str] = None,
|
|
1944
|
+
page: int = 0,
|
|
1945
|
+
page_size: int = 20,
|
|
1946
|
+
user_id: Optional[str] = None,
|
|
1947
|
+
) -> tuple:
|
|
1948
|
+
params: Dict[str, Any] = {
|
|
1949
|
+
"tenant_id": self.tenant_id,
|
|
1950
|
+
"page": page,
|
|
1951
|
+
"page_size": page_size,
|
|
1952
|
+
}
|
|
1953
|
+
if listing_type:
|
|
1954
|
+
params["listing_type"] = listing_type
|
|
1955
|
+
if category:
|
|
1956
|
+
params["category"] = category
|
|
1957
|
+
if search:
|
|
1958
|
+
params["search"] = search
|
|
1959
|
+
if featured_only:
|
|
1960
|
+
params["featured_only"] = "1"
|
|
1961
|
+
if min_rating is not None:
|
|
1962
|
+
params["min_rating"] = min_rating
|
|
1963
|
+
if plan_tier:
|
|
1964
|
+
params["plan_tier"] = plan_tier
|
|
1965
|
+
if sort_by:
|
|
1966
|
+
params["sort_by"] = sort_by
|
|
1967
|
+
if user_id:
|
|
1968
|
+
params["user_id"] = user_id
|
|
1969
|
+
return "listings.list_listings", params
|
|
1970
|
+
|
|
1971
|
+
def _prepare_get_listing(
|
|
1972
|
+
self, name: str, user_id: Optional[str] = None,
|
|
1973
|
+
include_source: bool = True,
|
|
1974
|
+
) -> tuple:
|
|
1975
|
+
params: Dict[str, Any] = {
|
|
1976
|
+
"tenant_id": self.tenant_id,
|
|
1977
|
+
"name": name,
|
|
1978
|
+
"include_source": "1" if include_source else "0",
|
|
1979
|
+
}
|
|
1980
|
+
if user_id:
|
|
1981
|
+
params["user_id"] = user_id
|
|
1982
|
+
return "listings.get_listing", params
|
|
1983
|
+
|
|
1984
|
+
def _prepare_import_listing(
|
|
1985
|
+
self, user_id: str, name: str,
|
|
1986
|
+
new_title: Optional[str] = None,
|
|
1987
|
+
variables: Optional[str] = None,
|
|
1988
|
+
default_model_id: Optional[str] = None,
|
|
1989
|
+
) -> tuple:
|
|
1990
|
+
payload: Dict[str, Any] = {
|
|
1991
|
+
"tenant_id": self.tenant_id,
|
|
1992
|
+
"user_id": user_id,
|
|
1993
|
+
"name": name,
|
|
1994
|
+
}
|
|
1995
|
+
if new_title:
|
|
1996
|
+
payload["new_title"] = new_title
|
|
1997
|
+
if variables:
|
|
1998
|
+
payload["variables"] = variables
|
|
1999
|
+
if default_model_id:
|
|
2000
|
+
payload["default_model_id"] = default_model_id
|
|
2001
|
+
return "listings.import_listing", payload
|
|
2002
|
+
|
|
2003
|
+
def _prepare_update_listing(
|
|
2004
|
+
self, name: str,
|
|
2005
|
+
title: Optional[str] = None,
|
|
2006
|
+
short_description: Optional[str] = None,
|
|
2007
|
+
description: Optional[str] = None,
|
|
2008
|
+
category: Optional[str] = None,
|
|
2009
|
+
tags: Optional[str] = None,
|
|
2010
|
+
icon: Optional[str] = None,
|
|
2011
|
+
is_public: Optional[bool] = None,
|
|
2012
|
+
is_published: Optional[bool] = None,
|
|
2013
|
+
plan_tier: Optional[str] = None,
|
|
2014
|
+
) -> tuple:
|
|
2015
|
+
payload: Dict[str, Any] = {"tenant_id": self.tenant_id, "name": name}
|
|
2016
|
+
for k, v in (
|
|
2017
|
+
("title", title), ("short_description", short_description),
|
|
2018
|
+
("description", description), ("category", category),
|
|
2019
|
+
("tags", tags), ("icon", icon),
|
|
2020
|
+
):
|
|
2021
|
+
if v is not None:
|
|
2022
|
+
payload[k] = v
|
|
2023
|
+
if is_public is not None:
|
|
2024
|
+
payload["is_public"] = 1 if is_public else 0
|
|
2025
|
+
if is_published is not None:
|
|
2026
|
+
payload["is_published"] = 1 if is_published else 0
|
|
2027
|
+
if plan_tier is not None:
|
|
2028
|
+
payload["plan_tier"] = plan_tier
|
|
2029
|
+
return "publishing.update_listing", payload
|
|
2030
|
+
|
|
2031
|
+
def _prepare_delete_listing(self, name: str) -> tuple:
|
|
2032
|
+
return "publishing.delete_listing", {
|
|
2033
|
+
"tenant_id": self.tenant_id, "name": name,
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
def _prepare_rate_listing(
|
|
2037
|
+
self, user_id: str, listing: str, rating: int, review: Optional[str] = None,
|
|
2038
|
+
) -> tuple:
|
|
2039
|
+
payload: Dict[str, Any] = {
|
|
2040
|
+
"tenant_id": self.tenant_id,
|
|
2041
|
+
"user_id": user_id,
|
|
2042
|
+
"listing": listing,
|
|
2043
|
+
"rating": rating,
|
|
2044
|
+
}
|
|
2045
|
+
if review:
|
|
2046
|
+
payload["review"] = review
|
|
2047
|
+
return "ratings.rate_listing", payload
|
|
2048
|
+
|
|
2049
|
+
def _prepare_report_listing(
|
|
2050
|
+
self, user_id: str, listing: str, reason: str,
|
|
2051
|
+
details: Optional[str] = None,
|
|
2052
|
+
) -> tuple:
|
|
2053
|
+
payload: Dict[str, Any] = {
|
|
2054
|
+
"tenant_id": self.tenant_id,
|
|
2055
|
+
"user_id": user_id,
|
|
2056
|
+
"listing": listing,
|
|
2057
|
+
"reason": reason,
|
|
2058
|
+
}
|
|
2059
|
+
if details:
|
|
2060
|
+
payload["details"] = details
|
|
2061
|
+
return "ratings.report_listing", payload
|
|
2062
|
+
|
|
2063
|
+
def _prepare_list_pending_reviews(
|
|
2064
|
+
self, page: int = 0, page_size: int = 20,
|
|
2065
|
+
) -> tuple:
|
|
2066
|
+
return "moderation.list_pending_reviews", {
|
|
2067
|
+
"tenant_id": self.tenant_id,
|
|
2068
|
+
"page": page,
|
|
2069
|
+
"page_size": page_size,
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
def _prepare_approve_listing(
|
|
2073
|
+
self, listing: str, notes: Optional[str] = None,
|
|
2074
|
+
) -> tuple:
|
|
2075
|
+
payload: Dict[str, Any] = {"tenant_id": self.tenant_id, "listing": listing}
|
|
2076
|
+
if notes:
|
|
2077
|
+
payload["notes"] = notes
|
|
2078
|
+
return "moderation.approve_listing", payload
|
|
2079
|
+
|
|
2080
|
+
def _prepare_reject_listing(
|
|
2081
|
+
self, listing: str, notes: Optional[str] = None,
|
|
2082
|
+
) -> tuple:
|
|
2083
|
+
payload: Dict[str, Any] = {"tenant_id": self.tenant_id, "listing": listing}
|
|
2084
|
+
if notes:
|
|
2085
|
+
payload["notes"] = notes
|
|
2086
|
+
return "moderation.reject_listing", payload
|
|
2087
|
+
|
|
2088
|
+
def _prepare_get_creator_stats(
|
|
2089
|
+
self, user_id: Optional[str] = None,
|
|
2090
|
+
) -> tuple:
|
|
2091
|
+
params: Dict[str, Any] = {"tenant_id": self.tenant_id}
|
|
2092
|
+
if user_id:
|
|
2093
|
+
params["user_id"] = user_id
|
|
2094
|
+
return "creator.get_creator_stats", params
|
|
2095
|
+
|
|
2096
|
+
def _prepare_list_my_listings(
|
|
2097
|
+
self,
|
|
2098
|
+
user_id: Optional[str] = None,
|
|
2099
|
+
listing_type: Optional[str] = None,
|
|
2100
|
+
page: int = 0,
|
|
2101
|
+
page_size: int = 20,
|
|
2102
|
+
) -> tuple:
|
|
2103
|
+
params: Dict[str, Any] = {
|
|
2104
|
+
"tenant_id": self.tenant_id,
|
|
2105
|
+
"page": page,
|
|
2106
|
+
"page_size": page_size,
|
|
2107
|
+
}
|
|
2108
|
+
if user_id:
|
|
2109
|
+
params["user_id"] = user_id
|
|
2110
|
+
if listing_type:
|
|
2111
|
+
params["listing_type"] = listing_type
|
|
2112
|
+
return "creator.list_my_listings", params
|
|
2113
|
+
|
|
2114
|
+
# -------------------------------------------------------------------
|
|
2115
|
+
# Marketplace publishing + downloads + version checks (chunk 5)
|
|
2116
|
+
# -------------------------------------------------------------------
|
|
2117
|
+
|
|
2118
|
+
def _prepare_publish_workflow(
|
|
2119
|
+
self,
|
|
2120
|
+
user_id: str,
|
|
2121
|
+
workflow_name: str,
|
|
2122
|
+
template_name: Optional[str] = None,
|
|
2123
|
+
category: str = "General",
|
|
2124
|
+
short_description: Optional[str] = None,
|
|
2125
|
+
description: Optional[str] = None,
|
|
2126
|
+
tags: Optional[str] = None,
|
|
2127
|
+
is_public: bool = False,
|
|
2128
|
+
plan_tier: Optional[str] = None,
|
|
2129
|
+
) -> tuple:
|
|
2130
|
+
payload: Dict[str, Any] = {
|
|
2131
|
+
"tenant_id": self.tenant_id,
|
|
2132
|
+
"user_id": user_id,
|
|
2133
|
+
"workflow_name": workflow_name,
|
|
2134
|
+
"category": category,
|
|
2135
|
+
"is_public": 1 if is_public else 0,
|
|
2136
|
+
}
|
|
2137
|
+
for k, v in (
|
|
2138
|
+
("template_name", template_name),
|
|
2139
|
+
("short_description", short_description),
|
|
2140
|
+
("description", description),
|
|
2141
|
+
("tags", tags),
|
|
2142
|
+
("plan_tier", plan_tier),
|
|
2143
|
+
):
|
|
2144
|
+
if v is not None:
|
|
2145
|
+
payload[k] = v
|
|
2146
|
+
return "publishing.publish_workflow", payload
|
|
2147
|
+
|
|
2148
|
+
def _prepare_download_listing_as_json(self, name: str) -> tuple:
|
|
2149
|
+
return "listings.download_listing_as_json", {
|
|
2150
|
+
"tenant_id": self.tenant_id,
|
|
2151
|
+
"name": name,
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
def _prepare_check_workflow_update(self, name: str) -> tuple:
|
|
2155
|
+
return "versions.check_workflow_update", {
|
|
2156
|
+
"tenant_id": self.tenant_id,
|
|
2157
|
+
"name": name,
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
def _prepare_check_all_workflow_updates(self) -> tuple:
|
|
2161
|
+
return "versions.check_all_workflow_updates", {
|
|
2162
|
+
"tenant_id": self.tenant_id,
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
# -------------------------------------------------------------------
|
|
2166
|
+
# Tenant Packs API prepares — route via marketplace_api_base
|
|
2167
|
+
#
|
|
2168
|
+
# These hit the *signed* endpoints in
|
|
2169
|
+
# ``assistant_runtime_marketplace.api.tenant_packs_signed`` (not the
|
|
2170
|
+
# whitelisted same-bench admin endpoints in ``api.tenant_packs``).
|
|
2171
|
+
# -------------------------------------------------------------------
|
|
2172
|
+
|
|
2173
|
+
def _prepare_list_packs(self) -> tuple:
|
|
2174
|
+
return "tenant_packs_signed.list_packs", {
|
|
2175
|
+
"tenant_id": self.tenant_id,
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
def _prepare_get_pack_contents(self, pack_id: str) -> tuple:
|
|
2179
|
+
return "tenant_packs_signed.get_pack_contents", {
|
|
2180
|
+
"tenant_id": self.tenant_id,
|
|
2181
|
+
"pack_id": pack_id,
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
def _prepare_set_industry(
|
|
2185
|
+
self,
|
|
2186
|
+
industry: Optional[str],
|
|
2187
|
+
auto_enable: bool = True,
|
|
2188
|
+
) -> tuple:
|
|
2189
|
+
payload: Dict[str, Any] = {
|
|
2190
|
+
"tenant_id": self.tenant_id,
|
|
2191
|
+
"auto_enable": 1 if auto_enable else 0,
|
|
2192
|
+
}
|
|
2193
|
+
# An empty industry is a valid clear; send it as empty string.
|
|
2194
|
+
payload["industry"] = industry or ""
|
|
2195
|
+
return "tenant_packs_signed.set_industry", payload
|
|
2196
|
+
|
|
2197
|
+
def _prepare_set_pack_enabled(
|
|
2198
|
+
self,
|
|
2199
|
+
pack_id: str,
|
|
2200
|
+
enabled: bool,
|
|
2201
|
+
source: str = "User",
|
|
2202
|
+
) -> tuple:
|
|
2203
|
+
return "tenant_packs_signed.set_pack_enabled", {
|
|
2204
|
+
"tenant_id": self.tenant_id,
|
|
2205
|
+
"pack_id": pack_id,
|
|
2206
|
+
"enabled": 1 if enabled else 0,
|
|
2207
|
+
"source": source,
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
def _prepare_get_recommended_pack(
|
|
2211
|
+
self, user_id: Optional[str] = None,
|
|
2212
|
+
) -> tuple:
|
|
2213
|
+
params: Dict[str, Any] = {"tenant_id": self.tenant_id}
|
|
2214
|
+
if user_id:
|
|
2215
|
+
params["user_id"] = user_id
|
|
2216
|
+
return "tenant_packs_signed.get_recommended_pack", params
|
|
2217
|
+
|
|
2218
|
+
def _prepare_dismiss_pack_recommendation(self, user_id: str) -> tuple:
|
|
2219
|
+
return "tenant_packs_signed.dismiss_pack_recommendation", {
|
|
2220
|
+
"tenant_id": self.tenant_id,
|
|
2221
|
+
"user_id": user_id,
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
# -------------------------------------------------------------------
|
|
2225
|
+
# Pack acquisition + checkout (paid packs v2)
|
|
2226
|
+
# -------------------------------------------------------------------
|
|
2227
|
+
|
|
2228
|
+
def _prepare_enable_pack_as_free_grant(self, pack_id: str) -> tuple:
|
|
2229
|
+
return "tenant_packs_signed.enable_pack_as_free_grant", {
|
|
2230
|
+
"tenant_id": self.tenant_id,
|
|
2231
|
+
"pack_id": pack_id,
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
def _prepare_grant_pack_as_admin(
|
|
2235
|
+
self, pack_id: str, granted_by_user: Optional[str] = None,
|
|
2236
|
+
) -> tuple:
|
|
2237
|
+
payload: Dict[str, Any] = {
|
|
2238
|
+
"tenant_id": self.tenant_id,
|
|
2239
|
+
"pack_id": pack_id,
|
|
2240
|
+
}
|
|
2241
|
+
if granted_by_user:
|
|
2242
|
+
payload["granted_by_user"] = granted_by_user
|
|
2243
|
+
return "tenant_packs_signed.grant_pack_as_admin", payload
|
|
2244
|
+
|
|
2245
|
+
def _prepare_toggle_purchased_pack(
|
|
2246
|
+
self, pack_id: str, enabled: bool,
|
|
2247
|
+
) -> tuple:
|
|
2248
|
+
return "tenant_packs_signed.toggle_purchased_pack", {
|
|
2249
|
+
"tenant_id": self.tenant_id,
|
|
2250
|
+
"pack_id": pack_id,
|
|
2251
|
+
"enabled": 1 if enabled else 0,
|
|
2252
|
+
}
|
|
2253
|
+
|
|
2254
|
+
def _prepare_list_pack_purchases(self) -> tuple:
|
|
2255
|
+
return "tenant_packs_signed.list_pack_purchases", {
|
|
2256
|
+
"tenant_id": self.tenant_id,
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
def _prepare_initiate_pack_checkout(self, pack_id: str) -> tuple:
|
|
2260
|
+
return "billing.pack_checkout.initiate_pack_checkout", {
|
|
2261
|
+
"tenant_id": self.tenant_id,
|
|
2262
|
+
"pack_id": pack_id,
|
|
2263
|
+
}
|
|
2264
|
+
|
|
2265
|
+
def _prepare_verify_razorpay_pack_payment(
|
|
2266
|
+
self,
|
|
2267
|
+
razorpay_payment_id: str,
|
|
2268
|
+
razorpay_order_id: str,
|
|
2269
|
+
razorpay_signature: str,
|
|
2270
|
+
) -> tuple:
|
|
2271
|
+
return "billing.pack_checkout.verify_razorpay_pack_payment", {
|
|
2272
|
+
"tenant_id": self.tenant_id,
|
|
2273
|
+
"razorpay_payment_id": razorpay_payment_id,
|
|
2274
|
+
"razorpay_order_id": razorpay_order_id,
|
|
2275
|
+
"razorpay_signature": razorpay_signature,
|
|
2276
|
+
}
|
|
2277
|
+
|