chainlit 2.7.0__py3-none-any.whl → 2.7.1__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 chainlit might be problematic. Click here for more details.

Files changed (85) hide show
  1. {chainlit-2.7.0.dist-info → chainlit-2.7.1.dist-info}/METADATA +1 -1
  2. chainlit-2.7.1.dist-info/RECORD +4 -0
  3. chainlit/__init__.py +0 -207
  4. chainlit/__main__.py +0 -4
  5. chainlit/_utils.py +0 -8
  6. chainlit/action.py +0 -33
  7. chainlit/auth/__init__.py +0 -95
  8. chainlit/auth/cookie.py +0 -197
  9. chainlit/auth/jwt.py +0 -42
  10. chainlit/cache.py +0 -45
  11. chainlit/callbacks.py +0 -433
  12. chainlit/chat_context.py +0 -64
  13. chainlit/chat_settings.py +0 -34
  14. chainlit/cli/__init__.py +0 -235
  15. chainlit/config.py +0 -621
  16. chainlit/context.py +0 -112
  17. chainlit/data/__init__.py +0 -111
  18. chainlit/data/acl.py +0 -19
  19. chainlit/data/base.py +0 -107
  20. chainlit/data/chainlit_data_layer.py +0 -687
  21. chainlit/data/dynamodb.py +0 -616
  22. chainlit/data/literalai.py +0 -501
  23. chainlit/data/sql_alchemy.py +0 -741
  24. chainlit/data/storage_clients/__init__.py +0 -0
  25. chainlit/data/storage_clients/azure.py +0 -84
  26. chainlit/data/storage_clients/azure_blob.py +0 -94
  27. chainlit/data/storage_clients/base.py +0 -28
  28. chainlit/data/storage_clients/gcs.py +0 -101
  29. chainlit/data/storage_clients/s3.py +0 -88
  30. chainlit/data/utils.py +0 -29
  31. chainlit/discord/__init__.py +0 -6
  32. chainlit/discord/app.py +0 -364
  33. chainlit/element.py +0 -454
  34. chainlit/emitter.py +0 -450
  35. chainlit/hello.py +0 -12
  36. chainlit/input_widget.py +0 -182
  37. chainlit/langchain/__init__.py +0 -6
  38. chainlit/langchain/callbacks.py +0 -682
  39. chainlit/langflow/__init__.py +0 -25
  40. chainlit/llama_index/__init__.py +0 -6
  41. chainlit/llama_index/callbacks.py +0 -206
  42. chainlit/logger.py +0 -16
  43. chainlit/markdown.py +0 -57
  44. chainlit/mcp.py +0 -99
  45. chainlit/message.py +0 -619
  46. chainlit/mistralai/__init__.py +0 -50
  47. chainlit/oauth_providers.py +0 -835
  48. chainlit/openai/__init__.py +0 -53
  49. chainlit/py.typed +0 -0
  50. chainlit/secret.py +0 -9
  51. chainlit/semantic_kernel/__init__.py +0 -111
  52. chainlit/server.py +0 -1616
  53. chainlit/session.py +0 -304
  54. chainlit/sidebar.py +0 -55
  55. chainlit/slack/__init__.py +0 -6
  56. chainlit/slack/app.py +0 -427
  57. chainlit/socket.py +0 -381
  58. chainlit/step.py +0 -490
  59. chainlit/sync.py +0 -43
  60. chainlit/teams/__init__.py +0 -6
  61. chainlit/teams/app.py +0 -348
  62. chainlit/translations/bn.json +0 -214
  63. chainlit/translations/el-GR.json +0 -214
  64. chainlit/translations/en-US.json +0 -214
  65. chainlit/translations/fr-FR.json +0 -214
  66. chainlit/translations/gu.json +0 -214
  67. chainlit/translations/he-IL.json +0 -214
  68. chainlit/translations/hi.json +0 -214
  69. chainlit/translations/ja.json +0 -214
  70. chainlit/translations/kn.json +0 -214
  71. chainlit/translations/ml.json +0 -214
  72. chainlit/translations/mr.json +0 -214
  73. chainlit/translations/nl.json +0 -214
  74. chainlit/translations/ta.json +0 -214
  75. chainlit/translations/te.json +0 -214
  76. chainlit/translations/zh-CN.json +0 -214
  77. chainlit/translations.py +0 -60
  78. chainlit/types.py +0 -334
  79. chainlit/user.py +0 -43
  80. chainlit/user_session.py +0 -153
  81. chainlit/utils.py +0 -173
  82. chainlit/version.py +0 -8
  83. chainlit-2.7.0.dist-info/RECORD +0 -84
  84. {chainlit-2.7.0.dist-info → chainlit-2.7.1.dist-info}/WHEEL +0 -0
  85. {chainlit-2.7.0.dist-info → chainlit-2.7.1.dist-info}/entry_points.txt +0 -0
