letta-nightly 0.6.1.dev20241206104246__py3-none-any.whl → 0.6.1.dev20241208104134__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 letta-nightly might be problematic. Click here for more details.

Files changed (49) hide show
  1. letta/agent.py +68 -76
  2. letta/agent_store/db.py +1 -77
  3. letta/agent_store/storage.py +0 -5
  4. letta/cli/cli.py +1 -4
  5. letta/client/client.py +11 -14
  6. letta/constants.py +1 -0
  7. letta/functions/function_sets/base.py +33 -5
  8. letta/functions/helpers.py +3 -3
  9. letta/llm_api/openai.py +0 -1
  10. letta/local_llm/llm_chat_completion_wrappers/chatml.py +13 -1
  11. letta/main.py +2 -2
  12. letta/memory.py +4 -82
  13. letta/metadata.py +0 -35
  14. letta/o1_agent.py +7 -2
  15. letta/offline_memory_agent.py +6 -0
  16. letta/orm/__init__.py +2 -0
  17. letta/orm/file.py +1 -1
  18. letta/orm/message.py +64 -0
  19. letta/orm/mixins.py +16 -0
  20. letta/orm/organization.py +1 -0
  21. letta/orm/sqlalchemy_base.py +118 -26
  22. letta/schemas/letta_base.py +7 -6
  23. letta/schemas/message.py +6 -12
  24. letta/schemas/tool.py +18 -11
  25. letta/server/rest_api/app.py +2 -3
  26. letta/server/rest_api/routers/v1/agents.py +7 -6
  27. letta/server/rest_api/routers/v1/blocks.py +2 -2
  28. letta/server/rest_api/routers/v1/tools.py +26 -4
  29. letta/server/rest_api/utils.py +3 -1
  30. letta/server/server.py +67 -62
  31. letta/server/static_files/assets/index-43ab4d62.css +1 -0
  32. letta/server/static_files/assets/index-4848e3d7.js +40 -0
  33. letta/server/static_files/index.html +2 -2
  34. letta/services/block_manager.py +1 -1
  35. letta/services/message_manager.py +194 -0
  36. letta/services/organization_manager.py +6 -9
  37. letta/services/sandbox_config_manager.py +16 -1
  38. letta/services/source_manager.py +1 -1
  39. letta/services/tool_manager.py +2 -4
  40. letta/services/user_manager.py +1 -1
  41. {letta_nightly-0.6.1.dev20241206104246.dist-info → letta_nightly-0.6.1.dev20241208104134.dist-info}/METADATA +2 -2
  42. {letta_nightly-0.6.1.dev20241206104246.dist-info → letta_nightly-0.6.1.dev20241208104134.dist-info}/RECORD +45 -45
  43. letta/agent_store/lancedb.py +0 -177
  44. letta/persistence_manager.py +0 -149
  45. letta/server/static_files/assets/index-1b5d1a41.js +0 -271
  46. letta/server/static_files/assets/index-56a3f8c6.css +0 -1
  47. {letta_nightly-0.6.1.dev20241206104246.dist-info → letta_nightly-0.6.1.dev20241208104134.dist-info}/LICENSE +0 -0
  48. {letta_nightly-0.6.1.dev20241206104246.dist-info → letta_nightly-0.6.1.dev20241208104134.dist-info}/WHEEL +0 -0
  49. {letta_nightly-0.6.1.dev20241206104246.dist-info → letta_nightly-0.6.1.dev20241208104134.dist-info}/entry_points.txt +0 -0
letta/server/server.py CHANGED
@@ -67,7 +67,7 @@ from letta.schemas.memory import (
67
67
  Memory,
68
68
  RecallMemorySummary,
69
69
  )
70
- from letta.schemas.message import Message, MessageCreate, MessageRole, UpdateMessage
70
+ from letta.schemas.message import Message, MessageCreate, MessageRole, MessageUpdate
71
71
  from letta.schemas.organization import Organization
72
72
  from letta.schemas.passage import Passage
