deploypod 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,126 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ import httpx
6
+
7
+ from deploypod._http import message_result, raise_for_status
8
+ from deploypod.types import CustomToolResponse, MessageResult
9
+
10
+
11
+ class CustomToolsResource:
12
+ def __init__(self, http: httpx.Client):
13
+ self._http = http
14
+
15
+ def create(self, *, name: str, tool_type: str = "webhook", **kwargs: Any) -> CustomToolResponse:
16
+ payload = {"name": name, "tool_type": tool_type, **kwargs}
17
+ resp = self._http.post("/custom-tools/", json=payload)
18
+ raise_for_status(resp)
19
+ return CustomToolResponse(**resp.json())
20
+
21
+ def list(self) -> list[CustomToolResponse]:
22
+ resp = self._http.get("/custom-tools/")
23
+ raise_for_status(resp)
24
+ return [CustomToolResponse(**t) for t in resp.json()]
25
+
26
+ def get(self, tool_id: int) -> CustomToolResponse:
27
+ resp = self._http.get(f"/custom-tools/{tool_id}")
28
+ raise_for_status(resp)
29
+ return CustomToolResponse(**resp.json())
30
+
31
+ def update(self, tool_id: int, **kwargs: Any) -> CustomToolResponse:
32
+ resp = self._http.put(f"/custom-tools/{tool_id}", json=kwargs)
33
+ raise_for_status(resp)
34
+ return CustomToolResponse(**resp.json())
35
+
36
+ def delete(self, tool_id: int) -> MessageResult:
37
+ resp = self._http.delete(f"/custom-tools/{tool_id}")
38
+ raise_for_status(resp)
39
+ return message_result(resp)
40
+
41
+ def test(self, tool_id: int) -> dict:
42
+ resp = self._http.post(f"/custom-tools/{tool_id}/test")
43
+ raise_for_status(resp)
44
+ return resp.json()
45
+
46
+ def test_configuration(self, **kwargs: Any) -> dict:
47
+ resp = self._http.post("/custom-tools/test-configuration", json=kwargs)
48
+ raise_for_status(resp)
49
+ return resp.json()
50
+
51
+ # ── Agent tool assignment ────────────────────────────────────
52
+
53
+ def assign_to_agent(self, agent_id: int, tool_id: int, *, enabled: bool = True) -> dict:
54
+ resp = self._http.post(f"/custom-tools/agents/{agent_id}/tools", json={"tool_id": tool_id, "enabled": enabled})
55
+ raise_for_status(resp)
56
+ return resp.json()
57
+
58
+ def remove_from_agent(self, agent_id: int, tool_id: int) -> MessageResult:
59
+ resp = self._http.delete(f"/custom-tools/agents/{agent_id}/tools/{tool_id}")
60
+ raise_for_status(resp)
61
+ return message_result(resp)
62
+
63
+ def list_for_agent(self, agent_id: int) -> list[dict]:
64
+ resp = self._http.get(f"/custom-tools/agents/{agent_id}/tools")
65
+ raise_for_status(resp)
66
+ return resp.json()
67
+
68
+ def get_agent_assignments(self, agent_id: int) -> list[dict]:
69
+ resp = self._http.get(f"/custom-tools/agents/{agent_id}/tool-assignments")
70
+ raise_for_status(resp)
71
+ return resp.json()
72
+
73
+ def update_agent_tool_hook(
74
+ self,
75
+ agent_id: int,
76
+ tool_id: int,
77
+ *,
78
+ trigger: str | None = None,
79
+ hook_config: dict | None = None,
80
+ ) -> dict:
81
+ """Update hook trigger/config for an agent-tool assignment.
82
+
83
+ Args:
84
+ agent_id: The agent ID.
85
+ tool_id: The custom tool ID.
86
+ trigger: Set to 'hook' to make this tool a pre-pipeline hook, or None to disable.
87
+ hook_config: Hook configuration dict with 'on_error' ('skip'|'block') and 'execution_order'.
88
+ """
89
+ resp = self._http.put(
90
+ f"/custom-tools/agents/{agent_id}/tools/{tool_id}/hook",
91
+ json={"trigger": trigger, "hook_config": hook_config},
92
+ )
93
+ raise_for_status(resp)
94
+ return resp.json()
95
+
96
+ # ── MCP tool assignment ──────────────────────────────────────
97
+
98
+ def assign_mcp_tool_to_agent(self, agent_id: int, mcp_tool_id: int) -> dict:
99
+ resp = self._http.post(f"/custom-tools/agents/{agent_id}/mcp-tools/{mcp_tool_id}")
100
+ raise_for_status(resp)
101
+ return resp.json()
102
+
103
+ def remove_mcp_tool_from_agent(self, agent_id: int, mcp_tool_id: int) -> MessageResult:
104
+ resp = self._http.delete(f"/custom-tools/agents/{agent_id}/mcp-tools/{mcp_tool_id}")
105
+ raise_for_status(resp)
106
+ return message_result(resp)
107
+
108
+ # ── Executions & Security ────────────────────────────────────
109
+
110
+ def list_executions(self, tool_id: int | None = None) -> list[dict]:
111
+ if tool_id is not None:
112
+ resp = self._http.get(f"/custom-tools/{tool_id}/executions")
113
+ else:
114
+ resp = self._http.get("/custom-tools/executions/all")
115
+ raise_for_status(resp)
116
+ return resp.json()
117
+
118
+ def list_security_events(self) -> list[dict]:
119
+ resp = self._http.get("/security/events")
120
+ raise_for_status(resp)
121
+ return resp.json()
122
+
123
+ def get_security_summary(self) -> dict:
124
+ resp = self._http.get("/security/summary")
125
+ raise_for_status(resp)
126
+ return resp.json()
@@ -0,0 +1,71 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ import httpx
6
+
7
+ from deploypod._http import message_result, raise_for_status
8
+ from deploypod.types import GroupMemberResponse, GroupResponse, MessageResult
9
+
10
+
11
+ class GroupsResource:
12
+ def __init__(self, http: httpx.Client):
13
+ self._http = http
14
+
15
+ def create(self, *, name: str) -> GroupResponse:
16
+ resp = self._http.post("/groups/", json={"name": name})
17
+ raise_for_status(resp)
18
+ return GroupResponse(**resp.json())
19
+
20
+ def list(self) -> list[GroupResponse]:
21
+ resp = self._http.get("/groups/")
22
+ raise_for_status(resp)
23
+ return [GroupResponse(**g) for g in resp.json()]
24
+
25
+ def get(self, group_id: int) -> GroupResponse:
26
+ resp = self._http.get(f"/groups/{group_id}")
27
+ raise_for_status(resp)
28
+ return GroupResponse(**resp.json())
29
+
30
+ def update(self, group_id: int, **kwargs: Any) -> GroupResponse:
31
+ resp = self._http.put(f"/groups/{group_id}", json=kwargs)
32
+ raise_for_status(resp)
33
+ return GroupResponse(**resp.json())
34
+
35
+ def delete(self, group_id: int) -> MessageResult:
36
+ resp = self._http.delete(f"/groups/{group_id}")
37
+ raise_for_status(resp)
38
+ return message_result(resp)
39
+
40
+ # ── Members ──────────────────────────────────────────────────
41
+
42
+ def list_members(self, group_id: int) -> list[dict]:
43
+ resp = self._http.get(f"/groups/{group_id}/members")
44
+ raise_for_status(resp)
45
+ return resp.json()
46
+
47
+ def add_member(self, group_id: int, *, user_id: str, permission_level: str = "viewer") -> dict:
48
+ resp = self._http.post(
49
+ f"/groups/{group_id}/members",
50
+ json={"user_id": user_id, "permission_level": permission_level},
51
+ )
52
+ raise_for_status(resp)
53
+ return resp.json()
54
+
55
+ def remove_member(self, group_id: int, user_id: str) -> MessageResult:
56
+ resp = self._http.delete(f"/groups/{group_id}/members/{user_id}")
57
+ raise_for_status(resp)
58
+ return message_result(resp)
59
+
60
+ def update_member_permission(self, group_id: int, user_id: str, permission_level: str) -> dict:
61
+ resp = self._http.put(
62
+ f"/groups/{group_id}/members/{user_id}/permission",
63
+ json={"permission_level": permission_level},
64
+ )
65
+ raise_for_status(resp)
66
+ return resp.json()
67
+
68
+ def get_accessible_resources(self, resource_type: str) -> list[dict]:
69
+ resp = self._http.get(f"/groups/resources/{resource_type}")
70
+ raise_for_status(resp)
71
+ return resp.json()
@@ -0,0 +1,28 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ import httpx
6
+
7
+ from deploypod._http import message_result, raise_for_status
8
+ from deploypod.types import JobResponse, MessageResult
9
+
10
+
11
+ class JobsResource:
12
+ def __init__(self, http: httpx.Client):
13
+ self._http = http
14
+
15
+ def list(self) -> list[JobResponse]:
16
+ resp = self._http.get("/agent-jobs/")
17
+ raise_for_status(resp)
18
+ return [JobResponse(**j) for j in resp.json()]
19
+
20
+ def get(self, job_uuid: str) -> JobResponse:
21
+ resp = self._http.get(f"/agent-jobs/{job_uuid}")
22
+ raise_for_status(resp)
23
+ return JobResponse(**resp.json())
24
+
25
+ def cancel(self, job_uuid: str) -> MessageResult:
26
+ resp = self._http.patch(f"/agent-jobs/{job_uuid}", json={"status": "cancelled"})
27
+ raise_for_status(resp)
28
+ return message_result(resp)
@@ -0,0 +1,47 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ import httpx
6
+
7
+ from deploypod._http import json_body, raise_for_status
8
+ from deploypod.types import KnowledgeBaseResponse, MessageResult
9
+
10
+
11
+ class KnowledgeBasesResource:
12
+ def __init__(self, http: httpx.Client):
13
+ self._http = http
14
+
15
+ def create(self, *, name: str, description: str = "", group_id: int | None = None) -> KnowledgeBaseResponse:
16
+ payload: dict[str, Any] = {"name": name, "description": description}
17
+ if group_id is not None:
18
+ payload["group_id"] = group_id
19
+ resp = self._http.post("/kb/", json=payload)
20
+ raise_for_status(resp)
21
+ return KnowledgeBaseResponse(**resp.json())
22
+
23
+ def list(self) -> list[KnowledgeBaseResponse]:
24
+ resp = self._http.get("/kb/")
25
+ raise_for_status(resp)
26
+ return [KnowledgeBaseResponse(**kb) for kb in resp.json()]
27
+
28
+ def get(self, kb_id: int) -> KnowledgeBaseResponse:
29
+ resp = self._http.get(f"/kb/{kb_id}")
30
+ raise_for_status(resp)
31
+ return KnowledgeBaseResponse(**resp.json())
32
+
33
+ def update(self, kb_id: int, **kwargs: Any) -> KnowledgeBaseResponse:
34
+ resp = self._http.put(f"/kb/{kb_id}", json=kwargs)
35
+ raise_for_status(resp)
36
+ return KnowledgeBaseResponse(**resp.json())
37
+
38
+ def delete(self, kb_id: int, *, force: bool = False) -> dict:
39
+ params = {"force": "true"} if force else {}
40
+ resp = self._http.delete(f"/kb/{kb_id}", params=params)
41
+ raise_for_status(resp)
42
+ return json_body(resp)
43
+
44
+ def get_constraints(self) -> dict:
45
+ resp = self._http.get("/kb/constraints")
46
+ raise_for_status(resp)
47
+ return resp.json()
@@ -0,0 +1,141 @@
1
+ from __future__ import annotations
2
+
3
+ import mimetypes
4
+ from pathlib import Path
5
+ from typing import Any, BinaryIO
6
+
7
+ import httpx
8
+
9
+ from deploypod._http import message_result, raise_for_status
10
+ from deploypod.types import KnowledgeSourceResponse, MessageResult
11
+
12
+
13
+ class KnowledgeSourcesResource:
14
+ def __init__(self, http: httpx.Client):
15
+ self._http = http
16
+
17
+ def upload(self, *, kb_id: int, name: str, file_path: str | Path | None = None,
18
+ file_obj: BinaryIO | None = None, file_name: str | None = None,
19
+ description: str = "", purpose: str = "KNOWLEDGE_ONLY") -> KnowledgeSourceResponse:
20
+ """Upload a file as a knowledge source.
21
+
22
+ Provide either ``file_path`` (path on disk) or ``file_obj`` + ``file_name``.
23
+ """
24
+ if file_path is not None:
25
+ file_path = Path(file_path)
26
+ file_name = file_name or file_path.name
27
+ fp = open(file_path, "rb")
28
+ should_close = True
29
+ elif file_obj is not None:
30
+ if not file_name:
31
+ raise ValueError("file_name is required when using file_obj")
32
+ fp = file_obj
33
+ should_close = False
34
+ else:
35
+ raise ValueError("Either file_path or file_obj must be provided")
36
+
37
+ try:
38
+ content_type = mimetypes.guess_type(file_name)[0] or "application/octet-stream"
39
+ resp = self._http.post(
40
+ "/ks/upload",
41
+ params={"kb_id": kb_id},
42
+ data={"name": name, "description": description, "purpose": purpose},
43
+ files={"file": (file_name, fp, content_type)},
44
+ )
45
+ finally:
46
+ if should_close:
47
+ fp.close()
48
+
49
+ raise_for_status(resp)
50
+ return KnowledgeSourceResponse(**resp.json())
51
+
52
+ def create(self, *, kb_id: int, name: str, source_type: str, source_identifier: str,
53
+ purpose: str = "knowledge_only", options: dict | None = None) -> KnowledgeSourceResponse:
54
+ payload: dict[str, Any] = {
55
+ "kb_id": kb_id,
56
+ "name": name,
57
+ "source_type": source_type,
58
+ "source_identifier": source_identifier,
59
+ "purpose": purpose,
60
+ }
61
+ if options is not None:
62
+ payload["options"] = options
63
+ resp = self._http.post("/ks/", json=payload)
64
+ raise_for_status(resp)
65
+ return KnowledgeSourceResponse(**resp.json())
66
+
67
+ def create_web(self, *, kb_id: int, name: str, url: str,
68
+ purpose: str = "KNOWLEDGE_ONLY", description: str = "",
69
+ auth_headers: dict[str, str] | None = None) -> KnowledgeSourceResponse:
70
+ payload: dict[str, Any] = {
71
+ "kb_id": kb_id,
72
+ "name": name,
73
+ "url": url,
74
+ "purpose": purpose,
75
+ }
76
+ if description:
77
+ payload["description"] = description
78
+ if auth_headers:
79
+ payload["auth_headers"] = auth_headers
80
+ resp = self._http.post("/ks/web", json=payload)
81
+ raise_for_status(resp)
82
+ return KnowledgeSourceResponse(**resp.json())
83
+
84
+ def list(self, *, kb_id: int) -> list[KnowledgeSourceResponse]:
85
+ resp = self._http.get("/ks/", params={"kb_id": kb_id})
86
+ raise_for_status(resp)
87
+ return [KnowledgeSourceResponse(**s) for s in resp.json()]
88
+
89
+ def get(self, source_id: int) -> KnowledgeSourceResponse:
90
+ resp = self._http.get(f"/ks/{source_id}")
91
+ raise_for_status(resp)
92
+ return KnowledgeSourceResponse(**resp.json())
93
+
94
+ def update(self, source_id: int, **kwargs: Any) -> KnowledgeSourceResponse:
95
+ resp = self._http.put(f"/ks/{source_id}", json=kwargs)
96
+ raise_for_status(resp)
97
+ return KnowledgeSourceResponse(**resp.json())
98
+
99
+ def delete(self, source_id: int) -> MessageResult:
100
+ resp = self._http.delete(f"/ks/{source_id}")
101
+ raise_for_status(resp)
102
+ return message_result(resp)
103
+
104
+ def parse(self, source_id: int, **kwargs: Any) -> dict:
105
+ """Trigger parsing/processing for a knowledge source."""
106
+ resp = self._http.post(f"/ks/{source_id}/parse", json=kwargs)
107
+ raise_for_status(resp)
108
+ return resp.json()
109
+
110
+ def create_database_table(self, *, kb_id: int, name: str, **kwargs: Any) -> KnowledgeSourceResponse:
111
+ payload: dict[str, Any] = {"kb_id": kb_id, "name": name, **kwargs}
112
+ resp = self._http.post("/ks/database-table", json=payload)
113
+ raise_for_status(resp)
114
+ return KnowledgeSourceResponse(**resp.json())
115
+
116
+ def create_graph(self, *, kb_id: int, name: str, **kwargs: Any) -> KnowledgeSourceResponse:
117
+ payload: dict[str, Any] = {"kb_id": kb_id, "name": name, **kwargs}
118
+ resp = self._http.post("/ks/graph", json=payload)
119
+ raise_for_status(resp)
120
+ return KnowledgeSourceResponse(**resp.json())
121
+
122
+ def sync_database_tables(self, source_id: int) -> dict:
123
+ resp = self._http.post(f"/ks/{source_id}/sync-database-tables")
124
+ raise_for_status(resp)
125
+ return resp.json()
126
+
127
+ def search(self, *, kb_id: int, query: str, **kwargs: Any) -> list[dict]:
128
+ payload: dict[str, Any] = {"kb_id": kb_id, "query": query, **kwargs}
129
+ resp = self._http.post("/ks/search", json=payload)
130
+ raise_for_status(resp)
131
+ return resp.json()
132
+
133
+ def get_progress(self, source_id: int) -> dict:
134
+ resp = self._http.get(f"/ks/{source_id}/progress")
135
+ raise_for_status(resp)
136
+ return resp.json()
137
+
138
+ def list_by_kbs(self, kb_ids: list[int]) -> list[KnowledgeSourceResponse]:
139
+ resp = self._http.get("/ks/by-kb", params={"kb_ids": ",".join(str(i) for i in kb_ids)})
140
+ raise_for_status(resp)
141
+ return [KnowledgeSourceResponse(**s) for s in resp.json()]
@@ -0,0 +1,117 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ import httpx
6
+
7
+ from deploypod._http import json_body, message_result, raise_for_status
8
+ from deploypod.types import MCPServerResponse, MCPToolResponse, MessageResult
9
+
10
+
11
+ class MCPServersResource:
12
+ def __init__(self, http: httpx.Client):
13
+ self._http = http
14
+
15
+ def create(self, *, name: str, **kwargs: Any) -> MCPServerResponse:
16
+ payload = {"name": name, **kwargs}
17
+ resp = self._http.post("/mcp/servers", json=payload)
18
+ raise_for_status(resp)
19
+ return MCPServerResponse(**resp.json())
20
+
21
+ def list(self) -> list[MCPServerResponse]:
22
+ resp = self._http.get("/mcp/servers")
23
+ raise_for_status(resp)
24
+ return [MCPServerResponse(**s) for s in resp.json()]
25
+
26
+ def get(self, server_id: int) -> MCPServerResponse:
27
+ resp = self._http.get(f"/mcp/servers/{server_id}")
28
+ raise_for_status(resp)
29
+ return MCPServerResponse(**resp.json())
30
+
31
+ def update(self, server_id: int, **kwargs: Any) -> MCPServerResponse:
32
+ resp = self._http.put(f"/mcp/servers/{server_id}", json=kwargs)
33
+ raise_for_status(resp)
34
+ return MCPServerResponse(**resp.json())
35
+
36
+ def delete(self, server_id: int, *, force: bool = False) -> dict:
37
+ params = {"force": "true"} if force else {}
38
+ resp = self._http.delete(f"/mcp/servers/{server_id}", params=params)
39
+ raise_for_status(resp)
40
+ return json_body(resp)
41
+
42
+ def health_check(self, server_id: int) -> dict:
43
+ resp = self._http.get(f"/mcp/servers/{server_id}/health")
44
+ raise_for_status(resp)
45
+ return resp.json()
46
+
47
+ def test_connection(self, server_id: int) -> dict:
48
+ resp = self._http.post(f"/mcp/servers/{server_id}/test")
49
+ raise_for_status(resp)
50
+ return resp.json()
51
+
52
+ def discover_capabilities(self, server_id: int) -> dict:
53
+ resp = self._http.get(f"/mcp/servers/{server_id}/capabilities")
54
+ raise_for_status(resp)
55
+ return resp.json()
56
+
57
+ # ── Tools ────────────────────────────────────────────────────
58
+
59
+ def list_tools(self, server_id: int) -> list[MCPToolResponse]:
60
+ resp = self._http.get(f"/mcp/servers/{server_id}/tools")
61
+ raise_for_status(resp)
62
+ return [MCPToolResponse(**t) for t in resp.json()]
63
+
64
+ def list_all_tools(self) -> list[MCPToolResponse]:
65
+ resp = self._http.get("/mcp/tools")
66
+ raise_for_status(resp)
67
+ return [MCPToolResponse(**t) for t in resp.json()]
68
+
69
+ def update_tool(self, tool_id: int, **kwargs: Any) -> MCPToolResponse:
70
+ resp = self._http.put(f"/mcp/tools/{tool_id}", json=kwargs)
71
+ raise_for_status(resp)
72
+ return MCPToolResponse(**resp.json())
73
+
74
+ def delete_tool(self, tool_id: int) -> MessageResult:
75
+ resp = self._http.delete(f"/mcp/tools/{tool_id}")
76
+ raise_for_status(resp)
77
+ return message_result(resp)
78
+
79
+ def test_tool(self, server_id: int, tool_name: str, parameters: dict[str, Any] | None = None) -> dict:
80
+ """Run one tool of a server by name. The API tests tools by id
81
+ (POST /mcp/tools/:id/test), so the name is resolved first."""
82
+ tool = next((t for t in self.list_tools(server_id) if t.tool_name == tool_name), None)
83
+ if tool is None:
84
+ raise ValueError(f"MCP server {server_id} has no tool {tool_name!r}")
85
+ return self.test_tool_by_id(tool.id, parameters)
86
+
87
+ def test_tool_by_id(self, tool_id: int, parameters: dict[str, Any] | None = None) -> dict:
88
+ resp = self._http.post(f"/mcp/tools/{tool_id}/test", json={"parameters": parameters or {}})
89
+ raise_for_status(resp)
90
+ return resp.json()
91
+
92
+ def get_agent_tools(self, agent_id: int) -> list[MCPToolResponse]:
93
+ resp = self._http.get(f"/mcp/agents/{agent_id}/tools")
94
+ raise_for_status(resp)
95
+ return [MCPToolResponse(**t) for t in resp.json()]
96
+
97
+ # ── OAuth ────────────────────────────────────────────────────
98
+
99
+ def get_oauth_url(self, server_id: int) -> dict:
100
+ resp = self._http.get(f"/mcp/servers/{server_id}/oauth/url")
101
+ raise_for_status(resp)
102
+ return resp.json()
103
+
104
+ def oauth_callback(self, server_id: int, **kwargs: Any) -> dict:
105
+ resp = self._http.post(f"/mcp/servers/{server_id}/oauth/callback", json=kwargs)
106
+ raise_for_status(resp)
107
+ return resp.json()
108
+
109
+ def oauth_refresh(self, server_id: int) -> dict:
110
+ resp = self._http.post(f"/mcp/servers/{server_id}/oauth/refresh")
111
+ raise_for_status(resp)
112
+ return resp.json()
113
+
114
+ def oauth_disconnect(self, server_id: int) -> dict:
115
+ resp = self._http.delete(f"/mcp/servers/{server_id}/oauth")
116
+ raise_for_status(resp)
117
+ return json_body(resp)
@@ -0,0 +1,150 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ import httpx
6
+
7
+ from deploypod._http import message_result, raise_for_status
8
+ from deploypod.resources.users import UsersResource
9
+ from deploypod.types import LicenseInfoResponse, MessageResult, UserResponse
10
+
11
+
12
+ class OrganizationResource:
13
+ def __init__(self, http: httpx.Client):
14
+ self._http = http
15
+
16
+ def get_current(self) -> dict:
17
+ resp = self._http.get("/orgs/current")
18
+ raise_for_status(resp)
19
+ return resp.json()
20
+
21
+ # ── Session / timeout settings ───────────────────────────────
22
+
23
+ def update_session_timeout(self, hours: int) -> dict:
24
+ resp = self._http.put("/orgs/session-timeout", json={"session_timeout_hours": hours})
25
+ raise_for_status(resp)
26
+ return resp.json()
27
+
28
+ def update_timeout_settings(self, **kwargs: Any) -> dict:
29
+ resp = self._http.put("/orgs/timeout-settings", json=kwargs)
30
+ raise_for_status(resp)
31
+ return resp.json()
32
+
33
+ # ── Speech / TTS config ──────────────────────────────────────
34
+
35
+ def get_speech_config(self) -> dict:
36
+ resp = self._http.get("/orgs/speech-config")
37
+ raise_for_status(resp)
38
+ return resp.json()
39
+
40
+ def update_speech_config(self, **kwargs: Any) -> dict:
41
+ resp = self._http.put("/orgs/speech-config", json=kwargs)
42
+ raise_for_status(resp)
43
+ return resp.json()
44
+
45
+ def list_voices(self) -> list[dict]:
46
+ resp = self._http.get("/orgs/voices")
47
+ raise_for_status(resp)
48
+ return resp.json()
49
+
50
+ # ── License ──────────────────────────────────────────────────
51
+
52
+ def setup_license(self, license_token: str) -> dict:
53
+ resp = self._http.put("/orgs/license", json={"license_token": license_token})
54
+ raise_for_status(resp)
55
+ return resp.json()
56
+
57
+ def update_license(self, license_token: str) -> dict:
58
+ resp = self._http.put("/orgs/license", json={"license_token": license_token})
59
+ raise_for_status(resp)
60
+ return resp.json()
61
+
62
+ def get_license_info(self) -> LicenseInfoResponse:
63
+ resp = self._http.get("/orgs/license")
64
+ raise_for_status(resp)
65
+ return LicenseInfoResponse(**resp.json())
66
+
67
+ # ── Invitations ──────────────────────────────────────────────
68
+
69
+ def invite_user(
70
+ self,
71
+ email: str,
72
+ *,
73
+ roles: list[str] | None = None,
74
+ app_url: str | None = None,
75
+ ) -> UserResponse:
76
+ """Create the user and email them a login link.
77
+
78
+ There are no pending invitations: the user exists as soon as this
79
+ returns and shows up in ``users.list()``. ``app_url`` builds the login
80
+ link on another app's host (e.g. SDT Copilot) instead of the platform's.
81
+ """
82
+ return UsersResource(self._http).create(
83
+ email=email, roles=roles, send_password_email=True, app_url=app_url
84
+ )
85
+
86
+ # ── Search / query settings ───────────────────────────────────
87
+
88
+ def get_search_config(self) -> dict:
89
+ resp = self._http.get("/orgs/search-config")
90
+ raise_for_status(resp)
91
+ return resp.json()
92
+
93
+ def update_search_config(self, **kwargs: Any) -> dict:
94
+ resp = self._http.put("/orgs/search-config", json=kwargs)
95
+ raise_for_status(resp)
96
+ return resp.json()
97
+
98
+ def update_query_settings(self, **kwargs: Any) -> dict:
99
+ resp = self._http.put("/orgs/query-settings", json=kwargs)
100
+ raise_for_status(resp)
101
+ return resp.json()
102
+
103
+ def update_pruning_settings(self, **kwargs: Any) -> dict:
104
+ resp = self._http.put("/orgs/pruning-settings", json=kwargs)
105
+ raise_for_status(resp)
106
+ return resp.json()
107
+
108
+ # ── Notifications ─────────────────────────────────────────────
109
+
110
+ def list_notifications(self) -> list[dict]:
111
+ resp = self._http.get("/orgs/notifications")
112
+ raise_for_status(resp)
113
+ return resp.json()
114
+
115
+ def get_unread_count(self) -> dict:
116
+ resp = self._http.get("/orgs/notifications/unread-count")
117
+ raise_for_status(resp)
118
+ return resp.json()
119
+
120
+ def mark_notification_read(self, notification_id: int) -> dict:
121
+ resp = self._http.patch(f"/orgs/notifications/{notification_id}/read")
122
+ raise_for_status(resp)
123
+ return resp.json()
124
+
125
+ def delete_notification(self, notification_id: int) -> MessageResult:
126
+ resp = self._http.delete(f"/orgs/notifications/{notification_id}")
127
+ raise_for_status(resp)
128
+ return message_result(resp)
129
+
130
+ def get_notification_preferences(self) -> dict:
131
+ resp = self._http.get("/orgs/notification-preferences")
132
+ raise_for_status(resp)
133
+ return resp.json()
134
+
135
+ def update_notification_preferences(self, **kwargs: Any) -> dict:
136
+ resp = self._http.patch("/orgs/notification-preferences", json=kwargs)
137
+ raise_for_status(resp)
138
+ return resp.json()
139
+
140
+ # ── Audit logs ────────────────────────────────────────────────
141
+
142
+ def get_audit_logs(self) -> list[dict]:
143
+ resp = self._http.get("/orgs/audit-logs")
144
+ raise_for_status(resp)
145
+ return resp.json()
146
+
147
+ def remove_license(self) -> MessageResult:
148
+ resp = self._http.delete("/orgs/license")
149
+ raise_for_status(resp)
150
+ return message_result(resp)