chainlit/data/dynamodb.py DELETED
@@ -1,616 +0,0 @@
1
- import asyncio
2
- import json
3
- import logging
4
- import os
5
- import random
6
- from dataclasses import asdict
7
- from datetime import datetime
8
- from decimal import Decimal
9
- from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
10
-
11
- import aiofiles
12
- import aiohttp
13
- import boto3 # type: ignore
14
- from boto3.dynamodb.types import TypeDeserializer, TypeSerializer
15
-
16
- from chainlit.context import context
17
- from chainlit.data.base import BaseDataLayer
18
- from chainlit.data.storage_clients.base import BaseStorageClient
19
- from chainlit.data.utils import queue_until_user_message
20
- from chainlit.element import ElementDict
21
- from chainlit.logger import logger
22
- from chainlit.step import StepDict
23
- from chainlit.types import (
24
- Feedback,
25
- PageInfo,
26
- PaginatedResponse,
27
- Pagination,
28
- ThreadDict,
29
- ThreadFilter,
30
- )
31
- from chainlit.user import PersistedUser, User
32
-
33
- if TYPE_CHECKING:
34
- from mypy_boto3_dynamodb import DynamoDBClient
35
-
36
- from chainlit.element import Element
37
-
38
-
39
- _logger = logger.getChild("DynamoDB")
40
- _logger.setLevel(logging.WARNING)
41
-
42
-
43
- class DynamoDBDataLayer(BaseDataLayer):
44
- def __init__(
45
- self,
46
- table_name: str,
47
- client: Optional["DynamoDBClient"] = None,
48
- storage_provider: Optional[BaseStorageClient] = None,
49
- user_thread_limit: int = 10,
50
- ):
51
- if client:
52
- self.client = client
53
- else:
54
- region_name = os.environ.get("AWS_REGION", "us-east-1")
55
- self.client = boto3.client("dynamodb", region_name=region_name) # type: ignore
56
-
57
- self.table_name = table_name
58
- self.storage_provider = storage_provider
59
- self.user_thread_limit = user_thread_limit
60
-
61
- self._type_deserializer = TypeDeserializer()
62
- self._type_serializer = TypeSerializer()
63
-
64
- def _get_current_timestamp(self) -> str:
65
- return datetime.now().isoformat() + "Z"
66
-
67
- def _serialize_item(self, item: dict[str, Any]) -> dict[str, Any]:
68
- def convert_floats(obj):
69
- if isinstance(obj, float):
70
- return Decimal(str(obj))
71
- elif isinstance(obj, dict):
72
- return {k: convert_floats(v) for k, v in obj.items()}
73
- elif isinstance(obj, list):
74
- return [convert_floats(v) for v in obj]
75
- else:
76
- return obj
77
-
78
- return {
79
- key: self._type_serializer.serialize(convert_floats(value))
80
- for key, value in item.items()
81
- }
82
-
83
- def _deserialize_item(self, item: dict[str, Any]) -> dict[str, Any]:
84
- def convert_decimals(obj):
85
- if isinstance(obj, Decimal):
86
- return float(obj)
87
- elif isinstance(obj, dict):
88
- return {k: convert_decimals(v) for k, v in obj.items()}
89
- elif isinstance(obj, list):
90
- return [convert_decimals(v) for v in obj]
91
- else:
92
- return obj
93
-
94
- return {
95
- key: convert_decimals(self._type_deserializer.deserialize(value))
96
- for key, value in item.items()
97
- }
98
-
99
- def _update_item(self, key: Dict[str, Any], updates: Dict[str, Any]):
100
- update_expr: List[str] = []
101
- expression_attribute_names = {}
102
- expression_attribute_values = {}
103
-
104
- for index, (attr, value) in enumerate(updates.items()):
105
- if not value:
106
- continue
107
-
108
- k, v = f"#{index}", f":{index}"
109
- update_expr.append(f"{k} = {v}")
110
- expression_attribute_names[k] = attr
111
- expression_attribute_values[v] = value
112
-
113
- self.client.update_item(
114
- TableName=self.table_name,
115
- Key=self._serialize_item(key),
116
- UpdateExpression="SET " + ", ".join(update_expr),
117
- ExpressionAttributeNames=expression_attribute_names,
118
- ExpressionAttributeValues=self._serialize_item(expression_attribute_values),
119
- )
120
-
121
- @property
122
- def context(self):
123
- return context
124
-
125
- async def get_user(self, identifier: str) -> Optional["PersistedUser"]:
126
- _logger.info("DynamoDB: get_user identifier=%s", identifier)
127
-
128
- response = self.client.get_item(
129
- TableName=self.table_name,
130
- Key={
131
- "PK": {"S": f"USER#{identifier}"},
132
- "SK": {"S": "USER"},
133
- },
134
- )
135
-
136
- if "Item" not in response:
137
- return None
138
-
139
- user = self._deserialize_item(response["Item"])
140
-
141
- return PersistedUser(
142
- id=user["id"],
143
- identifier=user["identifier"],
144
- createdAt=user["createdAt"],
145
- metadata=user["metadata"],
146
- )
147
-
148
- async def create_user(self, user: "User") -> Optional["PersistedUser"]:
149
- _logger.info("DynamoDB: create_user user.identifier=%s", user.identifier)
150
-
151
- ts = self._get_current_timestamp()
152
- metadata: Dict[Any, Any] = user.metadata # type: ignore
153
-
154
- item = {
155
- "PK": f"USER#{user.identifier}",
156
- "SK": "USER",
157
- "id": user.identifier,
158
- "identifier": user.identifier,
159
- "metadata": metadata,
160
- "createdAt": ts,
161
- }
162
-
163
- self.client.put_item(
164
- TableName=self.table_name,
165
- Item=self._serialize_item(item),
166
- )
167
-
168
- return PersistedUser(
169
- id=user.identifier,
170
- identifier=user.identifier,
171
- createdAt=ts,
172
- metadata=metadata,
173
- )
174
-
175
- async def delete_feedback(self, feedback_id: str) -> bool:
176
- _logger.info("DynamoDB: delete_feedback feedback_id=%s", feedback_id)
177
-
178
- # feedback id = THREAD#{thread_id}::STEP#{step_id}
179
- thread_id, step_id = feedback_id.split("::")
180
- thread_id = thread_id.strip("THREAD#")
181
- step_id = step_id.strip("STEP#")
182
-
183
- self.client.update_item(
184
- TableName=self.table_name,
185
- Key={
186
- "PK": {"S": f"THREAD#{thread_id}"},
187
- "SK": {"S": f"STEP#{step_id}"},
188
- },
189
- UpdateExpression="REMOVE #feedback",
190
- ExpressionAttributeNames={"#feedback": "feedback"},
191
- )
192
-
193
- return True
194
-
195
- async def upsert_feedback(self, feedback: Feedback) -> str:
196
- _logger.info(
197
- "DynamoDB: upsert_feedback thread=%s step=%s value=%s",
198
- feedback.threadId,
199
- feedback.forId,
200
- feedback.value,
201
- )
202
-
203
- if not feedback.forId:
204
- raise ValueError(
205
- "DynamoDB data layer expects value for feedback.threadId got None"
206
- )
207
-
208
- feedback.id = f"THREAD#{feedback.threadId}::STEP#{feedback.forId}"
209
- serialized_feedback = self._type_serializer.serialize(asdict(feedback))
210
-
211
- self.client.update_item(
212
- TableName=self.table_name,
213
- Key={
214
- "PK": {"S": f"THREAD#{feedback.threadId}"},
215
- "SK": {"S": f"STEP#{feedback.forId}"},
216
- },
217
- UpdateExpression="SET #feedback = :feedback",
218
- ExpressionAttributeNames={"#feedback": "feedback"},
219
- ExpressionAttributeValues={":feedback": serialized_feedback},
220
- )
221
-
222
- return feedback.id
223
-
224
- @queue_until_user_message()
225
- async def create_element(self, element: "Element"):
226
- _logger.info(
227
- "DynamoDB: create_element thread=%s step=%s type=%s",
228
- element.thread_id,
229
- element.for_id,
230
- element.type,
231
- )
232
- _logger.debug("DynamoDB: create_element: %s", element.to_dict())
233
-
234
- if not element.for_id:
235
- return
236
-
237
- if not self.storage_provider:
238
- _logger.warning(
239
- "DynamoDB: create_element error. No storage_provider is configured!"
240
- )
241
- return
242
-
243
- content: Optional[Union[bytes, str]] = None
244
-
245
- if element.content:
246
- content = element.content
247
-
248
- elif element.path:
249
- _logger.debug("DynamoDB: create_element reading file %s", element.path)
250
- async with aiofiles.open(element.path, "rb") as f:
251
- content = await f.read()
252
-
253
- elif element.url:
254
- _logger.debug("DynamoDB: create_element http %s", element.url)
255
- async with aiohttp.ClientSession() as session:
256
- async with session.get(element.url) as response:
257
- if response.status == 200:
258
- content = await response.read()
259
- else:
260
- raise ValueError(
261
- f"Failed to read content from {element.url} status {response.status}",
262
- )
263
-
264
- else:
265
- raise ValueError("Element url, path or content must be provided")
266
-
267
- if content is None:
268
- raise ValueError("Content is None, cannot upload file")
269
-
270
- if not element.mime:
271
- element.mime = "application/octet-stream"
272
-
273
- context_user = self.context.session.user
274
- user_folder = getattr(context_user, "id", "unknown")
275
- file_object_key = f"{user_folder}/{element.thread_id}/{element.id}"
276
-
277
- uploaded_file = await self.storage_provider.upload_file(
278
- object_key=file_object_key,
279
- data=content,
280
- mime=element.mime,
281
- overwrite=True,
282
- )
283
- if not uploaded_file:
284
- raise ValueError(
285
- "DynamoDB Error: create_element, Failed to persist data in storage_provider",
286
- )
287
-
288
- element_dict: Dict[str, Any] = element.to_dict() # type: ignore
289
- element_dict.update(
290
- {
291
- "PK": f"THREAD#{element.thread_id}",
292
- "SK": f"ELEMENT#{element.id}",
293
- "url": uploaded_file.get("url"),
294
- "objectKey": uploaded_file.get("object_key"),
295
- }
296
- )
297
-
298
- self.client.put_item(
299
- TableName=self.table_name,
300
- Item=self._serialize_item(element_dict),
301
- )
302
-
303
- async def get_element(
304
- self, thread_id: str, element_id: str
305
- ) -> Optional["ElementDict"]:
306
- _logger.info(
307
- "DynamoDB: get_element thread=%s element=%s", thread_id, element_id
308
- )
309
-
310
- response = self.client.get_item(
311
- TableName=self.table_name,
312
- Key={
313
- "PK": {"S": f"THREAD#{thread_id}"},
314
- "SK": {"S": f"ELEMENT#{element_id}"},
315
- },
316
- )
317
-
318
- if "Item" not in response:
319
- return None
320
-
321
- return self._deserialize_item(response["Item"]) # type: ignore
322
-
323
- @queue_until_user_message()
324
- async def delete_element(self, element_id: str, thread_id: Optional[str] = None):
325
- thread_id = self.context.session.thread_id
326
- _logger.info(
327
- "DynamoDB: delete_element thread=%s element=%s", thread_id, element_id
328
- )
329
-
330
- self.client.delete_item(
331
- TableName=self.table_name,
332
- Key={
333
- "PK": {"S": f"THREAD#{thread_id}"},
334
- "SK": {"S": f"ELEMENT#{element_id}"},
335
- },
336
- )
337
-
338
- @queue_until_user_message()
339
- async def create_step(self, step_dict: "StepDict"):
340
- _logger.info(
341
- "DynamoDB: create_step thread=%s step=%s",
342
- step_dict.get("threadId"),
343
- step_dict.get("id"),
344
- )
345
- _logger.debug("DynamoDB: create_step: %s", step_dict)
346
-
347
- item = dict(step_dict)
348
- item.update(
349
- {
350
- # ignore type, dynamo needs these so we want to fail if not set
351
- "PK": f"THREAD#{step_dict['threadId']}", # type: ignore
352
- "SK": f"STEP#{step_dict['id']}", # type: ignore
353
- }
354
- )
355
-
356
- self.client.put_item(
357
- TableName=self.table_name,
358
- Item=self._serialize_item(item),
359
- )
360
-
361
- @queue_until_user_message()
362
- async def update_step(self, step_dict: "StepDict"):
363
- _logger.info(
364
- "DynamoDB: update_step thread=%s step=%s",
365
- step_dict.get("threadId"),
366
- step_dict.get("id"),
367
- )
368
- _logger.debug("DynamoDB: update_step: %s", step_dict)
369
-
370
- self._update_item(
371
- key={
372
- # ignore type, dynamo needs these so we want to fail if not set
373
- "PK": f"THREAD#{step_dict['threadId']}", # type: ignore
374
- "SK": f"STEP#{step_dict['id']}", # type: ignore
375
- },
376
- updates=step_dict, # type: ignore
377
- )
378
-
379
- @queue_until_user_message()
380
- async def delete_step(self, step_id: str):
381
- thread_id = self.context.session.thread_id
382
- _logger.info("DynamoDB: delete_feedback thread=%s step=%s", thread_id, step_id)
383
-
384
- self.client.delete_item(
385
- TableName=self.table_name,
386
- Key={
387
- "PK": {"S": f"THREAD#{thread_id}"},
388
- "SK": {"S": f"STEP#{step_id}"},
389
- },
390
- )
391
-
392
- async def get_thread_author(self, thread_id: str) -> str:
393
- _logger.info("DynamoDB: get_thread_author thread=%s", thread_id)
394
-
395
- response = self.client.get_item(
396
- TableName=self.table_name,
397
- Key={
398
- "PK": {"S": f"THREAD#{thread_id}"},
399
- "SK": {"S": "THREAD"},
400
- },
401
- ProjectionExpression="userId",
402
- )
403
-
404
- if "Item" not in response:
405
- raise ValueError(f"Author not found for thread_id {thread_id}")
406
-
407
- item = self._deserialize_item(response["Item"])
408
- return item["userId"]
409
-
410
- async def delete_thread(self, thread_id: str):
411
- _logger.info("DynamoDB: delete_thread thread=%s", thread_id)
412
-
413
- thread = await self.get_thread(thread_id)
414
- if not thread:
415
- return
416
-
417
- items: List[Any] = thread["steps"]
418
- if thread["elements"]:
419
- items.extend(thread["elements"])
420
-
421
- delete_requests = []
422
- for item in items:
423
- key = self._serialize_item({"PK": item["PK"], "SK": item["SK"]})
424
- req = {"DeleteRequest": {"Key": key}}
425
- delete_requests.append(req)
426
-
427
- BATCH_ITEM_SIZE = 25 # pylint: disable=invalid-name
428
- for i in range(0, len(delete_requests), BATCH_ITEM_SIZE):
429
- chunk = delete_requests[i : i + BATCH_ITEM_SIZE]
430
- response = self.client.batch_write_item(
431
- RequestItems={
432
- self.table_name: chunk, # type: ignore
433
- }
434
- )
435
-
436
- backoff_time = 1
437
- while response.get("UnprocessedItems"):
438
- backoff_time *= 2
439
- # Cap the backoff time at 32 seconds & add jitter
440
- delay = min(backoff_time, 32) + random.uniform(0, 1)
441
- await asyncio.sleep(delay)
442
-
443
- response = self.client.batch_write_item(
444
- RequestItems=response["UnprocessedItems"]
445
- )
446
-
447
- self.client.delete_item(
448
- TableName=self.table_name,
449
- Key={
450
- "PK": {"S": f"THREAD#{thread_id}"},
451
- "SK": {"S": "THREAD"},
452
- },
453
- )
454
-
455
- async def list_threads(
456
- self, pagination: "Pagination", filters: "ThreadFilter"
457
- ) -> "PaginatedResponse[ThreadDict]":
458
- _logger.info("DynamoDB: list_threads filters.userId=%s", filters.userId)
459
-
460
- if filters.feedback:
461
- _logger.warning("DynamoDB: filters on feedback not supported")
462
-
463
- paginated_response: PaginatedResponse[ThreadDict] = PaginatedResponse(
464
- data=[],
465
- pageInfo=PageInfo(
466
- hasNextPage=False, startCursor=pagination.cursor, endCursor=None
467
- ),
468
- )
469
-
470
- query_args: Dict[str, Any] = {
471
- "TableName": self.table_name,
472
- "IndexName": "UserThread",
473
- "ScanIndexForward": False,
474
- "Limit": self.user_thread_limit,
475
- "KeyConditionExpression": "#UserThreadPK = :pk",
476
- "ExpressionAttributeNames": {
477
- "#UserThreadPK": "UserThreadPK",
478
- },
479
- "ExpressionAttributeValues": {
480
- ":pk": {"S": f"USER#{filters.userId}"},
481
- },
482
- }
483
-
484
- if pagination.cursor:
485
- query_args["ExclusiveStartKey"] = json.loads(pagination.cursor)
486
-
487
- if filters.search:
488
- query_args["FilterExpression"] = "contains(#name, :search)"
489
- query_args["ExpressionAttributeNames"]["#name"] = "name"
490
- query_args["ExpressionAttributeValues"][":search"] = {"S": filters.search}
491
-
492
- response = self.client.query(**query_args) # type: ignore
493
-
494
- if "LastEvaluatedKey" in response:
495
- paginated_response.pageInfo.hasNextPage = True
496
- paginated_response.pageInfo.endCursor = json.dumps(
497
- response["LastEvaluatedKey"]
498
- )
499
-
500
- for item in response["Items"]:
501
- deserialized_item: Dict[str, Any] = self._deserialize_item(item)
502
- thread = ThreadDict( # type: ignore
503
- id=deserialized_item["PK"].strip("THREAD#"),
504
- createdAt=deserialized_item["UserThreadSK"].strip("TS#"),
505
- name=deserialized_item["name"],
506
- )
507
- paginated_response.data.append(thread)
508
-
509
- return paginated_response
510
-
511
- async def get_thread(self, thread_id: str) -> "Optional[ThreadDict]":
512
- _logger.info("DynamoDB: get_thread thread=%s", thread_id)
513
-
514
- # Get all thread records
515
- thread_items: List[Any] = []
516
-
517
- cursor: Dict[str, Any] = {}
518
- while True:
519
- response = self.client.query(
520
- TableName=self.table_name,
521
- KeyConditionExpression="#pk = :pk",
522
- ExpressionAttributeNames={"#pk": "PK"},
523
- ExpressionAttributeValues={":pk": {"S": f"THREAD#{thread_id}"}},
524
- **cursor,
525
- )
526
-
527
- deserialized_items = map(self._deserialize_item, response["Items"])
528
- thread_items.extend(deserialized_items)
529
-
530
- if "LastEvaluatedKey" not in response:
531
- break
532
- cursor["ExclusiveStartKey"] = response["LastEvaluatedKey"]
533
-
534
- if len(thread_items) == 0:
535
- return None
536
-
537
- # process accordingly
538
- thread_dict: Optional[ThreadDict] = None
539
- steps = []
540
- elements = []
541
-
542
- for item in thread_items:
543
- if item["SK"] == "THREAD":
544
- thread_dict = item
545
-
546
- elif item["SK"].startswith("ELEMENT"):
547
- if self.storage_provider is not None:
548
- item["url"] = await self.storage_provider.get_read_url(
549
- object_key=item["objectKey"],
550
- )
551
- elements.append(item)
552
-
553
- elif item["SK"].startswith("STEP"):
554
- if "feedback" in item: # Decimal is not json serializable
555
- item["feedback"]["value"] = int(item["feedback"]["value"])
556
- steps.append(item)
557
-
558
- if not thread_dict:
559
- if len(thread_items) > 0:
560
- _logger.warning(
561
- "DynamoDB: found orphaned items for thread=%s", thread_id
562
- )
563
- return None
564
-
565
- steps.sort(key=lambda i: i["createdAt"])
566
- thread_dict.update(
567
- {
568
- "steps": steps,
569
- "elements": elements,
570
- }
571
- )
572
-
573
- return thread_dict
574
-
575
- async def update_thread(
576
- self,
577
- thread_id: str,
578
- name: Optional[str] = None,
579
- user_id: Optional[str] = None,
580
- metadata: Optional[Dict] = None,
581
- tags: Optional[List[str]] = None,
582
- ):
583
- _logger.info("DynamoDB: update_thread thread=%s userId=%s", thread_id, user_id)
584
- _logger.debug(
585
- "DynamoDB: update_thread name=%s tags=%s metadata=%s", name, tags, metadata
586
- )
587
-
588
- ts = self._get_current_timestamp()
589
-
590
- item = {
591
- # GSI: UserThread
592
- "UserThreadSK": f"TS#{ts}",
593
- #
594
- "id": thread_id,
595
- "createdAt": ts,
596
- "name": name,
597
- "userId": user_id,
598
- "userIdentifier": user_id,
599
- "tags": tags,
600
- "metadata": metadata,
601
- }
602
-
603
- if user_id:
604
- # user_id may be None on subsequent calls, don't update UserThreadPK to "USER#{None}"
605
- item["UserThreadPK"] = f"USER#{user_id}"
606
-
607
- self._update_item(
608
- key={
609
- "PK": f"THREAD#{thread_id}",
610
- "SK": "THREAD",
611
- },
612
- updates=item,
613
- )
614
-
615
- async def build_debug_url(self) -> str:
616
- return ""