solace-agent-mesh 0.1.3__py3-none-any.whl → 0.2.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 solace-agent-mesh might be problematic. Click here for more details.

Files changed (73) hide show
  1. solace_agent_mesh/agents/global/actions/plantuml_diagram.py +9 -2
  2. solace_agent_mesh/agents/global/actions/plotly_graph.py +70 -46
  3. solace_agent_mesh/agents/web_request/actions/do_web_request.py +34 -33
  4. solace_agent_mesh/cli/__init__.py +1 -1
  5. solace_agent_mesh/cli/commands/add/copy_from_plugin.py +8 -6
  6. solace_agent_mesh/cli/commands/add/gateway.py +162 -9
  7. solace_agent_mesh/cli/commands/build.py +15 -1
  8. solace_agent_mesh/cli/commands/init/ai_provider_step.py +45 -28
  9. solace_agent_mesh/cli/commands/init/broker_step.py +1 -4
  10. solace_agent_mesh/cli/commands/init/create_config_file_step.py +8 -0
  11. solace_agent_mesh/cli/commands/init/create_other_project_files_step.py +52 -1
  12. solace_agent_mesh/cli/commands/init/init.py +50 -37
  13. solace_agent_mesh/cli/commands/plugin/build.py +60 -9
  14. solace_agent_mesh/cli/commands/run.py +2 -2
  15. solace_agent_mesh/cli/config.py +4 -0
  16. solace_agent_mesh/cli/main.py +14 -8
  17. solace_agent_mesh/cli/utils.py +7 -2
  18. solace_agent_mesh/common/constants.py +10 -0
  19. solace_agent_mesh/common/prompt_templates.py +1 -3
  20. solace_agent_mesh/common/utils.py +104 -30
  21. solace_agent_mesh/config_portal/__init__.py +0 -0
  22. solace_agent_mesh/config_portal/backend/__init__.py +0 -0
  23. solace_agent_mesh/config_portal/backend/common.py +35 -0
  24. solace_agent_mesh/config_portal/backend/server.py +233 -0
  25. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-DRPGOzHj.js +42 -0
  26. solace_agent_mesh/config_portal/frontend/static/client/assets/components-ZIfdTbrV.js +191 -0
  27. solace_agent_mesh/config_portal/frontend/static/client/assets/entry.client-DX1misIU.js +19 -0
  28. solace_agent_mesh/config_portal/frontend/static/client/assets/index-BJHAE5s4.js +17 -0
  29. solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-8147e469.js +1 -0
  30. solace_agent_mesh/config_portal/frontend/static/client/assets/root-DgMDqKDc.js +10 -0
  31. solace_agent_mesh/config_portal/frontend/static/client/assets/root-hhS5izs8.css +1 -0
  32. solace_agent_mesh/config_portal/frontend/static/client/favicon.ico +0 -0
  33. solace_agent_mesh/config_portal/frontend/static/client/index.html +7 -0
  34. solace_agent_mesh/configs/orchestrator.yaml +1 -1
  35. solace_agent_mesh/configs/service_embedding.yaml +1 -1
  36. solace_agent_mesh/configs/service_llm.yaml +1 -1
  37. solace_agent_mesh/gateway/components/gateway_base.py +7 -1
  38. solace_agent_mesh/gateway/components/gateway_input.py +8 -5
  39. solace_agent_mesh/gateway/components/gateway_output.py +12 -3
  40. solace_agent_mesh/orchestrator/components/orchestrator_action_manager_timeout_component.py +4 -0
  41. solace_agent_mesh/orchestrator/components/orchestrator_stimulus_processor_component.py +43 -12
  42. solace_agent_mesh/orchestrator/components/orchestrator_streaming_output_component.py +19 -5
  43. solace_agent_mesh/orchestrator/orchestrator_main.py +11 -5
  44. solace_agent_mesh/orchestrator/orchestrator_prompt.py +184 -60
  45. solace_agent_mesh/services/file_service/file_service.py +5 -0
  46. solace_agent_mesh/services/file_service/file_service_constants.py +1 -1
  47. solace_agent_mesh/services/file_service/file_transformations.py +11 -1
  48. solace_agent_mesh/services/file_service/file_utils.py +2 -0
  49. solace_agent_mesh/services/history_service/history_providers/base_history_provider.py +21 -46
  50. solace_agent_mesh/services/history_service/history_providers/file_history_provider.py +74 -0
  51. solace_agent_mesh/services/history_service/history_providers/index.py +40 -0
  52. solace_agent_mesh/services/history_service/history_providers/memory_history_provider.py +19 -156
  53. solace_agent_mesh/services/history_service/history_providers/mongodb_history_provider.py +66 -0
  54. solace_agent_mesh/services/history_service/history_providers/redis_history_provider.py +40 -140
  55. solace_agent_mesh/services/history_service/history_providers/sql_history_provider.py +93 -0
  56. solace_agent_mesh/services/history_service/history_service.py +315 -41
  57. solace_agent_mesh/services/history_service/long_term_memory/__init__.py +0 -0
  58. solace_agent_mesh/services/history_service/long_term_memory/long_term_memory.py +399 -0
  59. solace_agent_mesh/services/llm_service/components/llm_request_component.py +19 -0
  60. solace_agent_mesh/templates/gateway-config-template.yaml +2 -1
  61. solace_agent_mesh/templates/gateway-default-config.yaml +3 -3
  62. solace_agent_mesh/templates/plugin-gateway-default-config.yaml +29 -0
  63. solace_agent_mesh/templates/rest-api-default-config.yaml +2 -1
  64. solace_agent_mesh/templates/slack-default-config.yaml +1 -1
  65. solace_agent_mesh/templates/solace-agent-mesh-default.yaml +9 -0
  66. solace_agent_mesh/templates/web-default-config.yaml +2 -1
  67. solace_agent_mesh-0.2.1.dist-info/METADATA +172 -0
  68. {solace_agent_mesh-0.1.3.dist-info → solace_agent_mesh-0.2.1.dist-info}/RECORD +71 -52
  69. solace_agent_mesh/common/prompt_templates_unused_delete.py +0 -161
  70. solace_agent_mesh-0.1.3.dist-info/METADATA +0 -208
  71. {solace_agent_mesh-0.1.3.dist-info → solace_agent_mesh-0.2.1.dist-info}/WHEEL +0 -0
  72. {solace_agent_mesh-0.1.3.dist-info → solace_agent_mesh-0.2.1.dist-info}/entry_points.txt +0 -0
  73. {solace_agent_mesh-0.1.3.dist-info → solace_agent_mesh-0.2.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,170 +1,33 @@
1
- import time
1
+ """
2
+ Memory history provider
3
+ """
4
+
2
5
  from .base_history_provider import BaseHistoryProvider
3
6
 
4
7
 
5
8
  class MemoryHistoryProvider(BaseHistoryProvider):
9
+ """
10
+ A history provider that stores history in memory.
11
+ """
12
+
6
13
  def __init__(self, config=None):
7
14
  super().__init__(config)
8
15
  self.history = {}
9
16
 
10
- def store_history(self, session_id: str, role: str, content: str | dict):
11
- """
12
- Store a new entry in the history.
13
-
14
- :param session_id: The session identifier.
15
- :param history_entry: The entry to be stored in the history.
16
- """
17
-
17
+ def store_session(self, session_id, data):
18
18
  if session_id not in self.history:
19
- self.history[session_id] = {
20
- "history": [],
21
- "files": [],
22
- "last_active_time": time.time(),
23
- "num_characters": 0,
24
- "num_turns": 0,
25
- }
26
-
27
- # Check if adding another entry would exceed the max_turns
28
- if self.history[session_id]["num_turns"] == self.max_turns:
29
- # Remove the oldest entry
30
- oldest_entry = self.history[session_id]["history"].pop(0)
31
- # Subtract the length of the oldest entry from the total length
32
- self.history[session_id]["num_characters"] -= len(
33
- str(oldest_entry["content"])
34
- )
35
- self.history[session_id]["num_turns"] -= 1
36
-
37
- if (
38
- self.enforce_alternate_message_roles
39
- and self.history[session_id]["num_turns"] > 0
40
- # Check if the last entry was by the same role
41
- and self.history[session_id]["history"]
42
- and self.history[session_id]["history"][-1]["role"] == role
43
- ):
44
- # Append to last entry
45
- self.history[session_id]["history"][-1]["content"] += content
46
- else:
47
- # Add the new entry
48
- self.history[session_id]["history"].append(
49
- {"role": role, "content": content}
50
- )
51
- # Update the number of turns
52
- self.history[session_id]["num_turns"] += 1
53
-
54
- # Update the length
55
- self.history[session_id]["num_characters"] += len(str(content))
56
- # Update the last active time
57
- self.history[session_id]["last_active_time"] = time.time()
19
+ self.history[session_id] = {}
20
+
21
+ self.history[session_id].update(data)
58
22
 
59
- # Check if we have exceeded max_characters
60
- if self.max_characters:
61
- while (
62
- self.history[session_id]["num_characters"] > self.max_characters
63
- and self.history[session_id]["num_turns"] > 0
64
- ):
65
- # Remove the oldest entry
66
- oldest_entry = self.history[session_id]["history"].pop(0)
67
- # Subtract the length of the oldest entry from the total length
68
- self.history[session_id]["num_characters"] -= len(
69
- str(oldest_entry["content"])
70
- )
71
- self.history[session_id]["num_turns"] -= 1
72
-
73
- def get_history(self, session_id: str):
74
- """
75
- Retrieve the entire history for a session.
76
-
77
- :param session_id: The session identifier.
78
- :return: The complete history for the session.
79
- """
80
- if session_id not in self.history:
81
- return []
82
- return self.history.get(session_id)["history"]
83
-
84
- def store_file(self, session_id: str, file: dict):
85
- """
86
- Store a file in the history.
87
-
88
- :param session_id: The session identifier.
89
- :param file: The file metadata to be stored in the history.
90
- """
23
+ def get_session(self, session_id):
91
24
  if session_id not in self.history:
92
- self.history[session_id] = {
93
- "history": [],
94
- "files": [],
95
- "last_active_time": time.time(),
96
- "num_characters": 0,
97
- "num_turns": 0,
98
- }
99
- self.history[session_id]["last_active_time"] = time.time()
100
-
101
- # Check duplicate
102
- for f in self.history[session_id]["files"]:
103
- if f.get("url") and f.get("url") == file.get("url"):
104
- return
105
-
106
- self.history[session_id]["files"].append(file)
107
-
108
- def get_files(self, session_id: str):
109
- """
110
- Retrieve the files for a session.
111
-
112
- :param session_id: The session identifier.
113
- :return: The files for the session.
114
- """
115
- if session_id not in self.history:
116
- return []
117
- files = []
118
- current_time = time.time()
119
- all_files = self.history.get(session_id)["files"].copy()
120
- for file in all_files:
121
- expiration_timestamp = file.get("expiration_timestamp")
122
- if expiration_timestamp and current_time > expiration_timestamp:
123
- self.history[session_id]["files"].remove(file)
124
- continue
125
- files.append(file)
126
- return files
127
-
128
- def clear_history(self, session_id: str, keep_levels=0, clear_files=True):
129
- """
130
- Clear the history for a session, optionally keeping a specified number of recent entries.
131
-
132
- :param session_id: The session identifier.
133
- :param keep_levels: Number of most recent history entries to keep. Default is 0 (clear all).
134
- :param clear_files: Whether to clear associated files. Default is True.
135
- """
136
- if session_id in self.history:
137
- if keep_levels <= 0:
138
- del self.history[session_id]
139
- else:
140
- self.history[session_id]["history"] = self.history[session_id][
141
- "history"
142
- ][-keep_levels:]
143
- # Recalculate the length and num_turns
144
- self.history[session_id]["num_characters"] = sum(
145
- len(str(entry)) for entry in self.history[session_id]["history"]
146
- )
147
- self.history[session_id]["num_turns"] = len(
148
- self.history[session_id]["history"]
149
- )
150
- if session_id in self.history and clear_files:
151
- self.history[session_id]["files"] = []
152
-
153
- def get_session_meta(self, session_id: str):
154
- """
155
- Retrieve the session metadata.
156
-
157
- :param session_id: The session identifier.
158
- :return: The session metadata.
159
- """
160
- if session_id in self.history:
161
- session = self.history[session_id]
162
- return {
163
- "num_characters": session["num_characters"],
164
- "num_turns": session["num_turns"],
165
- "last_active_time": session["last_active_time"],
166
- }
167
- return None
25
+ return {}
26
+ return self.history[session_id]
168
27
 
169
28
  def get_all_sessions(self) -> list[str]:
170
29
  return list(self.history.keys())
30
+
31
+ def delete_session(self, session_id):
32
+ if session_id in self.history:
33
+ del self.history[session_id]
@@ -0,0 +1,66 @@
1
+ """
2
+ MongoDB-based history provider for storing session data.
3
+ """
4
+ from .base_history_provider import BaseHistoryProvider
5
+
6
+ class MongoDBHistoryProvider(BaseHistoryProvider):
7
+ """
8
+ A MongoDB-based history provider for storing session data.
9
+ """
10
+ def __init__(self, config=None):
11
+ super().__init__(config)
12
+
13
+ try:
14
+ from pymongo import MongoClient
15
+ except ImportError:
16
+ raise ImportError("Please install the pymongo package to use the MongoDBHistoryProvider.\n\t$ pip install pymongo")
17
+
18
+ if not self.config.get("mongodb_uri"):
19
+ raise ValueError("Missing required configuration for MongoDBHistoryProvider, Missing 'mongodb_uri' in 'store_config'.")
20
+
21
+
22
+ self.client = MongoClient(self.config.get("mongodb_uri"))
23
+ self.db = self.client[self.config.get("mongodb_db", "history_db")]
24
+ self.collection = self.db[self.config.get("mongodb_collection", "sessions")]
25
+
26
+ def _get_key(self, session_id):
27
+ """
28
+ Generate a document identifier for a session.
29
+
30
+ :param session_id: The session identifier.
31
+ :return: The session ID as the primary key.
32
+ """
33
+ return {"_id": session_id}
34
+
35
+ def store_session(self, session_id: str, data: dict):
36
+ """
37
+ Store the session metadata.
38
+
39
+ :param session_id: The session identifier.
40
+ :param data: The session data to be stored.
41
+ """
42
+ self.collection.update_one(self._get_key(session_id), {"$set": {"data": data}}, upsert=True)
43
+
44
+ def get_session(self, session_id: str)->dict:
45
+ """
46
+ Retrieve the session.
47
+
48
+ :param session_id: The session identifier.
49
+ :return: The session metadata as a dictionary.
50
+ """
51
+ document = self.collection.find_one(self._get_key(session_id))
52
+ return document.get("data") if document else {}
53
+
54
+ def get_all_sessions(self) -> list[str]:
55
+ """
56
+ Retrieve all session identifiers.
57
+ """
58
+ return [doc["_id"] for doc in self.collection.find({}, {"_id": 1})]
59
+
60
+ def delete_session(self, session_id: str):
61
+ """
62
+ Delete the session.
63
+
64
+ :param session_id: The session identifier.
65
+ """
66
+ self.collection.delete_one(self._get_key(session_id))
@@ -1,8 +1,13 @@
1
+ """
2
+ A history provider that stores history in Redis.
3
+ """
1
4
  import json
2
- import time
3
5
  from .base_history_provider import BaseHistoryProvider
4
6
 
5
7
  class RedisHistoryProvider(BaseHistoryProvider):
8
+ """
9
+ A history provider that stores history in Redis.
10
+ """
6
11
  def __init__(self, config=None):
7
12
  super().__init__(config)
8
13
  try:
@@ -14,153 +19,48 @@ class RedisHistoryProvider(BaseHistoryProvider):
14
19
  host=self.config.get("redis_host", "localhost"),
15
20
  port=self.config.get("redis_port", 6379),
16
21
  db=self.config.get("redis_db", 0),
22
+ decode_responses=True # Ensures string output
17
23
  )
