@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/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
- heartbeatIntervalMs: config.heartbeatIntervalMs ?? 60000, // 60 seconds
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 SEKUIRE_API_KEY environment variable.');
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
- // Continue anyway - heartbeat will retry registration
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
- }, this.config.heartbeatIntervalMs);
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.apiKey) {
182
- throw new Error('API key required for beacon registration');
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
- agent_id: this.config.agentId,
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
- throw new Error(`Bootstrap failed: ${response.status} ${response.statusText}`);
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
- console.log(`[Beacon] Registered with installation ID: ${this.installationId}`);
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.config.apiKey) {
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 payload = this.config.onHeartbeat
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
- 'X-API-Key': this.config.apiKey,
241
+ 'Authorization': `Bearer ${this.runtimeToken}`,
225
242
  },
226
- body: JSON.stringify(payload),
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
- throw new Error(`Heartbeat failed: ${response.status}`);
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 && this.config.onStatusChange) {
234
- this.config.onStatusChange(this.getStatus());
262
+ if (prevFailed > 0) {
263
+ this.notifyStatusChange();
235
264
  }
236
265
  }
237
266
  catch (error) {
238
267
  this.failedHeartbeats++;
239
- if (this.config.onStatusChange) {
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
- heartbeatInterval: config.heartbeatInterval ?? 60000,
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
- heartbeatIntervalMs: this.config.heartbeatInterval,
789
- onHeartbeat: this.config.privateKey
790
- ? async (deploymentUrl) => {
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 (required for heartbeat)
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
- * - Sends initial heartbeat
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 && (this.config.privateKey || this.config.apiKey)) {
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
- * Send a signed heartbeat to the registry.
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
- * @param url Optional public URL where the agent can be reached
856
- * @returns True if heartbeat was acknowledged, False otherwise
922
+ * @returns True if the beacon has successfully bootstrapped
857
923
  */
858
- async heartbeat(url) {
859
- if (!this.config.privateKey) {
860
- return false;
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(...arguments);
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.initialized = false;
3193
+ this.connectionPromise = null;
3132
3194
  this.tableName = config.tableName || 'sekuire_memory';
3133
- this.initPool(config);
3195
+ this.config = config;
3196
+ if (!config.lazyConnect) {
3197
+ this.connectionPromise = this.connect();
3198
+ }
3134
3199
  }
3135
- async initPool(config) {
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.initialized = true;
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
- if (!this.initialized)
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
- if (!this.initialized)
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
- if (!this.initialized)
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
- if (!this.initialized)
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.initialized) {
3299
+ if (this._connected && this.pool) {
3225
3300
  await this.pool.end();
3226
- this.initialized = false;
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.connected = false;
3311
+ this.connectionPromise = null;
3236
3312
  this.keyPrefix = config.keyPrefix || 'sekuire:memory:';
3237
- this.initClient(config);
3313
+ this.config = config;
3314
+ if (!config.lazyConnect) {
3315
+ this.connectionPromise = this.connect();
3316
+ }
3238
3317
  }
3239
- async initClient(config) {
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.connected = true;
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
- if (!this.connected)
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
- if (!this.connected)
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
- if (!this.connected)
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
- if (!this.connected)
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.connected) {
3387
+ if (this._connected && this.client) {
3299
3388
  await this.client.quit();
3300
- this.connected = false;
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
- // Normalize type (support "buffer" as alias for "in-memory")
3307
- const normalizedType = config.type === 'buffer' ? 'in-memory' : config.type;
3308
- switch (normalizedType) {
3309
- case 'in-memory':
3310
- return new InMemoryStorage();
3311
- case 'redis':
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;