73
73
  from letta.schemas.source import Source
@@ -77,14 +77,15 @@ from letta.schemas.user import User
77
77
  from letta.services.agents_tags_manager import AgentsTagsManager
78
78
  from letta.services.block_manager import BlockManager
79
79
  from letta.services.blocks_agents_manager import BlocksAgentsManager
80
- from letta.services.tools_agents_manager import ToolsAgentsManager
81
80
  from letta.services.job_manager import JobManager
81
+ from letta.services.message_manager import MessageManager
82
82
  from letta.services.organization_manager import OrganizationManager
83
83
  from letta.services.per_agent_lock_manager import PerAgentLockManager
84
84
  from letta.services.sandbox_config_manager import SandboxConfigManager
85
85
  from letta.services.source_manager import SourceManager
86
86
  from letta.services.tool_execution_sandbox import ToolExecutionSandbox
87
87
  from letta.services.tool_manager import ToolManager
88
+ from letta.services.tools_agents_manager import ToolsAgentsManager
88
89
  from letta.services.user_manager import UserManager
89
90
  from letta.utils import create_random_username, get_utc_time, json_dumps, json_loads
90
91
 
@@ -235,11 +236,6 @@ class SyncServer(Server):
235
236
  # Locks
236
237
  self.send_message_lock = Lock()
237
238
 
238
- # Composio
239
- self.composio_client = None
240
- if tool_settings.composio_api_key:
241
- self.composio_client = Composio(api_key=tool_settings.composio_api_key)
242
-
243
239
  # Initialize the metadata store
244
240
  config = LettaConfig.load()
245
241
  if settings.letta_pg_uri_no_default:
@@ -260,6 +256,7 @@ class SyncServer(Server):
260
256
  self.agents_tags_manager = AgentsTagsManager()
261
257
  self.sandbox_config_manager = SandboxConfigManager(tool_settings)
262
258
  self.blocks_agents_manager = BlocksAgentsManager()
259
+ self.message_manager = MessageManager()
263
260
  self.tools_agents_manager = ToolsAgentsManager()
264
261
  self.job_manager = JobManager()
265
262
 
@@ -414,7 +411,7 @@ class SyncServer(Server):
414
411
  agent = OfflineMemoryAgent(agent_state=agent_state, interface=interface, user=actor)
415
412
  elif agent_state.agent_type == AgentType.chat_only_agent:
416
413
  agent = ChatOnlyAgent(agent_state=agent_state, interface=interface, user=actor)
417
- else:
414
+ else:
418
415
  raise ValueError(f"Invalid agent type {agent_state.agent_type}")
419
416
 
420
417
  # Rebuild the system prompt - may be linked to new blocks now
@@ -422,7 +419,7 @@ class SyncServer(Server):
422
419
 
423
420
  # Persist to agent
424
421
  save_agent(agent, self.ms)
425
- return agent
422
+ return agent
426
423
 