24
+
25
+ def _get_key(self, session_id):
26
+ """
27
+ Generate a Redis key with a specific prefix for a session.
18
28
 
19
- def _get_history_key(self, session_id: str):
20
- return f"session:{session_id}:history"
21
-
22
- def _get_files_key(self, session_id: str):
23
- return f"session:{session_id}:files"
24
-
25
- def store_history(self, session_id: str, role: str, content: str | dict):
26
- key = self._get_history_key(session_id)
27
- entry = {"role": role, "content": content}
28
- entry_json = json.dumps(entry)
29
-
30
- # Check if session exists, if not initialize it
31
- if not self.redis_client.exists(key):
32
- self.redis_client.hset(session_id, mapping={
33
- "num_characters": 0,
34
- "num_turns": 0,
35
- "last_active_time": time.time()
36
- })
37
-
38
- # Get current stats
39
- session_meta = self.redis_client.hgetall(session_id)
40
- num_characters = int(session_meta.get(b"num_characters", 0))
41
- num_turns = int(session_meta.get(b"num_turns", 0))
42
-
43
- # Add the new entry
44
- if self.enforce_alternate_message_roles and num_turns > 0:
45
- last_entry = json.loads(self.redis_client.lindex(key, -1))
46
- if last_entry["role"] == role:
47
- last_entry["content"] += content
48
- self.redis_client.lset(key, -1, json.dumps(last_entry))
49
- else:
50
- self.redis_client.rpush(key, entry_json)
51
- num_turns += 1
52
- else:
53
- self.redis_client.rpush(key, entry_json)
54
- num_turns += 1
55
- num_characters += len(str(content))
56
-
57
- # Enforce max_turns by trimming the oldest entry if needed
58
- if self.max_turns and num_turns > self.max_turns:
59
- oldest_entry = json.loads(self.redis_client.lpop(key))
60
- num_characters -= len(str(oldest_entry["content"]))
61
- num_turns -= 1
62
-
63
- # Enforce max_characters
64
- if self.max_characters:
65
- while num_characters > self.max_characters and num_turns > 0:
66
- oldest_entry = json.loads(self.redis_client.lpop(key))
67
- num_characters -= len(str(oldest_entry["content"]))
68
- num_turns -= 1
69
-
70
- # Update metadata and set expiration
71
- self.redis_client.hset(session_id, mapping={
72
- "num_characters": num_characters,
73
- "num_turns": num_turns,
74
- "last_active_time": time.time()
75
- })
76
-
77
- def get_history(self, session_id: str):
78
- key = self._get_history_key(session_id)
79
- history = self.redis_client.lrange(key, 0, -1)
80
-
81
- # Decode JSON entries and return a list of dictionaries
82
- return [json.loads(entry) for entry in history]
83
-
84
- def store_file(self, session_id: str, file: dict):
85
- key = self._get_files_key(session_id)
86
- file_entry = json.dumps(file)
87
-
88
- # Avoid duplicate files by checking existing URLs
89
- existing_files = self.get_files(session_id)
90
- if any(f.get("url") == file.get("url") for f in existing_files):
91
- return
92
-
93
- # Add the file and update metadata
94
- self.redis_client.rpush(key, file_entry)
95
- self.redis_client.hset(session_id, "last_active_time", time.time())
96
-
97
- def get_files(self, session_id: str):
98
- key = self._get_files_key(session_id)
99
- current_time = time.time()
100
- files = self.redis_client.lrange(key, 0, -1)
101
-
102
- valid_files = []
103
- for file_json in files:
104
- file = json.loads(file_json)
105
- expiration_timestamp = file.get("expiration_timestamp")
106
-
107
- # Remove expired files
108
- if expiration_timestamp and current_time > expiration_timestamp:
109
- self.redis_client.lrem(key, 0, file_json)
110
- else:
111
- valid_files.append(file)
112
-
113
- return valid_files
114
-
115
- def clear_history(self, session_id: str, keep_levels=0, clear_files=True):
116
- history_key = self._get_history_key(session_id)
117
-
118
- if keep_levels > 0:
119
- # Keep the latest `keep_levels` entries
120
- self.redis_client.ltrim(history_key, -keep_levels, -1)
121
-
122
- # Recalculate session metadata
123
- remaining_entries = self.redis_client.lrange(history_key, 0, -1)
124
- num_characters = sum(len(str(json.loads(entry)["content"])) for entry in remaining_entries)
125
- num_turns = len(remaining_entries)
126
-
127
- # Update metadata
128
- self.redis_client.hset(session_id, mapping={
129
- "num_characters": num_characters,
130
- "num_turns": num_turns
131
- })
132
- else:
133
- # Clear all history and files
134
- self.redis_client.delete(history_key, session_id)
29
+ :param session_id: The session identifier.
30
+ :return: A formatted Redis key string.
31
+ """
32
+ return f"sessions:{session_id}:history"
135
33
 
