@sekuire/sdk 0.1.10 → 0.1.11
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.
- package/README.md +69 -5
- package/dist/beacon.d.ts +40 -5
- package/dist/config/loader.d.ts +38 -1
- package/dist/index.d.ts +297 -20
- package/dist/index.esm.js +1527 -277
- package/dist/index.js +1127 -133
- package/dist/memory/base.d.ts +7 -0
- package/dist/memory/cloudflare-d1.d.ts +24 -0
- package/dist/memory/cloudflare-kv.d.ts +25 -0
- package/dist/memory/convex.d.ts +21 -0
- package/dist/memory/dynamodb.d.ts +28 -0
- package/dist/memory/in-memory.d.ts +1 -0
- package/dist/memory/index.d.ts +28 -1
- package/dist/memory/postgres.d.ts +5 -1
- package/dist/memory/redis.d.ts +5 -1
- package/dist/memory/registry.d.ts +12 -0
- package/dist/memory/sqlite.d.ts +22 -0
- package/dist/memory/turso.d.ts +23 -0
- package/dist/memory/upstash.d.ts +22 -0
- package/dist/sdk.d.ts +14 -9
- package/package.json +33 -2
package/dist/index.js
CHANGED
|
@@ -102,6 +102,10 @@ function detectDeploymentUrl() {
|
|
|
102
102
|
*
|
|
103
103
|
* Automatically registers the agent with Sekuire and sends periodic heartbeats
|
|
104
104
|
* to show online status in the Dashboard.
|
|
105
|
+
*
|
|
106
|
+
* Supports two authentication modes:
|
|
107
|
+
* 1. Install Token (recommended): Use an install token from the dashboard
|
|
108
|
+
* 2. API Key: Use an API key for SDK-initiated bootstrap (requires workspace)
|
|
105
109
|
*/
|
|
106
110
|
// ============================================================================
|
|
107
111
|
// Beacon Class
|
|
@@ -110,16 +114,21 @@ class Beacon {
|
|
|
110
114
|
constructor(config) {
|
|
111
115
|
this.intervalId = null;
|
|
112
116
|
this.installationId = null;
|
|
117
|
+
this.runtimeToken = null;
|
|
118
|
+
this.refreshToken = null;
|
|
113
119
|
this.lastHeartbeat = null;
|
|
114
120
|
this.failedHeartbeats = 0;
|
|
115
121
|
const resolvedApiKey = config.apiKey || this.getApiKey() || '';
|
|
122
|
+
const resolvedInstallToken = config.installToken || this.getInstallToken();
|
|
116
123
|
this.config = {
|
|
117
124
|
...config,
|
|
118
|
-
|
|
125
|
+
heartbeatIntervalSeconds: config.heartbeatIntervalSeconds ?? 60,
|
|
119
126
|
apiKey: resolvedApiKey,
|
|
127
|
+
installToken: resolvedInstallToken,
|
|
128
|
+
capabilities: config.capabilities ?? [],
|
|
120
129
|
};
|
|
121
|
-
if (!this.config.apiKey) {
|
|
122
|
-
console.warn('[Beacon] No API key provided. Set
|
|
130
|
+
if (!this.config.installToken && !this.config.apiKey) {
|
|
131
|
+
console.warn('[Beacon] No install token or API key provided. Set SEKUIRE_INSTALL_TOKEN or SEKUIRE_API_KEY.');
|
|
123
132
|
}
|
|
124
133
|
}
|
|
125
134
|
/**
|
|
@@ -135,22 +144,27 @@ class Beacon {
|
|
|
135
144
|
if (deploymentUrl) {
|
|
136
145
|
console.log(`[Beacon] Deployment URL: ${deploymentUrl}`);
|
|
137
146
|
}
|
|
138
|
-
// Bootstrap registration
|
|
147
|
+
// Bootstrap registration (exchanges install token for runtime credentials)
|
|
139
148
|
try {
|
|
140
149
|
await this.bootstrap(deploymentUrl);
|
|
150
|
+
console.log(`[Beacon] Bootstrap successful, installation ID: ${this.installationId}`);
|
|
141
151
|
}
|
|
142
152
|
catch (error) {
|
|
143
153
|
console.error('[Beacon] Bootstrap failed:', error);
|
|
144
|
-
|
|
154
|
+
this.notifyStatusChange();
|
|
155
|
+
// Cannot continue without bootstrap - runtime token required for heartbeats
|
|
156
|
+
throw error;
|
|
145
157
|
}
|
|
146
158
|
// Start heartbeat loop
|
|
159
|
+
const intervalMs = this.config.heartbeatIntervalSeconds * 1000;
|
|
147
160
|
this.intervalId = setInterval(() => {
|
|
148
161
|
this.heartbeat().catch((err) => {
|
|
149
162
|
console.error('[Beacon] Heartbeat failed:', err);
|
|
150
163
|
});
|
|
151
|
-
},
|
|
164
|
+
}, intervalMs);
|
|
152
165
|
// Send first heartbeat immediately
|
|
153
166
|
await this.heartbeat().catch(() => { });
|
|
167
|
+
this.notifyStatusChange();
|
|
154
168
|
}
|
|
155
169
|
/**
|
|
156
170
|
* Stop the beacon
|
|
@@ -169,6 +183,7 @@ class Beacon {
|
|
|
169
183
|
return {
|
|
170
184
|
isRunning: this.intervalId !== null,
|
|
171
185
|
installationId: this.installationId || undefined,
|
|
186
|
+
runtimeToken: this.runtimeToken ? '***' + this.runtimeToken.slice(-4) : undefined,
|
|
172
187
|
deploymentUrl: this.config.deploymentUrl || detectDeploymentUrl(),
|
|
173
188
|
lastHeartbeat: this.lastHeartbeat || undefined,
|
|
174
189
|
failedHeartbeats: this.failedHeartbeats,
|
|
@@ -176,72 +191,116 @@ class Beacon {
|
|
|
176
191
|
}
|
|
177
192
|
/**
|
|
178
193
|
* Bootstrap registration with Sekuire
|
|
194
|
+
*
|
|
195
|
+
* Exchanges an install token for runtime credentials (installation_id + runtime_token).
|
|
196
|
+
* The install token is obtained from the dashboard or CLI.
|
|
179
197
|
*/
|
|
180
198
|
async bootstrap(deploymentUrl) {
|
|
181
|
-
if (!this.config.
|
|
182
|
-
throw new Error('
|
|
199
|
+
if (!this.config.installToken) {
|
|
200
|
+
throw new Error('Install token required for beacon registration. ' +
|
|
201
|
+
'Generate one from the dashboard or use: sekuire install token --workspace <id>');
|
|
183
202
|
}
|
|
184
203
|
const response = await fetch(`${this.config.apiBaseUrl}/api/v1/installations/bootstrap`, {
|
|
185
204
|
method: 'POST',
|
|
186
205
|
headers: {
|
|
187
206
|
'Content-Type': 'application/json',
|
|
188
|
-
'X-API-Key': this.config.apiKey,
|
|
189
207
|
},
|
|
190
208
|
body: JSON.stringify({
|
|
191
|
-
|
|
209
|
+
install_token: this.config.installToken,
|
|
192
210
|
upstream_url: deploymentUrl,
|
|
211
|
+
capabilities: this.config.capabilities ?? [],
|
|
212
|
+
heartbeat_interval_seconds: this.config.heartbeatIntervalSeconds,
|
|
193
213
|
}),
|
|
194
214
|
});
|
|
195
215
|
if (!response.ok) {
|
|
196
|
-
|
|
216
|
+
const body = await response.text();
|
|
217
|
+
throw new Error(`Bootstrap failed: ${response.status} ${response.statusText} - ${body}`);
|
|
197
218
|
}
|
|
198
219
|
const data = await response.json();
|
|
199
220
|
this.installationId = data.installation_id;
|
|
200
|
-
|
|
221
|
+
this.runtimeToken = data.runtime_token;
|
|
222
|
+
this.refreshToken = data.refresh_token;
|
|
201
223
|
}
|
|
202
224
|
/**
|
|
203
|
-
* Send heartbeat to Sekuire
|
|
225
|
+
* Send heartbeat (lease renewal) to Sekuire
|
|
226
|
+
*
|
|
227
|
+
* Uses the installation-based lease endpoint with runtime token authentication.
|
|
204
228
|
*/
|
|
205
229
|
async heartbeat() {
|
|
206
|
-
if (!this.
|
|
230
|
+
if (!this.installationId || !this.runtimeToken) {
|
|
231
|
+
console.warn('[Beacon] Cannot send heartbeat - not bootstrapped');
|
|
207
232
|
return;
|
|
208
233
|
}
|
|
209
234
|
const deploymentUrl = this.config.deploymentUrl || detectDeploymentUrl();
|
|
210
235
|
const prevFailed = this.failedHeartbeats;
|
|
211
236
|
try {
|
|
212
|
-
const
|
|
213
|
-
? await this.config.onHeartbeat(deploymentUrl)
|
|
214
|
-
: {
|
|
215
|
-
agent_id: this.config.agentId,
|
|
216
|
-
status: 'running',
|
|
217
|
-
public_url: deploymentUrl,
|
|
218
|
-
load: 0.0,
|
|
219
|
-
};
|
|
220
|
-
const response = await fetch(`${this.config.apiBaseUrl}/api/v1/agents/heartbeat`, {
|
|
237
|
+
const response = await fetch(`${this.config.apiBaseUrl}/api/v1/installations/${this.installationId}/lease`, {
|
|
221
238
|
method: 'POST',
|
|
222
239
|
headers: {
|
|
223
240
|
'Content-Type': 'application/json',
|
|
224
|
-
'
|
|
241
|
+
'Authorization': `Bearer ${this.runtimeToken}`,
|
|
225
242
|
},
|
|
226
|
-
body: JSON.stringify(
|
|
243
|
+
body: JSON.stringify({
|
|
244
|
+
status: 'running',
|
|
245
|
+
heartbeat_interval_seconds: this.config.heartbeatIntervalSeconds,
|
|
246
|
+
upstream_url: deploymentUrl,
|
|
247
|
+
}),
|
|
227
248
|
});
|
|
249
|
+
if (response.status === 401) {
|
|
250
|
+
// Token expired - try to refresh
|
|
251
|
+
console.warn('[Beacon] Runtime token expired, attempting refresh...');
|
|
252
|
+
await this.refreshRuntimeToken();
|
|
253
|
+
// Retry heartbeat after refresh
|
|
254
|
+
return this.heartbeat();
|
|
255
|
+
}
|
|
228
256
|
if (!response.ok) {
|
|
229
|
-
|
|
257
|
+
const body = await response.text();
|
|
258
|
+
throw new Error(`Heartbeat failed: ${response.status} - ${body}`);
|
|
230
259
|
}
|
|
231
260
|
this.lastHeartbeat = new Date();
|
|
232
261
|
this.failedHeartbeats = 0;
|
|
233
|
-
if (prevFailed > 0
|
|
234
|
-
this.
|
|
262
|
+
if (prevFailed > 0) {
|
|
263
|
+
this.notifyStatusChange();
|
|
235
264
|
}
|
|
236
265
|
}
|
|
237
266
|
catch (error) {
|
|
238
267
|
this.failedHeartbeats++;
|
|
239
|
-
|
|
240
|
-
this.config.onStatusChange(this.getStatus());
|
|
241
|
-
}
|
|
268
|
+
this.notifyStatusChange();
|
|
242
269
|
throw error;
|
|
243
270
|
}
|
|
244
271
|
}
|
|
272
|
+
/**
|
|
273
|
+
* Refresh the runtime token using the refresh token
|
|
274
|
+
*/
|
|
275
|
+
async refreshRuntimeToken() {
|
|
276
|
+
if (!this.installationId || !this.refreshToken) {
|
|
277
|
+
throw new Error('Cannot refresh token - missing installation ID or refresh token');
|
|
278
|
+
}
|
|
279
|
+
const response = await fetch(`${this.config.apiBaseUrl}/api/v1/installations/${this.installationId}/refresh`, {
|
|
280
|
+
method: 'POST',
|
|
281
|
+
headers: {
|
|
282
|
+
'Content-Type': 'application/json',
|
|
283
|
+
},
|
|
284
|
+
body: JSON.stringify({
|
|
285
|
+
refresh_token: this.refreshToken,
|
|
286
|
+
}),
|
|
287
|
+
});
|
|
288
|
+
if (!response.ok) {
|
|
289
|
+
const body = await response.text();
|
|
290
|
+
throw new Error(`Token refresh failed: ${response.status} - ${body}`);
|
|
291
|
+
}
|
|
292
|
+
const data = await response.json();
|
|
293
|
+
this.runtimeToken = data.runtime_token;
|
|
294
|
+
console.log('[Beacon] Runtime token refreshed successfully');
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Notify status change callback
|
|
298
|
+
*/
|
|
299
|
+
notifyStatusChange() {
|
|
300
|
+
if (this.config.onStatusChange) {
|
|
301
|
+
this.config.onStatusChange(this.getStatus());
|
|
302
|
+
}
|
|
303
|
+
}
|
|
245
304
|
/**
|
|
246
305
|
* Get API key from environment
|
|
247
306
|
*/
|
|
@@ -251,6 +310,15 @@ class Beacon {
|
|
|
251
310
|
}
|
|
252
311
|
return undefined;
|
|
253
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* Get install token from environment
|
|
315
|
+
*/
|
|
316
|
+
getInstallToken() {
|
|
317
|
+
if (typeof process !== 'undefined' && process.env) {
|
|
318
|
+
return process.env.SEKUIRE_INSTALL_TOKEN;
|
|
319
|
+
}
|
|
320
|
+
return undefined;
|
|
321
|
+
}
|
|
254
322
|
}
|
|
255
323
|
// ============================================================================
|
|
256
324
|
// Convenience Export
|
|
@@ -754,17 +822,20 @@ class SekuireSDK {
|
|
|
754
822
|
throw new Error("agentId is required");
|
|
755
823
|
}
|
|
756
824
|
const apiKey = config.apiKey || process.env.SEKUIRE_API_KEY;
|
|
825
|
+
const installToken = config.installToken || process.env.SEKUIRE_INSTALL_TOKEN;
|
|
757
826
|
this.config = {
|
|
758
827
|
privateKey: config.privateKey,
|
|
759
828
|
apiKey,
|
|
829
|
+
installToken,
|
|
760
830
|
agentId: config.agentId,
|
|
761
831
|
agentName: config.agentName ?? "Unknown",
|
|
762
832
|
apiUrl: (config.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, ""),
|
|
763
833
|
workspaceId: config.workspaceId ?? "",
|
|
764
834
|
environment: config.environment ?? "production",
|
|
765
835
|
autoHeartbeat: config.autoHeartbeat !== false,
|
|
766
|
-
|
|
836
|
+
heartbeatIntervalSeconds: config.heartbeatIntervalSeconds ?? 60,
|
|
767
837
|
loggingEnabled: config.loggingEnabled !== false,
|
|
838
|
+
capabilities: config.capabilities ?? [],
|
|
768
839
|
};
|
|
769
840
|
this.identity = new AgentIdentity(this.config.agentName, this.config.agentId, this.config.privateKey);
|
|
770
841
|
const loggerConfig = {
|
|
@@ -783,22 +854,12 @@ class SekuireSDK {
|
|
|
783
854
|
});
|
|
784
855
|
this.beacon = new Beacon({
|
|
785
856
|
apiBaseUrl: this.config.apiUrl,
|
|
857
|
+
installToken: this.config.installToken,
|
|
786
858
|
apiKey: this.config.apiKey,
|
|
787
859
|
agentId: this.config.agentId,
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
const timestamp = Math.floor(Date.now() / 1000);
|
|
792
|
-
const message = `${this.config.agentId}|${deploymentUrl ?? ""}|${timestamp}`;
|
|
793
|
-
const signature = await this.identity.sign(message);
|
|
794
|
-
return {
|
|
795
|
-
sekuire_id: this.config.agentId,
|
|
796
|
-
url: deploymentUrl ?? null,
|
|
797
|
-
timestamp,
|
|
798
|
-
signature,
|
|
799
|
-
};
|
|
800
|
-
}
|
|
801
|
-
: undefined,
|
|
860
|
+
workspaceId: this.config.workspaceId || undefined,
|
|
861
|
+
heartbeatIntervalSeconds: this.config.heartbeatIntervalSeconds,
|
|
862
|
+
capabilities: this.config.capabilities,
|
|
802
863
|
});
|
|
803
864
|
}
|
|
804
865
|
/**
|
|
@@ -806,10 +867,11 @@ class SekuireSDK {
|
|
|
806
867
|
*
|
|
807
868
|
* Required env vars:
|
|
808
869
|
* SEKUIRE_AGENT_ID - The agent's sekuire_id
|
|
870
|
+
* SEKUIRE_INSTALL_TOKEN - Install token from dashboard (required for heartbeat)
|
|
809
871
|
*
|
|
810
872
|
* Optional env vars:
|
|
811
873
|
* SEKUIRE_API_KEY - API key for authentication (X-API-Key header)
|
|
812
|
-
* SEKUIRE_PRIVATE_KEY - Private key for signing
|
|
874
|
+
* SEKUIRE_PRIVATE_KEY - Private key for signing
|
|
813
875
|
* SEKUIRE_AGENT_NAME - Human-readable agent name
|
|
814
876
|
* SEKUIRE_API_URL - API base URL (default: https://api.sekuire.ai)
|
|
815
877
|
* SEKUIRE_WORKSPACE_ID - Workspace ID for policy enforcement
|
|
@@ -823,6 +885,7 @@ class SekuireSDK {
|
|
|
823
885
|
return new SekuireSDK({
|
|
824
886
|
privateKey: process.env.SEKUIRE_PRIVATE_KEY,
|
|
825
887
|
apiKey: process.env.SEKUIRE_API_KEY,
|
|
888
|
+
installToken: process.env.SEKUIRE_INSTALL_TOKEN,
|
|
826
889
|
agentId,
|
|
827
890
|
agentName: process.env.SEKUIRE_AGENT_NAME,
|
|
828
891
|
apiUrl: process.env.SEKUIRE_API_URL ?? DEFAULT_API_URL,
|
|
@@ -835,46 +898,32 @@ class SekuireSDK {
|
|
|
835
898
|
/**
|
|
836
899
|
* Start the SDK.
|
|
837
900
|
*
|
|
838
|
-
* -
|
|
901
|
+
* - Bootstraps with Sekuire using the install token
|
|
839
902
|
* - Starts auto-heartbeat loop if enabled
|
|
903
|
+
*
|
|
904
|
+
* Requires SEKUIRE_INSTALL_TOKEN to be set (from dashboard or CLI).
|
|
840
905
|
*/
|
|
841
906
|
async start() {
|
|
842
907
|
if (this.isRunning) {
|
|
843
908
|
return;
|
|
844
909
|
}
|
|
845
910
|
this.isRunning = true;
|
|
846
|
-
if (this.config.autoHeartbeat &&
|
|
911
|
+
if (this.config.autoHeartbeat && this.config.installToken) {
|
|
847
912
|
await this.beacon.start();
|
|
848
913
|
}
|
|
914
|
+
else if (this.config.autoHeartbeat && !this.config.installToken) {
|
|
915
|
+
console.warn("[SekuireSDK] Auto-heartbeat enabled but no install token provided. " +
|
|
916
|
+
"Set SEKUIRE_INSTALL_TOKEN or generate one from the dashboard.");
|
|
917
|
+
}
|
|
849
918
|
}
|
|
850
919
|
/**
|
|
851
|
-
*
|
|
852
|
-
*
|
|
853
|
-
* The heartbeat proves the agent is running and has access to the private key.
|
|
920
|
+
* Check if the beacon is connected and sending heartbeats.
|
|
854
921
|
*
|
|
855
|
-
* @
|
|
856
|
-
* @returns True if heartbeat was acknowledged, False otherwise
|
|
922
|
+
* @returns True if the beacon has successfully bootstrapped
|
|
857
923
|
*/
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
}
|
|
862
|
-
const timestamp = Math.floor(Date.now() / 1000);
|
|
863
|
-
const message = `${this.config.agentId}|${url ?? ""}|${timestamp}`;
|
|
864
|
-
const signature = await this.identity.sign(message);
|
|
865
|
-
const payload = {
|
|
866
|
-
sekuire_id: this.config.agentId,
|
|
867
|
-
url: url ?? null,
|
|
868
|
-
timestamp,
|
|
869
|
-
signature,
|
|
870
|
-
};
|
|
871
|
-
try {
|
|
872
|
-
const response = await this.client.post("/api/v1/agents/heartbeat", payload);
|
|
873
|
-
return response.status === 200;
|
|
874
|
-
}
|
|
875
|
-
catch {
|
|
876
|
-
return false;
|
|
877
|
-
}
|
|
924
|
+
isConnected() {
|
|
925
|
+
const status = this.beacon.getStatus();
|
|
926
|
+
return status.isRunning && !!status.installationId;
|
|
878
927
|
}
|
|
879
928
|
/**
|
|
880
929
|
* Log an event to Sekuire.
|
|
@@ -3094,12 +3143,25 @@ const llm = {
|
|
|
3094
3143
|
};
|
|
3095
3144
|
|
|
3096
3145
|
class BaseMemoryStorage {
|
|
3146
|
+
constructor() {
|
|
3147
|
+
this._connected = false;
|
|
3148
|
+
}
|
|
3149
|
+
async connect() {
|
|
3150
|
+
this._connected = true;
|
|
3151
|
+
}
|
|
3152
|
+
async disconnect() {
|
|
3153
|
+
this._connected = false;
|
|
3154
|
+
}
|
|
3155
|
+
isConnected() {
|
|
3156
|
+
return this._connected;
|
|
3157
|
+
}
|
|
3097
3158
|
}
|
|
3098
3159
|
|
|
3099
3160
|
class InMemoryStorage extends BaseMemoryStorage {
|
|
3100
3161
|
constructor() {
|
|
3101
|
-
super(
|
|
3162
|
+
super();
|
|
3102
3163
|
this.storage = new Map();
|
|
3164
|
+
this._connected = true;
|
|
3103
3165
|
}
|
|
3104
3166
|
async add(sessionId, message) {
|
|
3105
3167
|
if (!this.storage.has(sessionId)) {
|
|
@@ -3128,32 +3190,49 @@ class InMemoryStorage extends BaseMemoryStorage {
|
|
|
3128
3190
|
class PostgresStorage extends BaseMemoryStorage {
|
|
3129
3191
|
constructor(config) {
|
|
3130
3192
|
super();
|
|
3131
|
-
this.
|
|
3193
|
+
this.connectionPromise = null;
|
|
3132
3194
|
this.tableName = config.tableName || 'sekuire_memory';
|
|
3133
|
-
this.
|
|
3195
|
+
this.config = config;
|
|
3196
|
+
if (!config.lazyConnect) {
|
|
3197
|
+
this.connectionPromise = this.connect();
|
|
3198
|
+
}
|
|
3134
3199
|
}
|
|
3135
|
-
async
|
|
3200
|
+
async connect() {
|
|
3201
|
+
if (this._connected)
|
|
3202
|
+
return;
|
|
3203
|
+
if (this.connectionPromise)
|
|
3204
|
+
return this.connectionPromise;
|
|
3205
|
+
this.connectionPromise = this.initPool();
|
|
3206
|
+
return this.connectionPromise;
|
|
3207
|
+
}
|
|
3208
|
+
async initPool() {
|
|
3136
3209
|
try {
|
|
3137
3210
|
const { Pool } = await import('pg');
|
|
3138
|
-
if (config.connectionString) {
|
|
3139
|
-
this.pool = new Pool({ connectionString: config.connectionString });
|
|
3211
|
+
if (this.config.connectionString) {
|
|
3212
|
+
this.pool = new Pool({ connectionString: this.config.connectionString });
|
|
3140
3213
|
}
|
|
3141
3214
|
else {
|
|
3142
3215
|
this.pool = new Pool({
|
|
3143
|
-
host: config.host || 'localhost',
|
|
3144
|
-
port: config.port || 5432,
|
|
3145
|
-
database: config.database,
|
|
3146
|
-
user: config.user,
|
|
3147
|
-
password: config.password,
|
|
3216
|
+
host: this.config.host || 'localhost',
|
|
3217
|
+
port: this.config.port || 5432,
|
|
3218
|
+
database: this.config.database,
|
|
3219
|
+
user: this.config.user,
|
|
3220
|
+
password: this.config.password,
|
|
3148
3221
|
});
|
|
3149
3222
|
}
|
|
3150
3223
|
await this.createTableIfNotExists();
|
|
3151
|
-
this.
|
|
3224
|
+
this._connected = true;
|
|
3152
3225
|
}
|
|
3153
3226
|
catch (error) {
|
|
3227
|
+
this.connectionPromise = null;
|
|
3154
3228
|
throw new Error(`Failed to initialize Postgres: ${error}`);
|
|
3155
3229
|
}
|
|
3156
3230
|
}
|
|
3231
|
+
async ensureConnected() {
|
|
3232
|
+
if (!this._connected) {
|
|
3233
|
+
await this.connect();
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3157
3236
|
async createTableIfNotExists() {
|
|
3158
3237
|
const query = `
|
|
3159
3238
|
CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|
@@ -3170,8 +3249,7 @@ class PostgresStorage extends BaseMemoryStorage {
|
|
|
3170
3249
|
await this.pool.query(query);
|
|
3171
3250
|
}
|
|
3172
3251
|
async add(sessionId, message) {
|
|
3173
|
-
|
|
3174
|
-
throw new Error('Postgres not initialized');
|
|
3252
|
+
await this.ensureConnected();
|
|
3175
3253
|
const query = `
|
|
3176
3254
|
INSERT INTO ${this.tableName} (session_id, role, content, timestamp, metadata)
|
|
3177
3255
|
VALUES ($1, $2, $3, $4, $5)
|
|
@@ -3185,8 +3263,7 @@ class PostgresStorage extends BaseMemoryStorage {
|
|
|
3185
3263
|
]);
|
|
3186
3264
|
}
|
|
3187
3265
|
async get(sessionId, limit) {
|
|
3188
|
-
|
|
3189
|
-
throw new Error('Postgres not initialized');
|
|
3266
|
+
await this.ensureConnected();
|
|
3190
3267
|
let query = `
|
|
3191
3268
|
SELECT role, content, timestamp, metadata
|
|
3192
3269
|
FROM ${this.tableName}
|
|
@@ -3205,8 +3282,7 @@ class PostgresStorage extends BaseMemoryStorage {
|
|
|
3205
3282
|
}));
|
|
3206
3283
|
}
|
|
3207
3284
|
async clear(sessionId) {
|
|
3208
|
-
|
|
3209
|
-
throw new Error('Postgres not initialized');
|
|
3285
|
+
await this.ensureConnected();
|
|
3210
3286
|
const query = `DELETE FROM ${this.tableName} WHERE session_id = $1`;
|
|
3211
3287
|
await this.pool.query(query, [sessionId]);
|
|
3212
3288
|
}
|
|
@@ -3214,16 +3290,16 @@ class PostgresStorage extends BaseMemoryStorage {
|
|
|
3214
3290
|
await this.clear(sessionId);
|
|
3215
3291
|
}
|
|
3216
3292
|
async exists(sessionId) {
|
|
3217
|
-
|
|
3218
|
-
throw new Error('Postgres not initialized');
|
|
3293
|
+
await this.ensureConnected();
|
|
3219
3294
|
const query = `SELECT COUNT(*) FROM ${this.tableName} WHERE session_id = $1`;
|
|
3220
3295
|
const result = await this.pool.query(query, [sessionId]);
|
|
3221
3296
|
return parseInt(result.rows[0].count) > 0;
|
|
3222
3297
|
}
|
|
3223
3298
|
async disconnect() {
|
|
3224
|
-
if (this.
|
|
3299
|
+
if (this._connected && this.pool) {
|
|
3225
3300
|
await this.pool.end();
|
|
3226
|
-
this.
|
|
3301
|
+
this._connected = false;
|
|
3302
|
+
this.connectionPromise = null;
|
|
3227
3303
|
}
|
|
3228
3304
|
}
|
|
3229
3305
|
}
|
|
@@ -3232,56 +3308,70 @@ class RedisStorage extends BaseMemoryStorage {
|
|
|
3232
3308
|
constructor(config) {
|
|
3233
3309
|
super();
|
|
3234
3310
|
this.client = null;
|
|
3235
|
-
this.
|
|
3311
|
+
this.connectionPromise = null;
|
|
3236
3312
|
this.keyPrefix = config.keyPrefix || 'sekuire:memory:';
|
|
3237
|
-
this.
|
|
3313
|
+
this.config = config;
|
|
3314
|
+
if (!config.lazyConnect) {
|
|
3315
|
+
this.connectionPromise = this.connect();
|
|
3316
|
+
}
|
|
3238
3317
|
}
|
|
3239
|
-
async
|
|
3318
|
+
async connect() {
|
|
3319
|
+
if (this._connected)
|
|
3320
|
+
return;
|
|
3321
|
+
if (this.connectionPromise)
|
|
3322
|
+
return this.connectionPromise;
|
|
3323
|
+
this.connectionPromise = this.initClient();
|
|
3324
|
+
return this.connectionPromise;
|
|
3325
|
+
}
|
|
3326
|
+
async initClient() {
|
|
3240
3327
|
try {
|
|
3241
3328
|
const { createClient } = await import('redis');
|
|
3242
|
-
if (config.url) {
|
|
3243
|
-
this.client = createClient({ url: config.url });
|
|
3329
|
+
if (this.config.url) {
|
|
3330
|
+
this.client = createClient({ url: this.config.url });
|
|
3244
3331
|
}
|
|
3245
3332
|
else {
|
|
3246
3333
|
this.client = createClient({
|
|
3247
3334
|
socket: {
|
|
3248
|
-
host: config.host || 'localhost',
|
|
3249
|
-
port: config.port || 6379,
|
|
3335
|
+
host: this.config.host || 'localhost',
|
|
3336
|
+
port: this.config.port || 6379,
|
|
3250
3337
|
},
|
|
3251
|
-
password: config.password,
|
|
3252
|
-
database: config.db || 0,
|
|
3338
|
+
password: this.config.password,
|
|
3339
|
+
database: this.config.db || 0,
|
|
3253
3340
|
});
|
|
3254
3341
|
}
|
|
3255
3342
|
this.client.on('error', (err) => {
|
|
3256
3343
|
console.error('Redis client error:', err);
|
|
3257
3344
|
});
|
|
3258
3345
|
await this.client.connect();
|
|
3259
|
-
this.
|
|
3346
|
+
this._connected = true;
|
|
3260
3347
|
}
|
|
3261
3348
|
catch (error) {
|
|
3349
|
+
this.connectionPromise = null;
|
|
3262
3350
|
throw new Error(`Failed to initialize Redis: ${error}`);
|
|
3263
3351
|
}
|
|
3264
3352
|
}
|
|
3353
|
+
async ensureConnected() {
|
|
3354
|
+
if (!this._connected) {
|
|
3355
|
+
await this.connect();
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3265
3358
|
getKey(sessionId) {
|
|
3266
3359
|
return `${this.keyPrefix}${sessionId}`;
|
|
3267
3360
|
}
|
|
3268
3361
|
async add(sessionId, message) {
|
|
3269
|
-
|
|
3270
|
-
throw new Error('Redis not connected');
|
|
3362
|
+
await this.ensureConnected();
|
|
3271
3363
|
const key = this.getKey(sessionId);
|
|
3272
3364
|
await this.client.rPush(key, JSON.stringify(message));
|
|
3273
3365
|
}
|
|
3274
3366
|
async get(sessionId, limit) {
|
|
3275
|
-
|
|
3276
|
-
throw new Error('Redis not connected');
|
|
3367
|
+
await this.ensureConnected();
|
|
3277
3368
|
const key = this.getKey(sessionId);
|
|
3278
3369
|
const start = limit ? -limit : 0;
|
|
3279
3370
|
const messages = await this.client.lRange(key, start, -1);
|
|
3280
3371
|
return messages.map((msg) => JSON.parse(msg));
|
|
3281
3372
|
}
|
|
3282
3373
|
async clear(sessionId) {
|
|
3283
|
-
|
|
3284
|
-
throw new Error('Redis not connected');
|
|
3374
|
+
await this.ensureConnected();
|
|
3285
3375
|
const key = this.getKey(sessionId);
|
|
3286
3376
|
await this.client.del(key);
|
|
3287
3377
|
}
|
|
@@ -3289,38 +3379,928 @@ class RedisStorage extends BaseMemoryStorage {
|
|
|
3289
3379
|
await this.clear(sessionId);
|
|
3290
3380
|
}
|
|
3291
3381
|
async exists(sessionId) {
|
|
3292
|
-
|
|
3293
|
-
throw new Error('Redis not connected');
|
|
3382
|
+
await this.ensureConnected();
|
|
3294
3383
|
const key = this.getKey(sessionId);
|
|
3295
3384
|
return (await this.client.exists(key)) === 1;
|
|
3296
3385
|
}
|
|
3297
3386
|
async disconnect() {
|
|
3298
|
-
if (this.
|
|
3387
|
+
if (this._connected && this.client) {
|
|
3299
3388
|
await this.client.quit();
|
|
3300
|
-
this.
|
|
3389
|
+
this._connected = false;
|
|
3390
|
+
this.connectionPromise = null;
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3394
|
+
|
|
3395
|
+
class SQLiteStorage extends BaseMemoryStorage {
|
|
3396
|
+
constructor(config) {
|
|
3397
|
+
super();
|
|
3398
|
+
this.db = null;
|
|
3399
|
+
this.connectionPromise = null;
|
|
3400
|
+
this.config = config;
|
|
3401
|
+
this.tableName = config.tableName || 'sekuire_memory';
|
|
3402
|
+
this.connectionPromise = this.connect();
|
|
3403
|
+
}
|
|
3404
|
+
async connect() {
|
|
3405
|
+
if (this._connected)
|
|
3406
|
+
return;
|
|
3407
|
+
if (this.connectionPromise && !this._connected)
|
|
3408
|
+
return this.connectionPromise;
|
|
3409
|
+
try {
|
|
3410
|
+
const Database = (await import('better-sqlite3')).default;
|
|
3411
|
+
this.db = new Database(this.config.filename, {
|
|
3412
|
+
readonly: this.config.readonly || false,
|
|
3413
|
+
});
|
|
3414
|
+
this.db.pragma('journal_mode = WAL');
|
|
3415
|
+
this.ensureTableExists();
|
|
3416
|
+
this._connected = true;
|
|
3417
|
+
}
|
|
3418
|
+
catch (error) {
|
|
3419
|
+
this.connectionPromise = null;
|
|
3420
|
+
throw new Error(`Failed to initialize SQLite: ${error}`);
|
|
3421
|
+
}
|
|
3422
|
+
}
|
|
3423
|
+
ensureTableExists() {
|
|
3424
|
+
this.db.exec(`
|
|
3425
|
+
CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|
3426
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
3427
|
+
session_id TEXT NOT NULL,
|
|
3428
|
+
role TEXT NOT NULL,
|
|
3429
|
+
content TEXT NOT NULL,
|
|
3430
|
+
timestamp INTEGER NOT NULL,
|
|
3431
|
+
metadata TEXT,
|
|
3432
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
3433
|
+
)
|
|
3434
|
+
`);
|
|
3435
|
+
this.db.exec(`
|
|
3436
|
+
CREATE INDEX IF NOT EXISTS idx_${this.tableName}_session_id
|
|
3437
|
+
ON ${this.tableName}(session_id)
|
|
3438
|
+
`);
|
|
3439
|
+
}
|
|
3440
|
+
async ensureConnected() {
|
|
3441
|
+
if (!this._connected) {
|
|
3442
|
+
await this.connect();
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
async add(sessionId, message) {
|
|
3446
|
+
await this.ensureConnected();
|
|
3447
|
+
const stmt = this.db.prepare(`
|
|
3448
|
+
INSERT INTO ${this.tableName} (session_id, role, content, timestamp, metadata)
|
|
3449
|
+
VALUES (?, ?, ?, ?, ?)
|
|
3450
|
+
`);
|
|
3451
|
+
stmt.run(sessionId, message.role, message.content, message.timestamp, message.metadata ? JSON.stringify(message.metadata) : null);
|
|
3452
|
+
}
|
|
3453
|
+
async get(sessionId, limit) {
|
|
3454
|
+
await this.ensureConnected();
|
|
3455
|
+
let sql = `
|
|
3456
|
+
SELECT role, content, timestamp, metadata
|
|
3457
|
+
FROM ${this.tableName}
|
|
3458
|
+
WHERE session_id = ?
|
|
3459
|
+
ORDER BY timestamp ASC
|
|
3460
|
+
`;
|
|
3461
|
+
if (limit) {
|
|
3462
|
+
sql += ` LIMIT ${limit}`;
|
|
3463
|
+
}
|
|
3464
|
+
const stmt = this.db.prepare(sql);
|
|
3465
|
+
const rows = stmt.all(sessionId);
|
|
3466
|
+
return rows.map((row) => ({
|
|
3467
|
+
role: row.role,
|
|
3468
|
+
content: row.content,
|
|
3469
|
+
timestamp: row.timestamp,
|
|
3470
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
|
|
3471
|
+
}));
|
|
3472
|
+
}
|
|
3473
|
+
async clear(sessionId) {
|
|
3474
|
+
await this.ensureConnected();
|
|
3475
|
+
const stmt = this.db.prepare(`DELETE FROM ${this.tableName} WHERE session_id = ?`);
|
|
3476
|
+
stmt.run(sessionId);
|
|
3477
|
+
}
|
|
3478
|
+
async delete(sessionId) {
|
|
3479
|
+
await this.clear(sessionId);
|
|
3480
|
+
}
|
|
3481
|
+
async exists(sessionId) {
|
|
3482
|
+
await this.ensureConnected();
|
|
3483
|
+
const stmt = this.db.prepare(`SELECT COUNT(*) as count FROM ${this.tableName} WHERE session_id = ?`);
|
|
3484
|
+
const result = stmt.get(sessionId);
|
|
3485
|
+
return result.count > 0;
|
|
3486
|
+
}
|
|
3487
|
+
async disconnect() {
|
|
3488
|
+
if (this._connected && this.db) {
|
|
3489
|
+
this.db.close();
|
|
3490
|
+
this._connected = false;
|
|
3491
|
+
this.connectionPromise = null;
|
|
3492
|
+
}
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
3495
|
+
|
|
3496
|
+
class UpstashStorage extends BaseMemoryStorage {
|
|
3497
|
+
constructor(config) {
|
|
3498
|
+
super();
|
|
3499
|
+
this.client = null;
|
|
3500
|
+
this.connectionPromise = null;
|
|
3501
|
+
this.keyPrefix = config.keyPrefix || 'sekuire:memory:';
|
|
3502
|
+
this.config = config;
|
|
3503
|
+
this.connectionPromise = this.connect();
|
|
3504
|
+
}
|
|
3505
|
+
async connect() {
|
|
3506
|
+
if (this._connected)
|
|
3507
|
+
return;
|
|
3508
|
+
if (this.connectionPromise && !this._connected)
|
|
3509
|
+
return this.connectionPromise;
|
|
3510
|
+
try {
|
|
3511
|
+
const { Redis } = await import('@upstash/redis');
|
|
3512
|
+
this.client = new Redis({
|
|
3513
|
+
url: this.config.url,
|
|
3514
|
+
token: this.config.token,
|
|
3515
|
+
});
|
|
3516
|
+
this._connected = true;
|
|
3517
|
+
}
|
|
3518
|
+
catch (error) {
|
|
3519
|
+
this.connectionPromise = null;
|
|
3520
|
+
throw new Error(`Failed to initialize Upstash Redis: ${error}`);
|
|
3521
|
+
}
|
|
3522
|
+
}
|
|
3523
|
+
async ensureConnected() {
|
|
3524
|
+
if (!this._connected) {
|
|
3525
|
+
await this.connect();
|
|
3526
|
+
}
|
|
3527
|
+
}
|
|
3528
|
+
getKey(sessionId) {
|
|
3529
|
+
return `${this.keyPrefix}${sessionId}`;
|
|
3530
|
+
}
|
|
3531
|
+
async add(sessionId, message) {
|
|
3532
|
+
await this.ensureConnected();
|
|
3533
|
+
const key = this.getKey(sessionId);
|
|
3534
|
+
await this.client.rpush(key, JSON.stringify(message));
|
|
3535
|
+
}
|
|
3536
|
+
async get(sessionId, limit) {
|
|
3537
|
+
await this.ensureConnected();
|
|
3538
|
+
const key = this.getKey(sessionId);
|
|
3539
|
+
const start = limit ? -limit : 0;
|
|
3540
|
+
const messages = await this.client.lrange(key, start, -1);
|
|
3541
|
+
return messages.map((msg) => JSON.parse(msg));
|
|
3542
|
+
}
|
|
3543
|
+
async clear(sessionId) {
|
|
3544
|
+
await this.ensureConnected();
|
|
3545
|
+
const key = this.getKey(sessionId);
|
|
3546
|
+
await this.client.del(key);
|
|
3547
|
+
}
|
|
3548
|
+
async delete(sessionId) {
|
|
3549
|
+
await this.clear(sessionId);
|
|
3550
|
+
}
|
|
3551
|
+
async exists(sessionId) {
|
|
3552
|
+
await this.ensureConnected();
|
|
3553
|
+
const key = this.getKey(sessionId);
|
|
3554
|
+
return (await this.client.exists(key)) === 1;
|
|
3555
|
+
}
|
|
3556
|
+
async disconnect() {
|
|
3557
|
+
this._connected = false;
|
|
3558
|
+
this.client = null;
|
|
3559
|
+
this.connectionPromise = null;
|
|
3560
|
+
}
|
|
3561
|
+
}
|
|
3562
|
+
|
|
3563
|
+
class CloudflareKVStorage extends BaseMemoryStorage {
|
|
3564
|
+
constructor(config) {
|
|
3565
|
+
super();
|
|
3566
|
+
this.binding = null;
|
|
3567
|
+
this.config = config;
|
|
3568
|
+
this.keyPrefix = config.keyPrefix || 'sekuire:memory:';
|
|
3569
|
+
if (config.binding) {
|
|
3570
|
+
this.binding = config.binding;
|
|
3571
|
+
this.useRestApi = false;
|
|
3572
|
+
this._connected = true;
|
|
3573
|
+
}
|
|
3574
|
+
else if (config.accountId && config.namespaceId && config.apiToken) {
|
|
3575
|
+
this.useRestApi = true;
|
|
3576
|
+
this._connected = true;
|
|
3577
|
+
}
|
|
3578
|
+
else {
|
|
3579
|
+
throw new Error('CloudflareKVConfig requires either a binding (in Workers) or accountId, namespaceId, and apiToken (for REST API)');
|
|
3580
|
+
}
|
|
3581
|
+
}
|
|
3582
|
+
getKey(sessionId) {
|
|
3583
|
+
return `${this.keyPrefix}${sessionId}`;
|
|
3584
|
+
}
|
|
3585
|
+
async kvGet(key) {
|
|
3586
|
+
if (!this.useRestApi) {
|
|
3587
|
+
return this.binding.get(key);
|
|
3588
|
+
}
|
|
3589
|
+
const { accountId, namespaceId, apiToken } = this.config;
|
|
3590
|
+
const url = `https://api.cloudflare.com/client/v4/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/values/${encodeURIComponent(key)}`;
|
|
3591
|
+
const response = await fetch(url, {
|
|
3592
|
+
headers: { Authorization: `Bearer ${apiToken}` },
|
|
3593
|
+
});
|
|
3594
|
+
if (response.status === 404)
|
|
3595
|
+
return null;
|
|
3596
|
+
if (!response.ok)
|
|
3597
|
+
throw new Error(`KV GET failed: ${response.statusText}`);
|
|
3598
|
+
return response.text();
|
|
3599
|
+
}
|
|
3600
|
+
async kvPut(key, value) {
|
|
3601
|
+
if (!this.useRestApi) {
|
|
3602
|
+
await this.binding.put(key, value);
|
|
3603
|
+
return;
|
|
3604
|
+
}
|
|
3605
|
+
const { accountId, namespaceId, apiToken } = this.config;
|
|
3606
|
+
const url = `https://api.cloudflare.com/client/v4/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/values/${encodeURIComponent(key)}`;
|
|
3607
|
+
const response = await fetch(url, {
|
|
3608
|
+
method: 'PUT',
|
|
3609
|
+
headers: {
|
|
3610
|
+
Authorization: `Bearer ${apiToken}`,
|
|
3611
|
+
'Content-Type': 'text/plain',
|
|
3612
|
+
},
|
|
3613
|
+
body: value,
|
|
3614
|
+
});
|
|
3615
|
+
if (!response.ok)
|
|
3616
|
+
throw new Error(`KV PUT failed: ${response.statusText}`);
|
|
3617
|
+
}
|
|
3618
|
+
async kvDelete(key) {
|
|
3619
|
+
if (!this.useRestApi) {
|
|
3620
|
+
await this.binding.delete(key);
|
|
3621
|
+
return;
|
|
3622
|
+
}
|
|
3623
|
+
const { accountId, namespaceId, apiToken } = this.config;
|
|
3624
|
+
const url = `https://api.cloudflare.com/client/v4/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/values/${encodeURIComponent(key)}`;
|
|
3625
|
+
const response = await fetch(url, {
|
|
3626
|
+
method: 'DELETE',
|
|
3627
|
+
headers: { Authorization: `Bearer ${apiToken}` },
|
|
3628
|
+
});
|
|
3629
|
+
if (!response.ok && response.status !== 404) {
|
|
3630
|
+
throw new Error(`KV DELETE failed: ${response.statusText}`);
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
async add(sessionId, message) {
|
|
3634
|
+
const key = this.getKey(sessionId);
|
|
3635
|
+
const existing = await this.kvGet(key);
|
|
3636
|
+
const messages = existing ? JSON.parse(existing) : [];
|
|
3637
|
+
messages.push(message);
|
|
3638
|
+
await this.kvPut(key, JSON.stringify(messages));
|
|
3639
|
+
}
|
|
3640
|
+
async get(sessionId, limit) {
|
|
3641
|
+
const key = this.getKey(sessionId);
|
|
3642
|
+
const data = await this.kvGet(key);
|
|
3643
|
+
if (!data)
|
|
3644
|
+
return [];
|
|
3645
|
+
const messages = JSON.parse(data);
|
|
3646
|
+
if (limit) {
|
|
3647
|
+
return messages.slice(-limit);
|
|
3301
3648
|
}
|
|
3649
|
+
return messages;
|
|
3650
|
+
}
|
|
3651
|
+
async clear(sessionId) {
|
|
3652
|
+
const key = this.getKey(sessionId);
|
|
3653
|
+
await this.kvDelete(key);
|
|
3654
|
+
}
|
|
3655
|
+
async delete(sessionId) {
|
|
3656
|
+
await this.clear(sessionId);
|
|
3657
|
+
}
|
|
3658
|
+
async exists(sessionId) {
|
|
3659
|
+
const key = this.getKey(sessionId);
|
|
3660
|
+
const data = await this.kvGet(key);
|
|
3661
|
+
return data !== null;
|
|
3662
|
+
}
|
|
3663
|
+
async disconnect() {
|
|
3664
|
+
this._connected = false;
|
|
3302
3665
|
}
|
|
3303
3666
|
}
|
|
3304
3667
|
|
|
3668
|
+
class CloudflareD1Storage extends BaseMemoryStorage {
|
|
3669
|
+
constructor(config) {
|
|
3670
|
+
super();
|
|
3671
|
+
this.binding = null;
|
|
3672
|
+
this.tableInitialized = false;
|
|
3673
|
+
this.config = config;
|
|
3674
|
+
this.tableName = config.tableName || 'sekuire_memory';
|
|
3675
|
+
if (config.binding) {
|
|
3676
|
+
this.binding = config.binding;
|
|
3677
|
+
this.useRestApi = false;
|
|
3678
|
+
this._connected = true;
|
|
3679
|
+
}
|
|
3680
|
+
else if (config.accountId && config.databaseId && config.apiToken) {
|
|
3681
|
+
this.useRestApi = true;
|
|
3682
|
+
this._connected = true;
|
|
3683
|
+
}
|
|
3684
|
+
else {
|
|
3685
|
+
throw new Error('CloudflareD1Config requires either a binding (in Workers) or accountId, databaseId, and apiToken (for REST API)');
|
|
3686
|
+
}
|
|
3687
|
+
}
|
|
3688
|
+
async executeQuery(sql, params = []) {
|
|
3689
|
+
if (!this.useRestApi) {
|
|
3690
|
+
const stmt = this.binding.prepare(sql);
|
|
3691
|
+
if (params.length > 0) {
|
|
3692
|
+
return stmt.bind(...params).all();
|
|
3693
|
+
}
|
|
3694
|
+
return stmt.all();
|
|
3695
|
+
}
|
|
3696
|
+
const { accountId, databaseId, apiToken } = this.config;
|
|
3697
|
+
const url = `https://api.cloudflare.com/client/v4/accounts/${accountId}/d1/database/${databaseId}/query`;
|
|
3698
|
+
const response = await fetch(url, {
|
|
3699
|
+
method: 'POST',
|
|
3700
|
+
headers: {
|
|
3701
|
+
Authorization: `Bearer ${apiToken}`,
|
|
3702
|
+
'Content-Type': 'application/json',
|
|
3703
|
+
},
|
|
3704
|
+
body: JSON.stringify({ sql, params }),
|
|
3705
|
+
});
|
|
3706
|
+
if (!response.ok) {
|
|
3707
|
+
const errorText = await response.text();
|
|
3708
|
+
throw new Error(`D1 query failed: ${response.statusText} - ${errorText}`);
|
|
3709
|
+
}
|
|
3710
|
+
const result = await response.json();
|
|
3711
|
+
if (!result.success) {
|
|
3712
|
+
throw new Error(`D1 query failed: ${JSON.stringify(result.errors)}`);
|
|
3713
|
+
}
|
|
3714
|
+
return result.result?.[0] || { results: [] };
|
|
3715
|
+
}
|
|
3716
|
+
async ensureTableExists() {
|
|
3717
|
+
if (this.tableInitialized)
|
|
3718
|
+
return;
|
|
3719
|
+
const createTableSQL = `
|
|
3720
|
+
CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|
3721
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
3722
|
+
session_id TEXT NOT NULL,
|
|
3723
|
+
role TEXT NOT NULL,
|
|
3724
|
+
content TEXT NOT NULL,
|
|
3725
|
+
timestamp INTEGER NOT NULL,
|
|
3726
|
+
metadata TEXT,
|
|
3727
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
3728
|
+
)
|
|
3729
|
+
`;
|
|
3730
|
+
const createIndexSQL = `
|
|
3731
|
+
CREATE INDEX IF NOT EXISTS idx_${this.tableName}_session_id ON ${this.tableName}(session_id)
|
|
3732
|
+
`;
|
|
3733
|
+
await this.executeQuery(createTableSQL);
|
|
3734
|
+
await this.executeQuery(createIndexSQL);
|
|
3735
|
+
this.tableInitialized = true;
|
|
3736
|
+
}
|
|
3737
|
+
async add(sessionId, message) {
|
|
3738
|
+
await this.ensureTableExists();
|
|
3739
|
+
const sql = `
|
|
3740
|
+
INSERT INTO ${this.tableName} (session_id, role, content, timestamp, metadata)
|
|
3741
|
+
VALUES (?, ?, ?, ?, ?)
|
|
3742
|
+
`;
|
|
3743
|
+
await this.executeQuery(sql, [
|
|
3744
|
+
sessionId,
|
|
3745
|
+
message.role,
|
|
3746
|
+
message.content,
|
|
3747
|
+
message.timestamp,
|
|
3748
|
+
message.metadata ? JSON.stringify(message.metadata) : null,
|
|
3749
|
+
]);
|
|
3750
|
+
}
|
|
3751
|
+
async get(sessionId, limit) {
|
|
3752
|
+
await this.ensureTableExists();
|
|
3753
|
+
let sql = `
|
|
3754
|
+
SELECT role, content, timestamp, metadata
|
|
3755
|
+
FROM ${this.tableName}
|
|
3756
|
+
WHERE session_id = ?
|
|
3757
|
+
ORDER BY timestamp ASC
|
|
3758
|
+
`;
|
|
3759
|
+
if (limit) {
|
|
3760
|
+
sql += ` LIMIT ${limit}`;
|
|
3761
|
+
}
|
|
3762
|
+
const result = await this.executeQuery(sql, [sessionId]);
|
|
3763
|
+
return (result.results || []).map((row) => ({
|
|
3764
|
+
role: row.role,
|
|
3765
|
+
content: row.content,
|
|
3766
|
+
timestamp: row.timestamp,
|
|
3767
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
|
|
3768
|
+
}));
|
|
3769
|
+
}
|
|
3770
|
+
async clear(sessionId) {
|
|
3771
|
+
await this.ensureTableExists();
|
|
3772
|
+
const sql = `DELETE FROM ${this.tableName} WHERE session_id = ?`;
|
|
3773
|
+
await this.executeQuery(sql, [sessionId]);
|
|
3774
|
+
}
|
|
3775
|
+
async delete(sessionId) {
|
|
3776
|
+
await this.clear(sessionId);
|
|
3777
|
+
}
|
|
3778
|
+
async exists(sessionId) {
|
|
3779
|
+
await this.ensureTableExists();
|
|
3780
|
+
const sql = `SELECT COUNT(*) as count FROM ${this.tableName} WHERE session_id = ?`;
|
|
3781
|
+
const result = await this.executeQuery(sql, [sessionId]);
|
|
3782
|
+
return (result.results?.[0]?.count || 0) > 0;
|
|
3783
|
+
}
|
|
3784
|
+
async disconnect() {
|
|
3785
|
+
this._connected = false;
|
|
3786
|
+
}
|
|
3787
|
+
}
|
|
3788
|
+
|
|
3789
|
+
class DynamoDBStorage extends BaseMemoryStorage {
|
|
3790
|
+
constructor(config) {
|
|
3791
|
+
super();
|
|
3792
|
+
this.client = null;
|
|
3793
|
+
this.docClient = null;
|
|
3794
|
+
this.connectionPromise = null;
|
|
3795
|
+
this.tableReady = false;
|
|
3796
|
+
this.config = config;
|
|
3797
|
+
this.connectionPromise = this.connect();
|
|
3798
|
+
}
|
|
3799
|
+
async connect() {
|
|
3800
|
+
if (this._connected)
|
|
3801
|
+
return;
|
|
3802
|
+
if (this.connectionPromise && !this._connected)
|
|
3803
|
+
return this.connectionPromise;
|
|
3804
|
+
try {
|
|
3805
|
+
const { DynamoDBClient, CreateTableCommand, DescribeTableCommand } = await import('@aws-sdk/client-dynamodb');
|
|
3806
|
+
const { DynamoDBDocumentClient, PutCommand, QueryCommand, DeleteCommand } = await import('@aws-sdk/lib-dynamodb');
|
|
3807
|
+
const clientConfig = {
|
|
3808
|
+
region: this.config.region || process.env.AWS_REGION || 'us-east-1',
|
|
3809
|
+
};
|
|
3810
|
+
if (this.config.endpoint) {
|
|
3811
|
+
clientConfig.endpoint = this.config.endpoint;
|
|
3812
|
+
}
|
|
3813
|
+
if (this.config.credentials) {
|
|
3814
|
+
clientConfig.credentials = this.config.credentials;
|
|
3815
|
+
}
|
|
3816
|
+
this.client = new DynamoDBClient(clientConfig);
|
|
3817
|
+
this.docClient = DynamoDBDocumentClient.from(this.client);
|
|
3818
|
+
if (this.config.createTable) {
|
|
3819
|
+
await this.ensureTableExists();
|
|
3820
|
+
}
|
|
3821
|
+
this._connected = true;
|
|
3822
|
+
}
|
|
3823
|
+
catch (error) {
|
|
3824
|
+
this.connectionPromise = null;
|
|
3825
|
+
throw new Error(`Failed to initialize DynamoDB: ${error}`);
|
|
3826
|
+
}
|
|
3827
|
+
}
|
|
3828
|
+
async ensureTableExists() {
|
|
3829
|
+
if (this.tableReady)
|
|
3830
|
+
return;
|
|
3831
|
+
try {
|
|
3832
|
+
const { DescribeTableCommand } = await import('@aws-sdk/client-dynamodb');
|
|
3833
|
+
await this.client.send(new DescribeTableCommand({ TableName: this.config.tableName }));
|
|
3834
|
+
this.tableReady = true;
|
|
3835
|
+
return;
|
|
3836
|
+
}
|
|
3837
|
+
catch (error) {
|
|
3838
|
+
if (error.name !== 'ResourceNotFoundException') {
|
|
3839
|
+
throw error;
|
|
3840
|
+
}
|
|
3841
|
+
}
|
|
3842
|
+
const { CreateTableCommand } = await import('@aws-sdk/client-dynamodb');
|
|
3843
|
+
await this.client.send(new CreateTableCommand({
|
|
3844
|
+
TableName: this.config.tableName,
|
|
3845
|
+
KeySchema: [
|
|
3846
|
+
{ AttributeName: 'sessionId', KeyType: 'HASH' },
|
|
3847
|
+
{ AttributeName: 'timestamp', KeyType: 'RANGE' },
|
|
3848
|
+
],
|
|
3849
|
+
AttributeDefinitions: [
|
|
3850
|
+
{ AttributeName: 'sessionId', AttributeType: 'S' },
|
|
3851
|
+
{ AttributeName: 'timestamp', AttributeType: 'N' },
|
|
3852
|
+
],
|
|
3853
|
+
BillingMode: 'PAY_PER_REQUEST',
|
|
3854
|
+
}));
|
|
3855
|
+
const { waitUntilTableExists } = await import('@aws-sdk/client-dynamodb');
|
|
3856
|
+
await waitUntilTableExists({ client: this.client, maxWaitTime: 120 }, { TableName: this.config.tableName });
|
|
3857
|
+
this.tableReady = true;
|
|
3858
|
+
}
|
|
3859
|
+
async ensureConnected() {
|
|
3860
|
+
if (!this._connected) {
|
|
3861
|
+
await this.connect();
|
|
3862
|
+
}
|
|
3863
|
+
}
|
|
3864
|
+
async add(sessionId, message) {
|
|
3865
|
+
await this.ensureConnected();
|
|
3866
|
+
const { PutCommand } = await import('@aws-sdk/lib-dynamodb');
|
|
3867
|
+
await this.docClient.send(new PutCommand({
|
|
3868
|
+
TableName: this.config.tableName,
|
|
3869
|
+
Item: {
|
|
3870
|
+
sessionId,
|
|
3871
|
+
timestamp: message.timestamp,
|
|
3872
|
+
role: message.role,
|
|
3873
|
+
content: message.content,
|
|
3874
|
+
metadata: message.metadata || {},
|
|
3875
|
+
},
|
|
3876
|
+
}));
|
|
3877
|
+
}
|
|
3878
|
+
async get(sessionId, limit) {
|
|
3879
|
+
await this.ensureConnected();
|
|
3880
|
+
const { QueryCommand } = await import('@aws-sdk/lib-dynamodb');
|
|
3881
|
+
const params = {
|
|
3882
|
+
TableName: this.config.tableName,
|
|
3883
|
+
KeyConditionExpression: 'sessionId = :sid',
|
|
3884
|
+
ExpressionAttributeValues: {
|
|
3885
|
+
':sid': sessionId,
|
|
3886
|
+
},
|
|
3887
|
+
ScanIndexForward: true,
|
|
3888
|
+
};
|
|
3889
|
+
if (limit) {
|
|
3890
|
+
params.Limit = limit;
|
|
3891
|
+
params.ScanIndexForward = false;
|
|
3892
|
+
}
|
|
3893
|
+
const result = await this.docClient.send(new QueryCommand(params));
|
|
3894
|
+
const items = result.Items || [];
|
|
3895
|
+
const messages = items.map((item) => ({
|
|
3896
|
+
role: item.role,
|
|
3897
|
+
content: item.content,
|
|
3898
|
+
timestamp: item.timestamp,
|
|
3899
|
+
metadata: item.metadata && Object.keys(item.metadata).length > 0 ? item.metadata : undefined,
|
|
3900
|
+
}));
|
|
3901
|
+
if (limit) {
|
|
3902
|
+
messages.reverse();
|
|
3903
|
+
}
|
|
3904
|
+
return messages;
|
|
3905
|
+
}
|
|
3906
|
+
async clear(sessionId) {
|
|
3907
|
+
await this.ensureConnected();
|
|
3908
|
+
const messages = await this.get(sessionId);
|
|
3909
|
+
const { DeleteCommand } = await import('@aws-sdk/lib-dynamodb');
|
|
3910
|
+
for (const message of messages) {
|
|
3911
|
+
await this.docClient.send(new DeleteCommand({
|
|
3912
|
+
TableName: this.config.tableName,
|
|
3913
|
+
Key: {
|
|
3914
|
+
sessionId,
|
|
3915
|
+
timestamp: message.timestamp,
|
|
3916
|
+
},
|
|
3917
|
+
}));
|
|
3918
|
+
}
|
|
3919
|
+
}
|
|
3920
|
+
async delete(sessionId) {
|
|
3921
|
+
await this.clear(sessionId);
|
|
3922
|
+
}
|
|
3923
|
+
async exists(sessionId) {
|
|
3924
|
+
await this.ensureConnected();
|
|
3925
|
+
const { QueryCommand } = await import('@aws-sdk/lib-dynamodb');
|
|
3926
|
+
const result = await this.docClient.send(new QueryCommand({
|
|
3927
|
+
TableName: this.config.tableName,
|
|
3928
|
+
KeyConditionExpression: 'sessionId = :sid',
|
|
3929
|
+
ExpressionAttributeValues: {
|
|
3930
|
+
':sid': sessionId,
|
|
3931
|
+
},
|
|
3932
|
+
Limit: 1,
|
|
3933
|
+
Select: 'COUNT',
|
|
3934
|
+
}));
|
|
3935
|
+
return (result.Count || 0) > 0;
|
|
3936
|
+
}
|
|
3937
|
+
async disconnect() {
|
|
3938
|
+
if (this._connected && this.client) {
|
|
3939
|
+
this.client.destroy();
|
|
3940
|
+
this._connected = false;
|
|
3941
|
+
this.connectionPromise = null;
|
|
3942
|
+
}
|
|
3943
|
+
}
|
|
3944
|
+
}
|
|
3945
|
+
|
|
3946
|
+
class TursoStorage extends BaseMemoryStorage {
|
|
3947
|
+
constructor(config) {
|
|
3948
|
+
super();
|
|
3949
|
+
this.client = null;
|
|
3950
|
+
this.connectionPromise = null;
|
|
3951
|
+
this.tableInitialized = false;
|
|
3952
|
+
this.config = config;
|
|
3953
|
+
this.tableName = config.tableName || 'sekuire_memory';
|
|
3954
|
+
this.connectionPromise = this.connect();
|
|
3955
|
+
}
|
|
3956
|
+
async connect() {
|
|
3957
|
+
if (this._connected)
|
|
3958
|
+
return;
|
|
3959
|
+
if (this.connectionPromise && !this._connected)
|
|
3960
|
+
return this.connectionPromise;
|
|
3961
|
+
try {
|
|
3962
|
+
const { createClient } = await import('@libsql/client');
|
|
3963
|
+
this.client = createClient({
|
|
3964
|
+
url: this.config.url,
|
|
3965
|
+
authToken: this.config.authToken,
|
|
3966
|
+
});
|
|
3967
|
+
await this.ensureTableExists();
|
|
3968
|
+
this._connected = true;
|
|
3969
|
+
}
|
|
3970
|
+
catch (error) {
|
|
3971
|
+
this.connectionPromise = null;
|
|
3972
|
+
throw new Error(`Failed to initialize Turso: ${error}`);
|
|
3973
|
+
}
|
|
3974
|
+
}
|
|
3975
|
+
async ensureTableExists() {
|
|
3976
|
+
if (this.tableInitialized)
|
|
3977
|
+
return;
|
|
3978
|
+
await this.client.execute(`
|
|
3979
|
+
CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|
3980
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
3981
|
+
session_id TEXT NOT NULL,
|
|
3982
|
+
role TEXT NOT NULL,
|
|
3983
|
+
content TEXT NOT NULL,
|
|
3984
|
+
timestamp INTEGER NOT NULL,
|
|
3985
|
+
metadata TEXT,
|
|
3986
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
3987
|
+
)
|
|
3988
|
+
`);
|
|
3989
|
+
await this.client.execute(`
|
|
3990
|
+
CREATE INDEX IF NOT EXISTS idx_${this.tableName}_session_id ON ${this.tableName}(session_id)
|
|
3991
|
+
`);
|
|
3992
|
+
this.tableInitialized = true;
|
|
3993
|
+
}
|
|
3994
|
+
async ensureConnected() {
|
|
3995
|
+
if (!this._connected) {
|
|
3996
|
+
await this.connect();
|
|
3997
|
+
}
|
|
3998
|
+
}
|
|
3999
|
+
async add(sessionId, message) {
|
|
4000
|
+
await this.ensureConnected();
|
|
4001
|
+
await this.client.execute({
|
|
4002
|
+
sql: `
|
|
4003
|
+
INSERT INTO ${this.tableName} (session_id, role, content, timestamp, metadata)
|
|
4004
|
+
VALUES (?, ?, ?, ?, ?)
|
|
4005
|
+
`,
|
|
4006
|
+
args: [
|
|
4007
|
+
sessionId,
|
|
4008
|
+
message.role,
|
|
4009
|
+
message.content,
|
|
4010
|
+
message.timestamp,
|
|
4011
|
+
message.metadata ? JSON.stringify(message.metadata) : null,
|
|
4012
|
+
],
|
|
4013
|
+
});
|
|
4014
|
+
}
|
|
4015
|
+
async get(sessionId, limit) {
|
|
4016
|
+
await this.ensureConnected();
|
|
4017
|
+
let sql = `
|
|
4018
|
+
SELECT role, content, timestamp, metadata
|
|
4019
|
+
FROM ${this.tableName}
|
|
4020
|
+
WHERE session_id = ?
|
|
4021
|
+
ORDER BY timestamp ASC
|
|
4022
|
+
`;
|
|
4023
|
+
if (limit) {
|
|
4024
|
+
sql += ` LIMIT ${limit}`;
|
|
4025
|
+
}
|
|
4026
|
+
const result = await this.client.execute({
|
|
4027
|
+
sql,
|
|
4028
|
+
args: [sessionId],
|
|
4029
|
+
});
|
|
4030
|
+
return result.rows.map((row) => ({
|
|
4031
|
+
role: row.role,
|
|
4032
|
+
content: row.content,
|
|
4033
|
+
timestamp: Number(row.timestamp),
|
|
4034
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
|
|
4035
|
+
}));
|
|
4036
|
+
}
|
|
4037
|
+
async clear(sessionId) {
|
|
4038
|
+
await this.ensureConnected();
|
|
4039
|
+
await this.client.execute({
|
|
4040
|
+
sql: `DELETE FROM ${this.tableName} WHERE session_id = ?`,
|
|
4041
|
+
args: [sessionId],
|
|
4042
|
+
});
|
|
4043
|
+
}
|
|
4044
|
+
async delete(sessionId) {
|
|
4045
|
+
await this.clear(sessionId);
|
|
4046
|
+
}
|
|
4047
|
+
async exists(sessionId) {
|
|
4048
|
+
await this.ensureConnected();
|
|
4049
|
+
const result = await this.client.execute({
|
|
4050
|
+
sql: `SELECT COUNT(*) as count FROM ${this.tableName} WHERE session_id = ?`,
|
|
4051
|
+
args: [sessionId],
|
|
4052
|
+
});
|
|
4053
|
+
return Number(result.rows[0]?.count || 0) > 0;
|
|
4054
|
+
}
|
|
4055
|
+
async disconnect() {
|
|
4056
|
+
if (this._connected && this.client) {
|
|
4057
|
+
this.client.close();
|
|
4058
|
+
this._connected = false;
|
|
4059
|
+
this.connectionPromise = null;
|
|
4060
|
+
}
|
|
4061
|
+
}
|
|
4062
|
+
}
|
|
4063
|
+
|
|
4064
|
+
class ConvexStorage extends BaseMemoryStorage {
|
|
4065
|
+
constructor(config) {
|
|
4066
|
+
super();
|
|
4067
|
+
this.client = null;
|
|
4068
|
+
this.connectionPromise = null;
|
|
4069
|
+
this.config = config;
|
|
4070
|
+
this.connectionPromise = this.connect();
|
|
4071
|
+
}
|
|
4072
|
+
async connect() {
|
|
4073
|
+
if (this._connected)
|
|
4074
|
+
return;
|
|
4075
|
+
if (this.connectionPromise && !this._connected)
|
|
4076
|
+
return this.connectionPromise;
|
|
4077
|
+
try {
|
|
4078
|
+
const { ConvexHttpClient } = await import('convex/browser');
|
|
4079
|
+
this.client = new ConvexHttpClient(this.config.url);
|
|
4080
|
+
this._connected = true;
|
|
4081
|
+
}
|
|
4082
|
+
catch (error) {
|
|
4083
|
+
this.connectionPromise = null;
|
|
4084
|
+
throw new Error(`Failed to initialize Convex: ${error}`);
|
|
4085
|
+
}
|
|
4086
|
+
}
|
|
4087
|
+
async ensureConnected() {
|
|
4088
|
+
if (!this._connected) {
|
|
4089
|
+
await this.connect();
|
|
4090
|
+
}
|
|
4091
|
+
}
|
|
4092
|
+
async add(sessionId, message) {
|
|
4093
|
+
await this.ensureConnected();
|
|
4094
|
+
await this.client.mutation('sekuireMemory:add', {
|
|
4095
|
+
sessionId,
|
|
4096
|
+
role: message.role,
|
|
4097
|
+
content: message.content,
|
|
4098
|
+
timestamp: message.timestamp,
|
|
4099
|
+
metadata: message.metadata || {},
|
|
4100
|
+
});
|
|
4101
|
+
}
|
|
4102
|
+
async get(sessionId, limit) {
|
|
4103
|
+
await this.ensureConnected();
|
|
4104
|
+
const messages = await this.client.query('sekuireMemory:get', {
|
|
4105
|
+
sessionId,
|
|
4106
|
+
limit,
|
|
4107
|
+
});
|
|
4108
|
+
return messages.map((msg) => ({
|
|
4109
|
+
role: msg.role,
|
|
4110
|
+
content: msg.content,
|
|
4111
|
+
timestamp: msg.timestamp,
|
|
4112
|
+
metadata: msg.metadata && Object.keys(msg.metadata).length > 0 ? msg.metadata : undefined,
|
|
4113
|
+
}));
|
|
4114
|
+
}
|
|
4115
|
+
async clear(sessionId) {
|
|
4116
|
+
await this.ensureConnected();
|
|
4117
|
+
await this.client.mutation('sekuireMemory:clear', { sessionId });
|
|
4118
|
+
}
|
|
4119
|
+
async delete(sessionId) {
|
|
4120
|
+
await this.clear(sessionId);
|
|
4121
|
+
}
|
|
4122
|
+
async exists(sessionId) {
|
|
4123
|
+
await this.ensureConnected();
|
|
4124
|
+
return this.client.query('sekuireMemory:exists', { sessionId });
|
|
4125
|
+
}
|
|
4126
|
+
async disconnect() {
|
|
4127
|
+
this._connected = false;
|
|
4128
|
+
this.client = null;
|
|
4129
|
+
this.connectionPromise = null;
|
|
4130
|
+
}
|
|
4131
|
+
}
|
|
4132
|
+
const CONVEX_SCHEMA_TEMPLATE = `
|
|
4133
|
+
// convex/schema.ts
|
|
4134
|
+
import { defineSchema, defineTable } from "convex/server";
|
|
4135
|
+
import { v } from "convex/values";
|
|
4136
|
+
|
|
4137
|
+
export default defineSchema({
|
|
4138
|
+
sekuireMemory: defineTable({
|
|
4139
|
+
sessionId: v.string(),
|
|
4140
|
+
role: v.union(v.literal("user"), v.literal("assistant"), v.literal("system")),
|
|
4141
|
+
content: v.string(),
|
|
4142
|
+
timestamp: v.number(),
|
|
4143
|
+
metadata: v.optional(v.any()),
|
|
4144
|
+
}).index("by_session", ["sessionId", "timestamp"]),
|
|
4145
|
+
});
|
|
4146
|
+
`;
|
|
4147
|
+
const CONVEX_FUNCTIONS_TEMPLATE = `
|
|
4148
|
+
// convex/sekuireMemory.ts
|
|
4149
|
+
import { mutation, query } from "./_generated/server";
|
|
4150
|
+
import { v } from "convex/values";
|
|
4151
|
+
|
|
4152
|
+
export const add = mutation({
|
|
4153
|
+
args: {
|
|
4154
|
+
sessionId: v.string(),
|
|
4155
|
+
role: v.union(v.literal("user"), v.literal("assistant"), v.literal("system")),
|
|
4156
|
+
content: v.string(),
|
|
4157
|
+
timestamp: v.number(),
|
|
4158
|
+
metadata: v.optional(v.any()),
|
|
4159
|
+
},
|
|
4160
|
+
handler: async (ctx, args) => {
|
|
4161
|
+
await ctx.db.insert("sekuireMemory", args);
|
|
4162
|
+
},
|
|
4163
|
+
});
|
|
4164
|
+
|
|
4165
|
+
export const get = query({
|
|
4166
|
+
args: {
|
|
4167
|
+
sessionId: v.string(),
|
|
4168
|
+
limit: v.optional(v.number()),
|
|
4169
|
+
},
|
|
4170
|
+
handler: async (ctx, args) => {
|
|
4171
|
+
let query = ctx.db
|
|
4172
|
+
.query("sekuireMemory")
|
|
4173
|
+
.withIndex("by_session", (q) => q.eq("sessionId", args.sessionId))
|
|
4174
|
+
.order("asc");
|
|
4175
|
+
|
|
4176
|
+
const messages = await query.collect();
|
|
4177
|
+
|
|
4178
|
+
if (args.limit) {
|
|
4179
|
+
return messages.slice(-args.limit);
|
|
4180
|
+
}
|
|
4181
|
+
return messages;
|
|
4182
|
+
},
|
|
4183
|
+
});
|
|
4184
|
+
|
|
4185
|
+
export const clear = mutation({
|
|
4186
|
+
args: {
|
|
4187
|
+
sessionId: v.string(),
|
|
4188
|
+
},
|
|
4189
|
+
handler: async (ctx, args) => {
|
|
4190
|
+
const messages = await ctx.db
|
|
4191
|
+
.query("sekuireMemory")
|
|
4192
|
+
.withIndex("by_session", (q) => q.eq("sessionId", args.sessionId))
|
|
4193
|
+
.collect();
|
|
4194
|
+
|
|
4195
|
+
for (const message of messages) {
|
|
4196
|
+
await ctx.db.delete(message._id);
|
|
4197
|
+
}
|
|
4198
|
+
},
|
|
4199
|
+
});
|
|
4200
|
+
|
|
4201
|
+
export const exists = query({
|
|
4202
|
+
args: {
|
|
4203
|
+
sessionId: v.string(),
|
|
4204
|
+
},
|
|
4205
|
+
handler: async (ctx, args) => {
|
|
4206
|
+
const message = await ctx.db
|
|
4207
|
+
.query("sekuireMemory")
|
|
4208
|
+
.withIndex("by_session", (q) => q.eq("sessionId", args.sessionId))
|
|
4209
|
+
.first();
|
|
4210
|
+
return message !== null;
|
|
4211
|
+
},
|
|
4212
|
+
});
|
|
4213
|
+
`;
|
|
4214
|
+
|
|
4215
|
+
const storageRegistry = new Map();
|
|
4216
|
+
function registerStorage(type, factory, description) {
|
|
4217
|
+
if (storageRegistry.has(type)) {
|
|
4218
|
+
console.warn(`Storage type '${type}' is already registered. Overwriting.`);
|
|
4219
|
+
}
|
|
4220
|
+
storageRegistry.set(type, { factory, description });
|
|
4221
|
+
}
|
|
4222
|
+
function getStorageFactory(type) {
|
|
4223
|
+
const registration = storageRegistry.get(type);
|
|
4224
|
+
return registration?.factory;
|
|
4225
|
+
}
|
|
4226
|
+
function hasStorage(type) {
|
|
4227
|
+
return storageRegistry.has(type);
|
|
4228
|
+
}
|
|
4229
|
+
function listStorageTypes() {
|
|
4230
|
+
return Array.from(storageRegistry.keys());
|
|
4231
|
+
}
|
|
4232
|
+
function getStorageInfo() {
|
|
4233
|
+
return Array.from(storageRegistry.entries()).map(([type, reg]) => ({
|
|
4234
|
+
type,
|
|
4235
|
+
description: reg.description,
|
|
4236
|
+
}));
|
|
4237
|
+
}
|
|
4238
|
+
|
|
4239
|
+
registerStorage('in-memory', () => new InMemoryStorage(), 'Simple in-memory storage using Map');
|
|
4240
|
+
registerStorage('buffer', () => new InMemoryStorage(), 'Alias for in-memory storage');
|
|
4241
|
+
registerStorage('redis', (config) => {
|
|
4242
|
+
if (!config.redis) {
|
|
4243
|
+
throw new Error('Redis config required for redis memory type');
|
|
4244
|
+
}
|
|
4245
|
+
return new RedisStorage(config.redis);
|
|
4246
|
+
}, 'Self-hosted Redis for persistence and scaling');
|
|
4247
|
+
registerStorage('postgres', (config) => {
|
|
4248
|
+
if (!config.postgres) {
|
|
4249
|
+
throw new Error('Postgres config required for postgres memory type');
|
|
4250
|
+
}
|
|
4251
|
+
return new PostgresStorage(config.postgres);
|
|
4252
|
+
}, 'PostgreSQL with full SQL capabilities');
|
|
4253
|
+
registerStorage('sqlite', (config) => {
|
|
4254
|
+
if (!config.sqlite) {
|
|
4255
|
+
throw new Error('SQLite config required for sqlite memory type');
|
|
4256
|
+
}
|
|
4257
|
+
return new SQLiteStorage(config.sqlite);
|
|
4258
|
+
}, 'Local SQLite database for development and single-node deployments');
|
|
4259
|
+
registerStorage('upstash', (config) => {
|
|
4260
|
+
if (!config.upstash) {
|
|
4261
|
+
throw new Error('Upstash config required for upstash memory type');
|
|
4262
|
+
}
|
|
4263
|
+
return new UpstashStorage(config.upstash);
|
|
4264
|
+
}, 'Upstash Redis - serverless Redis with HTTP API');
|
|
4265
|
+
registerStorage('cloudflare-kv', (config) => {
|
|
4266
|
+
if (!config.cloudflareKV) {
|
|
4267
|
+
throw new Error('CloudflareKV config required for cloudflare-kv memory type');
|
|
4268
|
+
}
|
|
4269
|
+
return new CloudflareKVStorage(config.cloudflareKV);
|
|
4270
|
+
}, 'Cloudflare KV - edge key-value storage');
|
|
4271
|
+
registerStorage('cloudflare-d1', (config) => {
|
|
4272
|
+
if (!config.cloudflareD1) {
|
|
4273
|
+
throw new Error('CloudflareD1 config required for cloudflare-d1 memory type');
|
|
4274
|
+
}
|
|
4275
|
+
return new CloudflareD1Storage(config.cloudflareD1);
|
|
4276
|
+
}, 'Cloudflare D1 - edge SQLite database');
|
|
4277
|
+
registerStorage('dynamodb', (config) => {
|
|
4278
|
+
if (!config.dynamodb) {
|
|
4279
|
+
throw new Error('DynamoDB config required for dynamodb memory type');
|
|
4280
|
+
}
|
|
4281
|
+
return new DynamoDBStorage(config.dynamodb);
|
|
4282
|
+
}, 'AWS DynamoDB - serverless NoSQL database');
|
|
4283
|
+
registerStorage('turso', (config) => {
|
|
4284
|
+
if (!config.turso) {
|
|
4285
|
+
throw new Error('Turso config required for turso memory type');
|
|
4286
|
+
}
|
|
4287
|
+
return new TursoStorage(config.turso);
|
|
4288
|
+
}, 'Turso - edge SQLite with libSQL');
|
|
4289
|
+
registerStorage('convex', (config) => {
|
|
4290
|
+
if (!config.convex) {
|
|
4291
|
+
throw new Error('Convex config required for convex memory type');
|
|
4292
|
+
}
|
|
4293
|
+
return new ConvexStorage(config.convex);
|
|
4294
|
+
}, 'Convex - real-time backend platform');
|
|
3305
4295
|
function createMemoryStorage(config) {
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
if (!config.redis) {
|
|
3313
|
-
throw new Error('Redis config required for redis memory type');
|
|
3314
|
-
}
|
|
3315
|
-
return new RedisStorage(config.redis);
|
|
3316
|
-
case 'postgres':
|
|
3317
|
-
if (!config.postgres) {
|
|
3318
|
-
throw new Error('Postgres config required for postgres memory type');
|
|
3319
|
-
}
|
|
3320
|
-
return new PostgresStorage(config.postgres);
|
|
3321
|
-
default:
|
|
3322
|
-
throw new Error(`Unknown memory type: ${config.type}`);
|
|
4296
|
+
if (config.instance) {
|
|
4297
|
+
return config.instance;
|
|
4298
|
+
}
|
|
4299
|
+
const factory = getStorageFactory(config.type);
|
|
4300
|
+
if (factory) {
|
|
4301
|
+
return factory(config);
|
|
3323
4302
|
}
|
|
4303
|
+
throw new Error(`Unknown memory type: ${config.type}. Available types: ${listStorageTypes().join(', ')}`);
|
|
3324
4304
|
}
|
|
3325
4305
|
|
|
3326
4306
|
const DEFAULT_OVERRIDE_ENV = "SEKUIRE_POLICY_DEV_OVERRIDE";
|
|
@@ -16242,12 +17222,19 @@ exports.A2ATaskDelegator = A2ATaskDelegator;
|
|
|
16242
17222
|
exports.Agent = SekuireAgent;
|
|
16243
17223
|
exports.AgentIdentity = AgentIdentity;
|
|
16244
17224
|
exports.AnthropicProvider = AnthropicProvider;
|
|
17225
|
+
exports.BaseMemoryStorage = BaseMemoryStorage;
|
|
16245
17226
|
exports.Beacon = Beacon;
|
|
17227
|
+
exports.CONVEX_FUNCTIONS_TEMPLATE = CONVEX_FUNCTIONS_TEMPLATE;
|
|
17228
|
+
exports.CONVEX_SCHEMA_TEMPLATE = CONVEX_SCHEMA_TEMPLATE;
|
|
17229
|
+
exports.CloudflareD1Storage = CloudflareD1Storage;
|
|
17230
|
+
exports.CloudflareKVStorage = CloudflareKVStorage;
|
|
16246
17231
|
exports.ComplianceError = ComplianceError;
|
|
16247
17232
|
exports.ComplianceMonitor = ComplianceMonitor;
|
|
16248
17233
|
exports.ContentPolicyError = ContentPolicyError;
|
|
17234
|
+
exports.ConvexStorage = ConvexStorage;
|
|
16249
17235
|
exports.CryptoError = CryptoError;
|
|
16250
17236
|
exports.DEFAULT_API_URL = DEFAULT_API_URL;
|
|
17237
|
+
exports.DynamoDBStorage = DynamoDBStorage;
|
|
16251
17238
|
exports.FileAccessError = FileAccessError;
|
|
16252
17239
|
exports.GoogleProvider = GoogleProvider;
|
|
16253
17240
|
exports.InMemoryStorage = InMemoryStorage;
|
|
@@ -16261,6 +17248,7 @@ exports.PolicyViolationError = PolicyViolationError;
|
|
|
16261
17248
|
exports.PostgresStorage = PostgresStorage;
|
|
16262
17249
|
exports.ProtocolError = ProtocolError;
|
|
16263
17250
|
exports.RedisStorage = RedisStorage;
|
|
17251
|
+
exports.SQLiteStorage = SQLiteStorage;
|
|
16264
17252
|
exports.SekuireAgent = SekuireAgent$1;
|
|
16265
17253
|
exports.SekuireAgentBuilder = SekuireAgentBuilder;
|
|
16266
17254
|
exports.SekuireClient = SekuireClient;
|
|
@@ -16275,6 +17263,8 @@ exports.TaskWorker = TaskWorker;
|
|
|
16275
17263
|
exports.ToolPatternParser = ToolPatternParser;
|
|
16276
17264
|
exports.ToolRegistry = ToolRegistry;
|
|
16277
17265
|
exports.ToolUsageError = ToolUsageError;
|
|
17266
|
+
exports.TursoStorage = TursoStorage;
|
|
17267
|
+
exports.UpstashStorage = UpstashStorage;
|
|
16278
17268
|
exports.builtInTools = builtInTools;
|
|
16279
17269
|
exports.calculateSekuireId = calculateSekuireId;
|
|
16280
17270
|
exports.createAgent = createAgent;
|
|
@@ -16296,14 +17286,18 @@ exports.getAgent = getAgent;
|
|
|
16296
17286
|
exports.getAgentConfig = getAgentConfig;
|
|
16297
17287
|
exports.getAgents = getAgents;
|
|
16298
17288
|
exports.getLegacyTools = getTools$1;
|
|
17289
|
+
exports.getStorageInfo = getStorageInfo;
|
|
16299
17290
|
exports.getSystemPrompt = getSystemPrompt;
|
|
16300
17291
|
exports.getTools = getTools;
|
|
16301
17292
|
exports.getTracer = getTracer;
|
|
17293
|
+
exports.hasStorage = hasStorage;
|
|
16302
17294
|
exports.initTelemetry = initTelemetry;
|
|
17295
|
+
exports.listStorageTypes = listStorageTypes;
|
|
16303
17296
|
exports.llm = llm;
|
|
16304
17297
|
exports.loadConfig = loadConfig;
|
|
16305
17298
|
exports.loadSystemPrompt = loadSystemPrompt;
|
|
16306
17299
|
exports.loadTools = loadTools;
|
|
17300
|
+
exports.registerStorage = registerStorage;
|
|
16307
17301
|
exports.shutdownTelemetry = shutdownTelemetry;
|
|
16308
17302
|
exports.tool = tool;
|
|
16309
17303
|
exports.tools = tools;
|