427
424
  def _step(
428
425
  self,
@@ -518,12 +515,7 @@ class SyncServer(Server):
518
515
  letta_agent.interface.print_messages_raw(letta_agent.messages)
519
516
 
520
517
  elif command.lower() == "memory":
521
- ret_str = (
522
- f"\nDumping memory contents:\n"
523
- + f"\n{str(letta_agent.agent_state.memory)}"
524
- + f"\n{str(letta_agent.persistence_manager.archival_memory)}"
525
- + f"\n{str(letta_agent.persistence_manager.recall_memory)}"
526
- )
518
+ ret_str = f"\nDumping memory contents:\n" + f"\n{str(letta_agent.agent_state.memory)}" + f"\n{str(letta_agent.archival_memory)}"
527
519
  return ret_str
528
520
 
529
521
  elif command.lower() == "pop" or command.lower().startswith("pop "):
@@ -625,7 +617,6 @@ class SyncServer(Server):
625
617
  # Convert to a Message object
626
618
  if timestamp:
627
619
  message = Message(
628
- user_id=user_id,
629
620
  agent_id=agent_id,
630
621
  role="user",
631
622
  text=packaged_user_message,
@@ -633,7 +624,6 @@ class SyncServer(Server):
633
624
  )
634
625
  else:
635
626
  message = Message(
636
- user_id=user_id,
637
627
  agent_id=agent_id,
638
628
  role="user",
639
629
  text=packaged_user_message,
@@ -672,7 +662,6 @@ class SyncServer(Server):
672
662
 
673
663
  if timestamp:
674
664
  message = Message(
675
- user_id=user_id,
676
665
  agent_id=agent_id,
677
666
  role="system",
678
667
  text=packaged_system_message,
@@ -680,7 +669,6 @@ class SyncServer(Server):
680
669
  )
681
670
  else:
682
671
  message = Message(
683
- user_id=user_id,
684
672
  agent_id=agent_id,
685
673
  role="system",
686
674
  text=packaged_system_message,
@@ -743,7 +731,6 @@ class SyncServer(Server):
743
731
  # Create the Message object
744
732
  message_objects.append(
745
733
  Message(
746
- user_id=user_id,
747
734
  agent_id=agent_id,
748
735
  role=message.role,
749
736
  text=message.text,
@@ -876,7 +863,7 @@ class SyncServer(Server):
876
863
  else:
877
864
  raise ValueError(f"Invalid message role: {message.role}")
878
865
 
879
- init_messages.append(Message(role=message.role, text=packed_message, user_id=user_id, agent_id=agent_state.id))
866
+ init_messages.append(Message(role=message.role, text=packed_message, agent_id=agent_state.id))
880
867
  # init_messages = [Message.dict_to_message(user_id=user_id, agent_id=agent_state.id, openai_message_dict=message.model_dump()) for message in request.initial_message_sequence]
881
868
  else:
882
869
  init_messages = None
@@ -1160,11 +1147,11 @@ class SyncServer(Server):
1160
1147
 
1161
1148
  def get_archival_memory_summary(self, agent_id: str) -> ArchivalMemorySummary:
1162
1149
  agent = self.load_agent(agent_id=agent_id)
1163
- return ArchivalMemorySummary(size=len(agent.persistence_manager.archival_memory))
1150
+ return ArchivalMemorySummary(size=len(agent.archival_memory))
1164
1151
 
1165
1152
  def get_recall_memory_summary(self, agent_id: str) -> RecallMemorySummary:
1166
1153
  agent = self.load_agent(agent_id=agent_id)
1167
- return RecallMemorySummary(size=len(agent.persistence_manager.recall_memory))
1154
+ return RecallMemorySummary(size=len(agent.message_manager))
1168
1155
 
1169
1156
  def get_in_context_message_ids(self, agent_id: str) -> List[str]:
1170
1157
  """Get the message ids of the in-context messages in the agent's memory"""
@@ -1182,7 +1169,7 @@ class SyncServer(Server):
1182
1169
  """Get a single message from the agent's memory"""
1183
1170
  # Get the agent object (loaded in memory)
1184
1171
  agent = self.load_agent(agent_id=agent_id)
1185
- message = agent.persistence_manager.recall_memory.storage.get(id=message_id)
1172
+ message = agent.message_manager.get_message_by_id(id=message_id, actor=self.default_user)
1186
1173
  return message
1187
1174
 
1188
1175
  def get_agent_messages(
@@ -1213,14 +1200,16 @@ class SyncServer(Server):
1213
1200
 
1214
1201
  else:
1215
1202
  # need to access persistence manager for additional messages
1216
- db_iterator = letta_agent.persistence_manager.recall_memory.storage.get_all_paginated(page_size=count, offset=start)
1217
1203
 
1218
- # get a single page of messages
1219
- # TODO: handle stop iteration
1220
- page = next(db_iterator, [])
1204
+ # get messages using message manager
1205
+ page = letta_agent.message_manager.list_user_messages_for_agent(
1206
+ agent_id=agent_id,
1207
+ actor=self.default_user,
1208
+ cursor=start,
1209
+ limit=count,
1210
+ )
1221
1211
 
1222
- # return messages in reverse chronological order
1223
- messages = sorted(page, key=lambda x: x.created_at, reverse=True)
1212
+ messages = page
1224
1213
  assert all(isinstance(m, Message) for m in messages)
1225
1214
 
1226
1215
  ## Convert to json
@@ -1243,7 +1232,7 @@ class SyncServer(Server):
1243
1232
  letta_agent = self.load_agent(agent_id=agent_id)
1244
1233
 
1245
1234
  # iterate over records
1246
- db_iterator = letta_agent.persistence_manager.archival_memory.storage.get_all_paginated(page_size=count, offset=start)
1235
+ db_iterator = letta_agent.archival_memory.storage.get_all_paginated(page_size=count, offset=start)
1247
1236
 
1248
1237
  # get a single page of messages
1249
1238
  page = next(db_iterator, [])
@@ -1268,7 +1257,7 @@ class SyncServer(Server):
1268
1257
  letta_agent = self.load_agent(agent_id=agent_id)
1269
1258
 
1270
1259
  # iterate over recorde
1271
- cursor, records = letta_agent.persistence_manager.archival_memory.storage.get_all_cursor(
1260
+ cursor, records = letta_agent.archival_memory.storage.get_all_cursor(
1272
1261
  after=after, before=before, limit=limit, order_by=order_by, reverse=reverse
1273
1262
  )
1274
1263
  return records
@@ -1283,14 +1272,14 @@ class SyncServer(Server):
1283
1272
  letta_agent = self.load_agent(agent_id=agent_id)
1284
1273
 
1285
1274
  # Insert into archival memory
1286
- passage_ids = letta_agent.persistence_manager.archival_memory.insert(memory_string=memory_contents, return_ids=True)
1275
+ passage_ids = letta_agent.archival_memory.insert(memory_string=memory_contents, return_ids=True)
1287
1276
 
1288
1277
  # Update the agent
1289
1278
  # TODO: should this update the system prompt?
1290
1279
  save_agent(letta_agent, self.ms)
1291
1280
 
1292
1281
  # TODO: this is gross, fix
1293
- return [letta_agent.persistence_manager.archival_memory.storage.get(id=passage_id) for passage_id in passage_ids]
1282
+ return [letta_agent.archival_memory.storage.get(id=passage_id) for passage_id in passage_ids]
1294
1283
 
1295
1284
  def delete_archival_memory(self, user_id: str, agent_id: str, memory_id: str):
1296
1285
  if self.user_manager.get_user_by_id(user_id=user_id) is None:
@@ -1305,7 +1294,7 @@ class SyncServer(Server):
1305
1294
 
1306
1295
  # Delete by ID
1307
1296
  # TODO check if it exists first, and throw error if not
1308
- letta_agent.persistence_manager.archival_memory.storage.delete({"id": memory_id})
1297
+ letta_agent.archival_memory.storage.delete({"id": memory_id})
1309
1298
 
1310
1299
  # TODO: return archival memory
1311
1300
 
@@ -1313,17 +1302,15 @@ class SyncServer(Server):
1313
1302
  self,
1314
1303
  user_id: str,
1315
1304
  agent_id: str,
1316
- after: Optional[str] = None,
1317
- before: Optional[str] = None,
1305
+ cursor: Optional[str] = None,
1318
1306
  limit: Optional[int] = 100,
1319
- order_by: Optional[str] = "created_at",
1320
- order: Optional[str] = "asc",
1321
1307
  reverse: Optional[bool] = False,
1322
1308
  return_message_object: bool = True,
1323
1309
  assistant_message_tool_name: str = constants.DEFAULT_MESSAGE_TOOL,
1324
1310
  assistant_message_tool_kwarg: str = constants.DEFAULT_MESSAGE_TOOL_KWARG,
1325
1311
  ) -> Union[List[Message], List[LettaMessage]]:
1326
- if self.user_manager.get_user_by_id(user_id=user_id) is None:
1312
+ actor = self.user_manager.get_user_by_id(user_id=user_id)
1313
+ if actor is None:
1327
1314
  raise ValueError(f"User user_id={user_id} does not exist")
1328
1315
  if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
1329
1316
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
@@ -1332,8 +1319,12 @@ class SyncServer(Server):
1332
1319
  letta_agent = self.load_agent(agent_id=agent_id)
1333
1320
 
1334
1321
  # iterate over records
1335
- cursor, records = letta_agent.persistence_manager.recall_memory.storage.get_all_cursor(
1336
- after=after, before=before, limit=limit, order_by=order_by, reverse=reverse
1322
+ # TODO: Check "order_by", "order"
1323
+ records = letta_agent.message_manager.list_messages_for_agent(
1324
+ agent_id=agent_id,
1325
+ actor=actor,
1326
+ cursor=cursor,
1327
+ limit=limit,
1337
1328
  )
1338
1329
 
1339
1330
  assert all(isinstance(m, Message) for m in records)
@@ -1353,7 +1344,7 @@ class SyncServer(Server):
1353
1344
  records = records[::-1]
1354
1345
 
1355
1346
  return records
1356
-
1347
+
1357
1348
  def get_server_config(self, include_defaults: bool = False) -> dict:
1358
1349
  """Return the base config"""
1359
1350
 
@@ -1425,19 +1416,25 @@ class SyncServer(Server):
1425
1416
  self.agents_tags_manager.delete_all_tags_from_agent(agent_id=agent_id, actor=actor)
1426
1417
  self.blocks_agents_manager.remove_all_agent_blocks(agent_id=agent_id)
1427
1418
 
1428
- if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
1429
- raise ValueError(f"Agent agent_id={agent_id} does not exist")
1430
-
1431
1419
  # Verify that the agent exists and belongs to the org of the user
1432
1420
  agent_state = self.ms.get_agent(agent_id=agent_id, user_id=user_id)
1433
- if not agent_state:
1421
+ if agent_state is None:
1434
1422
  raise ValueError(f"Could not find agent_id={agent_id} under user_id={user_id}")
1435
1423
 
1436
- agent_state_user = self.user_manager.get_user_by_id(user_id=agent_state.user_id)
1437
- if agent_state_user.organization_id != actor.organization_id:
1438
- raise ValueError(
1439
- f"Could not authorize agent_id={agent_id} with user_id={user_id} because of differing organizations; agent_id was created in {agent_state_user.organization_id} while user belongs to {actor.organization_id}. How did they get the agent id?"
1440
- )
1424
+ # TODO: REMOVE THIS ONCE WE MIGRATE AGENTMODEL TO ORM MODEL
1425
+ messages = self.message_manager.list_messages_for_agent(agent_id=agent_state.id)
1426
+ for message in messages:
1427
+ self.message_manager.delete_message_by_id(message.id, actor=actor)
1428
+
1429
+ # TODO: REMOVE THIS ONCE WE MIGRATE AGENTMODEL TO ORM
1430
+ try:
1431
+ agent_state_user = self.user_manager.get_user_by_id(user_id=agent_state.user_id)
1432
+ if agent_state_user.organization_id != actor.organization_id:
1433
+ raise ValueError(
1434
+ f"Could not authorize agent_id={agent_id} with user_id={user_id} because of differing organizations; agent_id was created in {agent_state_user.organization_id} while user belongs to {actor.organization_id}. How did they get the agent id?"
1435
+ )
1436
+ except NoResultFound:
1437
+ logger.error(f"Agent with id {agent_state.id} has nonexistent user {agent_state.user_id}")
1441
1438
 
1442
1439
  # First, if the agent is in the in-memory cache we should remove it
1443
1440
  # List of {'user_id': user_id, 'agent_id': agent_id, 'agent': agent_obj} dicts
@@ -1582,7 +1579,7 @@ class SyncServer(Server):
1582
1579
 
1583
1580
  # delete all Passage objects with source_id==source_id from agent's archival memory
1584
1581
  agent = self.load_agent(agent_id=agent_id)
1585
- archival_memory = agent.persistence_manager.archival_memory
1582
+ archival_memory = agent.archival_memory
1586
1583
  archival_memory.storage.delete({"source_id": source_id})
1587
1584
 
1588
1585
  # delete agent-source mapping
@@ -1661,16 +1658,16 @@ class SyncServer(Server):
1661
1658
  """Get a single message from the agent's memory"""
1662
1659
  # Get the agent object (loaded in memory)
1663
1660
  letta_agent = self.load_agent(agent_id=agent_id)
1664
- message = letta_agent.persistence_manager.recall_memory.storage.get(id=message_id)
1661
+ message = letta_agent.message_manager.get_message_by_id(id=message_id)
1665
1662
  save_agent(letta_agent, self.ms)
1666
1663
  return message
1667
1664
 
1668
- def update_agent_message(self, agent_id: str, request: UpdateMessage) -> Message:
1665
+ def update_agent_message(self, agent_id: str, message_id: str, request: MessageUpdate) -> Message:
1669
1666
  """Update the details of a message associated with an agent"""
1670
1667
 
1671
1668
  # Get the current message
1672
1669
  letta_agent = self.load_agent(agent_id=agent_id)
1673
- response = letta_agent.update_message(request=request)
1670
+ response = letta_agent.update_message(message_id=message_id, request=request)
1674
1671
  save_agent(letta_agent, self.ms)
1675
1672
  return response
1676
1673
 
@@ -1705,7 +1702,7 @@ class SyncServer(Server):
1705
1702
 
1706
1703
  try:
1707
1704
  return self.user_manager.get_user_by_id(user_id=user_id)
1708
- except ValueError:
1705
+ except NoResultFound:
1709
1706
  raise HTTPException(status_code=404, detail=f"User with id {user_id} not found")
1710
1707
 
1711
1708
  def get_organization_or_default(self, org_id: Optional[str]) -> Organization:
@@ -1715,7 +1712,7 @@ class SyncServer(Server):
1715
1712
 
1716
1713
  try:
1717
1714
  return self.organization_manager.get_organization_by_id(org_id=org_id)
1718
- except ValueError:
1715
+ except NoResultFound:
1719
1716
  raise HTTPException(status_code=404, detail=f"Organization with id {org_id} not found")
1720
1717
 
1721
1718
  def list_llm_models(self) -> List[LLMConfig]:
@@ -1897,9 +1894,17 @@ class SyncServer(Server):
1897
1894
  )
1898
1895
 
1899
1896
  # Composio wrappers
1900
- def get_composio_apps(self) -> List["AppModel"]:
1897
+ def get_composio_client(self, api_key: Optional[str] = None):
1898
+ if api_key:
1899
+ return Composio(api_key=api_key)
1900
+ elif tool_settings.composio_api_key:
1901
+ return Composio(api_key=tool_settings.composio_api_key)
1902
+ else:
1903
+ return Composio()
1904
+
1905
+ def get_composio_apps(self, api_key: Optional[str] = None) -> List["AppModel"]:
1901
1906
  """Get a list of all Composio apps with actions"""
1902
- apps = self.composio_client.apps.get()
1907
+ apps = self.get_composio_client(api_key=api_key).apps.get()
1903
1908
  apps_with_actions = []
1904
1909
  for app in apps:
1905
1910
  # A bit of hacky logic until composio patches this
@@ -1908,6 +1913,6 @@ class SyncServer(Server):
1908
1913
 
1909
1914
  return apps_with_actions
1910
1915
 
1911
- def get_composio_actions_from_app_name(self, composio_app_name: str) -> List["ActionModel"]:
1912
- actions = self.composio_client.actions.get(apps=[composio_app_name])
1916
+ def get_composio_actions_from_app_name(self, composio_app_name: str, api_key: Optional[str] = None) -> List["ActionModel"]:
1917
+ actions = self.get_composio_client(api_key=api_key).actions.get(apps=[composio_app_name])
1913
1918
  return actions
@@ -0,0 +1 @@
1
+ *,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}:root{--background: 210, 10%, 92%;--background-lighter: 0, 0%, 100%;--background-darker: 210, 6%, 86%;--foreground: 224 71.4% 4.1%;--card: 0 0% 100%;--card-foreground: 224 71.4% 4.1%;--popover: 0 0% 100%;--popover-foreground: 224 71.4% 4.1%;--primary: 220.9 39.3% 11%;--primary-foreground: 210 20% 98%;--secondary: 240, 92%, 35%;--secondary-foreground: 0, 0%, 100%;--muted: 220 14.3% 95.9%;--muted-foreground: 220 8.9% 46.1%;--accent: 220 14.3% 95.9%;--accent-foreground: 220.9 39.3% 11%;--destructive: 0 84.2% 60.2%;--destructive-foreground: 210 20% 98%;--border: 210, 6%, 86%;--input: 210, 6%, 86%;--ring: 224 71.4% 4.1%;--radius: .5rem}.dark{--background: 224 71.4% 4.1%;--background-lighter: 224 71.4% 4.1%;--background-darker: 224 71.4% 4.1%;--foreground: 210 20% 98%;--card: 224 71.4% 4.1%;--card-foreground: 210 20% 98%;--popover: 224 71.4% 4.1%;--popover-foreground: 210 20% 98%;--primary: 210 20% 98%;--primary-foreground: 220.9 39.3% 11%;--secondary: 10, 100%, 60%;--secondary-foreground: 210 20% 98%;--muted: 215 27.9% 16.9%;--muted-foreground: 217.9 10.6% 64.9%;--accent: 215 27.9% 16.9%;--accent-foreground: 210 20% 98%;--destructive: 0 62.8% 30.6%;--destructive-foreground: 210 20% 98%;--border: 215 27.9% 16.9%;--input: 215 27.9% 16.9%;--ring: 216 12.2% 83.9%}*{border-color:hsl(var(--border))}html{height:100%}body{height:100%;width:100%;background-color:hsl(var(--background));color:hsl(var(--foreground));-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}input::file-selector-button{color:hsl(var(--foreground))}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.fixed{position:fixed}.mt-10{margin-top:2.5rem}.mt-3{margin-top:.75rem}.flex{display:flex}.h-\[100dvh\]{height:100dvh}.h-full{height:100%}.w-\[100dvh\]{width:100dvh}.w-full{width:100%}.max-w-\[600px\]{max-width:600px}.max-w-\[893px\]{max-width:893px}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-center{justify-content:center}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-5{gap:1.25rem}.border{border-width:1px}.bg-black{--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.p-0{padding:0}.p-10{padding:2.5rem}.px-4{padding-left:1rem;padding-right:1rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.font-semibold{font-weight:600}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}@keyframes enter{0%{opacity:var(--tw-enter-opacity, 1);transform:translate3d(var(--tw-enter-translate-x, 0),var(--tw-enter-translate-y, 0),0) scale3d(var(--tw-enter-scale, 1),var(--tw-enter-scale, 1),var(--tw-enter-scale, 1)) rotate(var(--tw-enter-rotate, 0))}}@keyframes exit{to{opacity:var(--tw-exit-opacity, 1);transform:translate3d(var(--tw-exit-translate-x, 0),var(--tw-exit-translate-y, 0),0) scale3d(var(--tw-exit-scale, 1),var(--tw-exit-scale, 1),var(--tw-exit-scale, 1)) rotate(var(--tw-exit-rotate, 0))}}.PopoverContent{width:var(--radix-popover-trigger-width);max-height:var(--radix-popover-content-available-height)}