136
- if clear_files:
137
- files_key = self._get_files_key(session_id)
138
- self.redis_client.delete(files_key)
34
+ def store_session(self, session_id: str, data: dict):
35
+ """
36
+ Store the session metadata.
139
37
 
38
+ :param session_id: The session identifier.
39
+ :param data: The session data to be stored.
40
+ """
41
+ self.redis_client.set(self._get_key(session_id), json.dumps(data))
140
42
 
141
- def get_session_meta(self, session_id: str):
43
+ def get_session(self, session_id: str)->dict:
142
44
  """
143
- Retrieve the session metadata.
45
+ Retrieve the session.
144
46
 
145
47
  :param session_id: The session identifier.
146
- :return: The session metadata.
48
+ :return: The session metadata as a dictionary.
147
49
  """
148
- # Check if session exists
149
- if not self.redis_client.exists(session_id):
150
- return None
151
- # Get current stats
152
- session_meta = self.redis_client.hgetall(session_id)
153
- num_characters = int(session_meta.get(b"num_characters", 0))
154
- num_turns = int(session_meta.get(b"num_turns", 0))
155
- last_active_time = float(session_meta.get(b"last_active_time", 0))
156
- return {
157
- "num_characters": num_characters,
158
- "num_turns": num_turns,
159
- "last_active_time": last_active_time,
160
- }
50
+ data = self.redis_client.get(self._get_key(session_id))
51
+ return json.loads(data) if data else {}
161
52
 
