sovant 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.

Potentially problematic release.


This version of sovant might be problematic. Click here for more details.

@@ -0,0 +1,317 @@
1
+ """Memory resource for the Sovant SDK."""
2
+
3
+ from typing import Any, Dict, List, Optional, Union
4
+
5
+ from ..base_client import AsyncBaseClient, BaseClient
6
+ from ..types import (
7
+ BatchCreateResult,
8
+ CreateMemoryInput,
9
+ Memory,
10
+ MemoryType,
11
+ PaginatedResponse,
12
+ SearchOptions,
13
+ SearchResult,
14
+ UpdateMemoryInput,
15
+ )
16
+
17
+
18
+ class Memories(BaseClient):
19
+ """Synchronous memory operations."""
20
+
21
+ def create(self, data: Union[CreateMemoryInput, Dict[str, Any]]) -> Memory:
22
+ """Create a new memory."""
23
+ if isinstance(data, dict):
24
+ data = CreateMemoryInput(**data)
25
+
26
+ response = self.request("POST", "/memory", data.model_dump(exclude_none=True))
27
+ return Memory(**response)
28
+
29
+ def get(self, memory_id: str) -> Memory:
30
+ """Get a memory by ID."""
31
+ response = self.request("GET", f"/memory/{memory_id}")
32
+ return Memory(**response)
33
+
34
+ def update(
35
+ self,
36
+ memory_id: str,
37
+ data: Union[UpdateMemoryInput, Dict[str, Any]]
38
+ ) -> Memory:
39
+ """Update a memory."""
40
+ if isinstance(data, dict):
41
+ data = UpdateMemoryInput(**data)
42
+
43
+ response = self.request("PUT", f"/memory/{memory_id}", data.model_dump(exclude_none=True))
44
+ return Memory(**response)
45
+
46
+ def delete(self, memory_id: str) -> None:
47
+ """Delete a memory."""
48
+ self.request("DELETE", f"/memory/{memory_id}")
49
+
50
+ def list(
51
+ self,
52
+ limit: Optional[int] = None,
53
+ offset: Optional[int] = None,
54
+ type: Optional[Union[MemoryType, List[MemoryType]]] = None,
55
+ tags: Optional[List[str]] = None,
56
+ is_archived: Optional[bool] = None,
57
+ sort_by: Optional[str] = None,
58
+ sort_order: Optional[str] = None,
59
+ ) -> PaginatedResponse:
60
+ """List memories with pagination."""
61
+ params = {
62
+ "limit": limit,
63
+ "offset": offset,
64
+ "type": type,
65
+ "tags": tags,
66
+ "is_archived": is_archived,
67
+ "sort_by": sort_by,
68
+ "sort_order": sort_order,
69
+ }
70
+
71
+ query_string = self._build_query_string(params)
72
+ response = self.request("GET", f"/memory{query_string}")
73
+
74
+ # Convert data items to Memory objects
75
+ response["data"] = [Memory(**item) for item in response["data"]]
76
+ return PaginatedResponse(**response)
77
+
78
+ def search(self, options: Union[SearchOptions, Dict[str, Any]]) -> List[SearchResult]:
79
+ """Search memories."""
80
+ if isinstance(options, dict):
81
+ options = SearchOptions(**options)
82
+
83
+ params = {
84
+ "q": options.query,
85
+ "limit": options.limit,
86
+ "offset": options.offset,
87
+ "type": options.type,
88
+ "tags": options.tags,
89
+ "created_after": options.created_after,
90
+ "created_before": options.created_before,
91
+ "search_type": options.search_type,
92
+ "include_archived": options.include_archived,
93
+ "sort_by": options.sort_by,
94
+ "sort_order": options.sort_order,
95
+ }
96
+
97
+ query_string = self._build_query_string(params)
98
+ response = self.request("GET", f"/memory/search{query_string}")
99
+
100
+ return [SearchResult(**result) for result in response.get("results", [])]
101
+
102
+ def create_batch(self, memories: List[Union[CreateMemoryInput, Dict[str, Any]]]) -> BatchCreateResult:
103
+ """Batch create memories."""
104
+ memories_data = []
105
+ for memory in memories:
106
+ if isinstance(memory, dict):
107
+ memory = CreateMemoryInput(**memory)
108
+ memories_data.append(memory.model_dump(exclude_none=True))
109
+
110
+ response = self.request("POST", "/memory/batch", {"memories": memories_data})
111
+
112
+ # Convert success items to Memory objects
113
+ response["success"] = [Memory(**item) for item in response["success"]]
114
+ return BatchCreateResult(**response)
115
+
116
+ def delete_batch(self, ids: List[str]) -> Dict[str, int]:
117
+ """Batch delete memories."""
118
+ response = self.request("POST", "/memory/batch/delete", {"ids": ids})
119
+ return response
120
+
121
+ def get_insights(
122
+ self,
123
+ time_range: Optional[str] = None,
124
+ group_by: Optional[str] = None,
125
+ ) -> Dict[str, Any]:
126
+ """Get memory insights and analytics."""
127
+ params = {
128
+ "time_range": time_range,
129
+ "group_by": group_by,
130
+ }
131
+
132
+ query_string = self._build_query_string(params)
133
+ return self.request("GET", f"/memory/insights{query_string}")
134
+
135
+ def archive(self, memory_id: str) -> Memory:
136
+ """Archive a memory."""
137
+ return self.update(memory_id, {"is_archived": True})
138
+
139
+ def unarchive(self, memory_id: str) -> Memory:
140
+ """Unarchive a memory."""
141
+ return self.update(memory_id, {"is_archived": False})
142
+
143
+ def pin(self, memory_id: str) -> Memory:
144
+ """Pin a memory."""
145
+ return self.update(memory_id, {"is_pinned": True})
146
+
147
+ def unpin(self, memory_id: str) -> Memory:
148
+ """Unpin a memory."""
149
+ return self.update(memory_id, {"is_pinned": False})
150
+
151
+ def get_related(
152
+ self,
153
+ memory_id: str,
154
+ limit: Optional[int] = None,
155
+ threshold: Optional[float] = None,
156
+ ) -> List[SearchResult]:
157
+ """Get related memories."""
158
+ params = {
159
+ "limit": limit,
160
+ "threshold": threshold,
161
+ }
162
+
163
+ query_string = self._build_query_string(params)
164
+ response = self.request("GET", f"/memory/{memory_id}/related{query_string}")
165
+
166
+ return [SearchResult(**result) for result in response.get("results", [])]
167
+
168
+
169
+ class AsyncMemories(AsyncBaseClient):
170
+ """Asynchronous memory operations."""
171
+
172
+ async def create(self, data: Union[CreateMemoryInput, Dict[str, Any]]) -> Memory:
173
+ """Create a new memory."""
174
+ if isinstance(data, dict):
175
+ data = CreateMemoryInput(**data)
176
+
177
+ response = await self.request("POST", "/memory", data.model_dump(exclude_none=True))
178
+ return Memory(**response)
179
+
180
+ async def get(self, memory_id: str) -> Memory:
181
+ """Get a memory by ID."""
182
+ response = await self.request("GET", f"/memory/{memory_id}")
183
+ return Memory(**response)
184
+
185
+ async def update(
186
+ self,
187
+ memory_id: str,
188
+ data: Union[UpdateMemoryInput, Dict[str, Any]]
189
+ ) -> Memory:
190
+ """Update a memory."""
191
+ if isinstance(data, dict):
192
+ data = UpdateMemoryInput(**data)
193
+
194
+ response = await self.request("PUT", f"/memory/{memory_id}", data.model_dump(exclude_none=True))
195
+ return Memory(**response)
196
+
197
+ async def delete(self, memory_id: str) -> None:
198
+ """Delete a memory."""
199
+ await self.request("DELETE", f"/memory/{memory_id}")
200
+
201
+ async def list(
202
+ self,
203
+ limit: Optional[int] = None,
204
+ offset: Optional[int] = None,
205
+ type: Optional[Union[MemoryType, List[MemoryType]]] = None,
206
+ tags: Optional[List[str]] = None,
207
+ is_archived: Optional[bool] = None,
208
+ sort_by: Optional[str] = None,
209
+ sort_order: Optional[str] = None,
210
+ ) -> PaginatedResponse:
211
+ """List memories with pagination."""
212
+ params = {
213
+ "limit": limit,
214
+ "offset": offset,
215
+ "type": type,
216
+ "tags": tags,
217
+ "is_archived": is_archived,
218
+ "sort_by": sort_by,
219
+ "sort_order": sort_order,
220
+ }
221
+
222
+ query_string = self._build_query_string(params)
223
+ response = await self.request("GET", f"/memory{query_string}")
224
+
225
+ # Convert data items to Memory objects
226
+ response["data"] = [Memory(**item) for item in response["data"]]
227
+ return PaginatedResponse(**response)
228
+
229
+ async def search(self, options: Union[SearchOptions, Dict[str, Any]]) -> List[SearchResult]:
230
+ """Search memories."""
231
+ if isinstance(options, dict):
232
+ options = SearchOptions(**options)
233
+
234
+ params = {
235
+ "q": options.query,
236
+ "limit": options.limit,
237
+ "offset": options.offset,
238
+ "type": options.type,
239
+ "tags": options.tags,
240
+ "created_after": options.created_after,
241
+ "created_before": options.created_before,
242
+ "search_type": options.search_type,
243
+ "include_archived": options.include_archived,
244
+ "sort_by": options.sort_by,
245
+ "sort_order": options.sort_order,
246
+ }
247
+
248
+ query_string = self._build_query_string(params)
249
+ response = await self.request("GET", f"/memory/search{query_string}")
250
+
251
+ return [SearchResult(**result) for result in response.get("results", [])]
252
+
253
+ async def create_batch(self, memories: List[Union[CreateMemoryInput, Dict[str, Any]]]) -> BatchCreateResult:
254
+ """Batch create memories."""
255
+ memories_data = []
256
+ for memory in memories:
257
+ if isinstance(memory, dict):
258
+ memory = CreateMemoryInput(**memory)
259
+ memories_data.append(memory.model_dump(exclude_none=True))
260
+
261
+ response = await self.request("POST", "/memory/batch", {"memories": memories_data})
262
+
263
+ # Convert success items to Memory objects
264
+ response["success"] = [Memory(**item) for item in response["success"]]
265
+ return BatchCreateResult(**response)
266
+
267
+ async def delete_batch(self, ids: List[str]) -> Dict[str, int]:
268
+ """Batch delete memories."""
269
+ response = await self.request("POST", "/memory/batch/delete", {"ids": ids})
270
+ return response
271
+
272
+ async def get_insights(
273
+ self,
274
+ time_range: Optional[str] = None,
275
+ group_by: Optional[str] = None,
276
+ ) -> Dict[str, Any]:
277
+ """Get memory insights and analytics."""
278
+ params = {
279
+ "time_range": time_range,
280
+ "group_by": group_by,
281
+ }
282
+
283
+ query_string = self._build_query_string(params)
284
+ return await self.request("GET", f"/memory/insights{query_string}")
285
+
286
+ async def archive(self, memory_id: str) -> Memory:
287
+ """Archive a memory."""
288
+ return await self.update(memory_id, {"is_archived": True})
289
+
290
+ async def unarchive(self, memory_id: str) -> Memory:
291
+ """Unarchive a memory."""
292
+ return await self.update(memory_id, {"is_archived": False})
293
+
294
+ async def pin(self, memory_id: str) -> Memory:
295
+ """Pin a memory."""
296
+ return await self.update(memory_id, {"is_pinned": True})
297
+
298
+ async def unpin(self, memory_id: str) -> Memory:
299
+ """Unpin a memory."""
300
+ return await self.update(memory_id, {"is_pinned": False})
301
+
302
+ async def get_related(
303
+ self,
304
+ memory_id: str,
305
+ limit: Optional[int] = None,
306
+ threshold: Optional[float] = None,
307
+ ) -> List[SearchResult]:
308
+ """Get related memories."""
309
+ params = {
310
+ "limit": limit,
311
+ "threshold": threshold,
312
+ }
313
+
314
+ query_string = self._build_query_string(params)
315
+ response = await self.request("GET", f"/memory/{memory_id}/related{query_string}")
316
+
317
+ return [SearchResult(**result) for result in response.get("results", [])]
@@ -0,0 +1,362 @@
1
+ """Thread resource for the Sovant SDK."""
2
+
3
+ from typing import Any, Dict, List, Optional, Union
4
+
5
+ from ..base_client import AsyncBaseClient, BaseClient
6
+ from ..types import (
7
+ CreateThreadInput,
8
+ Memory,
9
+ PaginatedResponse,
10
+ Thread,
11
+ ThreadStats,
12
+ ThreadStatus,
13
+ UpdateThreadInput,
14
+ )
15
+
16
+
17
+ class Threads(BaseClient):
18
+ """Synchronous thread operations."""
19
+
20
+ def create(self, data: Union[CreateThreadInput, Dict[str, Any]]) -> Thread:
21
+ """Create a new thread."""
22
+ if isinstance(data, dict):
23
+ data = CreateThreadInput(**data)
24
+
25
+ response = self.request("POST", "/threads", data.model_dump(exclude_none=True))
26
+ return Thread(**response)
27
+
28
+ def get(self, thread_id: str, include_memories: bool = False) -> Thread:
29
+ """Get a thread by ID."""
30
+ query_string = "?include_memories=true" if include_memories else ""
31
+ response = self.request("GET", f"/threads/{thread_id}{query_string}")
32
+
33
+ thread = Thread(**response)
34
+ if include_memories and "memories" in response:
35
+ # Add memories as an attribute
36
+ thread.memories = [Memory(**m) for m in response["memories"]]
37
+
38
+ return thread
39
+
40
+ def update(
41
+ self,
42
+ thread_id: str,
43
+ data: Union[UpdateThreadInput, Dict[str, Any]]
44
+ ) -> Thread:
45
+ """Update a thread."""
46
+ if isinstance(data, dict):
47
+ data = UpdateThreadInput(**data)
48
+
49
+ response = self.request("PUT", f"/threads/{thread_id}", data.model_dump(exclude_none=True))
50
+ return Thread(**response)
51
+
52
+ def delete(self, thread_id: str, delete_memories: bool = False) -> None:
53
+ """Delete a thread."""
54
+ query_string = "?delete_memories=true" if delete_memories else ""
55
+ self.request("DELETE", f"/threads/{thread_id}{query_string}")
56
+
57
+ def list(
58
+ self,
59
+ limit: Optional[int] = None,
60
+ offset: Optional[int] = None,
61
+ status: Optional[ThreadStatus] = None,
62
+ tags: Optional[List[str]] = None,
63
+ sort_by: Optional[str] = None,
64
+ sort_order: Optional[str] = None,
65
+ ) -> PaginatedResponse:
66
+ """List threads with pagination."""
67
+ params = {
68
+ "limit": limit,
69
+ "offset": offset,
70
+ "status": status,
71
+ "tags": tags,
72
+ "sort_by": sort_by,
73
+ "sort_order": sort_order,
74
+ }
75
+
76
+ query_string = self._build_query_string(params)
77
+ response = self.request("GET", f"/threads{query_string}")
78
+
79
+ # Convert data items to Thread objects
80
+ response["data"] = [Thread(**item) for item in response["data"]]
81
+ return PaginatedResponse(**response)
82
+
83
+ def add_memories(self, thread_id: str, memory_ids: List[str]) -> Thread:
84
+ """Add memories to a thread."""
85
+ response = self.request(
86
+ "POST",
87
+ f"/threads/{thread_id}/memories",
88
+ {"add": memory_ids}
89
+ )
90
+ return Thread(**response)
91
+
92
+ def remove_memories(self, thread_id: str, memory_ids: List[str]) -> Thread:
93
+ """Remove memories from a thread."""
94
+ response = self.request(
95
+ "POST",
96
+ f"/threads/{thread_id}/memories",
97
+ {"remove": memory_ids}
98
+ )
99
+ return Thread(**response)
100
+
101
+ def get_memories(
102
+ self,
103
+ thread_id: str,
104
+ limit: Optional[int] = None,
105
+ offset: Optional[int] = None,
106
+ type: Optional[Union[str, List[str]]] = None,
107
+ sort_by: Optional[str] = None,
108
+ sort_order: Optional[str] = None,
109
+ ) -> PaginatedResponse:
110
+ """Get memories in a thread."""
111
+ params = {
112
+ "limit": limit,
113
+ "offset": offset,
114
+ "type": type,
115
+ "sort_by": sort_by,
116
+ "sort_order": sort_order,
117
+ }
118
+
119
+ query_string = self._build_query_string(params)
120
+ response = self.request("GET", f"/threads/{thread_id}/memories{query_string}")
121
+
122
+ # Convert data items to Memory objects
123
+ response["data"] = [Memory(**item) for item in response["data"]]
124
+ return PaginatedResponse(**response)
125
+
126
+ def get_stats(self, thread_id: str) -> ThreadStats:
127
+ """Get thread statistics."""
128
+ response = self.request("GET", f"/threads/{thread_id}/stats")
129
+ return ThreadStats(**response)
130
+
131
+ def archive(self, thread_id: str) -> Thread:
132
+ """Archive a thread."""
133
+ return self.update(thread_id, {"status": ThreadStatus.ARCHIVED})
134
+
135
+ def unarchive(self, thread_id: str) -> Thread:
136
+ """Unarchive a thread."""
137
+ return self.update(thread_id, {"status": ThreadStatus.ACTIVE})
138
+
139
+ def complete(self, thread_id: str) -> Thread:
140
+ """Complete a thread."""
141
+ return self.update(thread_id, {"status": ThreadStatus.COMPLETED})
142
+
143
+ def search(
144
+ self,
145
+ query: str,
146
+ limit: Optional[int] = None,
147
+ offset: Optional[int] = None,
148
+ status: Optional[ThreadStatus] = None,
149
+ tags: Optional[List[str]] = None,
150
+ ) -> PaginatedResponse:
151
+ """Search threads."""
152
+ params = {
153
+ "q": query,
154
+ "limit": limit,
155
+ "offset": offset,
156
+ "status": status,
157
+ "tags": tags,
158
+ }
159
+
160
+ query_string = self._build_query_string(params)
161
+ response = self.request("GET", f"/threads/search{query_string}")
162
+
163
+ # Convert data items to Thread objects
164
+ response["data"] = [Thread(**item) for item in response["data"]]
165
+ return PaginatedResponse(**response)
166
+
167
+ def merge(self, target_id: str, source_ids: List[str]) -> Thread:
168
+ """Merge multiple threads into one."""
169
+ response = self.request(
170
+ "POST",
171
+ f"/threads/{target_id}/merge",
172
+ {"source_thread_ids": source_ids}
173
+ )
174
+ return Thread(**response)
175
+
176
+ def clone(
177
+ self,
178
+ thread_id: str,
179
+ name: Optional[str] = None,
180
+ include_memories: bool = True
181
+ ) -> Thread:
182
+ """Clone a thread."""
183
+ data = {
184
+ "name": name,
185
+ "include_memories": include_memories,
186
+ }
187
+ response = self.request("POST", f"/threads/{thread_id}/clone", data)
188
+ return Thread(**response)
189
+
190
+
191
+ class AsyncThreads(AsyncBaseClient):
192
+ """Asynchronous thread operations."""
193
+
194
+ async def create(self, data: Union[CreateThreadInput, Dict[str, Any]]) -> Thread:
195
+ """Create a new thread."""
196
+ if isinstance(data, dict):
197
+ data = CreateThreadInput(**data)
198
+
199
+ response = await self.request("POST", "/threads", data.model_dump(exclude_none=True))
200
+ return Thread(**response)
201
+
202
+ async def get(self, thread_id: str, include_memories: bool = False) -> Thread:
203
+ """Get a thread by ID."""
204
+ query_string = "?include_memories=true" if include_memories else ""
205
+ response = await self.request("GET", f"/threads/{thread_id}{query_string}")
206
+
207
+ thread = Thread(**response)
208
+ if include_memories and "memories" in response:
209
+ # Add memories as an attribute
210
+ thread.memories = [Memory(**m) for m in response["memories"]]
211
+
212
+ return thread
213
+
214
+ async def update(
215
+ self,
216
+ thread_id: str,
217
+ data: Union[UpdateThreadInput, Dict[str, Any]]
218
+ ) -> Thread:
219
+ """Update a thread."""
220
+ if isinstance(data, dict):
221
+ data = UpdateThreadInput(**data)
222
+
223
+ response = await self.request("PUT", f"/threads/{thread_id}", data.model_dump(exclude_none=True))
224
+ return Thread(**response)
225
+
226
+ async def delete(self, thread_id: str, delete_memories: bool = False) -> None:
227
+ """Delete a thread."""
228
+ query_string = "?delete_memories=true" if delete_memories else ""
229
+ await self.request("DELETE", f"/threads/{thread_id}{query_string}")
230
+
231
+ async def list(
232
+ self,
233
+ limit: Optional[int] = None,
234
+ offset: Optional[int] = None,
235
+ status: Optional[ThreadStatus] = None,
236
+ tags: Optional[List[str]] = None,
237
+ sort_by: Optional[str] = None,
238
+ sort_order: Optional[str] = None,
239
+ ) -> PaginatedResponse:
240
+ """List threads with pagination."""
241
+ params = {
242
+ "limit": limit,
243
+ "offset": offset,
244
+ "status": status,
245
+ "tags": tags,
246
+ "sort_by": sort_by,
247
+ "sort_order": sort_order,
248
+ }
249
+
250
+ query_string = self._build_query_string(params)
251
+ response = await self.request("GET", f"/threads{query_string}")
252
+
253
+ # Convert data items to Thread objects
254
+ response["data"] = [Thread(**item) for item in response["data"]]
255
+ return PaginatedResponse(**response)
256
+
257
+ async def add_memories(self, thread_id: str, memory_ids: List[str]) -> Thread:
258
+ """Add memories to a thread."""
259
+ response = await self.request(
260
+ "POST",
261
+ f"/threads/{thread_id}/memories",
262
+ {"add": memory_ids}
263
+ )
264
+ return Thread(**response)
265
+
266
+ async def remove_memories(self, thread_id: str, memory_ids: List[str]) -> Thread:
267
+ """Remove memories from a thread."""
268
+ response = await self.request(
269
+ "POST",
270
+ f"/threads/{thread_id}/memories",
271
+ {"remove": memory_ids}
272
+ )
273
+ return Thread(**response)
274
+
275
+ async def get_memories(
276
+ self,
277
+ thread_id: str,
278
+ limit: Optional[int] = None,
279
+ offset: Optional[int] = None,
280
+ type: Optional[Union[str, List[str]]] = None,
281
+ sort_by: Optional[str] = None,
282
+ sort_order: Optional[str] = None,
283
+ ) -> PaginatedResponse:
284
+ """Get memories in a thread."""
285
+ params = {
286
+ "limit": limit,
287
+ "offset": offset,
288
+ "type": type,
289
+ "sort_by": sort_by,
290
+ "sort_order": sort_order,
291
+ }
292
+
293
+ query_string = self._build_query_string(params)
294
+ response = await self.request("GET", f"/threads/{thread_id}/memories{query_string}")
295
+
296
+ # Convert data items to Memory objects
297
+ response["data"] = [Memory(**item) for item in response["data"]]
298
+ return PaginatedResponse(**response)
299
+
300
+ async def get_stats(self, thread_id: str) -> ThreadStats:
301
+ """Get thread statistics."""
302
+ response = await self.request("GET", f"/threads/{thread_id}/stats")
303
+ return ThreadStats(**response)
304
+
305
+ async def archive(self, thread_id: str) -> Thread:
306
+ """Archive a thread."""
307
+ return await self.update(thread_id, {"status": ThreadStatus.ARCHIVED})
308
+
309
+ async def unarchive(self, thread_id: str) -> Thread:
310
+ """Unarchive a thread."""
311
+ return await self.update(thread_id, {"status": ThreadStatus.ACTIVE})
312
+
313
+ async def complete(self, thread_id: str) -> Thread:
314
+ """Complete a thread."""
315
+ return await self.update(thread_id, {"status": ThreadStatus.COMPLETED})
316
+
317
+ async def search(
318
+ self,
319
+ query: str,
320
+ limit: Optional[int] = None,
321
+ offset: Optional[int] = None,
322
+ status: Optional[ThreadStatus] = None,
323
+ tags: Optional[List[str]] = None,
324
+ ) -> PaginatedResponse:
325
+ """Search threads."""
326
+ params = {
327
+ "q": query,
328
+ "limit": limit,
329
+ "offset": offset,
330
+ "status": status,
331
+ "tags": tags,
332
+ }
333
+
334
+ query_string = self._build_query_string(params)
335
+ response = await self.request("GET", f"/threads/search{query_string}")
336
+
337
+ # Convert data items to Thread objects
338
+ response["data"] = [Thread(**item) for item in response["data"]]
339
+ return PaginatedResponse(**response)
340
+
341
+ async def merge(self, target_id: str, source_ids: List[str]) -> Thread:
342
+ """Merge multiple threads into one."""
343
+ response = await self.request(
344
+ "POST",
345
+ f"/threads/{target_id}/merge",
346
+ {"source_thread_ids": source_ids}
347
+ )
348
+ return Thread(**response)
349
+
350
+ async def clone(
351
+ self,
352
+ thread_id: str,
353
+ name: Optional[str] = None,
354
+ include_memories: bool = True
355
+ ) -> Thread:
356
+ """Clone a thread."""
357
+ data = {
358
+ "name": name,
359
+ "include_memories": include_memories,
360
+ }
361
+ response = await self.request("POST", f"/threads/{thread_id}/clone", data)
362
+ return Thread(**response)