53
+ def get_all_sessions(self) -> list[str]:
54
+ """
55
+ Retrieve all session identifiers.
56
+ """
57
+ keys = self.redis_client.keys("sessions:*:history")
58
+ return [key.split(":")[1] for key in keys]
59
+
60
+ def delete_session(self, session_id: str):
61
+ """
62
+ Delete the session.
162
63
 
163
- def get_all_sessions(self)-> list[str]:
164
- # List all sessions based on Redis keys
165
- session_keys = self.redis_client.scan_iter("session:*:history")
166
- return [key.decode().split(":")[1] for key in session_keys]
64
+ :param session_id: The session identifier.
65
+ """
66
+ self.redis_client.delete(self._get_key(session_id))
@@ -0,0 +1,93 @@
1
+ import json
2
+
3
+ from .base_history_provider import BaseHistoryProvider
4
+ from ....common.postgres_database import PostgreSQLDatabase
5
+ from ....common.mysql_database import MySQLDatabase
6
+
7
+
8
+ class DatabaseFactory:
9
+ """
10
+ Factory class to create database instances.
11
+ """
12
+ DATABASE_PROVIDERS = {
13
+ "postgres": PostgreSQLDatabase,
14
+ "mysql": MySQLDatabase,
15
+ }
16
+
17
+ @staticmethod
18
+ def get_database(db_type, **kwargs):
19
+ if db_type not in DatabaseFactory.DATABASE_PROVIDERS:
20
+ raise ValueError(f"Unsupported database type: {db_type}")
21
+ return DatabaseFactory.DATABASE_PROVIDERS[db_type](**kwargs)
22
+
23
+ class SQLHistoryProvider(BaseHistoryProvider):
24
+ """
25
+ A history provider that stores session history in a SQL database.
26
+ """
27
+ def __init__(self, config=None):
28
+ super().__init__(config)
29
+ self.db_type = self.config.get("db_type", "postgres")
30
+ self.table_name = self.config.get("table_name", "session_history")
31
+ self.db = DatabaseFactory.get_database(
32
+ self.db_type,
33
+ host=self.config.get("sql_host"),
34
+ user=self.config.get("sql_user"),
35
+ password=self.config.get("sql_password"),
36
+ database=self.config.get("sql_database"),
37
+ )
38
+ self._ensure_table_exists()
39
+
40
+ def _ensure_table_exists(self):
41
+ """
42
+ Ensures the required table exists in the database.
43
+ """
44
+ query = f"""
45
+ CREATE TABLE IF NOT EXISTS {self.table_name} (
46
+ session_id TEXT PRIMARY KEY,
47
+ data JSON
48
+ )
49
+ """
50
+ self.db.execute(query)
51
+
52
+ def store_session(self, session_id: str, data: dict):
53
+ """
54
+ Store or update session metadata.
55
+ """
56
+ query = f"""
57
+ INSERT INTO {self.table_name} (session_id, data)
58
+ VALUES (%s, %s)
59
+ ON CONFLICT (session_id) DO UPDATE
60
+ SET data = EXCLUDED.data
61
+ """ if self.db_type == "postgres" else f"""
62
+ INSERT INTO {self.table_name} (session_id, data)
63
+ VALUES (%s, %s)
64
+ ON DUPLICATE KEY UPDATE data = VALUES(data)
65
+ """
66
+ self.db.execute(query, (session_id, json.dumps(data)))
67
+
68
+ def get_session(self, session_id: str) -> dict:
69
+ """
70
+ Retrieve a session by ID.
71
+ """
72
+ query = f"SELECT data FROM {self.table_name} WHERE session_id = %s"
73
+ cursor = self.db.execute(query, (session_id,))
74
+ row = cursor.fetchone()
75
+ if not row.get("data"):
76
+ return {}
77
+ data = row["data"] if isinstance(row["data"], dict) else json.loads(row["data"])
78
+ return data
79
+
80
+ def get_all_sessions(self) -> list[str]:
81
+ """
82
+ Retrieve all session identifiers.
83
+ """
84
+ query = f"SELECT session_id FROM {self.table_name}"
85
+ cursor = self.db.execute(query)
86
+ return [row["session_id"] for row in cursor.fetchall()]
87
+
88
+ def delete_session(self, session_id: str):
89
+ """
90
+ Delete a session by ID, ensuring only one row is deleted.
91
+ """
92
+ query = f"DELETE FROM {self.table_name} WHERE session_id = %s LIMIT 1"
93
+ self.db.execute(query, (session_id,))